▶ composable-video-player

Sources

You can give the player a source in four forms:

createPlayer({ container: 'player', source: 'https://example.com/video.mp4' });

player.load('[720p]https://example.com/720.mp4,[1080p]https://example.com/1080.mp4');
player.command('load', { qualities: [{ label: '', tracks: [{ label: '', urls: ['https://example.com/a.mp4'] }] }] });

Source strings

The simplest source is a single URL. The full syntax combines these pieces:

Syntax Meaning Example
url One file https://cdn/video.mp4
a,b Several qualities (comma-separated) https://cdn/480.mp4,https://cdn/1080.mp4
[Label]url Quality label [480p]https://cdn/480.mp4,[1080p]https://cdn/1080.mp4
{Label}url;{Label}url Audio tracks inside one quality (semicolon-separated) {English}https://cdn/en.mp4;{French}https://cdn/fr.mp4
a or b Fallback chain: try a, then b if it fails https://cdn/v.webm or https://cdn/v.mp4
a and b Random pick of one alternative (load balancing) https://cdn1/v.mp4 and https://cdn2/v.mp4
url[seek:N] Start at N seconds https://cdn/v.mp4[seek:90]
(subtitle:[Label]url,[Label]url) Inline subtitles, anywhere in the string https://cdn/v.mp4(subtitle:[English]https://cdn/en.vtt)
[<<<…] PlayerJS group separators, ignored

These combine. Each quality can have audio tracks, and each track can have a fallback chain:

const source =
  '[720p]{English}https://cdn/720-en.webm or https://cdn/720-en.mp4;{French}https://cdn/720-fr.mp4,' +
  '[1080p]{English}https://cdn/1080-en.mp4;{French}https://cdn/1080-fr.mp4' +
  '(subtitle:[English]https://cdn/en.vtt,[Français]https://cdn/fr.srt)';

Rules and behavior:

Fallback and errors

When a URL fails, the player emits loaderror ({ url, error }) and tries the next URL in the chain. When the whole chain fails, it waits and retries according to the reload option (by default no retries for VOD, 3 retries 2 s apart for live). After that it sets state.status = 'error', emits error, and the default layout shows the error overlay with a Retry button (reload, which resumes from the current position).

Error kinds (PlayerError.kind): network, decode, not-found, unsupported, unknown.

SourceSpec objects

Under the hood every source string is parsed into a SourceSpec. You can pass one directly. parseSource() is exported if you want to build on the string syntax.

import type { SourceSpecInput } from 'composable-video-player';

const spec: SourceSpecInput = {
  qualities: [
    { label: '720p', tracks: [{ label: 'English', urls: ['https://cdn/720,en.webm', 'https://cdn/720,en.mp4'] }] },
    { label: '1080p', tracks: [{ label: 'English', urls: ['https://cdn/1080,en.mp4'] }] },
  ],
  subtitles: [{ label: 'English', url: 'https://cdn/en.vtt' }],
  start: 30,
  key: 'movie-42', // stable identity for position memory
};
player.load(spec);
Field Type Description
qualities { label: string; tracks: { label: string; urls: string[] }[] }[] Required, non-empty. urls is the fallback chain of one audio track.
subtitles { label: string; url: string }[] Optional subtitle files.
start number Optional start time in seconds.
forceType 'hls' Optional. Treat the URLs as HLS. The forceType player option applies when this is not set.
key string Optional stable identity. The memory plugin uses it for the saved position (string sources use the raw string).

Other helpers: parseSource(str, { qualities? }), parseSubtitleList('[En]a.vtt,[Fr]b.vtt'), isPlaylistInput(input), and sourceKey(spec).

Playlists

With the playlist plugin (included in createPlayer), source, load(), and the PlayerJS file option also accept playlists:

[
  { "title": "Trailer", "file": "https://cdn/trailer.mp4", "id": "trailer", "poster": "https://cdn/trailer.jpg" },
  {
    "title": "Season 1",
    "folder": [
      { "title": "Episode 1", "file": "[720p]https://cdn/s1e1-720.mp4,[1080p]https://cdn/s1e1-1080.mp4", "id": "s1e1",
        "subtitle": "[English]https://cdn/s1e1-en.vtt", "thumbnails": "https://cdn/s1e1-thumbs.vtt" },
      { "title": "Episode 2", "file": "https://cdn/s1e2/master.m3u8", "id": "s1e2", "start": 15 }
    ]
  }
]

Item fields

Field Type Description
file string Source string (full syntax). Items with neither file nor folder are skipped.
title string Shown in the panel and as the player title.
poster string Poster for the item, also shown as a small image in the panel.
subtitle string [Label]url,[Label]url, added to any inline subtitles.
thumbnails string Thumbnails VTT for this item. Items without it fall back to the thumbnails option.
id string Used by plstart, playItem, api('play', 'id:…'), and api('find', …).
start number | 'continue' Start time, or 'continue' to keep the current position (for example when switching between dubs of the same video).
folder PlaylistItem[] Nested items, rendered as collapsible groups. Folders can nest.

Extra fields are allowed and kept on the item.

Behavior

Commands: next, prev, playItem(idOrIndex, autoplay = true), push(items), setPlaylist(input), togglePlaylist, setAutonext(on), setPlaylistLoop(on). State: state.playlist ({ items, flat, index, open }), state.autonext, state.playlistLoop. Events: playlist (items), next, previous.

HLS

URLs ending in .m3u8 (or any URL when forceType: 'hls' is set) are HLS. The player picks the engine per browser:

Environment Engine
MediaSource available (Chrome, Edge, Firefox, desktop Safari, Android, iPadOS) hls.js, loaded on first use
No MediaSource but native HLS (iPhone) The browser's native HLS via <video src>
Neither unsupported error (the next fallback URL is tried)

hls.js is preferred even where the browser reports native HLS. Native playback exposes no renditions, so there would be no quality menu.

About the hls.js loader:

With hls.js:

Live streams (duration Infinity) set state.live = true. The default layout then hides the timeline and duration and shows a LIVE badge, and seek is ignored.

DASH (.mpd) is not supported by the built-in providers. A plugin can add a provider (see Plugins).

Subtitles

Subtitles can come from:

In the subtitle option and a playlist item's subtitle, a URL without a [Label] is labelled with its file name (en.vtt becomes en). Inline (subtitle:…) entries must have labels.

Formats: WebVTT and SRT, detected automatically (any block with a --> timing line is a cue). Supported timestamps: hh:mm:ss.mmm, mm:ss.mmm, and hh:mm:ss,mmm.

WEBVTT

00:00:01.000 --> 00:00:04.000
Hello! This is <b>bold</b> and <i>italic</i>.
1
00:00:01,000 --> 00:00:04,000
Hello from an SRT file.

Commands: setTextTrack(index) (-1 = off), toggleSubtitles(), addSubtitle(label, url, select?). State: textTracks, textTrack. Event: subtitle (label or 'off').

Thumbnails

Timeline hover previews come from a WebVTT file whose cues point at images, usually rectangles of a sprite sheet (#xywh=x,y,width,height):

WEBVTT

00:00:00.000 --> 00:00:05.000
thumbs.jpg#xywh=0,0,160,90

00:00:05.000 --> 00:00:10.000
thumbs.jpg#xywh=160,0,160,90

00:00:10.000 --> 00:00:15.000
https://cdn.example.com/other-sprite.jpg#xywh=0,0,160,90

Without a thumbnails file, hovering the timeline still shows the time under the pointer.