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

vidstream-player

v2.0.0

Published

Production-ready video player SDK — HLS, DASH, React, Vue, Angular, Web Component, config-driven theming, playlist sidebar, chapters, PiP, mini player, volume animation, touch gestures, auto-resume, progress tracking, analytics plugin, full TypeScript

Readme

vidstream-player

The last video player you'll ever need.

A production-ready, framework-agnostic video player SDK with adaptive streaming, fully configurable theming, playlists, chapters, Picture-in-Picture, mini player, touch gestures, progress tracking, an analytics plugin interface, and full TypeScript support — all in one package.

npm version npm downloads TypeScript License: MIT Bundle size


Why vidstream-player?

Most video players do one thing. This one does everything.

| What you normally need | vidstream-player | |------------------------|-----------------| | HLS player + DASH player + quality selector | ✅ Built-in, lazy-loaded | | Full custom theme — your colors, fonts, radius | ✅ Config object, CSS variables | | Playlist with sidebar panel | ✅ Renders to the right of player | | Chapter markers + VTT chapters | ✅ Built-in | | Picture-in-Picture + floating mini player | ✅ Built-in | | Mobile touch gestures | ✅ Built-in | | Volume animation feedback | ✅ Icon + bar + percentage | | Analytics plugin slot | ✅ Built-in | | Progress tracking + milestones | ✅ Built-in | | React wrapper + hooks | ✅ Built-in | | Web Component for Vue / Angular | ✅ Built-in | | Full TypeScript types | ✅ Built-in |

No extra dependencies. No stitching together five libraries. Import, configure, ship.


Features at a glance

  • Adaptive streaming — HLS (.m3u8) via hls.js and DASH (.mpd) via dash.js, both lazy-loaded as separate chunks so they never bloat your initial bundle
  • Config-driven theming — pass a theme object with your brand colors, radii, fonts, and logo. Every detail is a CSS custom property you can also override directly
  • Watermark & anti-piracy — image or text watermarks with full color, opacity, size, and position control. Randomized anti-piracy text overlay with configurable intervals
  • Playlist sidebar — multi-item queue with auto-next, looping, and a built-in panel that renders to the right of the player in a flex row. Drop one element; both appear side-by-side
  • Chapters — inline config or WebVTT file; chapter-change events on the event bus
  • Quality selector — ABR levels from HLS/DASH manifests surfaced automatically; manual override supported
  • Subtitles & captions — WebVTT tracks with an in-player language switcher
  • Picture-in-Picture — browser-native PiP
  • Mini / floating player — scroll past the video and the entire player snaps to a fixed corner; click to scroll back; click × to dismiss. Works by repositioning the whole container — no video-element cloning or sync issues
  • Touch gestures — double-tap to seek, swipe right-half up/down for volume, long-press for speed boost
  • Volume animation — any volume change (swipe or / key) shows a volume icon + percentage + fill-bar overlay that fades out after 1.5 s
  • Error recovery — exponential-backoff retry with configurable max attempts
  • Progress tracking — milestone events (25 / 50 / 75 / 100 %), completion callback, periodic save with localStorage and API helpers
  • Analytics plugin — drop in any provider via a simple init(player) interface; exposes a full event bus (on/off/once)
  • Session ID — every player instance gets a UUID sessionId for correlation across events
  • React wrapperforwardRef component with usePlayerState and usePlayerControls hooks
  • Web Component — works in Vue, Angular, Svelte, or plain HTML with zero framework glue
  • TypeScript-first — every option, event, and method is fully typed

Install

npm install vidstream-player
# or
yarn add vidstream-player
# or
pnpm add vidstream-player

Peer dependencies

hls.js and dashjs are optional peer dependencies — install whichever streaming formats you need:

# For HLS (.m3u8) streams
npm install hls.js

# For DASH (.mpd) streams
npm install dashjs

# Both
npm install hls.js dashjs

If you only serve MP4 or WebM, you don't need either. The player lazy-loads them only when a matching source is played.


Quick start

Vanilla JS

import 'vidstream-player/styles';
import { createVideoPlayer } from 'vidstream-player';

const player = createVideoPlayer(document.getElementById('player'), {
  source: { src: 'https://example.com/video.m3u8', type: 'hls' },
  theme:  { accentColor: '#e50914', progressColor: '#e50914' },
  controls: { quality: true, pip: true, subtitles: true },
});

