- Closes #2556 - Closes #2450 **Key intended features:** - go2rtc compatible - 100% test coverage to significantly improve ability to test, maintain and work around browser weirdnesses (e.g. Safari). - Written from the ground up in the style of the rest of the project. **To use:** - Change `live_provider` from `go2rtc` to `go2rtc-experimental`.
62 lines
2.6 KiB
TypeScript
62 lines
2.6 KiB
TypeScript
// A GOP ("group of pictures") is the span from one keyframe to just before the
|
|
// next: a full keyframe followed by the smaller frames that only encode what
|
|
// changed since it. Streamed video arrives one GOP at a time, so a camera with
|
|
// a 2s keyframe interval delivers media in roughly 2s bursts. That cadence sets
|
|
// how far behind the live edge playback must sit to ride over the bursts
|
|
// without starving, so this file estimates the GOP length by timing the
|
|
// wall-clock interval between buffer advances (each advance is about one GOP of
|
|
// new media) and averaging it over a short rolling window.
|
|
//
|
|
// Measuring, rather than assuming a fixed length, is necessary here: the stream
|
|
// carries no declared GOP length (it's raw fragments arriving over a
|
|
// WebSocket), and camera GOPs vary widely -- a doorbell at ~1s, an NVR tuned
|
|
// for bandwidth at 4s+ -- so any single constant is either too small (high-GOP
|
|
// cameras stall) or needlessly laggy for everyone else. Measuring per stream
|
|
// right-sizes it without a configuration knob.
|
|
//
|
|
// This is necessary to adaptively calculate the buffer (and thus inflicted
|
|
// latency) to maintain for Safari, in order to avoid it "hitting the end" and
|
|
// thus pausing, requiring human intervention to play again.
|
|
|
|
// The assumed GOP length (seconds) until enough delivery cadence is measured,
|
|
// and how many recent cadence samples the rolling average spans.
|
|
const DEFAULT_GOP_SECONDS = 1;
|
|
export const GOP_SAMPLE_WINDOW_SIZE = 5;
|
|
|
|
// Estimates the GOP length (the delivery cadence) as a rolling average of the
|
|
// interval between buffer advances. A trim updates the buffer without growing
|
|
// it, so only advances are sampled.
|
|
export class GOPCadenceEstimator {
|
|
private _samples: number[] = [];
|
|
private _lastBufferedEnd: number | null = null;
|
|
private _lastAdvanceTime: Date | null = null;
|
|
|
|
public sample(bufferedEndSeconds: number, now: Date): void {
|
|
const advanced =
|
|
this._lastBufferedEnd === null || bufferedEndSeconds > this._lastBufferedEnd;
|
|
if (!advanced) {
|
|
return;
|
|
}
|
|
|
|
if (this._lastAdvanceTime !== null) {
|
|
const interval = (now.getTime() - this._lastAdvanceTime.getTime()) / 1000;
|
|
if (interval > 0) {
|
|
if (this._samples.length >= GOP_SAMPLE_WINDOW_SIZE) {
|
|
this._samples.shift();
|
|
}
|
|
this._samples.push(interval);
|
|
}
|
|
}
|
|
|
|
this._lastAdvanceTime = now;
|
|
this._lastBufferedEnd = bufferedEndSeconds;
|
|
}
|
|
|
|
public estimateSeconds(): number {
|
|
if (!this._samples.length) {
|
|
return DEFAULT_GOP_SECONDS;
|
|
}
|
|
return this._samples.reduce((sum, sample) => sum + sample, 0) / this._samples.length;
|
|
}
|
|
}
|