mekovid
v1.0.2
Published
Modern, hafif ve özelleştirilebilir HTML5 video player - HLS, altyazı ve kalite seçimi desteği
Maintainers
Readme
MekoVid — Modern HTML5 Video Player
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
- Features
- Installation
- Quick Start
- Usage Examples
- Subtitle Support
- Quality Selection
- Dub / Audio Switching
- Theme System
- Chapter Markers
- Feature System
- Keyboard Shortcuts
- Touch Support
- PiP (Picture-in-Picture)
- AirPlay & Chromecast
- Skip Intro
- localStorage Preferences
- Button Control
- API Reference
- CSS Customization
- Build System
- Project Structure
- Changelog
Features
Playback & Streaming
- 🎬 HLS Streaming — HLS.js integration with live streaming and adaptive bitrate support
- 📺 Quality Selection — HLS auto quality + normal video
sizeattribute support - 🎭 Dub / Audio Switching — Multi audio track management via
data-dubsattribute
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 System —
default,bar,blue,blue-barthemes; 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 mekovidvia 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
sizeattribute 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'); // booleanKeyboard Shortcuts
| Key | Action |
|-----|--------|
| Space / K | Play / Pause |
| ← | Seek -10 seconds |
| → | Seek +10 seconds |
| ↑ | Volume +10% |
| ↓ | Volume -10% |
| F | Toggle fullscreen |
| M | Toggle mute |
| 0–9 | 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(); // booleanHide 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 programmaticallyDisable 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 preferencesDisable 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() // PromisePiP
player.enterPiP() // Promise — enter PiP mode
player.exitPiP() // Promise — exit PiP mode
player.isPiP() // booleanSkip Intro
player.skipIntro() // Jump to data-skip-intro-end timeTheme
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 chaptersFeature Control
player.isFeatureEnabled('pip') // boolean
player.disableFeature('cast') // Disable at runtime
player.enableFeature('cast') // Enable at runtimelocalStorage
player.getPrefs() // { volume, muted, subtitleLang, quality }
player.clearPrefs() // Delete saved preferencesSource / 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 instanceEvents
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 instanceCSS 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 onlyOutput 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.jsonChangelog
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
sizeattribute 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
MutationObserverfordata-dubstracking
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
WebKitPlaybackTargetAvailabilityEventin 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"formatsetChapters(),getChapters(),getCurrentChapter(),clearChapters()setTheme(),getTheme()enterPiP(),exitPiP(),isPiP()skipIntro()isFeatureEnabled(),disableFeature(),enableFeature()getPrefs(),clearPrefs()hideButton(),showButton(),toggleButton(),isButtonVisible(),getButtons()
Feature & Button System:
data-disableHTML attributeoptions.featuresJS objectdata-hide-buttonsHTML attributeoptions.buttonsJS object- Every feature and button independently controllable
License
MIT License — LICENSE
Author
Meliksah Sarioglu
