@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,439 @@
1
+ /**
2
+ * GatewayClient.ts
3
+ *
4
+ * Framework-agnostic client for resolving viewer endpoints from the Gateway GraphQL API.
5
+ * Extracted from useViewerEndpoints.ts for use in headless core.
6
+ */
7
+
8
+ import { TypedEventEmitter } from './EventEmitter';
9
+ import type { ContentEndpoints, ContentType } from '../types';
10
+
11
+ // ============================================================================
12
+ // Types
13
+ // ============================================================================
14
+
15
+ export type GatewayStatus = 'idle' | 'loading' | 'ready' | 'error';
16
+
17
+ export interface GatewayClientConfig {
18
+ /** Gateway GraphQL endpoint URL */
19
+ gatewayUrl: string;
20
+ /** Content type to resolve */
21
+ contentType: ContentType;
22
+ /** Content identifier (stream name) */
23
+ contentId: string;
24
+ /** Optional auth token for private streams */
25
+ authToken?: string;
26
+ /** Maximum retry attempts (default: 3) */
27
+ maxRetries?: number;
28
+ /** Initial retry delay in ms (default: 500) */
29
+ initialDelayMs?: number;
30
+ }
31
+
32
+ export interface GatewayClientEvents {
33
+ /** Emitted when status changes */
34
+ statusChange: { status: GatewayStatus; error?: string };
35
+ /** Emitted when endpoints are successfully resolved */
36
+ endpointsResolved: { endpoints: ContentEndpoints };
37
+ }
38
+
39
+ // ============================================================================
40
+ // Constants
41
+ // ============================================================================
42
+
43
+ const DEFAULT_MAX_RETRIES = 3;
44
+ const DEFAULT_INITIAL_DELAY_MS = 500;
45
+ // F2: Cache TTL for resolved endpoints
46
+ const DEFAULT_CACHE_TTL_MS = 10000;
47
+ // F3: Circuit breaker constants
48
+ const CIRCUIT_BREAKER_THRESHOLD = 5; // Open after 5 consecutive failures
49
+ const CIRCUIT_BREAKER_TIMEOUT_MS = 30000; // Half-open after 30 seconds
50
+
51
+ type CircuitBreakerState = 'closed' | 'open' | 'half-open';
52
+
53
+ const RESOLVE_VIEWER_QUERY = `
54
+ query ResolveViewer($contentType: String!, $contentId: String!) {
55
+ resolveViewerEndpoint(contentType: $contentType, contentId: $contentId) {
56
+ primary { nodeId baseUrl protocol url geoDistance loadScore outputs }
57
+ fallbacks { nodeId baseUrl protocol url geoDistance loadScore outputs }
58
+ metadata { contentType contentId title description durationSeconds status isLive viewers recordingSizeBytes clipSource createdAt }
59
+ }
60
+ }
61
+ `;
62
+
63
+ // ============================================================================
64
+ // Helper Functions
65
+ // ============================================================================
66
+
67
+ /**
68
+ * Fetch with exponential backoff retry logic.
69
+ */
70
+ async function fetchWithRetry(
71
+ url: string,
72
+ options: RequestInit,
73
+ maxRetries: number,
74
+ initialDelay: number
75
+ ): Promise<Response> {
76
+ let lastError: Error | null = null;
77
+
78
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
79
+ try {
80
+ const response = await fetch(url, options);
81
+ return response;
82
+ } catch (e) {
83
+ lastError = e instanceof Error ? e : new Error('Fetch failed');
84
+
85
+ // Don't retry on abort
86
+ if (options.signal?.aborted) {
87
+ throw lastError;
88
+ }
89
+
90
+ // Wait before retrying (exponential backoff)
91
+ if (attempt < maxRetries - 1) {
92
+ const delay = initialDelay * Math.pow(2, attempt);
93
+ console.warn(`[GatewayClient] Retry ${attempt + 1}/${maxRetries - 1} after ${delay}ms`);
94
+ await new Promise(resolve => setTimeout(resolve, delay));
95
+ }
96
+ }
97
+ }
98
+
99
+ throw lastError ?? new Error('Gateway unreachable after retries');
100
+ }
101
+
102
+ // ============================================================================
103
+ // GatewayClient Class
104
+ // ============================================================================
105
+
106
+ /**
107
+ * Client for resolving viewer endpoints from the Gateway GraphQL API.
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * const client = new GatewayClient({
112
+ * gatewayUrl: 'https://gateway.example.com/graphql',
113
+ * contentType: 'live',
114
+ * contentId: 'my-stream',
115
+ * });
116
+ *
117
+ * client.on('statusChange', ({ status }) => console.log('Status:', status));
118
+ * client.on('endpointsResolved', ({ endpoints }) => console.log('Endpoints:', endpoints));
119
+ *
120
+ * const endpoints = await client.resolve();
121
+ * ```
122
+ */
123
+ export class GatewayClient extends TypedEventEmitter<GatewayClientEvents> {
124
+ private config: GatewayClientConfig;
125
+ private status: GatewayStatus = 'idle';
126
+ private endpoints: ContentEndpoints | null = null;
127
+ private error: string | null = null;
128
+ private abortController: AbortController | null = null;
129
+
130
+ // F2: Request deduplication - in-flight request tracking
131
+ private inFlightRequest: Promise<ContentEndpoints> | null = null;
132
+ // F2: Cache with TTL for resolved endpoints
133
+ private cacheTimestamp = 0;
134
+ private cacheTtlMs: number;
135
+
136
+ // F3: Circuit breaker state
137
+ private circuitState: CircuitBreakerState = 'closed';
138
+ private consecutiveFailures = 0;
139
+ private circuitOpenedAt = 0;
140
+
141
+ constructor(config: GatewayClientConfig) {
142
+ super();
143
+ this.config = config;
144
+ this.cacheTtlMs = DEFAULT_CACHE_TTL_MS;
145
+ }
146
+
147
+ /**
148
+ * Resolve endpoints from the gateway.
149
+ * F2: Returns cached result if still valid, deduplicates concurrent requests.
150
+ * F3: Respects circuit breaker state.
151
+ * @param forceRefresh - If true, bypasses cache and fetches fresh data
152
+ * @returns Promise resolving to ContentEndpoints
153
+ * @throws Error if resolution fails after retries or circuit is open
154
+ */
155
+ async resolve(forceRefresh = false): Promise<ContentEndpoints> {
156
+ // F2: Return cached result if still valid
157
+ if (!forceRefresh && this.endpoints && this.isCacheValid()) {
158
+ return this.endpoints;
159
+ }
160
+
161
+ // F3: Check circuit breaker
162
+ if (!this.canAttemptRequest()) {
163
+ throw new Error('Circuit breaker is open - too many recent failures');
164
+ }
165
+
166
+ // F2: Return in-flight request if one exists (deduplication)
167
+ if (this.inFlightRequest) {
168
+ return this.inFlightRequest;
169
+ }
170
+
171
+ // Create a new request and track it
172
+ this.inFlightRequest = this.doResolve();
173
+
174
+ try {
175
+ const result = await this.inFlightRequest;
176
+ // F3: Success - close circuit
177
+ this.onSuccess();
178
+ return result;
179
+ } catch (e) {
180
+ // F3: Failure - record for circuit breaker
181
+ this.onFailure();
182
+ throw e;
183
+ } finally {
184
+ this.inFlightRequest = null;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * F2: Check if cache is still valid
190
+ */
191
+ private isCacheValid(): boolean {
192
+ return Date.now() - this.cacheTimestamp < this.cacheTtlMs;
193
+ }
194
+
195
+ /**
196
+ * F2: Set cache TTL (for testing or custom requirements)
197
+ */
198
+ setCacheTtl(ttlMs: number): void {
199
+ this.cacheTtlMs = ttlMs;
200
+ }
201
+
202
+ /**
203
+ * F2: Invalidate the cache manually
204
+ */
205
+ invalidateCache(): void {
206
+ this.cacheTimestamp = 0;
207
+ }
208
+
209
+ // ==========================================================================
210
+ // F3: Circuit Breaker Methods
211
+ // ==========================================================================
212
+
213
+ /**
214
+ * F3: Check if a request can be attempted based on circuit state
215
+ */
216
+ private canAttemptRequest(): boolean {
217
+ switch (this.circuitState) {
218
+ case 'closed':
219
+ return true;
220
+
221
+ case 'open':
222
+ // Check if enough time has passed to try half-open
223
+ if (Date.now() - this.circuitOpenedAt >= CIRCUIT_BREAKER_TIMEOUT_MS) {
224
+ this.circuitState = 'half-open';
225
+ return true;
226
+ }
227
+ return false;
228
+
229
+ case 'half-open':
230
+ // Allow one request to test the circuit
231
+ return true;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * F3: Record a successful request
237
+ */
238
+ private onSuccess(): void {
239
+ this.consecutiveFailures = 0;
240
+ this.circuitState = 'closed';
241
+ }
242
+
243
+ /**
244
+ * F3: Record a failed request
245
+ */
246
+ private onFailure(): void {
247
+ this.consecutiveFailures++;
248
+
249
+ if (this.circuitState === 'half-open') {
250
+ // Failed during half-open - re-open the circuit
251
+ this.circuitState = 'open';
252
+ this.circuitOpenedAt = Date.now();
253
+ } else if (this.consecutiveFailures >= CIRCUIT_BREAKER_THRESHOLD) {
254
+ // Threshold reached - open the circuit
255
+ this.circuitState = 'open';
256
+ this.circuitOpenedAt = Date.now();
257
+ console.warn(`[GatewayClient] Circuit breaker opened after ${this.consecutiveFailures} consecutive failures`);
258
+ }
259
+ }
260
+
261
+ /**
262
+ * F3: Get current circuit breaker state (for monitoring/debugging)
263
+ */
264
+ getCircuitState(): { state: CircuitBreakerState; failures: number; openedAt: number | null } {
265
+ return {
266
+ state: this.circuitState,
267
+ failures: this.consecutiveFailures,
268
+ openedAt: this.circuitState === 'open' ? this.circuitOpenedAt : null,
269
+ };
270
+ }
271
+
272
+ /**
273
+ * F3: Manually reset the circuit breaker
274
+ */
275
+ resetCircuitBreaker(): void {
276
+ this.circuitState = 'closed';
277
+ this.consecutiveFailures = 0;
278
+ this.circuitOpenedAt = 0;
279
+ }
280
+
281
+ /**
282
+ * Internal method to perform the actual resolution.
283
+ * @returns Promise resolving to ContentEndpoints
284
+ */
285
+ private async doResolve(): Promise<ContentEndpoints> {
286
+ // Abort any in-flight fetch (different from inFlightRequest promise tracking)
287
+ this.abort();
288
+
289
+ const {
290
+ gatewayUrl,
291
+ contentType,
292
+ contentId,
293
+ authToken,
294
+ maxRetries = DEFAULT_MAX_RETRIES,
295
+ initialDelayMs = DEFAULT_INITIAL_DELAY_MS,
296
+ } = this.config;
297
+
298
+ // Validate required params
299
+ if (!gatewayUrl || !contentType || !contentId) {
300
+ const error = 'Missing required parameters: gatewayUrl, contentType, or contentId';
301
+ this.setStatus('error', error);
302
+ throw new Error(error);
303
+ }
304
+
305
+ this.setStatus('loading');
306
+
307
+ const ac = new AbortController();
308
+ this.abortController = ac;
309
+
310
+ try {
311
+ const graphqlEndpoint = gatewayUrl.replace(/\/$/, '');
312
+
313
+ const res = await fetchWithRetry(
314
+ graphqlEndpoint,
315
+ {
316
+ method: 'POST',
317
+ headers: {
318
+ 'Content-Type': 'application/json',
319
+ ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
320
+ },
321
+ body: JSON.stringify({
322
+ query: RESOLVE_VIEWER_QUERY,
323
+ variables: { contentType, contentId },
324
+ }),
325
+ signal: ac.signal,
326
+ },
327
+ maxRetries,
328
+ initialDelayMs
329
+ );
330
+
331
+ if (!res.ok) {
332
+ throw new Error(`Gateway GQL error ${res.status}`);
333
+ }
334
+
335
+ const payload = await res.json();
336
+
337
+ if (payload.errors?.length) {
338
+ throw new Error(payload.errors[0]?.message || 'GraphQL error');
339
+ }
340
+
341
+ const resp = payload.data?.resolveViewerEndpoint;
342
+ const primary = resp?.primary;
343
+ const fallbacks = Array.isArray(resp?.fallbacks) ? resp.fallbacks : [];
344
+
345
+ if (!primary) {
346
+ throw new Error('No endpoints available');
347
+ }
348
+
349
+ const endpoints: ContentEndpoints = {
350
+ primary,
351
+ fallbacks,
352
+ metadata: resp?.metadata,
353
+ };
354
+
355
+ this.endpoints = endpoints;
356
+ // F2: Update cache timestamp
357
+ this.cacheTimestamp = Date.now();
358
+ this.setStatus('ready');
359
+ this.emit('endpointsResolved', { endpoints });
360
+
361
+ return endpoints;
362
+ } catch (e) {
363
+ // Ignore abort errors
364
+ if (ac.signal.aborted) {
365
+ throw new Error('Request aborted');
366
+ }
367
+
368
+ const message = e instanceof Error ? e.message : 'Unknown gateway error';
369
+ console.error('[GatewayClient] Gateway resolution failed:', message);
370
+ this.setStatus('error', message);
371
+ throw new Error(message);
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Abort any in-flight request.
377
+ */
378
+ abort(): void {
379
+ if (this.abortController) {
380
+ this.abortController.abort();
381
+ this.abortController = null;
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Get current status.
387
+ */
388
+ getStatus(): GatewayStatus {
389
+ return this.status;
390
+ }
391
+
392
+ /**
393
+ * Get resolved endpoints (null if not yet resolved).
394
+ */
395
+ getEndpoints(): ContentEndpoints | null {
396
+ return this.endpoints;
397
+ }
398
+
399
+ /**
400
+ * Get error message (null if no error).
401
+ */
402
+ getError(): string | null {
403
+ return this.error;
404
+ }
405
+
406
+ /**
407
+ * Update configuration and reset state.
408
+ * F2: Also clears cache and in-flight request
409
+ * F3: Resets circuit breaker (new config = fresh start)
410
+ */
411
+ updateConfig(config: Partial<GatewayClientConfig>): void {
412
+ this.abort();
413
+ this.config = { ...this.config, ...config };
414
+ this.endpoints = null;
415
+ this.error = null;
416
+ // F2: Clear cache and in-flight tracking
417
+ this.cacheTimestamp = 0;
418
+ this.inFlightRequest = null;
419
+ // F3: Reset circuit breaker for new config
420
+ this.resetCircuitBreaker();
421
+ this.setStatus('idle');
422
+ }
423
+
424
+ /**
425
+ * Clean up resources.
426
+ */
427
+ destroy(): void {
428
+ this.abort();
429
+ this.removeAllListeners();
430
+ }
431
+
432
+ private setStatus(status: GatewayStatus, error?: string): void {
433
+ this.status = status;
434
+ this.error = error ?? null;
435
+ this.emit('statusChange', { status, error });
436
+ }
437
+ }
438
+
439
+ export default GatewayClient;