@scarlett-player/core
v1.23.0
Published
Core player with plugin system for Scarlett Player
Downloads
5,880
Maintainers
Readme
@scarlett-player/core
Core player engine for Scarlett Player - a lightweight, plugin-based video player.
Docs: scarlettplayer.com/documentation · For AI coding agents: llms.txt (index) and llms-full.txt (every guide as one Markdown file)
Installation
npm install @scarlett-player/coreUsage
import { createPlayer } from '@scarlett-player/core';
import { createHLSPlugin } from '@scarlett-player/hls';
import { uiPlugin } from '@scarlett-player/ui';
// createPlayer() constructs, initialises every plugin, and loads `src`.
const player = await createPlayer({
container: document.getElementById('player'),
src: 'https://example.com/video.m3u8',
plugins: [createHLSPlugin(), uiPlugin()],
});API
createPlayer(options)
const player = await createPlayer({
container: HTMLElement | string, // Required: container element or CSS selector
src?: string, // Initial source URL
poster?: string, // Poster image URL
autoplay?: boolean, // Auto-play on load (default: false)
muted?: boolean, // Start muted (default: false)
loop?: boolean, // Loop playback (default: false)
volume?: number, // Initial volume 0-1 (default: 1)
plugins?: Plugin[], // Plugins to register
logLevel?: 'debug' | 'info' | 'warn' | 'error',
});Methods
player.init() // Initialise plugins and load `src` (createPlayer calls this)
player.load(src) // Load a source (initialises the player first if needed)
player.load(src, { autoplay }) // Same, with `autoplay` overriding the option for this load
player.unload() // Leave the current source, keep the player (see below)
player.play() // Start playback
player.pause() // Pause playback
player.seek(time) // Seek to time in seconds
player.setVolume(0-1) // Set volume
player.setMuted(boolean) // Mute/unmute
player.setPoster(url) // Change the poster ('' clears it); load() never touches it
player.setPlaybackRate(rate) // Set playback speed
player.setAutoplay(boolean) // Change autoplay after construction
player.requestFullscreen() // Enter fullscreen
player.exitFullscreen() // Exit fullscreen
player.toggleFullscreen() // Toggle fullscreen
player.seekToLive() // Jump to the live edge (live streams)
player.getQualities() // QualityLevel[] from the active provider
player.setQuality(index) // Select a level (-1 for auto)
player.getCurrentQuality() // Current level index (-1 when auto)
player.requestAirPlay() // Show the AirPlay picker (needs the airplay plugin)
player.requestChromecast() // Start a Cast session (needs the chromecast plugin)
player.stopCasting() // End the active cast session
player.getState() // Read-only snapshot of the state store
player.getDiagnostics() // Synchronous, typed and bounded troubleshooting snapshot
player.getPlugin(id) // Registered plugin instance, or null
player.registerPlugin(plugin) // Register a plugin after construction
player.on(event, handler) // Subscribe; returns an unsubscribe function
player.once(event, handler) // Subscribe for one emission
player.destroy() // Cleanup and destroyThe second argument of load() is exported as the LoadOptions type
({ autoplay?: boolean }), for hosts and wrappers that pass it along:
import type { LoadOptions } from '@scarlett-player/core';
const next: LoadOptions = { autoplay: false }; // stay paused for this load only
await player.load('next.m3u8', next);unload()
player.unload(): Promise<void> leaves the current source without destroying
the player. It supersedes a load() still in flight, destroys the active
provider (closing its connection or pipeline and removing its media element; a
provider still inside its init() is destroyed as soon as that settles), then
applies the unloaded state and emits source:unloaded with { src }, the
source the last load() asked for. The next load() works as on a fresh
instance.
await player.unload(); // leave the stream, keep the player
await player.load('next.m3u8');The core unloaded-state reset sets source, buffered and error to null,
playbackState to 'idle', playing, ended, buffering, waiting and
seeking to false, paused to true, currentTime, duration and
bufferedAmount to 0, and mediaType to 'unknown'. It empties qualities,
audioTracks and textTracks, sets currentQuality, currentAudioTrack and
currentTextTrack to null, and resets live and liveEdge to false,
seekableRange to null, liveLatency to 0 and lowLatencyMode to false.
That reset leaves the other core keys alone: poster, title, chapters,
currentChapter, thumbnails, volume, muted, playbackRate, autoplay,
loop, fullscreen, pip, controlsVisible, bandwidth, airplayAvailable,
airplayActive, chromecastAvailable, chromecastActive, interacting,
hovering and focused. Plugins can still update their state during teardown.
With nothing loaded or loading it is a no-op and emits nothing. A load()
called before the unload finishes owns the state: the unload still destroys the
old provider, but applies no state and emits no source:unloaded, and the
load starts its provider only after that teardown. It rejects
once destroy() has been called; use destroy() to discard the player for
good.
getDiagnostics()
player.getDiagnostics(): PlayerDiagnosticsSnapshot returns a synchronous, typed,
bounded and side-effect free snapshot of playback state, recent structured errors, and
ready-provider contributions. Usable before playback, during outages, and after unload/destroy:
const snapshot = player.getDiagnostics();
console.log(snapshot.schemaVersion); // 1
console.log(snapshot.playbackState); // Whitelisted playback state projection
console.log(snapshot.errors); // Bounded recent structured errors (newest 20)
console.log(snapshot.providers); // Ready provider contributions keyed by plugin ID
console.log(snapshot.truncatedProviders); // Providers whose contribution lost values to sanitizationProvider contributions are untrusted: core keeps finite numbers, booleans, null
and a short allowlist of categorical strings (SAFE_DIAGNOSTIC_STRINGS) inside plain
objects and arrays, and drops everything else, including every other string. Caps per
contribution: 4 levels of nesting (primitives included), 64 keys per object, 50 items per
array and 500 values in total. Keys must be short identifiers ([A-Za-z][A-Za-z0-9_]{0,39});
identity and location-like keys (id, userId, viewerId, uid, email, ip, url,
uri, src, href, key) and token-, secret- or credential-like keys are dropped.
Typed arrays, Map, Set, Date, class instances and DOM nodes are dropped.
A provider whose hook throws or returns a promise appears as { unavailable: true }.
State getters
player.playing // boolean
player.paused // boolean
player.currentTime // seconds
player.duration // seconds
player.volume // 0-1
player.muted // boolean
player.poster // Current poster URL, '' when there is none
player.playbackRate // number
player.bufferedAmount // seconds buffered ahead
player.autoplay // boolean
player.fullscreen // boolean
player.live // boolean
player.currentProvider // Active provider plugin, or nullThe poster is state, not an element attribute: the provider plugins mirror it
onto the media element and re-apply it whenever it changes, so setPoster()
takes effect on a player that is already running. load() leaves it alone,
because the poster belongs to whoever set it (a consumer, or the playlist
plugin on a track change) and is written before the load it goes with.
Events
player.on('player:ready', () => {}); // Fires once, at the end of the first init()/load()
player.on('playback:play', () => {});
player.on('playback:pause', () => {});
player.on('playback:ended', () => {});
player.on('playback:timeupdate', ({ currentTime }) => {});
player.on('playback:seeking', ({ time }) => {});
player.on('volume:change', ({ volume, muted }) => {});
player.on('fullscreen:change', ({ fullscreen }) => {});
player.on('quality:change', ({ quality, auto }) => {}); // quality: 'level-<index>' (an id in `qualities`) or 'auto'
player.on('media:segment', ({ durationMs, bytes, ok, kind }) => {}); // hls.js segment request measurement
player.on('error', (error) => {}); // Structured PlayerError { code, message, fatal, detail? }; detail.reconnecting: true when a self-heal follows
player.on('error:reconnecting', ({ attempt, delayMs }) => {}); // Self-heal attempt scheduled
player.on('error:recovered', () => {}); // Self-heal succeeded, playback resumed
player.on('error:retry', ({ src }) => {}); // Viewer pressed Try Again
player.on('source:unloaded', ({ src }) => {}); // unload() finished (not sent by a no-op or superseded unload)player:ready is emitted once, at the end of the first initialisation pass, so
a listener has to be attached before init() or load() runs. With
createPlayer() the returned promise is the readiness signal and the event is
redundant.
media:segment is a typed PlayerEventMap event for providers that fetch
segments themselves. durationMs is the hls.js fragment load interval in
milliseconds, bytes is its loaded-byte count (including partial bytes on a
failure), ok is false for a non-fatal fragment load error/timeout, and
kind is 'main' | 'audio' | 'subtitle'. When hls.js has no valid measurements,
no event is sent; native HLS, progressive MP4 and WHEP do not report segment
measurements. Absence means unavailable, not zero.
Plugins
The core package provides the foundation. Add plugins for functionality:
@scarlett-player/hls- HLS streaming@scarlett-player/native- MP4, WebM, MOV, MKV, and progressive audio@scarlett-player/ui- Player controls@scarlett-player/audio-ui- Compact audio player UI@scarlett-player/airplay- AirPlay casting@scarlett-player/chromecast- Chromecast casting@scarlett-player/playlist- Playlist management@scarlett-player/analytics- QoE metrics and beacons@scarlett-player/media-session- Lock screen and media key controls@scarlett-player/captions- WebVTT captions@scarlett-player/chapters- Chapter markers@scarlett-player/gestures- Touch gestures@scarlett-player/watermark- Anti-piracy overlay@scarlett-player/share- Share sheet and embed codes
Framework and drop-in wrappers: @scarlett-player/vue and
@scarlett-player/embed.
License
MIT
