npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mekovid

v1.0.2

Published

Modern, hafif ve özelleştirilebilir HTML5 video player - HLS, altyazı ve kalite seçimi desteği

Readme

MekoVid — Modern HTML5 Video Player

npm version License: MIT

A modern, lightweight and customizable HTML5 video player. Packed with HLS streaming, automatic subtitle format conversion, quality selection, dub switching, theme system, keyboard/touch support, PiP, AirPlay, Chromecast, and much more.

🌐 Türkçe dokümantasyon için README_TR.md


Table of Contents

  1. Features
  2. Installation
  3. Quick Start
  4. Usage Examples
  5. Subtitle Support
  6. Quality Selection
  7. Dub / Audio Switching
  8. Theme System
  9. Chapter Markers
  10. Feature System
  11. Keyboard Shortcuts
  12. Touch Support
  13. PiP (Picture-in-Picture)
  14. AirPlay & Chromecast
  15. Skip Intro
  16. localStorage Preferences
  17. Button Control
  18. API Reference
  19. CSS Customization
  20. Build System
  21. Project Structure
  22. Changelog

Features

Playback & Streaming

  • 🎬 HLS Streaming — HLS.js integration with live streaming and adaptive bitrate support
  • 📺 Quality Selection — HLS auto quality + normal video size attribute support
  • 🎭 Dub / Audio Switching — Multi audio track management via data-dubs attribute

Subtitles

  • 📝 Subtitle Support — SRT, ASS/SSA and VTT formats; automatic conversion
  • 🎨 Subtitle Customization — Size, weight, color, background, offset, outline
  • 📱 Mobile Subtitles — Auto scaling based on viewport size (clamp() + ResizeObserver)

UI & Themes

  • 🎨 Theme Systemdefault, bar, blue, blue-bar themes; switchable at runtime
  • 📊 Chapter Markers — Colored segment blocks and dividers on the progress bar
  • 🖱️ Modern UI — Progress bar hover tooltip (with chapter name), volume slider fill, stack-based menu navigation

Controls

  • ⌨️ Keyboard Shortcuts — Space, arrows, F, M, 0–9 keys
  • 👆 Touch Support — Double-tap ±10s skip, swipe volume control, horizontal swipe seek
  • 📺 PiP — Picture-in-Picture; works in all modern desktop browsers
  • 📡 AirPlay — Auto-detected and enabled in Safari
  • 📡 Chromecast / Miracast — Remote Playback API; auto-visible in supported browsers
  • ⏭️ Skip Intro — Skip button via data-skip-intro-start/end

Personalization

  • 💾 localStorage Preferences — Volume, mute state and subtitle language saved automatically
  • 🔒 Feature System — Every feature can be disabled via HTML attribute or JS options
  • 🎛️ Button Control — Show/hide individual control buttons via HTML or JS API

Developer

  • Lightweight — Minimal footprint, maximum performance
  • 🔌 Extended API — Comprehensive programmatic control (seekTo, setChapters, setTheme, ...)

Installation

via npm

npm install mekovid

via CDN

<!-- CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mekovid/dist/css/mekovid.min.css">

<!-- JS (Modern) -->
<script type="module" src="https://cdn.jsdelivr.net/npm/mekovid/dist/js/mekovid.min.mjs"></script>

<!-- JS (Legacy browsers) -->
<script src="https://cdn.jsdelivr.net/npm/mekovid/dist/js/mekovid.polyfilled.min.js"></script>

Quick Start

HTML

<video id="myVideo" class="mekovid">
  <source src="video.mp4" type="video/mp4">
</video>

ES Module (npm / Vite / Webpack)

import mekovid from 'mekovid';
import 'mekovid/dist/css/mekovid.min.css';

const player = mekovid('myVideo', {
  thumbnail: 'poster.jpg',
  autoplay: false
});

CDN (Global Script)

<script src="https://cdn.jsdelivr.net/npm/mekovid/dist/js/mekovid.polyfilled.min.js"></script>
<script>
  const player = mekovid('myVideo');
  player.play();
</script>

Usage Examples

Modern Browsers (.mjs)

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="mekovid.min.css">
</head>
<body>
    <video id="player" class="mekovid">
        <source src="video.mp4" type="video/mp4">
    </video>

    <script type="module">
        import mekovid from './mekovid.min.mjs';

        const player = mekovid('player', {
            thumbnail: 'poster.jpg',
            autoplay: false
        });
    </script>