React

import 'vidstream-player/styles';
import VidstreamPlayer from 'vidstream-player/react';

export default function App() {
  return (
    <VidstreamPlayer
      source={{ src: 'https://example.com/video.m3u8', type: 'hls' }}
      theme={{ accentColor: '#e50914', progressColor: '#e50914' }}
      controls={{ quality: true, pip: true }}
      onPlay={() => console.log('playing')}
    />
  );
}

Vue 3

// main.ts — tell Vue this is a custom element
app.config.compilerOptions.isCustomElement = (tag) => tag === 'vidstream-player';
import 'vidstream-player/styles';
import 'vidstream-player'; // registers <vidstream-player>
<template>
  <vidstream-player ref="playerEl" style="width:100%;aspect-ratio:16/9" />
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue';
const playerEl = ref<any>(null);

onMounted(() => {
  playerEl.value.initialize({
    source: { src: 'https://example.com/video.m3u8', type: 'hls' },
    theme:  { accentColor: '#42b883' },
  });
});
</script>

Angular

import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import 'vidstream-player/styles';
import 'vidstream-player';

@Component({
  selector: 'app-root',
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `<vidstream-player #player style="width:100%;aspect-ratio:16/9"></vidstream-player>`,
})
export class AppComponent {
  // call player.initialize(config) in ngAfterViewInit
}

Configuration

Every option is optional except source.

import type { PlayerConfig } from 'vidstream-player';

Core options

| Option | Type | Default | Description | |--------|------|---------|-------------| | source | VideoSource | — | Required. { src, type, title?, poster?, chapters? } | | mode | 'vod' \| 'live' | 'vod' | VOD shows full controls; live hides seek bar and speed | | theme | PlayerTheme | defaults | Brand colors, radius, font, logo — see Theming | | autoplay | boolean | false | Autoplay on load (subject to browser policy) | | muted | boolean | false | Start muted | | loop | boolean | false | Loop playback | | startTime | number | — | Seek to this time (seconds) on load | | preload | 'none' \| 'metadata' \| 'auto' | 'metadata' | HTML video preload hint | | keyboardShortcuts | boolean | true | Space / arrows / F / M keyboard control | | debug | boolean | false | Log lifecycle events to console |

Source types

type VideoSourceType = 'hls' | 'dash' | 'mp4' | 'webm' | 'auto';

// 'auto' detects from the extension (.m3u8 → hls, .mpd → dash)
{ src: 'https://example.com/video.m3u8', type: 'auto' }

Controls

Pass only the controls you want. Omitted controls are hidden.

controls: {
  quality:       true,  // quality selector
  pip:           true,  // picture-in-picture button
  subtitles:     true,  // subtitle track switcher
  playbackSpeed: true,  // speed menu
  chapters:      true,  // chapter markers
  playlist:      true,  // prev/next buttons
  forward:       true,  // skip-forward button
  rewind:        true,  // skip-back button
  seekDuration:  10,    // seconds per skip
}

Theming

There are no preset theme names. You pass a plain object with exactly the colors you want. Every field is optional — omit it and the built-in default applies.

{
  theme: {
    primaryColor:  '#ffffff',          // icon and text color
    accentColor:   '#e50914',          // progress bar, active highlights
    textColor:     '#ffffff',
    controlBg:     'rgba(0,0,0,0.8)',  // control bar background
    progressColor: '#e50914',
    bufferColor:   'rgba(255,255,255,0.3)',
    hoverColor:    '#ff1a1a',
    borderRadius:  '4px',
    fontFamily:    'Inter, sans-serif',
    iconSize:      '24px',
    tooltipBg:     'rgba(0,0,0,0.9)',
    logoUrl:       'https://cdn.example.com/logo.png',
    logoPosition:  'top-left',    // 'top-left' | 'top-right'
    tenantName:    'My Platform',
  }
}

Change theme at runtime

player.updateConfig({
  theme: { accentColor: '#6366f1', progressColor: '#6366f1' },
});

CSS custom properties

Every theme value maps to a CSS variable on .video-player-container. Override directly for global or per-instance changes:

