Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/CeeblueTV/wrts-client/llms.txt

Use this file to discover all available pages before exploring further.

IPlaying is the contract between Source implementations and the Player. Every Source receives an IPlaying instance in its constructor and in its play() method. Through this interface, transport implementations can read buffer thresholds and metrics to make adaptive decisions (e.g., aborting a slow request before the buffer empties), subscribe to playback lifecycle events, and call computeStats() to build CMCD telemetry payloads. IPlaying extends EventEmitter from @ceeblue/web-utils, so its event methods (onBufferState, onStall, etc.) are both directly overridable and emittable.
import { BufferState, IPlaying } from '@ceeblue/wrts-client';

BufferState Enum

BufferState describes the current health of the MSE playback buffer and drives both the adaptive bitrate algorithm and the playback-rate controller.
ValueDescription
NONENo active playback state — player is stopped or still in the initial loading phase.
LOWBuffer is critically low; stream-recovery mechanisms (stall detection, segment skipping) are active.
OKBuffer is within the acceptable range between bufferLimitLow and bufferLimitHigh. Normal playback.
HIGHBuffer exceeds the target; live-sync adjustments (increased playbackRate, goLive seeks) are active.
import { BufferState } from '@ceeblue/wrts-client';

switch (playing.bufferState) {
  case BufferState.NONE: /* player not yet active */ break;
  case BufferState.LOW:  /* buffer starving        */ break;
  case BufferState.OK:   /* steady state           */ break;
  case BufferState.HIGH: /* buffer overflowing     */ break;
}

Events

These methods are implemented on the Player class (which implements IPlaying). Source subclasses receive the Player instance as their IPlaying, so they can react to these events by reading the interface properties or subscribing via playing.signal. Because IPlaying extends EventEmitter, each on* method can also be subscribed to programmatically using the EventEmitter.on() API. The event name strings used with .on() are the method names without the on prefix:
MethodEventEmitter nameExample
onBufferState'BufferState'playing.on('BufferState', handler, playing)
onStall'Stall'playing.on('Stall', handler, playing)
Pass playing as the third argument to playing.on() (the abort-signal owner) so the subscription is automatically removed when the player stops.

onBufferState()

onBufferState(oldState: BufferState): void
Fired when the buffer state transitions between NONE, LOW, OK, and HIGH. Read playing.bufferState for the new state; oldState holds the previous value.
oldState
BufferState
The buffer state before the transition.

onBufferChange()

onBufferChange(): void
Fired when bufferAmount changes by at least 50 ms (the internal BUFFER_CHANGE_STEP constant). The Player’s default implementation calls adjustPlaybackRate(). Sources may observe this event to react to buffer fluctuations without polling.

onStall()

onStall(): void
Fired when the browser’s video element stalls (emits waiting) and the buffer falls below bufferLimitLow. Indicates that the stream cannot feed the decoder fast enough.

onAudioSkipping()

onAudioSkipping(holeMs: number): void
Fired by the Source base class when a gap in the audio timeline is detected and skipped during timestamp fixing.
holeMs
number
Duration of the skipped audio hole in milliseconds.

onVideoSkipping()

onVideoSkipping(holeMs: number): void
Fired by the Source base class when video frames are skipped to maintain audio/video synchronisation.
holeMs
number
Duration of the skipped video region in milliseconds.

Properties

Abort Signal

signal
AbortSignal
An AbortSignal that fires when Player.stop() is called. Use this inside play() to abort in-flight fetch calls and other async operations, ensuring resources are cleaned up automatically when playback ends.
playing.signal.addEventListener('abort', () => {
  myWebSocket.close();
}, { once: true });

Reliability

reliable
boolean
false by default — playback operates in unreliable mode with frame skipping enabled for lowest latency. true when the player has been configured to not tolerate any frame loss (reliable mode).

Buffer Metrics

bufferAmount
number
Current buffer depth in milliseconds, computed as (endTime - currentTime) * 1000. Use this to decide whether to abort a slow request before the buffer empties.
bufferLimitLow
number
Low-buffer threshold in milliseconds (default 150 ms). When bufferAmount drops below this value, bufferState becomes LOW.
bufferLimitMiddle
number
Target (midpoint) buffer depth in milliseconds, automatically computed as the midpoint between bufferLimitLow and bufferLimitHigh. The player drives bufferAmount toward this value when recovering from a stall or after goLive().
bufferLimitHigh
number
High-buffer threshold in milliseconds (default 550 ms). When bufferAmount exceeds this value, bufferState becomes HIGH.
buffering
boolean
true while the player is accumulating data during the initial start or after a stall. Becomes false once bufferAmount reaches bufferLimitMiddle.
bufferState
BufferState
Current BufferState value: NONE, LOW, OK, or HIGH.