</body>
</html>

Legacy Browsers (Polyfilled)

<script src="mekovid.polyfilled.min.js"></script>
<script>
    var player = mekovid('player', {
        thumbnail: 'poster.jpg'
    });
</script>

HLS Streaming

<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script type="module">
    import mekovid from './mekovid.min.mjs';

    const player = mekovid('player');
    player.changeSource('https://example.com/stream.m3u8');
</script>

React

import { useEffect, useRef } from 'react';
import mekovid from 'mekovid';
import 'mekovid/dist/css/mekovid.min.css';

function VideoPlayer({ src, poster }) {
    const videoRef = useRef(null);
    const playerRef = useRef(null);

    useEffect(() => {
        if (videoRef.current && !playerRef.current) {
            playerRef.current = mekovid(videoRef.current.id, {
                thumbnail: poster
            });
        }
        return () => {
            if (playerRef.current) playerRef.current.destroy();
        };
    }, []);

    return (
        <video ref={videoRef} id="react-player" className="mekovid">
            <source src={src} type="video/mp4" />
        </video>
    );
}

Vue 3

<template>
    <video ref="videoElement" id="vue-player" class="mekovid">
        <source :src="src" type="video/mp4">
    </video>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import mekovid from 'mekovid';
import 'mekovid/dist/css/mekovid.min.css';

const props = defineProps({ src: String, poster: String });
let player = null;

onMounted(() => {
    player = mekovid('vue-player', { thumbnail: props.poster });
});

onUnmounted(() => {
    if (player) player.destroy();
});
</script>

Svelte

<script>
    import { onMount, onDestroy } from 'svelte';
    import mekovid from 'mekovid';
    import 'mekovid/dist/css/mekovid.min.css';

    export let src;
    export let poster;
    let player;

    onMount(() => {
        player = mekovid('svelte-player', { thumbnail: poster });
    });

    onDestroy(() => {
        if (player) player.destroy();
    });
</script>

<video id="svelte-player" class="mekovid">
    <source {src} type="video/mp4">
</video>

Lazy Loading

const loadPlayer = async () => {
    const { default: mekovid } = await import('mekovid/dist/js/mekovid.min.mjs');
    return mekovid('player');
};

document.querySelector('.play-button').addEventListener('click', async () => {
    const player = await loadPlayer();
    player.play();
});

Subtitle Support

MekoVid supports VTT, SRT and ASS/SSA formats. SRT and ASS files are automatically converted to VTT in the browser — no server required.

| Format | Extension | Conversion | |--------|-----------|------------| | VTT | .vtt | Used directly | | SRT | .srt | Auto-converted to VTT | | ASS/SSA | .ass, .ssa | Auto-converted to VTT |

Subtitles via HTML

<video id="player" class="mekovid" crossorigin="anonymous">
    <source src="video.mp4" type="video/mp4">

    <!-- VTT (direct) -->
    <track kind="subtitles" label="Turkish" srclang="tr" src="tr.vtt" default>

    <!-- SRT (auto-convert) -->
    <track kind="subtitles" label="English" srclang="en" src="en.srt">

    <!-- ASS (auto-convert) -->
    <track kind="subtitles" label="Français" srclang="fr" src="fr.ass">
</video>

Subtitle Management via API

const player = mekovid('player');

// Add subtitle (format auto-detected)
await player.addSubtitle('subtitle.srt', 'Turkish', 'tr', true);
await player.addSubtitle('subtitle.ass', 'English', 'en');
await player.addSubtitle('subtitle.vtt', 'Français', 'fr');

// Switch active subtitle
player.setActiveSubtitle('tr');
player.setActiveSubtitle('off');  // disable

// Remove all subtitles
player.clearSubtitles();

// Get subtitle list
const subs = player.getSubtitles();
// [{ language: 'tr', label: 'Turkish', mode: 'showing' }, ...]

Manual Format Conversion

const { converter } = player;

// Detect format (from content)
const format = converter.detectFormat(content);  // 'srt' | 'vtt' | 'ass' | 'unknown'

// Detect format (from URL)
const format = converter.detectFormatFromUrl('subtitle.srt');  // 'srt'

// SRT → VTT
const vtt = converter.srtToVtt(srtContent);