.video-player-container {
  --vp-accent-color:   #e50914;
  --vp-primary-color:  #ffffff;
  --vp-control-bg:     rgba(0,0,0,0.85);
  --vp-progress-color: #e50914;
  --vp-border-radius:  4px;
  --vp-font-family:    'Inter', sans-serif;
  --vp-text-color:     #ffffff;
  --vp-hover-color:    #ff1a1a;
  --vp-tooltip-bg:     rgba(0,0,0,0.9);
  --vp-icon-size:      24px;
}

Watermark & Anti-Piracy

Watermark

Full control over color, size, opacity, and position:

{
  watermark: {
    enabled:    true,
    text:       'MyPlatform',           // text watermark
    // imageUrl: 'https://.../logo.svg', // OR image watermark
    opacity:    0.45,
    position:   'top-right',  // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center' | 'random'
    imageWidth: '80px',
    className:  'my-watermark',  // add your own CSS for full control
  },
}

Anti-Piracy Text

Randomized floating text to deter screen recording:

{
  antiPiracyText: {
    enabled:     true,
    text:        '[email protected]',
    color:       'rgba(255,255,255,0.15)',
    fontSize:    '14px',
    opacity:     0.15,
    minInterval: 5000,
    maxInterval: 12000,
    className:   'my-anti-piracy',
  },
}

Adaptive Streaming — HLS & DASH

HLS.js and dash.js are loaded on demand as separate chunks — they add nothing to your initial bundle if the viewer only watches MP4.

// HLS
{ source: { src: 'https://cdn.example.com/hls/master.m3u8', type: 'hls' } }

// DASH
{ source: { src: 'https://cdn.example.com/dash/manifest.mpd', type: 'dash' } }

// Quality control
quality: {
  showAuto:         true,
  defaultQuality:   1080,
  allowedQualities: [1080, 720, 480],
}

Playlist

When showPanel: true, a 280 px sidebar is automatically rendered to the right of the player in a flex row. You pass a single container element; both the player and the sidebar appear side-by-side without any extra layout work.

{
  playlist: [
    { src: 'https://cdn.example.com/ep1.m3u8', title: 'Episode 1', poster: '...', duration: 1440, chapters: [{ id: '1', title: 'Intro', startTime: 0, endTime: 120 }] },
    { src: 'https://cdn.example.com/ep2.m3u8', title: 'Episode 2', poster: '...' },
  ],
  playlistOptions: {
    autoNext:  true,
    loop:      false,
    showPanel: true,   // sidebar renders to the right automatically
  },
}
player.next();    // advance
player.prev();    // go back
player.goTo(2);   // jump to index

Chapters

Inline

{
  chapters: [
    { id: '1', title: 'Introduction', startTime: 0,   endTime: 120 },
    { id: '2', title: 'Main Content', startTime: 120, endTime: 600 },
    { id: '3', title: 'Conclusion',   startTime: 600, endTime: 720 },
  ],
  controls: { chapters: true },
}

From a WebVTT file

{ chaptersVtt: 'https://cdn.example.com/chapters.vtt' }

Picture-in-Picture & Mini Player

{
  pip: { enabled: true },

  miniPlayer: {
    enabled:  true,
    position: 'bottom-right',  // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
    width:    320,             // px; height is computed 16:9
  },
}

The mini player activates automatically via IntersectionObserver when the player scrolls out of view. The entire player container (video + controls) is repositioned to a fixed corner of the screen — no element cloning or sync issues. Click the mini player body to scroll back to the original position. Click × to dismiss permanently.

In mini player mode, controls are automatically reduced to Play/Pause + Mute + Fullscreen so they fit in the compact overlay without crowding.

player.togglePip();  // browser-native PiP

Responsive / container scaling

The player uses a ResizeObserver to watch its container width — not the viewport — so it adapts correctly whether it is full-width, embedded in a sidebar, or rendered inside a narrow column.

| Container width | CSS class | Controls hidden | |----------------|-----------|----------------| | ≥ 720 px | (none) | All shown | | 560–719 px | vp-size-lg | — | | 400–559 px | vp-size-md | Volume slider, PiP, speed/quality labels | | 280–399 px | vp-size-sm | + Time display, rewind/forward, chapters/subtitles dropdowns | | < 280 px | vp-size-xs | All non-essential controls hidden |

This is the same mechanism used internally for the mini player. Because it uses container width rather than @media viewport queries, it works correctly when the player is embedded in any layout — grid, flex, sidebar, modal, or full-page.


