@livepeer-frameworks/player-core 0.0.3
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/dist/cjs/index.js +19493 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.js +19398 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/player.css +2140 -0
- package/dist/types/core/ABRController.d.ts +164 -0
- package/dist/types/core/CodecUtils.d.ts +54 -0
- package/dist/types/core/Disposable.d.ts +61 -0
- package/dist/types/core/EventEmitter.d.ts +73 -0
- package/dist/types/core/GatewayClient.d.ts +144 -0
- package/dist/types/core/InteractionController.d.ts +121 -0
- package/dist/types/core/LiveDurationProxy.d.ts +102 -0
- package/dist/types/core/MetaTrackManager.d.ts +220 -0
- package/dist/types/core/MistReporter.d.ts +163 -0
- package/dist/types/core/MistSignaling.d.ts +148 -0
- package/dist/types/core/PlayerController.d.ts +665 -0
- package/dist/types/core/PlayerInterface.d.ts +230 -0
- package/dist/types/core/PlayerManager.d.ts +182 -0
- package/dist/types/core/PlayerRegistry.d.ts +27 -0
- package/dist/types/core/QualityMonitor.d.ts +184 -0
- package/dist/types/core/ScreenWakeLockManager.d.ts +70 -0
- package/dist/types/core/SeekingUtils.d.ts +142 -0
- package/dist/types/core/StreamStateClient.d.ts +108 -0
- package/dist/types/core/SubtitleManager.d.ts +111 -0
- package/dist/types/core/TelemetryReporter.d.ts +79 -0
- package/dist/types/core/TimeFormat.d.ts +97 -0
- package/dist/types/core/TimerManager.d.ts +83 -0
- package/dist/types/core/UrlUtils.d.ts +81 -0
- package/dist/types/core/detector.d.ts +149 -0
- package/dist/types/core/index.d.ts +49 -0
- package/dist/types/core/scorer.d.ts +167 -0
- package/dist/types/core/selector.d.ts +9 -0
- package/dist/types/index.d.ts +45 -0
- package/dist/types/lib/utils.d.ts +2 -0
- package/dist/types/players/DashJsPlayer.d.ts +102 -0
- package/dist/types/players/HlsJsPlayer.d.ts +70 -0
- package/dist/types/players/MewsWsPlayer/SourceBufferManager.d.ts +119 -0
- package/dist/types/players/MewsWsPlayer/WebSocketManager.d.ts +60 -0
- package/dist/types/players/MewsWsPlayer/index.d.ts +220 -0
- package/dist/types/players/MewsWsPlayer/types.d.ts +89 -0
- package/dist/types/players/MistPlayer.d.ts +25 -0
- package/dist/types/players/MistWebRTCPlayer/index.d.ts +133 -0
- package/dist/types/players/NativePlayer.d.ts +143 -0
- package/dist/types/players/VideoJsPlayer.d.ts +59 -0
- package/dist/types/players/WebCodecsPlayer/JitterBuffer.d.ts +118 -0
- package/dist/types/players/WebCodecsPlayer/LatencyProfiles.d.ts +64 -0
- package/dist/types/players/WebCodecsPlayer/RawChunkParser.d.ts +63 -0
- package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +174 -0
- package/dist/types/players/WebCodecsPlayer/WebSocketController.d.ts +164 -0
- package/dist/types/players/WebCodecsPlayer/index.d.ts +149 -0
- package/dist/types/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.d.ts +105 -0
- package/dist/types/players/WebCodecsPlayer/types.d.ts +395 -0
- package/dist/types/players/WebCodecsPlayer/worker/decoder.worker.d.ts +13 -0
- package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +197 -0
- package/dist/types/players/index.d.ts +14 -0
- package/dist/types/styles/index.d.ts +11 -0
- package/dist/types/types.d.ts +363 -0
- package/dist/types/vanilla/FrameWorksPlayer.d.ts +143 -0
- package/dist/types/vanilla/index.d.ts +19 -0
- package/dist/workers/decoder.worker.js +989 -0
- package/dist/workers/decoder.worker.js.map +1 -0
- package/package.json +80 -0
- package/src/core/ABRController.ts +550 -0
- package/src/core/CodecUtils.ts +257 -0
- package/src/core/Disposable.ts +120 -0
- package/src/core/EventEmitter.ts +113 -0
- package/src/core/GatewayClient.ts +439 -0
- package/src/core/InteractionController.ts +712 -0
- package/src/core/LiveDurationProxy.ts +270 -0
- package/src/core/MetaTrackManager.ts +753 -0
- package/src/core/MistReporter.ts +543 -0
- package/src/core/MistSignaling.ts +346 -0
- package/src/core/PlayerController.ts +2829 -0
- package/src/core/PlayerInterface.ts +432 -0
- package/src/core/PlayerManager.ts +900 -0
- package/src/core/PlayerRegistry.ts +149 -0
- package/src/core/QualityMonitor.ts +597 -0
- package/src/core/ScreenWakeLockManager.ts +163 -0
- package/src/core/SeekingUtils.ts +364 -0
- package/src/core/StreamStateClient.ts +457 -0
- package/src/core/SubtitleManager.ts +297 -0
- package/src/core/TelemetryReporter.ts +308 -0
- package/src/core/TimeFormat.ts +205 -0
- package/src/core/TimerManager.ts +209 -0
- package/src/core/UrlUtils.ts +179 -0
- package/src/core/detector.ts +382 -0
- package/src/core/index.ts +140 -0
- package/src/core/scorer.ts +553 -0
- package/src/core/selector.ts +16 -0
- package/src/global.d.ts +11 -0
- package/src/index.ts +75 -0
- package/src/lib/utils.ts +6 -0
- package/src/players/DashJsPlayer.ts +642 -0
- package/src/players/HlsJsPlayer.ts +483 -0
- package/src/players/MewsWsPlayer/SourceBufferManager.ts +572 -0
- package/src/players/MewsWsPlayer/WebSocketManager.ts +241 -0
- package/src/players/MewsWsPlayer/index.ts +1065 -0
- package/src/players/MewsWsPlayer/types.ts +106 -0
- package/src/players/MistPlayer.ts +188 -0
- package/src/players/MistWebRTCPlayer/index.ts +703 -0
- package/src/players/NativePlayer.ts +820 -0
- package/src/players/VideoJsPlayer.ts +643 -0
- package/src/players/WebCodecsPlayer/JitterBuffer.ts +299 -0
- package/src/players/WebCodecsPlayer/LatencyProfiles.ts +151 -0
- package/src/players/WebCodecsPlayer/RawChunkParser.ts +151 -0
- package/src/players/WebCodecsPlayer/SyncController.ts +456 -0
- package/src/players/WebCodecsPlayer/WebSocketController.ts +564 -0
- package/src/players/WebCodecsPlayer/index.ts +1650 -0
- package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +379 -0
- package/src/players/WebCodecsPlayer/types.ts +542 -0
- package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +1360 -0
- package/src/players/WebCodecsPlayer/worker/types.ts +276 -0
- package/src/players/index.ts +22 -0
- package/src/styles/animations.css +21 -0
- package/src/styles/index.ts +52 -0
- package/src/styles/player.css +2126 -0
- package/src/styles/tailwind.css +1015 -0
- package/src/types.ts +421 -0
- package/src/vanilla/FrameWorksPlayer.ts +367 -0
- package/src/vanilla/index.ts +22 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncController - Buffer Management & Playback Timing
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates:
|
|
5
|
+
* - Buffer level monitoring
|
|
6
|
+
* - Adaptive playback speed (catchup/slowdown)
|
|
7
|
+
* - Jitter tracking integration
|
|
8
|
+
* - Server delay estimation
|
|
9
|
+
* - Seek coordination
|
|
10
|
+
*
|
|
11
|
+
* Based on legacy rawws.js FrameTiming + buffer management with improvements:
|
|
12
|
+
* - Post-decode drift correction
|
|
13
|
+
* - Better seek cancellation
|
|
14
|
+
* - TypeScript types
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
LatencyProfile,
|
|
19
|
+
BufferState,
|
|
20
|
+
SyncState,
|
|
21
|
+
TrackInfo,
|
|
22
|
+
} from './types';
|
|
23
|
+
import { MultiTrackJitterTracker } from './JitterBuffer';
|
|
24
|
+
import { getLatencyProfile } from './LatencyProfiles';
|
|
25
|
+
|
|
26
|
+
/** Events emitted by SyncController */
|
|
27
|
+
export interface SyncControllerEvents {
|
|
28
|
+
speedchange: { speed: number; reason: 'catchup' | 'slowdown' | 'normal' };
|
|
29
|
+
bufferlow: { current: number; desired: number };
|
|
30
|
+
bufferhigh: { current: number; desired: number };
|
|
31
|
+
underrun: void;
|
|
32
|
+
livecatchup: { fastForwardMs: number };
|
|
33
|
+
seekstart: { seekId: number; time: number };
|
|
34
|
+
seekcomplete: { seekId: number };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type EventListener<K extends keyof SyncControllerEvents> = (
|
|
38
|
+
data: SyncControllerEvents[K]
|
|
39
|
+
) => void;
|
|
40
|
+
|
|
41
|
+
/** Seek state tracking */
|
|
42
|
+
interface SeekState {
|
|
43
|
+
active: boolean;
|
|
44
|
+
id: number;
|
|
45
|
+
targetTime: number;
|
|
46
|
+
startedAt: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* SyncController - Manages playback synchronization
|
|
51
|
+
*/
|
|
52
|
+
export class SyncController {
|
|
53
|
+
private profile: LatencyProfile;
|
|
54
|
+
private jitterTracker: MultiTrackJitterTracker;
|
|
55
|
+
private listeners = new Map<keyof SyncControllerEvents, Set<Function>>();
|
|
56
|
+
|
|
57
|
+
// Playback speed state
|
|
58
|
+
private mainSpeed = 1;
|
|
59
|
+
private tweakSpeed = 1;
|
|
60
|
+
|
|
61
|
+
// Buffer state
|
|
62
|
+
private lastBufferCheck = 0;
|
|
63
|
+
private bufferCheckInterval = 100; // ms
|
|
64
|
+
|
|
65
|
+
// Server delay tracking
|
|
66
|
+
private serverDelays: number[] = [];
|
|
67
|
+
private maxServerDelaysSamples = 3;
|
|
68
|
+
|
|
69
|
+
// Live catchup tracking
|
|
70
|
+
// Reference: rawws.js:489-503 - proactively request 5s fast forward
|
|
71
|
+
private lastLiveCatchup = 0;
|
|
72
|
+
private liveCatchupCooldown = 2000; // Minimum ms between catchup requests
|
|
73
|
+
private liveCatchupThresholdMs = 5000; // Request catchup when within 5s of live
|
|
74
|
+
private liveCatchupRequestMs = 5000; // Request 5 seconds of fast forward
|
|
75
|
+
|
|
76
|
+
// Time tracking
|
|
77
|
+
private serverTime = 0;
|
|
78
|
+
private localTimeAtServerUpdate = 0;
|
|
79
|
+
|
|
80
|
+
// Seek state
|
|
81
|
+
private seekState: SeekState = {
|
|
82
|
+
active: false,
|
|
83
|
+
id: 0,
|
|
84
|
+
targetTime: 0,
|
|
85
|
+
startedAt: 0,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Stream info
|
|
89
|
+
private isLive = true;
|
|
90
|
+
|
|
91
|
+
// Callbacks for external control
|
|
92
|
+
private onSpeedChange?: (main: number, tweak: number) => void;
|
|
93
|
+
private onFastForwardRequest?: (ms: number) => void;
|
|
94
|
+
|
|
95
|
+
constructor(options: {
|
|
96
|
+
profile?: LatencyProfile;
|
|
97
|
+
isLive?: boolean;
|
|
98
|
+
onSpeedChange?: (main: number, tweak: number) => void;
|
|
99
|
+
onFastForwardRequest?: (ms: number) => void;
|
|
100
|
+
} = {}) {
|
|
101
|
+
this.profile = options.profile ?? getLatencyProfile('low');
|
|
102
|
+
this.isLive = options.isLive ?? true;
|
|
103
|
+
this.onSpeedChange = options.onSpeedChange;
|
|
104
|
+
this.onFastForwardRequest = options.onFastForwardRequest;
|
|
105
|
+
|
|
106
|
+
this.jitterTracker = new MultiTrackJitterTracker({
|
|
107
|
+
initialJitter: 120,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Update latency profile
|
|
113
|
+
*/
|
|
114
|
+
setProfile(profile: LatencyProfile): void {
|
|
115
|
+
this.profile = profile;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Update stream type (live vs VOD)
|
|
120
|
+
*/
|
|
121
|
+
setLive(isLive: boolean): void {
|
|
122
|
+
this.isLive = isLive;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Record a chunk arrival for jitter tracking
|
|
127
|
+
*/
|
|
128
|
+
recordChunkArrival(trackIndex: number, mediaTimeMs: number): void {
|
|
129
|
+
this.jitterTracker.addChunk(trackIndex, mediaTimeMs);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Update server time from on_time message
|
|
134
|
+
*/
|
|
135
|
+
updateServerTime(currentTime: number): void {
|
|
136
|
+
this.serverTime = currentTime;
|
|
137
|
+
this.localTimeAtServerUpdate = performance.now();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Record server delay measurement
|
|
142
|
+
*/
|
|
143
|
+
recordServerDelay(delayMs: number): void {
|
|
144
|
+
this.serverDelays.push(delayMs);
|
|
145
|
+
if (this.serverDelays.length > this.maxServerDelaysSamples) {
|
|
146
|
+
this.serverDelays.shift();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get current server delay estimate
|
|
152
|
+
*/
|
|
153
|
+
getServerDelay(): number {
|
|
154
|
+
if (this.serverDelays.length === 0) return 0;
|
|
155
|
+
return this.serverDelays.reduce((sum, d) => sum + d, 0) / this.serverDelays.length;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get estimated current server time (interpolated)
|
|
160
|
+
*/
|
|
161
|
+
getEstimatedServerTime(): number {
|
|
162
|
+
const elapsed = (performance.now() - this.localTimeAtServerUpdate) / 1000;
|
|
163
|
+
return this.serverTime + elapsed * this.getCombinedSpeed();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Calculate desired buffer size based on profile + jitter + server delay
|
|
168
|
+
* Matches MistServer mews.js pattern with Chrome-specific handling
|
|
169
|
+
*/
|
|
170
|
+
getDesiredBuffer(): number {
|
|
171
|
+
// Chrome needs larger base buffer (per mews.js:482)
|
|
172
|
+
const isChrome = typeof navigator !== 'undefined' &&
|
|
173
|
+
/Chrome/.test(navigator.userAgent) && !/Edge|Edg/.test(navigator.userAgent);
|
|
174
|
+
const baseBuffer = isChrome ? 1000 : 100;
|
|
175
|
+
|
|
176
|
+
const serverDelay = this.getServerDelay();
|
|
177
|
+
const jitter = this.jitterTracker.getMax() * this.profile.jitterMultiplier;
|
|
178
|
+
|
|
179
|
+
// Match mews.js formula: Math.max(baseBuffer + serverDelay, serverDelay * 2) + jitter
|
|
180
|
+
const liveBuffer = Math.max(baseBuffer + serverDelay, serverDelay * 2) + jitter;
|
|
181
|
+
|
|
182
|
+
// VoD gets extra buffer (mews.js:480)
|
|
183
|
+
return this.isLive ? liveBuffer : liveBuffer + 2000;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Evaluate buffer state and adjust playback speed if needed
|
|
188
|
+
*
|
|
189
|
+
* @param currentBufferMs - Current buffer level in milliseconds
|
|
190
|
+
* @returns Updated sync state
|
|
191
|
+
*/
|
|
192
|
+
evaluateBuffer(currentBufferMs: number): SyncState {
|
|
193
|
+
const now = performance.now();
|
|
194
|
+
|
|
195
|
+
// Rate limit buffer checks
|
|
196
|
+
if (now - this.lastBufferCheck < this.bufferCheckInterval) {
|
|
197
|
+
return this.getState(currentBufferMs);
|
|
198
|
+
}
|
|
199
|
+
this.lastBufferCheck = now;
|
|
200
|
+
|
|
201
|
+
const desired = this.getDesiredBuffer();
|
|
202
|
+
const ratio = currentBufferMs / desired;
|
|
203
|
+
const jitterMs = this.jitterTracker.getMax();
|
|
204
|
+
|
|
205
|
+
// Proactive live catchup logic
|
|
206
|
+
// Reference: rawws.js:489-503 - request 5s fast forward when close to live
|
|
207
|
+
// distanceToLive = how much buffer we have (close to live = small buffer)
|
|
208
|
+
// Conditions:
|
|
209
|
+
// - Within liveCatchupThreshold of live edge (small buffer)
|
|
210
|
+
// - Buffer is more than jitter + safety margin (not stalled)
|
|
211
|
+
// - Buffer is less than 1s above desired
|
|
212
|
+
// - Cooldown period has passed
|
|
213
|
+
if (this.isLive &&
|
|
214
|
+
currentBufferMs < this.liveCatchupThresholdMs &&
|
|
215
|
+
currentBufferMs > Math.max(jitterMs * 1.1, jitterMs + 250) &&
|
|
216
|
+
(currentBufferMs - desired) < 1000 &&
|
|
217
|
+
(now - this.lastLiveCatchup) > this.liveCatchupCooldown) {
|
|
218
|
+
this.lastLiveCatchup = now;
|
|
219
|
+
this.requestFastForward(this.liveCatchupRequestMs);
|
|
220
|
+
this.emit('livecatchup', { fastForwardMs: this.liveCatchupRequestMs });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Determine if speed adjustment needed
|
|
224
|
+
if (ratio > this.profile.speedUpThreshold && this.isLive) {
|
|
225
|
+
// Buffer too high - speed up to catch up to live edge
|
|
226
|
+
this.setTweakSpeed(this.profile.maxSpeedUp, 'catchup');
|
|
227
|
+
} else if (ratio < this.profile.speedDownThreshold) {
|
|
228
|
+
// Buffer too low - slow down to build buffer
|
|
229
|
+
this.setTweakSpeed(this.profile.minSpeedDown, 'slowdown');
|
|
230
|
+
|
|
231
|
+
// Request additional data if critically low
|
|
232
|
+
if (ratio < 0.3 && this.isLive) {
|
|
233
|
+
this.requestFastForward(desired - currentBufferMs);
|
|
234
|
+
this.emit('underrun', undefined);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
this.emit('bufferlow', { current: currentBufferMs, desired });
|
|
238
|
+
} else {
|
|
239
|
+
// Buffer in acceptable range - return to normal speed
|
|
240
|
+
if (this.tweakSpeed !== 1) {
|
|
241
|
+
this.setTweakSpeed(1, 'normal');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return this.getState(currentBufferMs);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get current sync state
|
|
250
|
+
*/
|
|
251
|
+
getState(currentBufferMs?: number): SyncState {
|
|
252
|
+
const buffer = currentBufferMs ?? 0;
|
|
253
|
+
const desired = this.getDesiredBuffer();
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
buffer: {
|
|
257
|
+
current: buffer,
|
|
258
|
+
desired,
|
|
259
|
+
ratio: desired > 0 ? buffer / desired : 1,
|
|
260
|
+
},
|
|
261
|
+
jitter: 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
|
+
playbackSpeed: this.getCombinedSpeed(),
|
|
269
|
+
serverTime: this.serverTime,
|
|
270
|
+
serverDelay: this.getServerDelay(),
|
|
271
|
+
avOffset: 0, // Server handles A/V sync via timestamp+offset
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get combined playback speed (main * tweak)
|
|
277
|
+
*/
|
|
278
|
+
getCombinedSpeed(): number {
|
|
279
|
+
return this.mainSpeed * this.tweakSpeed;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Set main playback speed (user-controlled)
|
|
284
|
+
*/
|
|
285
|
+
setMainSpeed(speed: number): void {
|
|
286
|
+
this.mainSpeed = speed;
|
|
287
|
+
this.jitterTracker.setSpeed(speed);
|
|
288
|
+
this.notifySpeedChange();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Set tweak speed (automatic adjustment)
|
|
293
|
+
*/
|
|
294
|
+
private setTweakSpeed(
|
|
295
|
+
speed: number,
|
|
296
|
+
reason: 'catchup' | 'slowdown' | 'normal'
|
|
297
|
+
): void {
|
|
298
|
+
if (this.tweakSpeed !== speed) {
|
|
299
|
+
this.tweakSpeed = speed;
|
|
300
|
+
this.notifySpeedChange();
|
|
301
|
+
this.emit('speedchange', { speed: this.getCombinedSpeed(), reason });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Notify external listeners of speed change
|
|
307
|
+
*/
|
|
308
|
+
private notifySpeedChange(): void {
|
|
309
|
+
this.onSpeedChange?.(this.mainSpeed, this.tweakSpeed);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Request additional data from server
|
|
314
|
+
*/
|
|
315
|
+
private requestFastForward(ms: number): void {
|
|
316
|
+
this.onFastForwardRequest?.(Math.max(0, ms));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// Seek Management
|
|
321
|
+
// ============================================================================
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Start a seek operation
|
|
325
|
+
* Returns a seek ID that can be used to check if seek is still active
|
|
326
|
+
*/
|
|
327
|
+
startSeek(targetTimeMs: number): number {
|
|
328
|
+
// Cancel any existing seek
|
|
329
|
+
this.seekState.id++;
|
|
330
|
+
|
|
331
|
+
this.seekState = {
|
|
332
|
+
active: true,
|
|
333
|
+
id: this.seekState.id,
|
|
334
|
+
targetTime: targetTimeMs,
|
|
335
|
+
startedAt: performance.now(),
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Reset jitter tracking on seek
|
|
339
|
+
this.jitterTracker.reset();
|
|
340
|
+
|
|
341
|
+
this.emit('seekstart', { seekId: this.seekState.id, time: targetTimeMs });
|
|
342
|
+
|
|
343
|
+
return this.seekState.id;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Check if a seek is still the active one
|
|
348
|
+
*/
|
|
349
|
+
isSeekActive(seekId: number): boolean {
|
|
350
|
+
return this.seekState.active && this.seekState.id === seekId;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Complete a seek operation
|
|
355
|
+
*/
|
|
356
|
+
completeSeek(seekId: number): void {
|
|
357
|
+
if (this.seekState.id === seekId) {
|
|
358
|
+
this.seekState.active = false;
|
|
359
|
+
this.emit('seekcomplete', { seekId });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Cancel any active seek
|
|
365
|
+
*/
|
|
366
|
+
cancelSeek(): void {
|
|
367
|
+
if (this.seekState.active) {
|
|
368
|
+
this.seekState.active = false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Check if currently seeking
|
|
374
|
+
*/
|
|
375
|
+
isSeeking(): boolean {
|
|
376
|
+
return this.seekState.active;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ============================================================================
|
|
380
|
+
// Track Management
|
|
381
|
+
// ============================================================================
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Register a new track
|
|
385
|
+
*/
|
|
386
|
+
addTrack(trackIndex: number, track: TrackInfo): void {
|
|
387
|
+
// Jitter tracking will be initialized on first chunk
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Remove a track
|
|
392
|
+
*/
|
|
393
|
+
removeTrack(trackIndex: number): void {
|
|
394
|
+
this.jitterTracker.removeTrack(trackIndex);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ============================================================================
|
|
398
|
+
// Reset
|
|
399
|
+
// ============================================================================
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Reset all state
|
|
403
|
+
*/
|
|
404
|
+
reset(): void {
|
|
405
|
+
this.mainSpeed = 1;
|
|
406
|
+
this.tweakSpeed = 1;
|
|
407
|
+
this.serverDelays = [];
|
|
408
|
+
this.serverTime = 0;
|
|
409
|
+
this.localTimeAtServerUpdate = 0;
|
|
410
|
+
this.lastLiveCatchup = 0;
|
|
411
|
+
this.jitterTracker.reset();
|
|
412
|
+
this.seekState = {
|
|
413
|
+
active: false,
|
|
414
|
+
id: 0,
|
|
415
|
+
targetTime: 0,
|
|
416
|
+
startedAt: 0,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// ============================================================================
|
|
421
|
+
// Event Emitter
|
|
422
|
+
// ============================================================================
|
|
423
|
+
|
|
424
|
+
on<K extends keyof SyncControllerEvents>(
|
|
425
|
+
event: K,
|
|
426
|
+
listener: EventListener<K>
|
|
427
|
+
): void {
|
|
428
|
+
if (!this.listeners.has(event)) {
|
|
429
|
+
this.listeners.set(event, new Set());
|
|
430
|
+
}
|
|
431
|
+
this.listeners.get(event)!.add(listener);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
off<K extends keyof SyncControllerEvents>(
|
|
435
|
+
event: K,
|
|
436
|
+
listener: EventListener<K>
|
|
437
|
+
): void {
|
|
438
|
+
this.listeners.get(event)?.delete(listener);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private emit<K extends keyof SyncControllerEvents>(
|
|
442
|
+
event: K,
|
|
443
|
+
data: SyncControllerEvents[K]
|
|
444
|
+
): void {
|
|
445
|
+
const eventListeners = this.listeners.get(event);
|
|
446
|
+
if (eventListeners) {
|
|
447
|
+
for (const listener of eventListeners) {
|
|
448
|
+
try {
|
|
449
|
+
listener(data);
|
|
450
|
+
} catch (err) {
|
|
451
|
+
console.error(`Error in ${event} listener:`, err);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|