@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.
Files changed (120) hide show
  1. package/dist/cjs/index.js +19493 -0
  2. package/dist/cjs/index.js.map +1 -0
  3. package/dist/esm/index.js +19398 -0
  4. package/dist/esm/index.js.map +1 -0
  5. package/dist/player.css +2140 -0
  6. package/dist/types/core/ABRController.d.ts +164 -0
  7. package/dist/types/core/CodecUtils.d.ts +54 -0
  8. package/dist/types/core/Disposable.d.ts +61 -0
  9. package/dist/types/core/EventEmitter.d.ts +73 -0
  10. package/dist/types/core/GatewayClient.d.ts +144 -0
  11. package/dist/types/core/InteractionController.d.ts +121 -0
  12. package/dist/types/core/LiveDurationProxy.d.ts +102 -0
  13. package/dist/types/core/MetaTrackManager.d.ts +220 -0
  14. package/dist/types/core/MistReporter.d.ts +163 -0
  15. package/dist/types/core/MistSignaling.d.ts +148 -0
  16. package/dist/types/core/PlayerController.d.ts +665 -0
  17. package/dist/types/core/PlayerInterface.d.ts +230 -0
  18. package/dist/types/core/PlayerManager.d.ts +182 -0
  19. package/dist/types/core/PlayerRegistry.d.ts +27 -0
  20. package/dist/types/core/QualityMonitor.d.ts +184 -0
  21. package/dist/types/core/ScreenWakeLockManager.d.ts +70 -0
  22. package/dist/types/core/SeekingUtils.d.ts +142 -0
  23. package/dist/types/core/StreamStateClient.d.ts +108 -0
  24. package/dist/types/core/SubtitleManager.d.ts +111 -0
  25. package/dist/types/core/TelemetryReporter.d.ts +79 -0
  26. package/dist/types/core/TimeFormat.d.ts +97 -0
  27. package/dist/types/core/TimerManager.d.ts +83 -0
  28. package/dist/types/core/UrlUtils.d.ts +81 -0
  29. package/dist/types/core/detector.d.ts +149 -0
  30. package/dist/types/core/index.d.ts +49 -0
  31. package/dist/types/core/scorer.d.ts +167 -0
  32. package/dist/types/core/selector.d.ts +9 -0
  33. package/dist/types/index.d.ts +45 -0
  34. package/dist/types/lib/utils.d.ts +2 -0
  35. package/dist/types/players/DashJsPlayer.d.ts +102 -0
  36. package/dist/types/players/HlsJsPlayer.d.ts +70 -0
  37. package/dist/types/players/MewsWsPlayer/SourceBufferManager.d.ts +119 -0
  38. package/dist/types/players/MewsWsPlayer/WebSocketManager.d.ts +60 -0
  39. package/dist/types/players/MewsWsPlayer/index.d.ts +220 -0
  40. package/dist/types/players/MewsWsPlayer/types.d.ts +89 -0
  41. package/dist/types/players/MistPlayer.d.ts +25 -0
  42. package/dist/types/players/MistWebRTCPlayer/index.d.ts +133 -0
  43. package/dist/types/players/NativePlayer.d.ts +143 -0
  44. package/dist/types/players/VideoJsPlayer.d.ts +59 -0
  45. package/dist/types/players/WebCodecsPlayer/JitterBuffer.d.ts +118 -0
  46. package/dist/types/players/WebCodecsPlayer/LatencyProfiles.d.ts +64 -0
  47. package/dist/types/players/WebCodecsPlayer/RawChunkParser.d.ts +63 -0
  48. package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +174 -0
  49. package/dist/types/players/WebCodecsPlayer/WebSocketController.d.ts +164 -0
  50. package/dist/types/players/WebCodecsPlayer/index.d.ts +149 -0
  51. package/dist/types/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.d.ts +105 -0
  52. package/dist/types/players/WebCodecsPlayer/types.d.ts +395 -0
  53. package/dist/types/players/WebCodecsPlayer/worker/decoder.worker.d.ts +13 -0
  54. package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +197 -0
  55. package/dist/types/players/index.d.ts +14 -0
  56. package/dist/types/styles/index.d.ts +11 -0
  57. package/dist/types/types.d.ts +363 -0
  58. package/dist/types/vanilla/FrameWorksPlayer.d.ts +143 -0
  59. package/dist/types/vanilla/index.d.ts +19 -0
  60. package/dist/workers/decoder.worker.js +989 -0
  61. package/dist/workers/decoder.worker.js.map +1 -0
  62. package/package.json +80 -0
  63. package/src/core/ABRController.ts +550 -0
  64. package/src/core/CodecUtils.ts +257 -0
  65. package/src/core/Disposable.ts +120 -0
  66. package/src/core/EventEmitter.ts +113 -0
  67. package/src/core/GatewayClient.ts +439 -0
  68. package/src/core/InteractionController.ts +712 -0
  69. package/src/core/LiveDurationProxy.ts +270 -0
  70. package/src/core/MetaTrackManager.ts +753 -0
  71. package/src/core/MistReporter.ts +543 -0
  72. package/src/core/MistSignaling.ts +346 -0
  73. package/src/core/PlayerController.ts +2829 -0
  74. package/src/core/PlayerInterface.ts +432 -0
  75. package/src/core/PlayerManager.ts +900 -0
  76. package/src/core/PlayerRegistry.ts +149 -0
  77. package/src/core/QualityMonitor.ts +597 -0
  78. package/src/core/ScreenWakeLockManager.ts +163 -0
  79. package/src/core/SeekingUtils.ts +364 -0
  80. package/src/core/StreamStateClient.ts +457 -0
  81. package/src/core/SubtitleManager.ts +297 -0
  82. package/src/core/TelemetryReporter.ts +308 -0
  83. package/src/core/TimeFormat.ts +205 -0
  84. package/src/core/TimerManager.ts +209 -0
  85. package/src/core/UrlUtils.ts +179 -0
  86. package/src/core/detector.ts +382 -0
  87. package/src/core/index.ts +140 -0
  88. package/src/core/scorer.ts +553 -0
  89. package/src/core/selector.ts +16 -0
  90. package/src/global.d.ts +11 -0
  91. package/src/index.ts +75 -0
  92. package/src/lib/utils.ts +6 -0
  93. package/src/players/DashJsPlayer.ts +642 -0
  94. package/src/players/HlsJsPlayer.ts +483 -0
  95. package/src/players/MewsWsPlayer/SourceBufferManager.ts +572 -0
  96. package/src/players/MewsWsPlayer/WebSocketManager.ts +241 -0
  97. package/src/players/MewsWsPlayer/index.ts +1065 -0
  98. package/src/players/MewsWsPlayer/types.ts +106 -0
  99. package/src/players/MistPlayer.ts +188 -0
  100. package/src/players/MistWebRTCPlayer/index.ts +703 -0
  101. package/src/players/NativePlayer.ts +820 -0
  102. package/src/players/VideoJsPlayer.ts +643 -0
  103. package/src/players/WebCodecsPlayer/JitterBuffer.ts +299 -0
  104. package/src/players/WebCodecsPlayer/LatencyProfiles.ts +151 -0
  105. package/src/players/WebCodecsPlayer/RawChunkParser.ts +151 -0
  106. package/src/players/WebCodecsPlayer/SyncController.ts +456 -0
  107. package/src/players/WebCodecsPlayer/WebSocketController.ts +564 -0
  108. package/src/players/WebCodecsPlayer/index.ts +1650 -0
  109. package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +379 -0
  110. package/src/players/WebCodecsPlayer/types.ts +542 -0
  111. package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +1360 -0
  112. package/src/players/WebCodecsPlayer/worker/types.ts +276 -0
  113. package/src/players/index.ts +22 -0
  114. package/src/styles/animations.css +21 -0
  115. package/src/styles/index.ts +52 -0
  116. package/src/styles/player.css +2126 -0
  117. package/src/styles/tailwind.css +1015 -0
  118. package/src/types.ts +421 -0
  119. package/src/vanilla/FrameWorksPlayer.ts +367 -0
  120. package/src/vanilla/index.ts +22 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * JitterBuffer - Network Jitter Estimation
