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

@scarlett-player/core

v1.23.0

Published

Core player with plugin system for Scarlett Player

Downloads

5,880

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/core

Usage

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 destroy

The 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 sanitization

Provider 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 null

The 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