Touch Gestures

{
  gestures: {
    enabled:        true,
    doubleTapSeek:  10,    // seconds to seek on double-tap (default: 10)
    swipeVolume:    true,  // swipe right half up/down → volume
    longPressSpeed: true,  // hold → 2× speed (true = 2×, or pass a number)
  },
}

Volume feedback: any volume change — swipe or / keyboard — shows a centered overlay with a volume icon, a percentage readout, and a horizontal fill bar. It fades out after 1.5 s automatically.


Error Recovery

{
  errorRecovery: {
    maxRetries: 5,
    retryDelay: 2000,  // ms (doubles each attempt)
  },
}

Progress Tracking

Track milestone completion, save progress, and restore on return. Works with both vod and live modes:

{
  lmsProgress: {
    enabled:    true,
    videoId:    'course-101-lesson-1',
    userId:     'user-abc-123',
    milestones: [25, 50, 75, 100],
    onMilestoneReached: (pct, data) => {
      api.recordMilestone({ videoId: data.videoId, milestone: pct });
    },
    onComplete: (data) => {
      api.markComplete(data.videoId);
    },
    onProgressSave: (data) => {
      // called every 5 s automatically
      localStorage.setItem('progress_' + data.videoId, JSON.stringify(data));
    },
    initialProgress: {
      currentTime:      240,  // resume from 4:00
      watchedPercentage: 33,
    },
  },
}

Storage helpers

import {
  saveProgressToStorage,
  loadProgressFromStorage,
  clearProgressFromStorage,
  createProgressAPICallbacks, // ready-made fetch callbacks for a REST API
} from 'vidstream-player';

const saved = loadProgressFromStorage('course-101-lesson-1');

Live Streaming

{
  mode:   'live',
  source: { src: 'https://live.example.com/stream.m3u8', type: 'hls' },
}

In live mode the seek bar and duration are hidden, a LIVE badge is shown, speed selector and chapter controls are disabled.


Analytics Plugin

import type { VidstreamAnalyticsPlugin } from 'vidstream-player';

const myPlugin: VidstreamAnalyticsPlugin = {
  name: 'my-analytics',
  init(player) {
    console.log('Session:', player.sessionId);

    player.on('play',          () => track('video_play'));
    player.on('pause',         () => track('video_pause'));
    player.on('seek',          (e) => track('seek', { from: e.fromTime, to: e.toTime }));
    player.on('qualityChange', (e) => track('quality', { level: e.toQuality }));
    player.on('ended',         () => track('video_complete'));
    player.on('timeupdate',    (e) => {
      if (Math.floor(e.currentTime) % 10 === 0) track('heartbeat', e);
    });
  },
  destroy() {},
};

createVideoPlayer(el, {
  source:  { src: '...', type: 'hls' },
  plugins: { analytics: myPlugin },
});

Event bus

player.on('play', handler);    // subscribe
player.off('play', handler);   // unsubscribe
player.once('ended', handler); // fire once then remove

Available events

| Event | Payload | |-------|---------| | play | { currentTime, src, sessionId } | | pause | { currentTime, src, sessionId } | | ended | { src, sessionId } | | seek | { fromTime, toTime, src, sessionId } | | timeupdate | { currentTime, duration, percent, sessionId } | | qualityChange | { fromQuality, toQuality, auto, sessionId } | | chapterChange | { chapter, index, sessionId } | | playlistChange | { index, item, sessionId } | | buffering | { currentTime, duration, sessionId } | | error | { code, message } |


React — Hooks & Ref

usePlayerState

import VidstreamPlayer, { usePlayerState } from 'vidstream-player/react';
import type { VidstreamPlayerRef } from 'vidstream-player/react';
import { useRef } from 'react';

function VideoPage() {
  const ref = useRef<VidstreamPlayerRef>(null);
  const state = usePlayerState(ref); // { playing, currentTime, duration, volume, ... }

  return (
    <>
      <VidstreamPlayer
        ref={ref}
        source={{ src: 'https://example.com/video.m3u8', type: 'hls' }}
        theme={{ accentColor: '#e50914', progressColor: '#e50914' }}
        controls={{ quality: true, pip: true }}
      />
      <p>{state.currentTime.toFixed(0)}s / {state.duration.toFixed(0)}s</p>
    </>
  );
}

