Player reference
Every attribute, property, method and event of the player element, the play() and createPlayer options, and the React props.
Taken from js/player/src/element.js, play.js and player.js, the types in
js/player/types and js/react on commit 79b96c7 of the SDK repository.
If a value here disagrees with the package you installed, the package wins;
this page is checked against the source of the release it ships with.
How to use the player is in Embed the player. The
full generated API of every module, including internals this page leaves out,
is docs/api/JS-API.md in the SDK repository (generated by tools/jsapi.mjs).
There are three layers, each a thin shell over the next:
| Layer | Use it when |
|---|---|
<nearglass-player> | you want the whole player in one tag, with its own bar |
play() | you want your own page around the video: admission, seats, relay choice and the LL-HLS fallback, without the bar |
createPlayer() | you already have a descriptor and want only the playback engine |
The @nearglass/react component wraps the element (React props).
The nearglass-player element
Load it once with import '@nearglass/player/element.js'; or a
<script type="module">. Importing it defines the tag. The element needs one
of: token-url, a descriptor property, or api with stream and
publishable-key. With none of them the bar reads "Set token-url, or api,
stream and publishable-key." and nothing starts.
Attributes
Changing any attribute restarts the player (once per task, however many you
set), except background-audio, which changes on the running player.
| Attribute | Type | Default | What |
|---|---|---|---|
api | URL | none | the control API's base address. With stream and publishable-key: public admission |
stream | string | none | the stream id (st_...) |
publishable-key | string | none | your pk_... key |
token-url | URL | none | server admission: your backend's route, POSTed { seat, join } for each viewer token (Viewer tokens) |
hls-url | URL | the descriptor's hls | the LL-HLS playlist, when the descriptor names none |
auto-join | boolean (presence) | off | take an offered seat without pressing Join |
mode | latency, smooth, paced | latency | how frames are presented (Modes) |
background-audio | boolean (presence) | off | keep the sound playing while the page is hidden |
Properties
| Property | Type | Default | What |
|---|---|---|---|
controller | PlayController or null | null | the running play() controller. Read only |
hls | Hls class or null | globalThis.Hls, else null | hls.js, for browsers without native HLS. Not bundled |
descriptor | object, function or null | null | server admission without a route: the descriptor JSON, or a function returning it (or a promise of it), called again for each fresh token. Setting it restarts the player |
backgroundAudio | boolean | false | the background-audio attribute, as a property |
Methods
| Method | Returns | What |
|---|---|---|
join() | nothing | take the seat on offer. Does nothing outside the offer state |
setMuted(muted) | Promise<boolean> | mute or unmute. Call it from a click: a user gesture is what lets the browser start sound. Resolves true when the change took |
Events
Events fire on the element and carry their data in detail.
| Event | detail | When |
|---|---|---|
statechange | { state, ...info } | the state changed (States) |
modechange | { mode, label, engine?, reason? } | the path changed between moq, ll-hls and none |
note | text | a status line for the page to show |
audiochange | { state, reason } | the sound changed (Sound states) |
Styling
The shadow DOM has these parts, for ::part():
| Part | What |
|---|---|
video | the box holding the picture |
canvas | the MoQ picture |
fallback | the LL-HLS <video> |
bar | the status bar under the picture |
mode | the mode label in the bar |
status | the status text in the bar |
join | the Join button |
sound | the sound button |
| Custom property | Default | What |
|---|---|---|
--nearglass-player-bar-bg | rgba(127,127,127,.12) | the bar's background |
--nearglass-player-bar-fg | currentColor | the bar's text |
--nearglass-player-accent | #2f6fde | the live dot, the Join button, button outlines |
--nearglass-player-radius | 6px | corner radius |
The element is display: block (and hidden by the hidden attribute), and
inherits the page's font. The picture is 16:9. Nothing is drawn over it.
What the bar says
| State | Bar text |
|---|---|
admitting | Getting a seat... |
waiting, line | In line, number N... (without the number when the API gives none) |
waiting, full | The stream is full. Waiting for a seat... |
offer | A seat is free. Join within N s. (the Join button shows) |
connecting | Connecting... |
seat-lost | Your seat was given up. Reload to watch again. |
unsupported | the reason, or "This browser cannot play this stream." |
error | Cannot play: and the message, or "Cannot play." |
live, fallback, closed | empty |
joining, stalled | unchanged: the text from note stays |
The mode label reads "Low latency" for moq and a "Compatibility mode" label
for ll-hls. The sound button reads "Sound on" while muted and "Sound off"
while playing.
play()
import { play } from '@nearglass/player/play.js';
const p = play({ api: 'https://api.nearglass.com', stream: 'st_...', publishableKey: 'pk_...', canvas, video });
p.on('state', (state, info) => {});
p.join();
p.close();play() returns a controller at once and starts on the next microtask, so
listeners added straight after the call miss nothing.
Options
| Option | Type | Default | What |
|---|---|---|---|
api | string | none | the control API's base address. Trailing slashes are dropped |
stream | string | none | the stream id |
publishableKey | string | none | public admission: sent as the bearer key to POST /v1/play/{id}/admit |
tokenUrl | string | none | server admission through your route. Ignored when descriptor is set |
descriptor | object or function | none | server admission with a descriptor in hand. A function is called for each fresh token |
autoJoin | boolean | false | take an offered seat without join(). A tab nobody watches should not hold a seat |
canvas | HTMLCanvasElement | required | where MoQ frames are drawn |
video | HTMLVideoElement | none | where the LL-HLS fallback plays. Without it there is no fallback |
hls | Hls class | none | hls.js, for browsers without native HLS |
hlsUrl | string | the descriptor's hls | the LL-HLS playlist |
track | string | video | the video track |
backgroundAudio | boolean | the descriptor's background_audio, else false | keep the sound on while the page is hidden. The fallback's sound follows the same rule |
player | object | {} | more createPlayer options (mode, audio, and so on) |
fetch | function | globalThis.fetch | for tests |
relayProbe | function | built in | probe(url, certHash), for tests |
createPlayer | function | the real one | for tests |
Give exactly one way to be admitted: api with stream and publishableKey,
or tokenUrl, or descriptor.
The controller
| Member | Type | What |
|---|---|---|
state | PlayState | the current state |
mode | moq, ll-hls, none | the current path |
seat | string or null | the seat id once seated |
engine | Player or null | the playback engine, once a descriptor is in hand |
on(event, fn) | controller | add a listener. Returns the controller |
join() | nothing | take the seat on offer |
setMuted(muted) | Promise<boolean> | as the element's |
setBackgroundAudio(on) | nothing | change the background-audio choice while playing |
stats() | object | { state, mode, seat, hls, engine, relay, region, rtt }: hls is native, hls.js or null; engine is the engine's stats() or null; relay and region name the relay in use; rtt is the measured round trip per relay |
close() | nothing | stop, give the seat up at once, and go to closed |
States
| State | info | Meaning |
|---|---|---|
idle | before anything has happened (only the state property reads this) | |
admitting | asking for a seat | |
waiting | { position }, or { full, edge, waitMs } | in line; or every seat or relay is full and the player asks again. full: true means a full stream or account; edge: true means every relay is full |
offer | { seconds } | a seat is free. join() takes it; the offer lapses after seconds and the player asks again |
connecting | opening a session on a relay | |
joining | session open, waiting for the first picture | |
live | playing over MoQ | |
stalled | no frames; reconnecting | |
fallback | { reason, permanent } | playing the LL-HLS stream (Fallback reasons) |
seat-lost | the API no longer knows the seat (HTTP 410). The player stops | |
unsupported | { reason, message } | this browser cannot play the stream, and there is no fallback either |
error | { message } | admission or the first descriptor failed. The player stops |
closed | close() was called |
Other events
| Event | Payload | When |
|---|---|---|
mode | { mode, label, engine?, reason? } | the path changed. engine is native or hls.js for ll-hls |
relay | { url, region, rttMs, failover, full } | the relay in use changed: first connect and each failover |
note | text | status text for the page |
audio-available | boolean | the catalog says whether the stream has sound. Show your sound control when true |
audio | { state, reason } | the sound changed (Sound states) |
Timing constants
| Export | Value | What |
|---|---|---|
CONNECT_DEADLINE_MS | 3000 | no relay session within this long: fall back to LL-HLS while MoQ keeps trying |
KEYFRAME_DEADLINE_MS | 3000 | a session but no picture within this much longer: the same |
ACCOUNT_FULL_RETRY_MS | 10000 | how often to ask again when every seat of the account is taken |
MODE_LABELS | object | the label for each path |
The deadlines apply to the start only. A stall after MoQ has played is the engine's to recover; it is not a reason to fall back.
A seat is kept by a heartbeat to its play_url every heartbeat_ms of the
descriptor (5000 when it names none). A hidden tab says so in the heartbeat,
and a tab that comes back beats at once. When the page closes, a beacon gives
the seat up.
Fallback reasons
reason | Meaning | Permanent |
|---|---|---|
no-webtransport | the browser has no WebTransport | yes |
no-webcodecs | no WebCodecs video decoder | yes |
no-h264 | the decoder does not play H.264 | yes |
webkit-flow-control | WebKit's WebTransport stops within minutes (Safari, and every browser on iPhone and iPad) | yes |
connect-timeout | no relay session in time | no |
no-keyframe | a session but no picture in time | no |
A permanent reason goes to LL-HLS at once and stays. For the others the
player keeps trying MoQ and switches back at the first live frame. The
engine itself also reports network after a stretch without frames; play()
does not act on it.
Sound states
The audio event (and the element's audiochange) carries state:
state | Meaning |
|---|---|
muted | the sound is off. reason is hidden when a hidden page turned it off (show your sound control off), no-output when a start never played, otherwise null |
starting | unmuted, the output is starting |
playing | sound is playing |
unavailable | the browser would not make a sound output |
A hidden page without backgroundAudio turns the sound off, and it stays
off until the viewer taps the sound control. That tap is also the gesture iOS
needs to play.
createPlayer
createPlayer(opts) is the playback engine: one MoQ session at a time, with
decode on arrival, the live-edge guard, token renewal, renditions and sound.
It draws on a canvas and has no other UI. Use it when you do your own
admission; play() calls it for you.
import { createPlayer } from '@nearglass/player/player.js';createPlayer options
Options that play() fills in itself (relay, relayFull, certHash,
tokens, namespace, track, canvas, backgroundAudio) are listed first.
Everything else can be given to play() through its player option.
| Option | Type | Default | What |
|---|---|---|---|
relay | string or function | required | the relay's WebTransport URL; or a function asked before every connect with { failed, full } |
relayFull | function | none | relayFull(retryMs): a relay refused the viewer as full; returns the ms until some relay may take it (0: another may now) |
certHash | Uint8Array or null | null | SHA-256 of a test (self-signed) relay certificate |
tokens | () => Promise<Token[]> | none | role tokens, fetched on every (re)connect. Token is { value, receivedAt? }; receivedAt is performance.now() when it arrived |
namespace | string array | required | the stream's namespace as segments, from the descriptor |
track | string | video | the video track |
canvas | HTMLCanvasElement | required | the windowed present target |
video | HTMLVideoElement | none | the <video> used for fullscreen scanout |
now | () => ms | Date.now | the publisher's clock |
mode | latency, smooth, paced | latency | presentation mode |
pumpClock | raf, now | raf | smooth and paced: time frames against the display refresh's timestamp, or the wall clock |
slew | boolean | false | follow a clock re-sync gradually instead of jumping |
presentLevels | boolean | true | smooth and paced: hold in whole display refreshes, with hysteresis |
holdRule | adaptive, p90 | adaptive | smooth and paced: how the hold is chosen |
present | worker | main thread | decode and draw in a worker |
videoWindowed | boolean | false | present through the <video> windowed too (for comparison runs) |
layout | function | the exact scanout fit | (streamW, streamH) => { left, top, width, height, fills, pure, note? } in CSS px, for fullscreen |
scanTargetClass | string | scan-target | class put on the canvas when it is the fullscreen target |
trace | function | none | (kind, row): per-frame rows |
renditions | auto or a track name | auto | auto moves between the catalog's renditions by itself; a name pins one |
rotate | auto, off, or { bytes, groups, ms } | auto | move to a fresh session before a limit is reached. auto rotates only on WebKit's WebTransport |
renew | auto, off | auto | move to a fresh session with fresh tokens before the token expires. off lets the relay end the session at expiry plus its grace, and the engine reconnects (tests) |
webkit | off, on | off | off treats WebKit's WebTransport as unusable; on tries it (testing) |
fallback | { hls, video } | none | when MoQ cannot play here, play this LL-HLS playlist in that <video> if the browser plays HLS natively |
audio | boolean or track name | true | the catalog's audio track, a named track, or none. Always starts muted |
audioWorkletUrl | string | built in | the audio worklet module's URL, for pages whose CSP blocks blob: worklets |
volume | number 0 to 1 | 1 | initial volume |
audioLatencyHint | string or number | interactive | the AudioContext's latencyHint |
audioRing | object | {} | audio ring options, for tests |
backgroundAudio | boolean | false | keep sound on while the page is hidden. false: a hidden page turns it off until setMuted(false) |
fec | auto, off | auto | play a track's FEC twin when the catalog names one; off: never |
worker | boolean | on, except on WebKit | run the receive path in a dedicated worker |
Other options exist for tests and measurements (audioRenderWatchMs); they
are not part of the supported surface.
Presentation modes
| Mode | What |
|---|---|
latency | draw each frame as it arrives; a frame older than the next queued is skipped |
smooth | hold frames by recent lateness and draw the newest due one at each display refresh |
paced | "Every frame": the same hold, one frame per refresh in capture order |
Player methods
| Member | What |
|---|---|
mode, setMode(m) | read and change the presentation mode |
setMuted(muted) | Promise<boolean>. Call from a click |
setVolume(v) | 0 to 1 |
backgroundAudio, setBackgroundAudio(on) | read and change the background-audio choice |
muted, hasAudio | hasAudio is null until the catalog is read, then whether the stream has sound |
setRendition(name) | auto, or a rendition's track name to pin it |
renditions() | the catalog's renditions, highest bitrate first |
stats() | counters and current values (path, size, codec, mode, rendition, audio, FEC, adaptive-bitrate state) |
sessionInfo() | the current and ended sessions |
jumpToLive() | drop to the live edge now |
close() | end the session; the relay releases the viewer |
done | a promise that settles when the engine has stopped, or null |
Player events
Listeners receive one argument.
| Event | Payload | When |
|---|---|---|
state | connecting, joining, live, stalled, waiting, fallback | the engine's state |
note | text | status text |
unsupported | text | this browser cannot play MoQ |
fallback | { reason, message, playing } | MoQ cannot play here. playing is true when the engine played opts.fallback.hls itself |
edge-full | { relay, waitMs, everywhere, round } | a relay refused the viewer as full. everywhere is true when every relay is |
frame | { captureMs, lagMs, path, count } | a frame was shown |
renditions | list | the catalog was read |
rendition | { from, to, why, auto } | the rendition changed. why is late, stale, stalled, join-rate, bunching, headroom, pinned, unavailable, underdelivered, not-live or abandoned |
rotation | { n, bytes, groups, ms, gapMs, why } | a session was handed over to a fresh one; why is token or limits |
renew | { n, kind } | a fresh session because requests were refused (expired or session), or the tab came back from hidden past the session's age (aged) or token time (token) |
audio | { state, reason } | as Sound states |
audio-available | boolean | whether the stream has sound |
audio-stats | object | the audio ring's numbers, four times a second |
handoff, bytes, skip, jump, resubscribe, session | engine internals, for measurements |
Other exports
| Export | What |
|---|---|
supported() | true when the browser has WebTransport and a video decoder |
blocker() | { reason, message } when MoQ cannot play here, else null |
onPageVisibility(onAway, onBack) | call back when the page is hidden or put away, and when it is shown. Returns the remover |
FALLBACK_REASONS, FALLBACK_MESSAGES, SUPPORTED_BROWSERS | the sentences shown to viewers |
ELEMENT_TAG | the tag name |
The descriptor
play() and createPlayer read the control API's descriptor JSON (the
Descriptor schema). The fields the player uses:
| Field | Required | What |
|---|---|---|
token | yes | the viewer token |
relays | yes | relay URLs, or { url } objects |
namespace | yes | the stream's namespace, a string or segments |
catalog | no | the catalog track's name |
relay_cert_sha256 | no | pins a self-signed relay certificate |
hls | no | the LL-HLS playlist for the fallback |
seat, state | when seated | the seat id, and in |
heartbeat_ms, play_url | when seated | where and how often the seat is kept |
background_audio | no | the stream's choice for background sound. The page's backgroundAudio wins |
React props
@nearglass/react exports NearglassPlayer, useNearglassPlayer and ELEMENT_TAG.
React 18 or later is a peer dependency. NearglassPlayer renders the element
and forwards a ref to it.
| Prop | Type | Default | Maps to |
|---|---|---|---|
api | string | none | the api attribute |
stream | string | none | stream |
publishableKey | string | none | publishable-key |
tokenUrl | string | none | token-url |
hlsUrl | string | none | hls-url |
mode | latency, smooth, paced | latency | mode |
autoJoin | boolean | false | auto-join |
backgroundAudio | boolean | false | background-audio |
descriptor | object or function | none | the descriptor property |
hls | Hls class | none | the hls property |
onStateChange | (detail) => void | none | the statechange event |
onModeChange | (detail) => void | none | the modechange event |
onNote | (text) => void | none | the note event |
id, className, style | passed through to the element |
There is no prop for audiochange; add a listener to the element through the
ref.
useNearglassPlayer(ref) follows an element and returns { state, info, mode, label, note }: state starts as idle and is the last statechange state,
info is that event's detail, mode and label come from modechange
(none and an empty string at first), and note is the last status text.