// ASS → VTT
const vtt = converter.assToVtt(assContent);

// Auto-convert
const vtt = converter.toVtt(content);

Important Notes

  • Subtitle files must be UTF-8 encoded
  • Cross-origin subtitles require crossorigin="anonymous" and CORS headers
  • Complex ASS styling is not supported — only text is extracted

Quality Selection

Normal Video — Multiple Sources (size attribute)

<video id="player" class="mekovid">
    <source src="video-480p.mp4"  type="video/mp4" size="480">
    <source src="video-720p.mp4"  type="video/mp4" size="720">
    <source src="video-1080p.mp4" type="video/mp4" size="1080">
    <source src="video-2160p.mp4" type="video/mp4" size="2160">
</video>

All sources with a size attribute are scanned at initialization and a quality menu is built automatically. Playback position is preserved during quality changes.

| Size | Resolution | |------|-----------| | 360 | 360p | | 480 | 480p SD | | 720 | 720p HD | | 1080 | 1080p Full HD | | 1440 | 1440p 2K | | 2160 | 2160p 4K |

HLS Video — Auto Quality

<video id="player" class="mekovid">
    <source src="video.m3u8" type="application/vnd.apple.mpegurl">
</video>

Quality levels are automatically added to the menu when the HLS manifest is parsed. Users can select "Auto" or a manual quality level.

⚠️ Do not use size attribute together with HLS.


Dub / Audio Switching

<video id="player" class="mekovid"
    data-dubs='[
        {"group":"tr","name":"Turkish","icon":"🇹🇷"},
        {"group":"en","name":"English","icon":"🇺🇸"},
        {"group":"jp","name":"日本語","icon":"🇯🇵"}
    ]'
    data-current-dub="tr">
    <source src="video.mp4" type="video/mp4">
</video>
// Listen for dub change events
videoElement.addEventListener('dubchange', (e) => {
    console.log('New dub:', e.detail.group, e.detail.name);
    // Update video source here
});

The menu automatically updates when data-dubs attribute changes (MutationObserver).


Theme System

Four built-in themes are available:

| Theme | Progress Color | Progress Position | |-------|---------------|-------------------| | default | Red #ff0000 | Same row as buttons | | bar | Red #ff0000 | Above buttons (full width) | | blue | Blue #1a8cff | Same row as buttons | | blue-bar | Blue #1a8cff | Above buttons (full width) |

<!-- HTML attribute -->
<video data-theme="blue-bar">

<!-- JS options -->
mekovid('player', { theme: 'bar' });

<!-- Switch at runtime -->
player.setTheme('blue');
player.getTheme(); // 'blue'

Chapter Markers

Colored segment blocks and chapter name tooltip on the progress bar.

<!-- HTML attribute -->
<video data-chapters='[
  {"start": "0:00",    "end": "1:38:33", "label": "Chapter 1", "color": "#4fc3f7"},
  {"start": "1:38:33", "end": "1:56:43", "label": "Ad Break",  "color": "#ff4444"},
  {"start": "1:56:43", "end": "3:00:00", "label": "Chapter 2", "color": "#81c784"}
]'>
// JS options
mekovid('player', {
    chapters: [
        { start: 0,    end: 5853,  label: 'Chapter 1', color: '#4fc3f7' },
        { start: 5853, end: 6903,  label: 'Ad Break',  color: '#ff4444' },
        { start: 6903, end: 10800, label: 'Chapter 2', color: '#81c784' }
    ]
});

// Update at runtime
player.setChapters([...]);
player.getCurrentChapter(); // { start, end, label, color } | null
player.clearChapters();

start and end values accept seconds (number) or time strings in "1:38:33" / "01:38:33" format.


Feature System

Every feature can be disabled via HTML attribute or JS options.

<!-- Disable via HTML -->
<video data-disable="pip,airplay,cast">
// Via JS options
mekovid('player', {
    features: {
        pip:       true,   // PiP button
        airplay:   true,   // AirPlay button (Safari only)
        cast:      true,   // Chromecast button
        keyboard:  true,   // Keyboard shortcuts
        touch:     true,   // Touch support
        skipIntro: true,   // Skip Intro button
        storage:   true,   // localStorage preferences
    }
});

// Toggle at runtime
player.disableFeature('pip');
player.enableFeature('pip');
player.isFeatureEnabled('pip'); // boolean