usePlayerControls

const controls = usePlayerControls(ref);

<button onClick={controls.togglePlay}>Play / Pause</button>
<button onClick={() => controls.seek(60)}>Jump to 1 min</button>
<button onClick={controls.togglePip}>PiP</button>

Full ref API

ref.current?.play()
ref.current?.pause()
ref.current?.seek(seconds)
ref.current?.setVolume(0.8)
ref.current?.setMuted(true)
ref.current?.setSpeed(1.5)
ref.current?.toggleFullscreen()
ref.current?.togglePip()
ref.current?.next()
ref.current?.prev()
ref.current?.goTo(index)
ref.current?.on('play', handler)
ref.current?.once('ended', handler)
ref.current?.getState()    // full PlayerState snapshot
ref.current?.sessionId     // UUID string

TypeScript

import type {
  PlayerConfig,
  VideoSource,
  PlayerMode,           // 'vod' | 'live'
  PlayerTheme,          // theme config object
  PlayerState,
  GesturesConfig,
  ErrorRecoveryConfig,
  PipConfig,
  MiniPlayerConfig,
  PlaylistItem,
  PlaylistOptions,
  Chapter,
  QualityLevel,
  SubtitleTrack,
  WatermarkConfig,
  AntiPiracyTextConfig,
  LMSProgressConfig,
  LMSProgressData,
  ProgressMilestone,
  VidstreamPlayerInstance,
  VidstreamAnalyticsPlugin,
  ChapterChangeEvent,
  PlaylistChangeEvent,
  PluginsConfig,
} from 'vidstream-player';

Player API reference

// Playback
player.play()
player.pause()
player.seek(seconds)
player.setVolume(0–1)
player.setMuted(boolean)
player.setSpeed(rate)           // 0.25 – 4
player.toggleFullscreen()
player.togglePip()
player.loadSource(VideoSource)  // swap source without re-init

// Playlist
player.next()
player.prev()
player.goTo(index)

// State
player.getState()               // PlayerState snapshot
player.getCurrentTime()
player.getDuration()
player.getVolume()
player.isPaused()
player.getQuality()

// Config
player.updateConfig(Partial<PlayerConfig>)

// Event bus
player.on(event, handler)
player.off(event, handler)
player.once(event, handler)

// Meta
player.sessionId                // UUID string
player.isReady()                // boolean

// Cleanup
player.destroy()

Web Component API (Vue / Angular / plain HTML)

<vidstream-player id="p"></vidstream-player>
<script type="module">
  import 'vidstream-player/styles';
  import 'vidstream-player'; // registers the custom element

  const el = document.getElementById('p');
  el.initialize({
    source: { src: 'https://example.com/video.m3u8', type: 'hls' },
    theme:  { accentColor: '#e50914' },
  });

  el.play();
  el.seek(30);
  el.setMuted(true);

  el.addEventListener('player-play',           () => console.log('play'));
  el.addEventListener('player-pause',          () => console.log('pause'));
  el.addEventListener('player-quality-change', (e) => console.log(e.detail.data));
</script>

DOM events

| DOM event | Fires when | |-----------|-----------| | player-ready | Player fully initialized | | player-play | Playback started | | player-pause | Playback paused | | player-ended | Playback finished | | player-timeupdate | Every animation frame during playback | | player-quality-change | ABR level changed | | player-chapter-change | Chapter boundary crossed | | player-playlist-change | Playlist item changed | | player-error | Playback error | | player-pip | PiP entered or exited | | player-buffering | Buffering state changed |


Framework compatibility

| Framework | Import path | Notes | |-----------|-------------|-------| | Vanilla JS | vidstream-player | createVideoPlayer(el, config) | | React 17+ | vidstream-player/react | Full wrapper with hooks | | Vue 3 | vidstream-player | Web component; add isCustomElement | | Angular 14+ | vidstream-player | Web component; add CUSTOM_ELEMENTS_SCHEMA | | Svelte | vidstream-player | Web component; no extra config | | Next.js | vidstream-player/react | Use dynamic(..., { ssr: false }) | | Nuxt 3 | vidstream-player | Wrap in <ClientOnly> | | Astro | vidstream-player | Add client:only="vanilla" directive |


Bundle impact

