Skip to content

Latest commit

 

History

History
208 lines (152 loc) · 7.13 KB

File metadata and controls

208 lines (152 loc) · 7.13 KB

PlaysVideoEngine API

npm install playsvideo
import { PlaysVideoEngine } from 'playsvideo';

How it works

PlaysVideoEngine wraps a standard <video> element — the same pattern as hls.js. The video element you pass in is the playback surface — all native HTMLVideoElement properties, methods, and events work exactly as usual. The engine just replaces video.src with a pipeline that can play formats the browser doesn't natively support (MKV, AVI, non-MP4 containers, non-native audio codecs, embedded subtitles, etc.).

The underlying video element is accessible as engine.video.

What stays the same (use the <video> element directly)

Everything you'd normally do with a <video> element works unchanged:

  • Playback control: video.play(), video.pause(), video.playbackRate
  • Seeking: video.currentTime = 30
  • Volume: video.volume, video.muted
  • State: video.paused, video.ended, video.readyState, video.duration
  • Events: timeupdate, play, pause, ended, seeking, seeked, volumechange, canplay, etc.
  • UI: video.controls, fullscreen, Picture-in-Picture, CSS styling
  • Text tracks: video.textTracks (engine adds subtitle <track> elements automatically)

What's different (use the engine)

Instead of Use
video.src = '...' engine.loadFile(file) or engine.loadUrl(url)
external subtitle <track> plumbing engine.loadExternalSubtitle(file)
engine.destroy() to release resources
engine.addEventListener('loading' | 'ready' | 'error', ...) for engine lifecycle
engine.phase, engine.loading, engine.subtitleTracks for engine state

Constructor

const engine = new PlaysVideoEngine(video: HTMLVideoElement);

Attaches to a <video> element. Respects the autoplay attribute — if set, playback starts automatically after loading. Otherwise, call video.play() manually.

Methods

loadFile(file: File): void

Load a video from a local File object (from <input type="file">, drag-and-drop, File Handling API, etc.).

input.addEventListener('change', () => {
  const file = input.files?.[0];
  if (file) engine.loadFile(file);
});

loadUrl(url: string): void

Load a video from an HTTP URL. The server must support CORS and HTTP range requests.

engine.loadUrl('https://example.com/video.mkv');

loadExternalSubtitle(file: File, options?): Promise<void>

Attach one user-provided external subtitle file to the current video and make it the active text track. Loading a new external subtitle replaces the previous external subtitle, but leaves embedded tracks intact.

Currently supports .srt and .vtt.

const subFile = subtitleInput.files?.[0];
if (subFile) {
  await engine.loadExternalSubtitle(subFile);
}

clearExternalSubtitles(): void

Remove any previously loaded external subtitle tracks.

engine.clearExternalSubtitles();

destroy(): void

Tear down the engine, terminate the worker, and release all resources. Safe to call multiple times.

Properties

Property Type Description
video HTMLVideoElement The underlying video element (readonly)
phase 'idle' | 'demuxing' | 'ready' | 'error' Current engine state
loading boolean true while demuxing (shorthand for phase === 'demuxing')
totalSegments number Number of segments in the plan (0 until ready)
durationSec number Video duration in seconds (0 until ready)
subtitleTracks SubtitleTrackInfo[] Embedded subtitle tracks (empty until ready)

Engine Events

The engine extends EventTarget and emits a small number of lifecycle events. These are separate from the native video events (which fire on the <video> element as usual).

loading

Fired when a file or URL starts loading.

engine.addEventListener('loading', (e) => {
  // e.detail.file — the File object (if loadFile was called)
  // e.detail.url  — the URL string (if loadUrl was called)
});

ready

Fired when demuxing is complete and playback begins.

engine.addEventListener('ready', (e) => {
  console.log(e.detail.totalSegments);  // number of segments
  console.log(e.detail.durationSec);    // duration in seconds
  console.log(e.detail.subtitleTracks); // SubtitleTrackInfo[]
});

error

Fired on fatal errors (demux failure, unsupported format, playback error, etc.).

engine.addEventListener('error', (e) => {
  console.error(e.detail.message);
});

Types

SubtitleTrackInfo

interface SubtitleTrackInfo {
  index: number;        // 0-based track index
  codec: string;        // original codec (e.g. 'subrip', 'ass')
  language: string;     // ISO 639-2/T code (e.g. 'eng', 'spa', 'und')
  name: string | null;  // user-visible track name, if any
  disposition: {
    default: boolean;
    forced: boolean;
    hearingImpaired: boolean;
  };
}

Full example

import { PlaysVideoEngine } from 'playsvideo';

const video = document.querySelector('video')!;
const status = document.querySelector('#status')!;
const engine = new PlaysVideoEngine(video);

// Engine lifecycle events
engine.addEventListener('loading', () => {
  status.textContent = 'Loading...';
});

engine.addEventListener('ready', (e) => {
  const { totalSegments, durationSec, subtitleTracks } = e.detail;
  const mins = Math.floor(durationSec / 60);
  const secs = Math.floor(durationSec % 60);
  status.textContent = `${totalSegments} segments, ${mins}:${secs.toString().padStart(2, '0')}`;
  if (subtitleTracks.length > 0) {
    console.log(`${subtitleTracks.length} subtitle tracks`);
  }
});

engine.addEventListener('error', (e) => {
  status.textContent = `Error: ${e.detail.message}`;
});

// Standard <video> events work as usual
video.addEventListener('timeupdate', () => {
  console.log(`Position: ${video.currentTime.toFixed(1)}s`);
});

// Load from file input
document.querySelector('input[type=file]')!.addEventListener('change', (e) => {
  const file = (e.target as HTMLInputElement).files?.[0];
  if (file) engine.loadFile(file);
});

// Or load from URL
engine.loadUrl('https://example.com/video.mkv');

Notes

  • Calling loadFile or loadUrl while already playing will cleanly tear down the previous session and start a new one.
  • Embedded subtitles (SRT, ASS/SSA) are automatically extracted and attached as <track> elements on the video.
  • External subtitle import currently supports .srt and .vtt. External .ass/.ssa files are not yet rendered because the engine's external-file path converts imported subtitles to WebVTT, and the ASS/SSA parser path is still a placeholder.
  • Audio codecs unsupported by the active playback path are transcoded to AAC on the fly using a lightweight ffmpeg.wasm build that is lazy-loaded only when needed. Passthrough/native playback and remuxed HLS/MSE playback are evaluated separately, so a codec may be allowed for direct playback but still be transcoded in the remux pipeline.
  • URL loading uses HTTP range requests for random access — the entire file is not downloaded upfront.