Keyboard Shortcuts

| Key | Action | |-----|--------| | Space / K | Play / Pause | | | Seek -10 seconds | | | Seek +10 seconds | | | Volume +10% | | | Volume -10% | | F | Toggle fullscreen | | M | Toggle mute | | 09 | Jump to 0%–90% position |

Disable with data-disable="keyboard" or features: { keyboard: false }.


Touch Support

| Gesture | Action | |---------|--------| | Single tap | Show controls; brief wait → play/pause | | Double tap (left) | Seek -10 seconds | | Double tap (right) | Seek +10 seconds | | Horizontal swipe (60px+) | ±10 seconds | | Vertical swipe (left half) | Volume control |

Disable with data-disable="touch" or features: { touch: false }.


PiP (Picture-in-Picture)

A button is automatically added in browsers that support document.pictureInPictureEnabled.

player.enterPiP();   // Promise
player.exitPiP();    // Promise
player.isPiP();      // boolean

Hide with data-disable="pip" or features: { pip: false }.


AirPlay & Chromecast

AirPlay (Safari only)

A button is created when window.WebKitPlaybackTargetAvailabilityEvent is detected.
Button automatically appears when an AirPlay device is found, stays hidden otherwise.

<!-- Disable -->
<video data-disable="airplay">

Chromecast / Miracast (Remote Playback API)

A button is created in browsers that support HTMLVideoElement.prototype.remote (Chrome, Edge, Samsung Browser).

<!-- Disable -->
<video data-disable="cast">

Skip Intro

A "Skip Intro ▶" button appears on screen during the specified time range.

<video data-skip-intro-start="82" data-skip-intro-end="172">
player.skipIntro(); // Skip programmatically

Disable with data-disable="skipIntro" or features: { skipIntro: false }.


localStorage Preferences

When enabled, the following values are automatically saved and restored:

| Value | When Saved | |-------|-----------| | volume | Volume level changes | | muted | Mute toggled | | subtitleLang | Subtitle language changes | | quality | Quality selected |

player.getPrefs();    // { volume, muted, subtitleLang, quality }
player.clearPrefs();  // Delete all saved preferences

Disable with data-disable="storage" or features: { storage: false }.


Button Control

Show or hide any control bar button via HTML attribute or JS API.

Hide at startup via HTML:

<video data-hide-buttons="fullscreen,settings" ...></video>

Hide at startup via JS options:

const player = mekovid('video-id', {
    buttons: {
        fullscreen: false,
        settings: false,
    }
});

Show/hide at runtime:

player.hideButton('fullscreen')          // Hide
player.showButton('fullscreen')          // Show
player.toggleButton('fullscreen')        // Toggle
player.toggleButton('fullscreen', false) // Force hide
player.isButtonVisible('fullscreen')     // boolean
player.getButtons()                      // [{ name, visible }, ...]

Available button names:

| Name | Description | |------|-------------| | play | Play/Pause button | | mute | Mute button | | volume | Volume slider container | | time | Time display (00:00 / 00:00) | | progress | Progress bar container | | settings | Settings (gear) button | | fullscreen | Fullscreen button | | pip | Picture-in-Picture button | | airplay | AirPlay button | | cast | Chromecast button |


API Reference

Create Player

const player = mekovid(videoId, options);

options:

| Parameter | Type | Description | |-----------|------|-------------| | thumbnail | string | Poster/thumbnail URL | | autoplay | boolean | Auto play (default: false) | | subtitles | Array | Initial subtitles [{ src, label, language }] | | defaultSubtitle | string | Default language code | | theme | string | Theme: 'default' | 'bar' | 'blue' | 'blue-bar' | | chapters | Array | Chapter markers [{ start, end, label, color }] | | features | Object | Feature enable/disable settings | | buttons | Object | Initial button visibility { fullscreen: false, ... } |

Playback Control

player.play()              // Play → Promise
player.pause()             // Pause
player.isPlaying()         // boolean
player.isPaused()          // boolean
player.getCurrentTime()    // number (seconds)
player.setCurrentTime(30)  // Seek to second
player.seekTo('1:38:33')   // Accepts time string OR seconds
player.getDuration()       // number (seconds)

Volume

player.getVolume()        // 0–1
player.setVolume(0.5)     // 0–1 range
player.isMuted()          // boolean
player.setMuted(true)

