@livepeer-frameworks/player-core 0.0.4 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -6
- package/dist/cjs/index.js +792 -146
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +792 -146
- package/dist/esm/index.js.map +1 -1
- package/dist/player.css +185 -373
- package/dist/types/core/GatewayClient.d.ts +3 -4
- package/dist/types/core/InteractionController.d.ts +12 -0
- package/dist/types/core/MetaTrackManager.d.ts +1 -1
- package/dist/types/core/PlayerController.d.ts +18 -2
- package/dist/types/core/PlayerInterface.d.ts +10 -0
- package/dist/types/core/SeekingUtils.d.ts +3 -1
- package/dist/types/core/StreamStateClient.d.ts +1 -1
- package/dist/types/players/HlsJsPlayer.d.ts +8 -0
- package/dist/types/players/MewsWsPlayer/index.d.ts +1 -1
- package/dist/types/players/VideoJsPlayer.d.ts +12 -4
- package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +1 -1
- package/dist/types/players/WebCodecsPlayer/index.d.ts +11 -0
- package/dist/types/players/WebCodecsPlayer/types.d.ts +25 -3
- package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +20 -2
- package/dist/types/types.d.ts +32 -1
- package/dist/types/vanilla/FrameWorksPlayer.d.ts +5 -5
- package/dist/types/vanilla/index.d.ts +3 -3
- package/dist/workers/decoder.worker.js +183 -6
- package/dist/workers/decoder.worker.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ABRController.ts +38 -36
- package/src/core/CodecUtils.ts +50 -47
- package/src/core/Disposable.ts +4 -4
- package/src/core/EventEmitter.ts +1 -1
- package/src/core/GatewayClient.ts +48 -48
- package/src/core/InteractionController.ts +89 -82
- package/src/core/LiveDurationProxy.ts +14 -16
- package/src/core/MetaTrackManager.ts +74 -66
- package/src/core/MistReporter.ts +72 -45
- package/src/core/MistSignaling.ts +59 -56
- package/src/core/PlayerController.ts +724 -375
- package/src/core/PlayerInterface.ts +89 -59
- package/src/core/PlayerManager.ts +118 -123
- package/src/core/PlayerRegistry.ts +59 -42
- package/src/core/QualityMonitor.ts +38 -31
- package/src/core/ScreenWakeLockManager.ts +8 -9
- package/src/core/SeekingUtils.ts +31 -22
- package/src/core/StreamStateClient.ts +75 -69
- package/src/core/SubtitleManager.ts +25 -23
- package/src/core/TelemetryReporter.ts +34 -31
- package/src/core/TimeFormat.ts +13 -17
- package/src/core/TimerManager.ts +25 -9
- package/src/core/UrlUtils.ts +20 -17
- package/src/core/detector.ts +44 -44
- package/src/core/index.ts +57 -48
- package/src/core/scorer.ts +137 -138
- package/src/core/selector.ts +2 -6
- package/src/global.d.ts +1 -1
- package/src/index.ts +46 -35
- package/src/players/DashJsPlayer.ts +175 -114
- package/src/players/HlsJsPlayer.ts +154 -76
- package/src/players/MewsWsPlayer/SourceBufferManager.ts +44 -39
- package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -10
- package/src/players/MewsWsPlayer/index.ts +196 -154
- package/src/players/MewsWsPlayer/types.ts +21 -21
- package/src/players/MistPlayer.ts +46 -27
- package/src/players/MistWebRTCPlayer/index.ts +175 -129
- package/src/players/NativePlayer.ts +203 -143
- package/src/players/VideoJsPlayer.ts +200 -146
- package/src/players/WebCodecsPlayer/JitterBuffer.ts +6 -7
- package/src/players/WebCodecsPlayer/LatencyProfiles.ts +43 -43
- package/src/players/WebCodecsPlayer/RawChunkParser.ts +10 -10
- package/src/players/WebCodecsPlayer/SyncController.ts +46 -55
- package/src/players/WebCodecsPlayer/WebSocketController.ts +67 -69
- package/src/players/WebCodecsPlayer/index.ts +280 -220
- package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
- package/src/players/WebCodecsPlayer/types.ts +81 -53
- package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +255 -192
- package/src/players/WebCodecsPlayer/worker/types.ts +33 -29
- package/src/players/index.ts +8 -8
- package/src/styles/animations.css +2 -1
- package/src/styles/player.css +182 -356
- package/src/styles/tailwind.css +473 -159
- package/src/types.ts +75 -33
- package/src/vanilla/FrameWorksPlayer.ts +34 -19
- package/src/vanilla/index.ts +7 -7
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* 5. Limit lowering rate to prevent oscillation
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { JitterState } from
|
|
18
|
+
import type { JitterState } from "./types";
|
|
19
19
|
|
|
20
20
|
/** Default sliding window size for chunk tracking */
|
|
21
21
|
const DEFAULT_CHUNK_WINDOW = 8;
|
|
@@ -156,8 +156,7 @@ export class JitterTracker {
|
|
|
156
156
|
// Calculate new weighted average
|
|
157
157
|
if (this.peaks.length > 0) {
|
|
158
158
|
const maxPeak = Math.max(...this.peaks);
|
|
159
|
-
const avgPeak =
|
|
160
|
-
this.peaks.reduce((sum, p) => sum + p, 0) / this.peaks.length;
|
|
159
|
+
const avgPeak = this.peaks.reduce((sum, p) => sum + p, 0) / this.peaks.length;
|
|
161
160
|
|
|
162
161
|
// Weighted: emphasize max peak for safety
|
|
163
162
|
let weighted = (avgPeak + maxPeak * 2) / 3 + MIN_JITTER;
|
|
@@ -193,8 +192,8 @@ export class JitterTracker {
|
|
|
193
192
|
/**
|
|
194
193
|
* Set playback speed for jitter calculation
|
|
195
194
|
*/
|
|
196
|
-
setSpeed(speed: number |
|
|
197
|
-
const newSpeed = speed ===
|
|
195
|
+
setSpeed(speed: number | "auto"): void {
|
|
196
|
+
const newSpeed = speed === "auto" ? 1 : speed;
|
|
198
197
|
if (newSpeed !== this.speed) {
|
|
199
198
|
this.speed = newSpeed;
|
|
200
199
|
this.reset();
|
|
@@ -267,8 +266,8 @@ export class MultiTrackJitterTracker {
|
|
|
267
266
|
/**
|
|
268
267
|
* Set playback speed for all trackers
|
|
269
268
|
*/
|
|
270
|
-
setSpeed(speed: number |
|
|
271
|
-
this.globalSpeed = speed ===
|
|
269
|
+
setSpeed(speed: number | "auto"): void {
|
|
270
|
+
this.globalSpeed = speed === "auto" ? 1 : speed;
|
|
272
271
|
for (const tracker of this.trackers.values()) {
|
|
273
272
|
tracker.setSpeed(speed);
|
|
274
273
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - If buffer < desired * speedDownThreshold → slow down to minSpeedDown
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { LatencyProfile, LatencyProfileName } from
|
|
13
|
+
import type { LatencyProfile, LatencyProfileName } from "./types";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Ultra-low latency profile
|
|
@@ -19,15 +19,15 @@ import type { LatencyProfile, LatencyProfileName } from './types';
|
|
|
19
19
|
* Trade-offs: May stutter on poor networks
|
|
20
20
|
*/
|
|
21
21
|
export const ULTRA_LOW_PROFILE: LatencyProfile = {
|
|
22
|
-
name:
|
|
23
|
-
keepAway: 50,
|
|
24
|
-
jitterMultiplier: 1.0,
|
|
25
|
-
speedUpThreshold: 1.5,
|
|
26
|
-
speedDownThreshold: 0.5,
|
|
27
|
-
maxSpeedUp: 1.08,
|
|
28
|
-
minSpeedDown: 0.95,
|
|
29
|
-
audioBufferMs: 100,
|
|
30
|
-
optimizeForLatency: true,
|
|
22
|
+
name: "Ultra Low Latency",
|
|
23
|
+
keepAway: 50, // 50ms base buffer
|
|
24
|
+
jitterMultiplier: 1.0, // Full jitter protection (no extra margin)
|
|
25
|
+
speedUpThreshold: 1.5, // Speed up when buffer > 150% of desired
|
|
26
|
+
speedDownThreshold: 0.5, // Slow down when buffer < 50% of desired
|
|
27
|
+
maxSpeedUp: 1.08, // 8% speed up max
|
|
28
|
+
minSpeedDown: 0.95, // 5% slow down max
|
|
29
|
+
audioBufferMs: 100, // 100ms audio ring buffer
|
|
30
|
+
optimizeForLatency: true, // Tell decoders to optimize for latency
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -37,14 +37,14 @@ export const ULTRA_LOW_PROFILE: LatencyProfile = {
|
|
|
37
37
|
* Trade-offs: Balanced latency/stability
|
|
38
38
|
*/
|
|
39
39
|
export const LOW_PROFILE: LatencyProfile = {
|
|
40
|
-
name:
|
|
41
|
-
keepAway: 100,
|
|
42
|
-
jitterMultiplier: 1.2,
|
|
43
|
-
speedUpThreshold: 2.0,
|
|
44
|
-
speedDownThreshold: 0.6,
|
|
45
|
-
maxSpeedUp: 1.05,
|
|
46
|
-
minSpeedDown: 0.98,
|
|
47
|
-
audioBufferMs: 150,
|
|
40
|
+
name: "Low Latency",
|
|
41
|
+
keepAway: 100, // 100ms base buffer
|
|
42
|
+
jitterMultiplier: 1.2, // 20% extra jitter protection
|
|
43
|
+
speedUpThreshold: 2.0, // Speed up when buffer > 200% of desired
|
|
44
|
+
speedDownThreshold: 0.6, // Slow down when buffer < 60% of desired
|
|
45
|
+
maxSpeedUp: 1.05, // 5% speed up max (legacy default)
|
|
46
|
+
minSpeedDown: 0.98, // 2% slow down max (legacy default)
|
|
47
|
+
audioBufferMs: 150, // 150ms audio ring buffer
|
|
48
48
|
optimizeForLatency: true,
|
|
49
49
|
};
|
|
50
50
|
|
|
@@ -55,14 +55,14 @@ export const LOW_PROFILE: LatencyProfile = {
|
|
|
55
55
|
* Trade-offs: Prioritizes stability over latency
|
|
56
56
|
*/
|
|
57
57
|
export const BALANCED_PROFILE: LatencyProfile = {
|
|
58
|
-
name:
|
|
59
|
-
keepAway: 200,
|
|
60
|
-
jitterMultiplier: 1.5,
|
|
61
|
-
speedUpThreshold: 2.5,
|
|
62
|
-
speedDownThreshold: 0.5,
|
|
63
|
-
maxSpeedUp: 1.03,
|
|
64
|
-
minSpeedDown: 0.97,
|
|
65
|
-
audioBufferMs: 200,
|
|
58
|
+
name: "Balanced",
|
|
59
|
+
keepAway: 200, // 200ms base buffer
|
|
60
|
+
jitterMultiplier: 1.5, // 50% extra jitter protection
|
|
61
|
+
speedUpThreshold: 2.5, // Speed up when buffer > 250% of desired
|
|
62
|
+
speedDownThreshold: 0.5, // Slow down when buffer < 50% of desired
|
|
63
|
+
maxSpeedUp: 1.03, // 3% speed up max
|
|
64
|
+
minSpeedDown: 0.97, // 3% slow down max
|
|
65
|
+
audioBufferMs: 200, // 200ms audio ring buffer
|
|
66
66
|
optimizeForLatency: false, // Let decoders optimize for quality
|
|
67
67
|
};
|
|
68
68
|
|
|
@@ -73,14 +73,14 @@ export const BALANCED_PROFILE: LatencyProfile = {
|
|
|
73
73
|
* Trade-offs: Maximum stability, higher latency
|
|
74
74
|
*/
|
|
75
75
|
export const QUALITY_PROFILE: LatencyProfile = {
|
|
76
|
-
name:
|
|
77
|
-
keepAway: 500,
|
|
78
|
-
jitterMultiplier: 2.0,
|
|
79
|
-
speedUpThreshold: 3.0,
|
|
80
|
-
speedDownThreshold: 0.4,
|
|
81
|
-
maxSpeedUp: 1.02,
|
|
82
|
-
minSpeedDown: 0.98,
|
|
83
|
-
audioBufferMs: 300,
|
|
76
|
+
name: "Quality Priority",
|
|
77
|
+
keepAway: 500, // 500ms base buffer (legacy VOD default)
|
|
78
|
+
jitterMultiplier: 2.0, // Double jitter protection
|
|
79
|
+
speedUpThreshold: 3.0, // Speed up when buffer > 300% of desired
|
|
80
|
+
speedDownThreshold: 0.4, // Slow down when buffer < 40% of desired
|
|
81
|
+
maxSpeedUp: 1.02, // 2% speed up max
|
|
82
|
+
minSpeedDown: 0.98, // 2% slow down max
|
|
83
|
+
audioBufferMs: 300, // 300ms audio ring buffer
|
|
84
84
|
optimizeForLatency: false,
|
|
85
85
|
};
|
|
86
86
|
|
|
@@ -88,10 +88,10 @@ export const QUALITY_PROFILE: LatencyProfile = {
|
|
|
88
88
|
* All available latency profiles
|
|
89
89
|
*/
|
|
90
90
|
export const LATENCY_PROFILES: Record<LatencyProfileName, LatencyProfile> = {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
"ultra-low": ULTRA_LOW_PROFILE,
|
|
92
|
+
low: LOW_PROFILE,
|
|
93
|
+
balanced: BALANCED_PROFILE,
|
|
94
|
+
quality: QUALITY_PROFILE,
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
/**
|
|
@@ -103,7 +103,7 @@ export function getLatencyProfile(name?: LatencyProfileName): LatencyProfile {
|
|
|
103
103
|
if (name && name in LATENCY_PROFILES) {
|
|
104
104
|
return LATENCY_PROFILES[name];
|
|
105
105
|
}
|
|
106
|
-
return LATENCY_PROFILES[
|
|
106
|
+
return LATENCY_PROFILES["low"];
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/**
|
|
@@ -116,7 +116,7 @@ export function mergeLatencyProfile(
|
|
|
116
116
|
base: LatencyProfileName | LatencyProfile,
|
|
117
117
|
custom?: Partial<LatencyProfile>
|
|
118
118
|
): LatencyProfile {
|
|
119
|
-
const baseProfile = typeof base ===
|
|
119
|
+
const baseProfile = typeof base === "string" ? getLatencyProfile(base) : base;
|
|
120
120
|
|
|
121
121
|
if (!custom) {
|
|
122
122
|
return baseProfile;
|
|
@@ -140,12 +140,12 @@ export function selectDefaultProfile(
|
|
|
140
140
|
preferLowLatency = false
|
|
141
141
|
): LatencyProfileName {
|
|
142
142
|
if (!isLive) {
|
|
143
|
-
return
|
|
143
|
+
return "quality";
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
if (preferLowLatency) {
|
|
147
|
-
return
|
|
147
|
+
return "low";
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
return
|
|
150
|
+
return "balanced";
|
|
151
151
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Combined presentation time = timestamp + offset
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import type { RawChunk, ChunkType } from
|
|
16
|
+
import type { RawChunk, ChunkType } from "./types";
|
|
17
17
|
|
|
18
18
|
const HEADER_LENGTH = 12;
|
|
19
19
|
|
|
@@ -23,11 +23,11 @@ const HEADER_LENGTH = 12;
|
|
|
23
23
|
function parseChunkType(typeByte: number): ChunkType {
|
|
24
24
|
switch (typeByte) {
|
|
25
25
|
case 1:
|
|
26
|
-
return
|
|
26
|
+
return "key";
|
|
27
27
|
case 2:
|
|
28
|
-
return
|
|
28
|
+
return "init";
|
|
29
29
|
default:
|
|
30
|
-
return
|
|
30
|
+
return "delta";
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -92,14 +92,14 @@ export function getPresentationTimestamp(chunk: RawChunk): number {
|
|
|
92
92
|
* Check if this chunk is a keyframe
|
|
93
93
|
*/
|
|
94
94
|
export function isKeyframe(chunk: RawChunk): boolean {
|
|
95
|
-
return chunk.type ===
|
|
95
|
+
return chunk.type === "key";
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* Check if this chunk contains codec initialization data
|
|
100
100
|
*/
|
|
101
101
|
export function isInitData(chunk: RawChunk): boolean {
|
|
102
|
-
return chunk.type ===
|
|
102
|
+
return chunk.type === "init";
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
/**
|
|
@@ -132,12 +132,12 @@ export class RawChunkParser {
|
|
|
132
132
|
const chunk = parseRawChunk(data);
|
|
133
133
|
|
|
134
134
|
if (this.debug) {
|
|
135
|
-
console.log(
|
|
135
|
+
console.log("▶️", formatChunkForLog(chunk));
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
return chunk;
|
|
139
139
|
} catch (err) {
|
|
140
|
-
console.error(
|
|
140
|
+
console.error("▶️ Failed to parse chunk:", err);
|
|
141
141
|
return null;
|
|
142
142
|
}
|
|
143
143
|
}
|
|
@@ -145,7 +145,7 @@ export class RawChunkParser {
|
|
|
145
145
|
/**
|
|
146
146
|
* Set debug mode
|
|
147
147
|
*/
|
|
148
|
-
setDebug(enabled: boolean |
|
|
149
|
-
this.debug = enabled ===
|
|
148
|
+
setDebug(enabled: boolean | "verbose"): void {
|
|
149
|
+
this.debug = enabled === "verbose" || enabled === true;
|
|
150
150
|
}
|
|
151
151
|
}
|
|
@@ -14,18 +14,13 @@
|
|
|
14
14
|
* - TypeScript types
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
SyncState,
|
|
21
|
-
TrackInfo,
|
|
22
|
-
} from './types';
|
|
23
|
-
import { MultiTrackJitterTracker } from './JitterBuffer';
|
|
24
|
-
import { getLatencyProfile } from './LatencyProfiles';
|
|
17
|
+
import type { LatencyProfile, SyncState, TrackInfo } from "./types";
|
|
18
|
+
import { MultiTrackJitterTracker } from "./JitterBuffer";
|
|
19
|
+
import { getLatencyProfile } from "./LatencyProfiles";
|
|
25
20
|
|
|
26
21
|
/** Events emitted by SyncController */
|
|
27
22
|
export interface SyncControllerEvents {
|
|
28
|
-
speedchange: { speed: number; reason:
|
|
23
|
+
speedchange: { speed: number; reason: "catchup" | "slowdown" | "normal" };
|
|
29
24
|
bufferlow: { current: number; desired: number };
|
|
30
25
|
bufferhigh: { current: number; desired: number };
|
|
31
26
|
underrun: void;
|
|
@@ -34,9 +29,7 @@ export interface SyncControllerEvents {
|
|
|
34
29
|
seekcomplete: { seekId: number };
|
|
35
30
|
}
|
|
36
31
|
|
|
37
|
-
type EventListener<K extends keyof SyncControllerEvents> = (
|
|
38
|
-
data: SyncControllerEvents[K]
|
|
39
|
-
) => void;
|
|
32
|
+
type EventListener<K extends keyof SyncControllerEvents> = (data: SyncControllerEvents[K]) => void;
|
|
40
33
|
|
|
41
34
|
/** Seek state tracking */
|
|
42
35
|
interface SeekState {
|
|
@@ -92,13 +85,15 @@ export class SyncController {
|
|
|
92
85
|
private onSpeedChange?: (main: number, tweak: number) => void;
|
|
93
86
|
private onFastForwardRequest?: (ms: number) => void;
|
|
94
87
|
|
|
95
|
-
constructor(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
88
|
+
constructor(
|
|
89
|
+
options: {
|
|
90
|
+
profile?: LatencyProfile;
|
|
91
|
+
isLive?: boolean;
|
|
92
|
+
onSpeedChange?: (main: number, tweak: number) => void;
|
|
93
|
+
onFastForwardRequest?: (ms: number) => void;
|
|
94
|
+
} = {}
|
|
95
|
+
) {
|
|
96
|
+
this.profile = options.profile ?? getLatencyProfile("low");
|
|
102
97
|
this.isLive = options.isLive ?? true;
|
|
103
98
|
this.onSpeedChange = options.onSpeedChange;
|
|
104
99
|
this.onFastForwardRequest = options.onFastForwardRequest;
|
|
@@ -169,8 +164,10 @@ export class SyncController {
|
|
|
169
164
|
*/
|
|
170
165
|
getDesiredBuffer(): number {
|
|
171
166
|
// Chrome needs larger base buffer (per mews.js:482)
|
|
172
|
-
const isChrome =
|
|
173
|
-
|
|
167
|
+
const isChrome =
|
|
168
|
+
typeof navigator !== "undefined" &&
|
|
169
|
+
/Chrome/.test(navigator.userAgent) &&
|
|
170
|
+
!/Edge|Edg/.test(navigator.userAgent);
|
|
174
171
|
const baseBuffer = isChrome ? 1000 : 100;
|
|
175
172
|
|
|
176
173
|
const serverDelay = this.getServerDelay();
|
|
@@ -210,35 +207,37 @@ export class SyncController {
|
|
|
210
207
|
// - Buffer is more than jitter + safety margin (not stalled)
|
|
211
208
|
// - Buffer is less than 1s above desired
|
|
212
209
|
// - Cooldown period has passed
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
210
|
+
if (
|
|
211
|
+
this.isLive &&
|
|
212
|
+
currentBufferMs < this.liveCatchupThresholdMs &&
|
|
213
|
+
currentBufferMs > Math.max(jitterMs * 1.1, jitterMs + 250) &&
|
|
214
|
+
currentBufferMs - desired < 1000 &&
|
|
215
|
+
now - this.lastLiveCatchup > this.liveCatchupCooldown
|
|
216
|
+
) {
|
|
218
217
|
this.lastLiveCatchup = now;
|
|
219
218
|
this.requestFastForward(this.liveCatchupRequestMs);
|
|
220
|
-
this.emit(
|
|
219
|
+
this.emit("livecatchup", { fastForwardMs: this.liveCatchupRequestMs });
|
|
221
220
|
}
|
|
222
221
|
|
|
223
222
|
// Determine if speed adjustment needed
|
|
224
223
|
if (ratio > this.profile.speedUpThreshold && this.isLive) {
|
|
225
224
|
// Buffer too high - speed up to catch up to live edge
|
|
226
|
-
this.setTweakSpeed(this.profile.maxSpeedUp,
|
|
225
|
+
this.setTweakSpeed(this.profile.maxSpeedUp, "catchup");
|
|
227
226
|
} else if (ratio < this.profile.speedDownThreshold) {
|
|
228
227
|
// Buffer too low - slow down to build buffer
|
|
229
|
-
this.setTweakSpeed(this.profile.minSpeedDown,
|
|
228
|
+
this.setTweakSpeed(this.profile.minSpeedDown, "slowdown");
|
|
230
229
|
|
|
231
230
|
// Request additional data if critically low
|
|
232
231
|
if (ratio < 0.3 && this.isLive) {
|
|
233
232
|
this.requestFastForward(desired - currentBufferMs);
|
|
234
|
-
this.emit(
|
|
233
|
+
this.emit("underrun", undefined);
|
|
235
234
|
}
|
|
236
235
|
|
|
237
|
-
this.emit(
|
|
236
|
+
this.emit("bufferlow", { current: currentBufferMs, desired });
|
|
238
237
|
} else {
|
|
239
238
|
// Buffer in acceptable range - return to normal speed
|
|
240
239
|
if (this.tweakSpeed !== 1) {
|
|
241
|
-
this.setTweakSpeed(1,
|
|
240
|
+
this.setTweakSpeed(1, "normal");
|
|
242
241
|
}
|
|
243
242
|
}
|
|
244
243
|
|
|
@@ -258,13 +257,14 @@ export class SyncController {
|
|
|
258
257
|
desired,
|
|
259
258
|
ratio: desired > 0 ? buffer / desired : 1,
|
|
260
259
|
},
|
|
261
|
-
jitter:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
260
|
+
jitter:
|
|
261
|
+
this.jitterTracker.getMax() > 0
|
|
262
|
+
? {
|
|
263
|
+
current: 0, // Would need per-frame tracking
|
|
264
|
+
peak: 0,
|
|
265
|
+
weighted: this.jitterTracker.getMax(),
|
|
266
|
+
}
|
|
267
|
+
: { current: 0, peak: 0, weighted: 0 },
|
|
268
268
|
playbackSpeed: this.getCombinedSpeed(),
|
|
269
269
|
serverTime: this.serverTime,
|
|
270
270
|
serverDelay: this.getServerDelay(),
|
|
@@ -291,14 +291,11 @@ export class SyncController {
|
|
|
291
291
|
/**
|
|
292
292
|
* Set tweak speed (automatic adjustment)
|
|
293
293
|
*/
|
|
294
|
-
private setTweakSpeed(
|
|
295
|
-
speed: number,
|
|
296
|
-
reason: 'catchup' | 'slowdown' | 'normal'
|
|
297
|
-
): void {
|
|
294
|
+
private setTweakSpeed(speed: number, reason: "catchup" | "slowdown" | "normal"): void {
|
|
298
295
|
if (this.tweakSpeed !== speed) {
|
|
299
296
|
this.tweakSpeed = speed;
|
|
300
297
|
this.notifySpeedChange();
|
|
301
|
-
this.emit(
|
|
298
|
+
this.emit("speedchange", { speed: this.getCombinedSpeed(), reason });
|
|
302
299
|
}
|
|
303
300
|
}
|
|
304
301
|
|
|
@@ -338,7 +335,7 @@ export class SyncController {
|
|
|
338
335
|
// Reset jitter tracking on seek
|
|
339
336
|
this.jitterTracker.reset();
|
|
340
337
|
|
|
341
|
-
this.emit(
|
|
338
|
+
this.emit("seekstart", { seekId: this.seekState.id, time: targetTimeMs });
|
|
342
339
|
|
|
343
340
|
return this.seekState.id;
|
|
344
341
|
}
|
|
@@ -356,7 +353,7 @@ export class SyncController {
|
|
|
356
353
|
completeSeek(seekId: number): void {
|
|
357
354
|
if (this.seekState.id === seekId) {
|
|
358
355
|
this.seekState.active = false;
|
|
359
|
-
this.emit(
|
|
356
|
+
this.emit("seekcomplete", { seekId });
|
|
360
357
|
}
|
|
361
358
|
}
|
|
362
359
|
|
|
@@ -383,7 +380,7 @@ export class SyncController {
|
|
|
383
380
|
/**
|
|
384
381
|
* Register a new track
|
|
385
382
|
*/
|
|
386
|
-
addTrack(
|
|
383
|
+
addTrack(_trackIndex: number, _track: TrackInfo): void {
|
|
387
384
|
// Jitter tracking will be initialized on first chunk
|
|
388
385
|
}
|
|
389
386
|
|
|
@@ -421,20 +418,14 @@ export class SyncController {
|
|
|
421
418
|
// Event Emitter
|
|
422
419
|
// ============================================================================
|
|
423
420
|
|
|
424
|
-
on<K extends keyof SyncControllerEvents>(
|
|
425
|
-
event: K,
|
|
426
|
-
listener: EventListener<K>
|
|
427
|
-
): void {
|
|
421
|
+
on<K extends keyof SyncControllerEvents>(event: K, listener: EventListener<K>): void {
|
|
428
422
|
if (!this.listeners.has(event)) {
|
|
429
423
|
this.listeners.set(event, new Set());
|
|
430
424
|
}
|
|
431
425
|
this.listeners.get(event)!.add(listener);
|
|
432
426
|
}
|
|
433
427
|
|
|
434
|
-
off<K extends keyof SyncControllerEvents>(
|
|
435
|
-
event: K,
|
|
436
|
-
listener: EventListener<K>
|
|
437
|
-
): void {
|
|
428
|
+
off<K extends keyof SyncControllerEvents>(event: K, listener: EventListener<K>): void {
|
|
438
429
|
this.listeners.get(event)?.delete(listener);
|
|
439
430
|
}
|
|
440
431
|
|