| Chunk | Size (gzip) | Loaded when | |-------|------------|-------------| | Core player (ESM) | ~38 kB | Always | | React wrapper | ~2.4 kB | React only | | UMD bundle | ~34 kB | UMD consumers | | hls.js (peer dep) | ~195 kB | HLS source played | | dashjs (peer dep) | ~282 kB | DASH source played |

hls.js and dashjs are peer dependencies — they are NOT bundled into vidstream-player. Install them separately only if you use HLS or DASH. They are lazy-loaded at runtime and add nothing to your initial bundle.

A pure MP4 deployment ships ~38 kB gzipped with zero streaming overhead.

Previous behavior (< 1.0): Both were bundled into the package, inflating the install size to ~11 MB with source maps. The current approach reduces the published package to ~400 KB.


Full example

import 'vidstream-player/styles';
import { createVideoPlayer } from 'vidstream-player';
import type { VidstreamAnalyticsPlugin, PlayerConfig } from 'vidstream-player';

const analyticsPlugin: VidstreamAnalyticsPlugin = {
  name: 'my-tracker',
  init(player) {
    player.on('play',       () => track('play',     { session: player.sessionId }));
    player.on('ended',      () => track('complete', { session: player.sessionId }));
    player.on('timeupdate', (e) => {
      if (Math.floor(e.currentTime) % 30 === 0) track('heartbeat', e);
    });
  },
};

const config: PlayerConfig = {
  source: {
    src:    'https://cdn.example.com/course/intro.m3u8',
    type:   'hls',
    title:  'Lesson 1 — Introduction',
    poster: '/thumb.jpg',
  },
  mode: 'vod',

  // Brand colors — full control, no preset strings
  theme: {
    accentColor:   '#10b981',
    progressColor: '#10b981',
    hoverColor:    '#34d399',
    controlBg:     'rgba(0,0,0,0.75)',
    borderRadius:  '6px',
  },

  // Playlist — sidebar renders automatically to the right
  playlist: [
    { src: 'https://cdn.example.com/ep1.m3u8', title: 'Episode 1' },
    { src: 'https://cdn.example.com/ep2.m3u8', title: 'Episode 2' },
    { src: 'https://cdn.example.com/ep3.m3u8', title: 'Episode 3' },
  ],
  playlistOptions: { autoNext: true, showPanel: true },

  // Chapters
  chapters: [
    { id: '1', title: 'Intro',    startTime: 0,   endTime: 60  },
    { id: '2', title: 'Concepts', startTime: 60,  endTime: 300 },
    { id: '3', title: 'Exercise', startTime: 300, endTime: 540 },
  ],

  // Controls
  quality:  { showAuto: true },
  controls: { quality: true, subtitles: true, chapters: true, playlist: true, pip: true },

  // Features
  pip:           { enabled: true },
  miniPlayer:    { enabled: true, position: 'bottom-right', width: 320 },
  gestures:      { enabled: true, doubleTapSeek: 10, swipeVolume: true, longPressSpeed: true },
  errorRecovery: { maxRetries: 5, retryDelay: 2000 },

  // Branding
  watermark: {
    enabled:  true,
    text:     'MyPlatform',
    position: 'top-right',
    opacity:  0.4,
  },
  antiPiracyText: {
    enabled:     true,
    text:        'Licensed to [email protected]',
    color:       'rgba(255,255,255,0.12)',
    fontSize:    '13px',
    minInterval: 8000,
    maxInterval: 15000,
  },

  // Progress tracking
  lmsProgress: {
    enabled:    true,
    videoId:    'course-101-lesson-1',
    userId:     'user-abc-123',
    milestones: [25, 50, 75, 100],
    onMilestoneReached: (pct) => api.recordMilestone(pct),
    onComplete:         ()    => api.markComplete('course-101-lesson-1'),
  },

  // Analytics
  plugins: { analytics: analyticsPlugin },
};

const player = createVideoPlayer(document.getElementById('player')!, config);

Documentation

Full API reference, advanced recipes, and a live playground are available at:

[docs-url]


License

MIT — free for personal and commercial use.

A Pro tier with advanced features (DRM / Widevine, server-side analytics, white-label builds, priority support) is coming. Star the repo to be notified.


Contributing

Found a bug or want a feature? Open an issue or PR at github.com/antor-arif/vidstream-player.