Fullscreen

player.isFullscreen()
player.enterFullscreen()  // Promise
player.exitFullscreen()   // Promise
player.toggleFullscreen() // Promise

PiP

player.enterPiP()   // Promise — enter PiP mode
player.exitPiP()    // Promise — exit PiP mode
player.isPiP()      // boolean

Skip Intro

player.skipIntro()  // Jump to data-skip-intro-end time

Theme

player.getTheme()          // 'default' | 'bar' | 'blue' | 'blue-bar'
player.setTheme('blue')    // Change theme (applied instantly)

Chapters

player.setChapters([
    { start: '0:00',    end: '1:38:33', label: 'Chapter 1', color: '#4fc3f7' },
    { start: '1:38:33', end: '1:56:43', label: 'Ad Break',  color: '#ff4444' },
    { start: '1:56:43', end: '3:00:00', label: 'Chapter 2', color: '#81c784' }
]);

player.getChapters()        // Full chapters array
player.getCurrentChapter()  // Active chapter object or null
player.clearChapters()      // Remove all chapters

Feature Control

player.isFeatureEnabled('pip')   // boolean
player.disableFeature('cast')    // Disable at runtime
player.enableFeature('cast')     // Enable at runtime

localStorage

player.getPrefs()    // { volume, muted, subtitleLang, quality }
player.clearPrefs()  // Delete saved preferences

Source / Poster

player.changeSource('new-video.mp4')  // Promise
player.changeSource('stream.m3u8')    // HLS auto-detected
player.setPoster('poster.jpg')
player.getPoster()

Subtitles

await player.addSubtitle(src, label, language, isDefault)
player.clearSubtitles()
player.setActiveSubtitle('en')  // language code or 'off'
player.getSubtitles()           // [{ language, label, mode }]

HLS Info

const info = player.getHLSInfo();
// {
//   format: 'HLS',
//   url: '...',
//   duration: 3600,
//   isLive: false,
//   levels: [{ index, width, height, bitrate, name }],
//   currentLevel: 2,
//   currentLevelName: '1080p',
//   videoWidth: 1920,
//   videoHeight: 1080,
//   buffered: [{ start, end }]
// }

player.getHLSInstance()  // Access the HLS.js instance

Events

player.on('play', () => console.log('Playing'));
player.on('pause', () => console.log('Paused'));
player.on('ended', () => console.log('Ended'));
player.on('timeupdate', () => console.log(player.getCurrentTime()));
player.on('dubchange', (e) => console.log(e.detail)); // { group, name }
player.off('play', handler);

Other

player.getElement()     // HTMLVideoElement
player.getContainer()   // Wrapper div
player.getVersion()     // { player, version, features }
player.destroy()        // Completely remove the player

player.showNotification('Message', 'info', 2000)  // 'info' | 'success' | 'warning' | 'error'
player.converter        // Access the SubtitleConverter instance

CSS Customization

CSS Variables

:root {
    --mekovid-primary: #ff0000;       /* primary color (progress, highlights) */
    --mekovid-primary-light: #FF5028;
    --mekovid-bg: #000;               /* video background */
    --mekovid-controls-bg: rgba(0, 0, 0, 0.3);
    --mekovid-menu-bg: rgba(28, 28, 28, 0.9);
    --mekovid-text: #fff;
    --mekovid-text-muted: rgba(255, 255, 255, 0.7);
    --mekovid-border: rgba(255, 255, 255, 0.2);
    --mekovid-border-radius: 16px;
    --mekovid-font: 'Helvetica Neue 55', Helvetica, Arial, sans-serif;
    --mekovid-transition: 0.3s ease;
}

Subtitle Style

/* General subtitle appearance */
::cue {
    font-size: 20px;
    font-weight: bold;
    color: white;
    background-color: rgba(0, 0, 0, 0.85);
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 1);
}

/* WebKit — subtitle position */
video::-webkit-media-text-track-container {
    bottom: 70px;
}

The following customizations can also be made from the player settings menu:

  • Font Size: 20–60px
  • Font Weight: Normal, Bold, Extra Bold
  • Background Opacity: 0–100%
  • Font Color: White, Yellow, Cyan, Green, Pink
  • Bottom Offset: 0–50px
  • Outline Size: 0–4px

Browser Support