3
+ *
4
+ * Tracks network jitter to inform buffer sizing decisions.
5
+ * Ported from legacy rawws.js JitterTracker with improvements:
6
+ * - Per-track jitter tracking (audio/video can differ)
7
+ * - TypeScript types
8
+ * - Better edge case handling
9
+ *
10
+ * Algorithm:
11
+ * 1. Track arrival time vs media time for last N chunks
12
+ * 2. Calculate jitter = (mediaTimePassed / speed) - clockTimePassed
13
+ * 3. Maintain sliding window of peak jitter per second
14
+ * 4. Weighted average: (avgPeak + maxPeak * 2) / 3 + 1ms
15
+ * 5. Limit lowering rate to prevent oscillation
16
+ */
17
+
18
+ import type { JitterState } from './types';
19
+
20
+ /** Default sliding window size for chunk tracking */
21
+ const DEFAULT_CHUNK_WINDOW = 8;
22
+
23
+ /** Default sliding window size for peak tracking */
24
+ const DEFAULT_PEAK_WINDOW = 8;
25
+
26
+ /** Interval between peak calculations (ms) */
27
+ const PEAK_INTERVAL_MS = 1000;
28
+
29
+ /** Maximum jitter decrease per interval (ms) */
30
+ const MAX_JITTER_DECREASE = 500;
31
+
32
+ /** Initial jitter estimate (ms) */
33
+ const INITIAL_JITTER = 120;
34
+
35
+ /** Minimum jitter floor (ms) */
36
+ const MIN_JITTER = 1;
37
+
38
+ interface ChunkTiming {
39
+ /** Wall clock time when chunk arrived (performance.now()) */
40
+ receiveTime: number;
41
+ /** Media timestamp from chunk (ms) */
42
+ mediaTime: number;
43
+ }
44
+
45
+ export interface JitterTrackerOptions {
46
+ /** Initial jitter estimate (ms) */
47
+ initialJitter?: number;
48
+ /** Sliding window size for chunks */
49
+ chunkWindowSize?: number;
50
+ /** Sliding window size for peaks */
51
+ peakWindowSize?: number;
52
+ }
53
+
54
+ /**
55
+ * JitterTracker - Estimates network jitter for a single track
56
+ */
57
+ export class JitterTracker {
58
+ /** Sliding window of chunk timings */
59
+ private chunks: ChunkTiming[] = [];
60
+
61
+ /** Current playback speed (1 = realtime) */
62
+ private speed = 1;
63
+
64
+ /** Last time a peak was recorded */
65
+ private lastPeakTime = 0;
66
+
67
+ /** Maximum jitter observed in current interval */
68
+ private currentPeak = 0;
69
+
70
+ /** Sliding window of peak jitter values */
71
+ private peaks: number[] = [];
72
+
73
+ /** Weighted average jitter estimate */
74
+ private maxJitter: number;
75
+
76
+ /** Configuration */
77
+ private readonly chunkWindowSize: number;
78
+ private readonly peakWindowSize: number;
79
+
80
+ constructor(options: JitterTrackerOptions = {}) {
81
+ this.maxJitter = options.initialJitter ?? INITIAL_JITTER;
82
+ this.chunkWindowSize = options.chunkWindowSize ?? DEFAULT_CHUNK_WINDOW;
83
+ this.peakWindowSize = options.peakWindowSize ?? DEFAULT_PEAK_WINDOW;
84
+ }
85
+
86
+ /**
87
+ * Add a received chunk to jitter calculation
88
+ *
89
+ * @param mediaTime - Media timestamp from chunk (ms)
90
+ * @param receiveTime - Wall clock time (performance.now())
91
+ */
92
+ addChunk(mediaTime: number, receiveTime: number = performance.now()): void {
93
+ // Add to sliding window
94
+ this.chunks.push({ receiveTime, mediaTime });
95
+ if (this.chunks.length > this.chunkWindowSize) {
96
+ this.chunks.shift();
97
+ }
98
+
99
+ // Calculate instantaneous jitter
100
+ const jitter = this.calculateJitter();
101
+ if (jitter > this.currentPeak) {
102
+ this.currentPeak = jitter;
103
+ }
104
+
105
+ // Update peaks every second
106
+ const now = performance.now();
107
+ if (now > this.lastPeakTime + PEAK_INTERVAL_MS) {
108
+ this.recordPeak();
109
+ this.lastPeakTime = now;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Calculate current instantaneous jitter
115
+ */
116
+ private calculateJitter(): number {
117
+ if (this.chunks.length <= 1) {
118
+ return 0;
119
+ }
120
+
121
+ // Skip calculation during fast-forward
122
+ if (this.speed === 0 || !isFinite(this.speed)) {
123
+ return 0;
124
+ }
125
+
126
+ const oldest = this.chunks[0];
127
+ const newest = this.chunks[this.chunks.length - 1];
128
+
129
+ // Time passed on wall clock
130
+ const clockTimePassed = newest.receiveTime - oldest.receiveTime;
131
+
132
+ // Time that should have passed based on media timestamps
133
+ const mediaTimePassed = newest.mediaTime - oldest.mediaTime;
134
+
135
+ // Jitter = expected - actual
136
+ // Positive jitter means chunks arriving faster than expected (buffering)
137
+ // Negative jitter means chunks arriving slower than expected (starving)
138
+ const jitter = mediaTimePassed / this.speed - clockTimePassed;
139
+
140
+ return Math.max(0, jitter);
141
+ }
142
+
143
+ /**
144
+ * Record current peak and update weighted average
145
+ */
146
+ private recordPeak(): void {
147
+ // Add current peak to sliding window
148
+ this.peaks.push(this.currentPeak);
149
+ if (this.peaks.length > this.peakWindowSize) {
150
+ this.peaks.shift();
151
+ }
152
+
153
+ // Reset for next interval
154
+ this.currentPeak = 0;
155
+
156
+ // Calculate new weighted average
157
+ if (this.peaks.length > 0) {
158
+ const maxPeak = Math.max(...this.peaks);
159
+ const avgPeak =
160
+ this.peaks.reduce((sum, p) => sum + p, 0) / this.peaks.length;
161
+
162
+ // Weighted: emphasize max peak for safety
163
+ let weighted = (avgPeak + maxPeak * 2) / 3 + MIN_JITTER;
164
+
165
+ // Limit rate of decrease to prevent oscillation
166
+ if (this.maxJitter > weighted + MAX_JITTER_DECREASE) {
167
+ weighted = this.maxJitter - MAX_JITTER_DECREASE;
168
+ }
169
+
170
+ // Smooth transition
171
+ this.maxJitter = (this.maxJitter + weighted) / 2;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Get current jitter estimate (ms)
177
+ */
178
+ get(): number {
179
+ return this.maxJitter;
180
+ }
181
+
182
+ /**
183
+ * Get detailed jitter state
184
+ */
185
+ getState(): JitterState {
186
+ return {
187
+ current: this.calculateJitter(),
188
+ peak: this.currentPeak,
189
+ weighted: this.maxJitter,
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Set playback speed for jitter calculation
195
+ */
196
+ setSpeed(speed: number | 'auto'): void {
197
+ const newSpeed = speed === 'auto' ? 1 : speed;
198
+ if (newSpeed !== this.speed) {
199
+ this.speed = newSpeed;
200
+ this.reset();
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Reset jitter tracking (e.g., after seek)
206
+ */
207
+ reset(): void {
208
+ this.chunks = [];
209
+ this.currentPeak = 0;
210
+ // Don't reset maxJitter - keep the learned estimate
211
+ }
212
+
213
+ /**
214
+ * Full reset including learned jitter estimate
215
+ */
216
+ fullReset(): void {
217
+ this.reset();
218
+ this.peaks = [];
219
+ this.maxJitter = INITIAL_JITTER;
220
+ this.lastPeakTime = 0;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * MultiTrackJitterTracker - Manages jitter tracking for multiple tracks
226
+ */
227
+ export class MultiTrackJitterTracker {
228
+ private trackers = new Map<number, JitterTracker>();
229
+ private globalSpeed = 1;
230
+ private options: JitterTrackerOptions;
231
+
232
+ constructor(options: JitterTrackerOptions = {}) {
233
+ this.options = options;
234
+ }
235
+
236
+ /**
237
+ * Add a chunk for a specific track
238
+ */
239
+ addChunk(trackIndex: number, mediaTime: number, receiveTime?: number): void {
240
+ let tracker = this.trackers.get(trackIndex);
241
+ if (!tracker) {
242
+ tracker = new JitterTracker(this.options);
243
+ tracker.setSpeed(this.globalSpeed);
244
+ this.trackers.set(trackIndex, tracker);
245
+ }
246
+ tracker.addChunk(mediaTime, receiveTime);
247
+ }
248
+
249
+ /**
250
+ * Get maximum jitter across all tracks
251
+ */
252
+ getMax(): number {
253
+ let max = 0;
254
+ for (const tracker of this.trackers.values()) {
255
+ max = Math.max(max, tracker.get());
256
+ }
257
+ return max;
258
+ }
259
+
260
+ /**
261
+ * Get jitter for a specific track
262
+ */
263
+ getForTrack(trackIndex: number): number {
264
+ return this.trackers.get(trackIndex)?.get() ?? INITIAL_JITTER;
265
+ }
266
+
267
+ /**
268
+ * Set playback speed for all trackers
269
+ */
270
+ setSpeed(speed: number | 'auto'): void {
271
+ this.globalSpeed = speed === 'auto' ? 1 : speed;
272
+ for (const tracker of this.trackers.values()) {
273
+ tracker.setSpeed(speed);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Reset all trackers
279
+ */
280
+ reset(): void {
281
+ for (const tracker of this.trackers.values()) {
282
+ tracker.reset();
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Remove a track's tracker
288
+ */
289
+ removeTrack(trackIndex: number): void {
290
+ this.trackers.delete(trackIndex);
291
+ }
292
+
293
+ /**
294
+ * Clear all trackers
295
+ */
296
+ clear(): void {
297
+ this.trackers.clear();
298
+ }
299
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Latency Profiles for WebCodecs Player
3
+ *
4
+ * Presets for trading off latency vs stability.
5
+ *
6
+ * Buffer calculation: desiredBuffer = keepAway + serverDelay + (jitter * jitterMultiplier)
7
+ *
8
+ * Speed tweaking:
9
+ * - If buffer > desired * speedUpThreshold → speed up to maxSpeedUp
10
+ * - If buffer < desired * speedDownThreshold → slow down to minSpeedDown
11
+ */
12
+
13
+ import type { LatencyProfile, LatencyProfileName } from './types';
14
+
15
+ /**
16
+ * Ultra-low latency profile
17
+ * Target: <200ms end-to-end latency
18
+ * Use case: Real-time conferencing, remote control
19
+ * Trade-offs: May stutter on poor networks
20
+ */
21
+ export const ULTRA_LOW_PROFILE: LatencyProfile = {
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
+ };
32
+
33
+ /**
34
+ * Low latency profile
35
+ * Target: ~300-500ms end-to-end latency
36
+ * Use case: Live sports, gaming streams
37
+ * Trade-offs: Balanced latency/stability
38
+ */
39
+ export const LOW_PROFILE: LatencyProfile = {
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
+ optimizeForLatency: true,
49
+ };
50
+
51
+ /**
52
+ * Balanced profile
53
+ * Target: ~500-1000ms end-to-end latency
54
+ * Use case: General live streaming
55
+ * Trade-offs: Prioritizes stability over latency
56
+ */
57
+ export const BALANCED_PROFILE: LatencyProfile = {
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
+ optimizeForLatency: false, // Let decoders optimize for quality
67
+ };
68
+
69
+ /**
70
+ * Quality priority profile
71
+ * Target: ~1-2s end-to-end latency
72
+ * Use case: VOD, recorded content, poor networks
73
+ * Trade-offs: Maximum stability, higher latency
74
+ */
75
+ export const QUALITY_PROFILE: LatencyProfile = {
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
+ optimizeForLatency: false,
85
+ };
86
+
87
+ /**
88
+ * All available latency profiles
89
+ */
90
+ export const LATENCY_PROFILES: Record<LatencyProfileName, LatencyProfile> = {
91
+ 'ultra-low': ULTRA_LOW_PROFILE,
92
+ 'low': LOW_PROFILE,
93
+ 'balanced': BALANCED_PROFILE,
94
+ 'quality': QUALITY_PROFILE,
95
+ };
96
+
97
+ /**
98
+ * Get a latency profile by name
99
+ * @param name - Profile name
100
+ * @returns The profile, or 'low' as default
101
+ */
102
+ export function getLatencyProfile(name?: LatencyProfileName): LatencyProfile {
103
+ if (name && name in LATENCY_PROFILES) {
104
+ return LATENCY_PROFILES[name];
105
+ }
106
+ return LATENCY_PROFILES['low'];
107
+ }
108
+
109
+ /**
110
+ * Merge a custom partial profile with a base profile
111
+ * @param base - Base profile name or profile object
112
+ * @param custom - Partial overrides
113
+ * @returns Merged profile
114
+ */
115
+ export function mergeLatencyProfile(
116
+ base: LatencyProfileName | LatencyProfile,
117
+ custom?: Partial<LatencyProfile>
118
+ ): LatencyProfile {
119
+ const baseProfile = typeof base === 'string' ? getLatencyProfile(base) : base;
120
+
121
+ if (!custom) {
122
+ return baseProfile;
123
+ }
124
+
125
+ return {
126
+ ...baseProfile,
127
+ ...custom,
128
+ name: custom.name ?? `${baseProfile.name} (Custom)`,
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Select appropriate profile based on stream type
134
+ * @param isLive - Whether the stream is live
135
+ * @param preferLowLatency - Whether to prefer low latency (e.g., WebRTC source)
136
+ * @returns Recommended profile name
137
+ */
138
+ export function selectDefaultProfile(
139
+ isLive: boolean,
140
+ preferLowLatency = false
141
+ ): LatencyProfileName {
142
+ if (!isLive) {
143
+ return 'quality';
144
+ }
145
+
146
+ if (preferLowLatency) {
147
+ return 'low';
148
+ }
149
+
150
+ return 'balanced';
151
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * RawChunkParser - Binary Frame Header Parser
3
+ *
4
+ * Parses the 12-byte binary header from MistServer's raw WebSocket stream.
5
+ *
6
+ * Header format:
7
+ * Byte 0: Track index (uint8)
8
+ * Byte 1: Chunk type: 0=delta, 1=key, 2=init
9
+ * Bytes 2-9: Timestamp in milliseconds (uint64 big-endian)
10
+ * Bytes 10-11: Offset in milliseconds (int16 big-endian, signed)
11
+ *
12
+ * The offset is server-calculated and used for A/V synchronization.
13
+ * Combined presentation time = timestamp + offset
14
+ */
15
+
16
+ import type { RawChunk, ChunkType } from './types';
17
+
18
+ const HEADER_LENGTH = 12;
19
+
20
+ /**
21
+ * Parse chunk type byte to string
22
+ */
23
+ function parseChunkType(typeByte: number): ChunkType {
24
+ switch (typeByte) {
25
+ case 1:
26
+ return 'key';
27
+ case 2:
28
+ return 'init';
29
+ default:
30
+ return 'delta';
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Parse a raw binary chunk from MistServer
36
+ *
37
+ * @param data - ArrayBuffer containing header + frame data
38
+ * @returns Parsed RawChunk object
39
+ * @throws Error if data is too short
40
+ */
41
+ export function parseRawChunk(data: ArrayBuffer): RawChunk {
42
+ if (data.byteLength < HEADER_LENGTH) {
43
+ throw new Error(
44
+ `Invalid chunk: expected at least ${HEADER_LENGTH} bytes, got ${data.byteLength}`
45
+ );
46
+ }
47
+
48
+ const headerView = new DataView(data, 0, HEADER_LENGTH);
49
+
50
+ // Byte 0: track index
51
+ const trackIndex = headerView.getUint8(0);
52
+
53
+ // Byte 1: chunk type
54
+ const type = parseChunkType(headerView.getUint8(1));
55
+
56
+ // Bytes 2-9: timestamp (uint64 big-endian)
57
+ // getBigUint64 returns BigInt, convert to Number (safe for values < 2^53)
58
+ const timestampBig = headerView.getBigUint64(2, false); // false = big-endian
59
+ const timestamp = Number(timestampBig);
60
+
61
+ // Bytes 10-11: offset (int16 big-endian, signed)
62
+ const offset = headerView.getInt16(10, false); // false = big-endian
63
+
64
+ // Remaining bytes: actual frame data
65
+ // Use slice() to create a copy (like reference rawws.js line 462)
66
+ // This ensures the data survives buffer transfers
67
+ const frameData = new Uint8Array(data.slice(HEADER_LENGTH));
68
+
69
+ return {
70
+ trackIndex,
71
+ type,
72
+ timestamp,
73
+ offset,
74
+ data: frameData,
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Calculate the presentation timestamp for a chunk
80
+ * This combines the server timestamp with the sync offset
81
+ *
82
+ * @param chunk - Parsed raw chunk
83
+ * @returns Presentation timestamp in microseconds (for WebCodecs API)
84
+ */
85
+ export function getPresentationTimestamp(chunk: RawChunk): number {
86
+ // timestamp and offset are in milliseconds
87
+ // WebCodecs expects microseconds
88
+ return (chunk.timestamp + chunk.offset) * 1000;
89
+ }
90
+
91
+ /**
92
+ * Check if this chunk is a keyframe
93
+ */
94
+ export function isKeyframe(chunk: RawChunk): boolean {
95
+ return chunk.type === 'key';
96
+ }
97
+
98
+ /**
99
+ * Check if this chunk contains codec initialization data
100
+ */
101
+ export function isInitData(chunk: RawChunk): boolean {
102
+ return chunk.type === 'init';
103
+ }
104
+
105
+ /**
106
+ * Format chunk for debug logging
107
+ */
108
+ export function formatChunkForLog(chunk: RawChunk): string {
109
+ const pts = (chunk.timestamp + chunk.offset) / 1000; // seconds
110
+ const ptsFormatted = pts.toFixed(3);
111
+ return `[Track ${chunk.trackIndex}] ${chunk.type.toUpperCase()} @ ${ptsFormatted}s (${chunk.data.byteLength} bytes)`;
112
+ }
113
+
114
+ /**
115
+ * RawChunkParser class for stateful parsing with validation
116
+ */
117
+ export class RawChunkParser {
118
+ private debug: boolean;
119
+
120
+ constructor(options: { debug?: boolean } = {}) {
121
+ this.debug = options.debug ?? false;
122
+ }
123
+
124
+ /**
125
+ * Parse binary data from WebSocket
126
+ *
127
+ * @param data - ArrayBuffer from WebSocket message
128
+ * @returns Parsed chunk or null if invalid
129
+ */
130
+ parse(data: ArrayBuffer): RawChunk | null {
131
+ try {
132
+ const chunk = parseRawChunk(data);
133
+
134
+ if (this.debug) {
135
+ console.log('▶️', formatChunkForLog(chunk));
136
+ }
137
+
138
+ return chunk;
139
+ } catch (err) {
140
+ console.error('▶️ Failed to parse chunk:', err);
141
+ return null;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Set debug mode
147
+ */
148
+ setDebug(enabled: boolean | 'verbose'): void {
149
+ this.debug = enabled === 'verbose' || enabled === true;
150
+ }
151
+ }