Getting started

mattebox@0.14.0

This chapter goes from install to a playing HLS stream.

Requirements

Mattebox runs in browsers with Media Source Extensions. That covers Chrome, Edge, Firefox, Safari on macOS and iPadOS, and Safari on iOS 17.1 or later through ManagedMediaSource. The package is ESM and has no runtime dependencies.

Install

npm install mattebox

Create an engine

An engine is the kernel plus the stages you pass in. The kernel alone attaches and manages buffers, but it cannot read a manifest. Add a protocol adapter for the format you stream.

import { mattebox } from 'mattebox';
import hlsCmaf from 'mattebox/protocols/hls-cmaf';

const engine = mattebox({ stages: [hlsCmaf()] });

Every stage is a factory function. It returns a plain object, and nothing runs until attach. Stages you don’t import never end up in your bundle.

Attach and load

Attach the engine to a media element, then load a manifest URL.

const video = document.querySelector('video');

await engine.attach(video);
engine.load('https://example.com/vod/master.m3u8');
video.play();

The engine fetches the manifest and the matching adapter parses it. Playback starts on the lowest rendition and stays there until you add the abr stage, see chapter 05.

The media element

Mattebox does not wrap the media element. Use the element for everything the browser already does.

Need Use
Play, pause, seek, volume The element
Buffered and seekable The element
Native controls The element’s controls attribute
Renditions and quality engine.quality
Track selection engine.tracks
Throughput and trace engine.stats
The last fatal error engine.error

mattebox.from(video) returns the engine attached to an element.

import { mattebox } from 'mattebox';

const engine = mattebox.from(video);

The CDN bundles expose the same mattebox as a global, plus the bundle’s preset and stage factories. Chapter 14 has the script tag.

Preload the next source

engine.preload(url) fetches a source’s manifest while the current source plays, so the next load of the same URL starts without that round trip.

await engine.preload(next.url);
// later, when the current item ends
engine.load(next.url);

With segments, the engine also fetches the media playlists, the init segments, and that many media segments per track. The load then starts with no request, which suits a feed of short clips.

await engine.preload(next.url, { segments: 3, keepSeconds: 60 });
Option Meaning Default
segments Media segments to fetch per track, from the start. 0 is none 0
keepSeconds Seconds each response is kept 30
  • The responses are kept in memory, at most 32 of them and 48 MB.
  • A response answers one request. The load that uses it removes it.
  • The requests go through your request hooks, as a load’s would.
  • The promise rejects when the manifest fails, or when you pass a mimeType no composed protocol reads. The load then fetches as usual.
  • segments needs an attached engine. Before attach, the manifest alone is preloaded.
  • segments is for on-demand fragmented MP4. A live stream stops at the playlists, because its edge moves before the load.
  • With DRM, the start still waits for the license.
  • One engine plays one source. Your playlist decides what comes next and when to preload it.

Unload and detach

Unload stops fetching and clears the buffers. Detach removes the engine from the element and runs every stage’s teardown. Both are safe to call twice. Loading another URL replaces the current source, so a player that switches streams only ever calls load.

engine.unload();
await engine.detach();

Call detach when the element leaves the page. Otherwise a single-page app leaks a media pipeline on every navigation.

Suspend and resume

Suspend stops every request while the source stays loaded: the element stays attached, the buffers stay, and no playlist reload, segment fetch, or steering refresh runs until resume. Use it when the element is paused for a long time and nothing local will play, such as while a Chromecast or AirPlay receiver plays the same content, or while the page is in the background. A paused live stream would otherwise reload its playlists for as long as the pause lasts.

video.pause();
engine.suspend();
// later
engine.resume();
video.currentTime = remoteTime;
await video.play();

Suspend expects a paused element and is accepted in the ready phase only. Anywhere else, such as while the manifest is still loading, the reducer rejects it with a command:rejected event and nothing changes. Resume refills the buffer from the playhead. A live presentation reloads its playlists first and rejoins at the live edge, the way a fresh load does, so engine.live.edge reads null while suspended.

Suspend does not refresh signed URLs: a token that expires makes the next request fail. It keeps DRM key sessions open unless you set releaseOnSuspend (see chapter 07). A license that expires during the freeze renews when playback resumes.

The engine emits lifecycle:suspended and lifecycle:resumed.

Example

import { mattebox } from 'mattebox';
import hlsCmaf from 'mattebox/protocols/hls-cmaf';

const video = document.querySelector('video');
const engine = mattebox({ stages: [hlsCmaf()] });

engine.on('error', (error) => console.error(error));

await engine.attach(video);
engine.load('https://example.com/vod/master.m3u8');

window.addEventListener('pagehide', () => engine.detach());

Next: 02 Presets and stages.

Read this page's source on GitHub