| Build | IE11 | Chrome | Firefox | Safari | Edge | |-------|------|--------|---------|--------|------| | .mjs | ✗ | 90+ | 88+ | 14+ | 90+ | | .polyfilled.js | ✓ | 60+ | 55+ | 11+ | 15+ |


Build System

Commands

npm run build        # Run all builds
npm run dev          # Watch mode (development)
npm run clean        # Clean dist/
npm run build:js     # JS build only
npm run build:css    # CSS build only
npm run build:rollup # Rollup build only

Output Files

dist/js/ (generated by Rollup):

| File | Format | Polyfill | Description | |------|--------|----------|-------------| | mekovid.cjs.js | CJS | ✗ | CommonJS (Node.js / require()) | | mekovid.esm.js | ESM | ✗ | ES Module (bundlers) | | mekovid.mjs | ESM | ✗ | Modern browsers | | mekovid.min.mjs | ESM | ✗ | Modern (minified) | | mekovid.js | UMD | ✗ | Universal build | | mekovid.min.js | UMD | ✗ | Universal (minified) | | mekovid.polyfilled.mjs | ESM | ✓ | Polyfilled ESM | | mekovid.polyfilled.min.mjs | ESM | ✓ | Polyfilled ESM (minified) |

dist/css/:

| File | Description | |------|-------------| | mekovid.css | Full CSS | | mekovid.min.css | Minified CSS |


Project Structure

mekovid/
├── src/
│   ├── js/
│   │   ├── core.js          ← Main player class and API
│   │   ├── controls.js      ← UI HTML generators
│   │   ├── hls.js           ← HLS.js integration
│   │   ├── subtitles.js     ← SubtitleConverter
│   │   ├── notifications.js ← Notification system
│   │   ├── utils.js         ← Utility functions
│   │   └── index.js         ← Main export
│   └── css/
│       └── mekovid.scss     ← Main stylesheet
├── dist/
│   ├── js/                  ← Compiled JS files
│   └── css/                 ← Compiled CSS files
├── build/
│   ├── build.js             ← Main build script
│   └── rollup-summary.js    ← Build summary script
├── examples/
│   └── simple-player.html   ← Test page
├── rollup.config.js
├── README.md                ← English docs (this file)
├── README_TR.md             ← Turkish docs
└── package.json

Changelog

v1.0.0 (Current)

Core Features:

  • HLS streaming support (HLS.js)
  • SRT, ASS/SSA → VTT automatic conversion
  • 6 subtitle customization options (size, weight, color, background, offset, outline)
  • Multi-quality selection via size attribute for normal videos
  • Dub / audio switching (data-dubs, data-groups)
  • Stack-based settings menu navigation
  • Progress bar hover time tooltip (dynamic positioning)
  • Volume slider fill gradient
  • MutationObserver for data-dubs tracking

Control Features:

  • Keyboard shortcuts (Space/K, ←→↑↓, F, M, 0-9)
  • Touch support (double-tap skip, swipe volume, horizontal swipe)
  • PiP (Picture-in-Picture) — automatic button in supported browsers
  • AirPlay — auto-detection via WebKitPlaybackTargetAvailabilityEvent in Safari
  • Chromecast / Miracast — auto-detection via Remote Playback API
  • Skip Intro — animated button via data-skip-intro-start/end

UI & Themes:

  • Theme system: default, bar, blue, blue-bar
  • Chapter markers — colored segment blocks, divider lines, tooltip integration
  • Mobile subtitle optimization — responsive sizing via clamp() + ResizeObserver
  • Mobile control optimization — hide volume slider, large touch targets
  • Double-tap visual indicator (animated overlay)

API Extensions:

  • seekTo(time) — seconds or "1:38:33" format
  • setChapters(), getChapters(), getCurrentChapter(), clearChapters()
  • setTheme(), getTheme()
  • enterPiP(), exitPiP(), isPiP()
  • skipIntro()
  • isFeatureEnabled(), disableFeature(), enableFeature()
  • getPrefs(), clearPrefs()
  • hideButton(), showButton(), toggleButton(), isButtonVisible(), getButtons()

Feature & Button System:

  • data-disable HTML attribute
  • options.features JS object
  • data-hide-buttons HTML attribute
  • options.buttons JS object
  • Every feature and button independently controllable

License

MIT License — LICENSE

Author

Meliksah Sarioglu