Events and errors

mattebox@0.12.1

This chapter covers events, errors, and the recovery stage.

Events

Subscribe with on. It returns the unsubscribe function.

const off = engine.on('playback:stalled', ({ at }) => console.log('stalled at', at));
off();

Media element events stay on the element. Subscribe to timeupdate, waiting, ended, and the rest there.

Event Meaning
tracks:changed The set of available tracks changed
tracks:selected A track was selected or deselected: { contentType, trackId }, trackId null on deselect
playback:stalled The decoder stopped inside buffered data, or waiting fired
playback:ended The presentation reached its end
lifecycle:suspended engine.suspend() took effect
lifecycle:resumed engine.resume() took effect
quality:constraints-unsatisfiable Constraints were dropped to keep a playable rendition
quality:pin-unsatisfiable A pinned rendition is not allowed
quality:coupling-unsatisfiable No rendition satisfies the active audio and text tracks
quota:exhausted Eviction could not free enough buffer
command:rejected The reducer refused a command in the current state
recovery:* What the recovery stage did. See below
drm:* Key system and key status. See chapter 07
steering:failover The steering stage switched pathway
codecprobe:detected The probe read a codec string from an init segment
error Every error, fatal or not

Errors

An error is a plain object with a category, a code, and two flags.

engine.on('error', (error) => {
  console.log(error.category, error.code, error.fatal, error.recoverable);
});
Category Codes
network NETWORK_FAILED, NETWORK_TIMEOUT, NETWORK_HTTP_STATUS, NETWORK_ABORTED
manifest MANIFEST_PARSE_FAILED, MANIFEST_UNSUPPORTED, MANIFEST_EMPTY, MANIFEST_REFRESH_FAILED
media MEDIA_APPEND_FAILED, MEDIA_CONTAINER_INVALID, MEDIA_QUOTA_EXCEEDED, MEDIA_CODEC_UNSUPPORTED, MEDIA_CODEC_MISMATCH, MEDIA_DECODE_ERROR, MEDIA_SOURCE_CLOSED
drm DRM_KEY_SYSTEM_UNAVAILABLE, DRM_LICENSE_FAILED, DRM_KEY_EXPIRED, DRM_KEY_STATUS_ERROR, DRM_OUTPUT_RESTRICTED, DRM_SESSION_FAILED, DRM_INIT_DATA_INVALID
config CONFIG_INVALID, CONFIG_STAGE_REQUIREMENT_MISSING, CONFIG_ELEMENT_OCCUPIED
internal INTERNAL_ASSERTION

fatal means playback halted. recoverable means the recovery stage could act on it.

MANIFEST_UNSUPPORTED means no loaded adapter can parse the source: a mimeType none of them handles (reported before any request), an audio or video Content-Type on the manifest response, or bytes none of them recognizes. A manifest fetch that fails after retries is fatal too, with its network code. Chapter 03 shows the fallback to native playback.

MEDIA_CODEC_MISMATCH is a warning, never fatal. The codec-probe stage reports it once per rendition when an init segment holds another codec than the manifest declares. Its context has kind, declared, and probed.

kind The manifest declares Example
family Another codec avc1.640028 for HEVC
profile The same codec in another profile avc1.4d401f (Main) for High

A difference in level only is not reported. Chrome can refuse the appends of a buffer typed with the wrong profile. When an append fails after such a mismatch, the MEDIA_APPEND_FAILED error’s context has declaredType and probedType, and engine.error keeps them.

Fatal errors

A fatal error stops the engine and is kept on engine.error with the trace attached. Show a message, log the trace, and call load again to retry.

engine.on('error', (error) => {
  if (!error.fatal) return;
  report(engine.error); // includes the trace
  showRetry();
});

The element’s own MediaError is reported the same way, as MEDIA_DECODE_ERROR or MEDIA_CODEC_UNSUPPORTED. The engine never restarts on its own.

The recovery stage

The kernel retries network requests. recovery handles what retries don’t, and every action it takes is in the trace.

Situation Action Event
A segment fails twice on one rendition Excludes the rendition, readmits it after a backoff recovery:excluded, recovery:readmitted
A segment fails across renditions Seeks over the hole recovery:skip
A stall with data ahead Nudges the playhead recovery:nudge
A stall that a nudge did not fix Flushes from the playhead and refetches recovery:flush
A small gap in the buffer Jumps it recovery:gap-jump
import recovery from 'mattebox/stages/recovery';

recovery({
  excludeAfter: 2,          // failures on one rendition before exclusion
  readmitAfterSeconds: 15,  // how long an excluded rendition sits out
  skipAfter: 3,             // failures across renditions before a skip
  maxGapSeconds: 2,         // largest hole a stall may jump
});

Load it in production. Skip it only when you would rather see the failure.

Playlists and indexes that fail

The protocol stages handle a media playlist or segment index that fails after the transport’s retries, or that holds no segment. Its renditions sit out, and playback moves to the others. After 15 seconds they return, and the playlist or index is fetched again when the selection needs it. The load fails only when a video track has no rendition left.

Stage Fails on
hls-cmaf A media playlist that fails or is empty
dash-cmaf A segment index that fails or is empty
hls-live A live playlist whose reloads keep failing

Each takes unavailableRetrySeconds. Infinity keeps the renditions out until the next load.

hlsCmaf({ unavailableRetrySeconds: 30 });
dashCmaf({ unavailableRetrySeconds: 30 });
hlsLive({ unavailableRetrySeconds: Infinity });

Example

A player with a status line and a retry button.

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

const engine = mattebox({ stages: [hlsCmaf(), recovery()] });
let lastUrl = '';

engine.on('recovery:excluded', ({ renditionId }) => {
  status.textContent = `rendition ${renditionId} excluded`;
});
engine.on('error', (error) => {
  status.textContent = `${error.category}: ${error.code}`;
  retry.hidden = !error.fatal;
});
retry.onclick = () => engine.load(lastUrl);

function play(url) {
  lastUrl = url;
  engine.load(url);
}

Next: 10 Thumbnails.

Read this page's source on GitHub