Byte Rates

recvByteRate
number
Total incoming byte rate in bytes per second, covering all channels (audio + video + overhead).
audioByteRate
number
Audio payload byte rate in bytes per second.
videoByteRate
number
Video payload byte rate in bytes per second.
dataByteRate
number
Data / metadata payload byte rate in bytes per second.

Frame Rates

videoPerSecond
number
Video frames being decoded per second.
audioPerSecond
number
Audio frames being decoded per second.

Playback Rate

playbackRate
number
Current video.playbackRate. 1.0 is real-time; values above 1.0 mean the player is catching up to the live edge; values below 1.0 mean it is slowing down to fill the buffer.
playbackSpeed
number
Effective playback speed measured from elapsed currentTime changes, expressed as a ratio (1.0 = real-time). Distinct from playbackRate because it reflects actual decoder throughput rather than the requested rate.

MBR & Format Hints

maximumResolution
Media.Resolution | undefined
Upper-bound resolution for the MBR algorithm. Defaults to Media.screenResolution(), updated on window.resize. Sources use this to avoid requesting renditions the display cannot show.
passthroughCMAF
boolean | undefined
Debug flag. true when the Player was started with mediaExt: 'cmaf', bypassing the internal demuxer and passing CMAF segments directly to MSE. Sources should avoid demuxing when this is true.
tracksCombinable
boolean | undefined
When true (default), audio and video may be requested in a single combined HTTP request. false forces separate requests per track. undefined when the player is not running.

computeStats()

computeStats(): PlayerStats
Returns a PlayerStats snapshot (from @ceeblue/web-utils) capturing the current state of the player. Source implementations call this to build CMCD telemetry payloads inside fetchMedia(). The snapshot includes buffer depth, latency, playback rate, per-track byte rates, frame rates, track IDs, and stall count.

Using IPlaying in a Custom Source

The most common use of IPlaying inside a custom Source is reacting to buffer state changes to implement adaptive request management — for example, aborting a slow segment fetch when the buffer is critically low.
import { Source, SourceError } from '@ceeblue/wrts-client';
import { BufferState, IPlaying } from '@ceeblue/wrts-client';
import * as Media from '@ceeblue/wrts-client';
import { Connect } from '@ceeblue/web-utils';

@Source.registerClass('myproto')
export class MyAdaptiveSource extends Source {
  private _skipController = new AbortController();

  constructor(playing: IPlaying, params: Connect.Params) {
    super(playing, 'myproto', params);
  }

  protected play(url: URL, tracks: Media.Tracks, playing: IPlaying): void {
    // Cancel in-flight skippable requests when the buffer goes low
    playing.onBufferState = (oldState: BufferState) => {
      if (playing.bufferState === BufferState.LOW) {
        // Abort any segment that can be skipped to recover faster
        this._skipController.abort();
        this._skipController = new AbortController();
      }
    };

    // Stop everything when the Player calls stop()
    playing.signal.addEventListener('abort', () => {
      this._skipController.abort();
    }, { once: true });

    this._fetchLoop(url, playing);
  }

  private async _fetchLoop(url: URL, playing: IPlaying) {
    while (!this.closed) {
      // Use bufferAmount to decide whether to fetch the next segment now
      if (playing.bufferAmount > playing.bufferLimitHigh) {
        await new Promise(r => setTimeout(r, 200));
        continue;
      }

      try {
        const response = await this.fetchMedia(
          url,
          [this.videoTrack ?? -1],
          { signal: this._skipController.signal }
        );
        if (!response.ok) {
          this.close({ type: 'SourceError', name: 'Request error', detail: response.statusText });
          return;
        }
        // Feed to demuxer…
        const reader = this.newReader({ isStream: false });
        reader.read(await response.arrayBuffer());
      } catch (_) {
        // Aborted by _skipController — loop and retry
      }
    }
  }

  protected setReliability(reliable: boolean): void {}
  protected setTracks(tracks: Media.Tracks): void {}
}
IPlaying extends EventEmitter from @ceeblue/web-utils. The on* event methods on the Player can also be subscribed to programmatically through the EventEmitter API, but overriding the method directly (as shown above) is the idiomatic pattern for Source implementations.
Always use playing.signal to scope your async operations to the playback session. Any fetch, setTimeout, or WebSocket opened inside play() should be aborted or cleared when playing.signal fires — otherwise resources will leak across stop() / start() cycles.

Build docs developers (and LLMs) love