@livepeer-frameworks/player-react 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 +16 -5
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/types/components/PlayerControls.d.ts +2 -0
- package/dist/types/components/StatsPanel.d.ts +2 -14
- package/dist/types/hooks/useMetaTrack.d.ts +1 -1
- package/dist/types/hooks/usePlayerController.d.ts +2 -0
- package/dist/types/hooks/useStreamState.d.ts +1 -1
- package/dist/types/hooks/useTelemetry.d.ts +1 -1
- package/dist/types/hooks/useViewerEndpoints.d.ts +2 -2
- package/dist/types/types.d.ts +1 -1
- package/dist/types/ui/button.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/DevModePanel.tsx +249 -170
- package/src/components/Icons.tsx +105 -25
- package/src/components/IdleScreen.tsx +262 -142
- package/src/components/LoadingScreen.tsx +171 -153
- package/src/components/LogoOverlay.tsx +3 -6
- package/src/components/Player.tsx +86 -74
- package/src/components/PlayerControls.tsx +351 -263
- package/src/components/PlayerErrorBoundary.tsx +6 -13
- package/src/components/SeekBar.tsx +96 -88
- package/src/components/SkipIndicator.tsx +2 -12
- package/src/components/SpeedIndicator.tsx +2 -11
- package/src/components/StatsPanel.tsx +65 -34
- package/src/components/StreamStateOverlay.tsx +105 -49
- package/src/components/SubtitleRenderer.tsx +29 -29
- package/src/components/ThumbnailOverlay.tsx +5 -6
- package/src/components/TitleOverlay.tsx +2 -8
- package/src/components/players/DashJsPlayer.tsx +13 -11
- package/src/components/players/HlsJsPlayer.tsx +13 -11
- package/src/components/players/MewsWsPlayer/index.tsx +13 -11
- package/src/components/players/MistPlayer.tsx +13 -11
- package/src/components/players/MistWebRTCPlayer/index.tsx +19 -10
- package/src/components/players/NativePlayer.tsx +10 -12
- package/src/components/players/VideoJsPlayer.tsx +13 -11
- package/src/context/PlayerContext.tsx +4 -8
- package/src/context/index.ts +3 -3
- package/src/hooks/useMetaTrack.ts +28 -28
- package/src/hooks/usePlaybackQuality.ts +3 -3
- package/src/hooks/usePlayerController.ts +186 -140
- package/src/hooks/usePlayerSelection.ts +6 -6
- package/src/hooks/useStreamState.ts +53 -58
- package/src/hooks/useTelemetry.ts +19 -4
- package/src/hooks/useViewerEndpoints.ts +40 -30
- package/src/index.tsx +36 -28
- package/src/types.ts +9 -9
- package/src/ui/badge.tsx +6 -5
- package/src/ui/button.tsx +9 -8
- package/src/ui/context-menu.tsx +42 -61
- package/src/ui/select.tsx +13 -7
- package/src/ui/slider.tsx +18 -29
|
@@ -5,26 +5,23 @@
|
|
|
5
5
|
* Manages the complete player lifecycle and provides reactive state.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { useState, useEffect, useRef, useCallback
|
|
8
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
9
9
|
import {
|
|
10
10
|
PlayerController,
|
|
11
11
|
type PlayerControllerConfig,
|
|
12
|
-
type PlayerControllerEvents,
|
|
13
12
|
type PlayerState,
|
|
14
13
|
type StreamState,
|
|
15
|
-
type StreamSource,
|
|
16
14
|
type StreamInfo,
|
|
17
15
|
type PlaybackQuality,
|
|
18
16
|
type ContentEndpoints,
|
|
19
17
|
type ContentMetadata,
|
|
20
|
-
|
|
21
|
-
} from '@livepeer-frameworks/player-core';
|
|
18
|
+
} from "@livepeer-frameworks/player-core";
|
|
22
19
|
|
|
23
20
|
// ============================================================================
|
|
24
21
|
// Types
|
|
25
22
|
// ============================================================================
|
|
26
23
|
|
|
27
|
-
export interface UsePlayerControllerConfig extends Omit<PlayerControllerConfig,
|
|
24
|
+
export interface UsePlayerControllerConfig extends Omit<PlayerControllerConfig, "playerManager"> {
|
|
28
25
|
/** Enable/disable the hook */
|
|
29
26
|
enabled?: boolean;
|
|
30
27
|
/** Callback when state changes */
|
|
@@ -95,7 +92,15 @@ export interface PlayerControllerState {
|
|
|
95
92
|
/** Subtitles enabled */
|
|
96
93
|
subtitlesEnabled: boolean;
|
|
97
94
|
/** Available quality levels */
|
|
98
|
-
qualities: Array<{
|
|
95
|
+
qualities: Array<{
|
|
96
|
+
id: string;
|
|
97
|
+
label: string;
|
|
98
|
+
bitrate?: number;
|
|
99
|
+
width?: number;
|
|
100
|
+
height?: number;
|
|
101
|
+
isAuto?: boolean;
|
|
102
|
+
active?: boolean;
|
|
103
|
+
}>;
|
|
99
104
|
/** Available text/caption tracks */
|
|
100
105
|
textTracks: Array<{ id: string; label: string; language?: string; active: boolean }>;
|
|
101
106
|
/** Stream info for player selection (sources + tracks) */
|
|
@@ -156,7 +161,7 @@ export interface UsePlayerControllerReturn {
|
|
|
156
161
|
forcePlayer?: string;
|
|
157
162
|
forceType?: string;
|
|
158
163
|
forceSource?: number;
|
|
159
|
-
playbackMode?:
|
|
164
|
+
playbackMode?: "auto" | "low-latency" | "quality" | "vod";
|
|
160
165
|
}) => Promise<void>;
|
|
161
166
|
}
|
|
162
167
|
|
|
@@ -165,7 +170,7 @@ export interface UsePlayerControllerReturn {
|
|
|
165
170
|
// ============================================================================
|
|
166
171
|
|
|
167
172
|
const initialState: PlayerControllerState = {
|
|
168
|
-
state:
|
|
173
|
+
state: "booting",
|
|
169
174
|
streamState: null,
|
|
170
175
|
endpoints: null,
|
|
171
176
|
metadata: null,
|
|
@@ -202,10 +207,15 @@ const initialState: PlayerControllerState = {
|
|
|
202
207
|
// Hook
|
|
203
208
|
// ============================================================================
|
|
204
209
|
|
|
205
|
-
export function usePlayerController(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
210
|
+
export function usePlayerController(config: UsePlayerControllerConfig): UsePlayerControllerReturn {
|
|
211
|
+
const {
|
|
212
|
+
enabled = true,
|
|
213
|
+
onStateChange,
|
|
214
|
+
onStreamStateChange,
|
|
215
|
+
onError,
|
|
216
|
+
onReady,
|
|
217
|
+
...controllerConfig
|
|
218
|
+
} = config;
|
|
209
219
|
|
|
210
220
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
211
221
|
const controllerRef = useRef<PlayerController | null>(null);
|
|
@@ -246,7 +256,7 @@ export function usePlayerController(
|
|
|
246
256
|
const syncState = () => {
|
|
247
257
|
if (!controllerRef.current) return;
|
|
248
258
|
const c = controllerRef.current;
|
|
249
|
-
setState(prev => ({
|
|
259
|
+
setState((prev) => ({
|
|
250
260
|
...prev,
|
|
251
261
|
isPlaying: c.isPlaying(),
|
|
252
262
|
isPaused: c.isPaused(),
|
|
@@ -264,134 +274,167 @@ export function usePlayerController(
|
|
|
264
274
|
}));
|
|
265
275
|
};
|
|
266
276
|
|
|
267
|
-
unsubs.push(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
unsubs.push(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
277
|
+
unsubs.push(
|
|
278
|
+
controller.on("stateChange", ({ state: newState }) => {
|
|
279
|
+
setState((prev) => ({ ...prev, state: newState }));
|
|
280
|
+
onStateChange?.(newState);
|
|
281
|
+
})
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
unsubs.push(
|
|
285
|
+
controller.on("streamStateChange", ({ state: streamState }) => {
|
|
286
|
+
setState((prev) => ({
|
|
287
|
+
...prev,
|
|
288
|
+
streamState,
|
|
289
|
+
metadata: controller.getMetadata(),
|
|
290
|
+
isEffectivelyLive: controller.isEffectivelyLive(),
|
|
291
|
+
shouldShowIdleScreen: controller.shouldShowIdleScreen(),
|
|
292
|
+
}));
|
|
293
|
+
onStreamStateChange?.(streamState);
|
|
294
|
+
})
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
unsubs.push(
|
|
298
|
+
controller.on("timeUpdate", ({ currentTime, duration }) => {
|
|
299
|
+
setState((prev) => ({ ...prev, currentTime, duration }));
|
|
300
|
+
})
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
unsubs.push(
|
|
304
|
+
controller.on("error", ({ error }) => {
|
|
305
|
+
setState((prev) => ({
|
|
306
|
+
...prev,
|
|
307
|
+
error,
|
|
308
|
+
isPassiveError: controller.isPassiveError(),
|
|
309
|
+
}));
|
|
310
|
+
onError?.(error);
|
|
311
|
+
})
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
unsubs.push(
|
|
315
|
+
controller.on("errorCleared", () => {
|
|
316
|
+
setState((prev) => ({ ...prev, error: null, isPassiveError: false }));
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
unsubs.push(
|
|
321
|
+
controller.on("ready", ({ videoElement }) => {
|
|
322
|
+
setState((prev) => ({
|
|
323
|
+
...prev,
|
|
324
|
+
videoElement,
|
|
325
|
+
endpoints: controller.getEndpoints(),
|
|
326
|
+
metadata: controller.getMetadata(),
|
|
327
|
+
streamInfo: controller.getStreamInfo(),
|
|
328
|
+
isEffectivelyLive: controller.isEffectivelyLive(),
|
|
329
|
+
shouldShowIdleScreen: controller.shouldShowIdleScreen(),
|
|
330
|
+
currentPlayerInfo: controller.getCurrentPlayerInfo(),
|
|
331
|
+
currentSourceInfo: controller.getCurrentSourceInfo(),
|
|
332
|
+
qualities: controller.getQualities(),
|
|
333
|
+
}));
|
|
334
|
+
onReady?.(videoElement);
|
|
335
|
+
|
|
336
|
+
// Set up video event listeners AFTER video is ready
|
|
337
|
+
// syncState is defined below - this closure captures it
|
|
338
|
+
const handleVideoEvent = () => {
|
|
339
|
+
if (controllerRef.current?.shouldSuppressVideoEvents?.()) return;
|
|
340
|
+
syncState();
|
|
341
|
+
};
|
|
342
|
+
videoElement.addEventListener("play", handleVideoEvent);
|
|
343
|
+
videoElement.addEventListener("pause", handleVideoEvent);
|
|
344
|
+
videoElement.addEventListener("waiting", handleVideoEvent);
|
|
345
|
+
videoElement.addEventListener("playing", handleVideoEvent);
|
|
346
|
+
unsubs.push(() => {
|
|
347
|
+
videoElement.removeEventListener("play", handleVideoEvent);
|
|
348
|
+
videoElement.removeEventListener("pause", handleVideoEvent);
|
|
349
|
+
videoElement.removeEventListener("waiting", handleVideoEvent);
|
|
350
|
+
videoElement.removeEventListener("playing", handleVideoEvent);
|
|
351
|
+
});
|
|
352
|
+
})
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
unsubs.push(
|
|
356
|
+
controller.on("playerSelected", ({ player: _player, source }) => {
|
|
357
|
+
setState((prev) => ({
|
|
358
|
+
...prev,
|
|
359
|
+
currentPlayerInfo: controller.getCurrentPlayerInfo(),
|
|
360
|
+
currentSourceInfo: { url: source.url, type: source.type },
|
|
361
|
+
qualities: controller.getQualities(),
|
|
362
|
+
}));
|
|
363
|
+
})
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
unsubs.push(
|
|
367
|
+
controller.on("volumeChange", ({ volume, muted }) => {
|
|
368
|
+
setState((prev) => ({ ...prev, volume, isMuted: muted }));
|
|
369
|
+
})
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
unsubs.push(
|
|
373
|
+
controller.on("loopChange", ({ isLoopEnabled }) => {
|
|
374
|
+
setState((prev) => ({ ...prev, isLoopEnabled }));
|
|
375
|
+
})
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
unsubs.push(
|
|
379
|
+
controller.on("fullscreenChange", ({ isFullscreen }) => {
|
|
380
|
+
setState((prev) => ({ ...prev, isFullscreen }));
|
|
381
|
+
})
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
unsubs.push(
|
|
385
|
+
controller.on("pipChange", ({ isPiP }) => {
|
|
386
|
+
setState((prev) => ({ ...prev, isPiPActive: isPiP }));
|
|
387
|
+
})
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
unsubs.push(
|
|
391
|
+
controller.on("holdSpeedStart", ({ speed }) => {
|
|
392
|
+
setState((prev) => ({ ...prev, isHoldingSpeed: true, holdSpeed: speed }));
|
|
393
|
+
})
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
unsubs.push(
|
|
397
|
+
controller.on("holdSpeedEnd", () => {
|
|
398
|
+
setState((prev) => ({ ...prev, isHoldingSpeed: false }));
|
|
399
|
+
})
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
unsubs.push(
|
|
403
|
+
controller.on("hoverStart", () => {
|
|
404
|
+
setState((prev) => ({ ...prev, isHovering: true, shouldShowControls: true }));
|
|
405
|
+
})
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
unsubs.push(
|
|
409
|
+
controller.on("hoverEnd", () => {
|
|
410
|
+
setState((prev) => ({
|
|
411
|
+
...prev,
|
|
412
|
+
isHovering: false,
|
|
413
|
+
shouldShowControls: controller.shouldShowControls(),
|
|
414
|
+
}));
|
|
415
|
+
})
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
unsubs.push(
|
|
419
|
+
controller.on("captionsChange", ({ enabled }) => {
|
|
420
|
+
setState((prev) => ({ ...prev, subtitlesEnabled: enabled }));
|
|
421
|
+
})
|
|
422
|
+
);
|
|
380
423
|
|
|
381
424
|
// Attach controller to container
|
|
382
425
|
// Note: Video event listeners are set up in the 'ready' handler above
|
|
383
|
-
controller.attach(container).catch(err => {
|
|
384
|
-
console.warn(
|
|
426
|
+
controller.attach(container).catch((err) => {
|
|
427
|
+
console.warn("[usePlayerController] Attach failed:", err);
|
|
385
428
|
});
|
|
386
429
|
|
|
387
430
|
// Set initial state
|
|
388
|
-
setState(prev => ({
|
|
431
|
+
setState((prev) => ({
|
|
389
432
|
...prev,
|
|
390
433
|
isLoopEnabled: controller.isLoopEnabled(),
|
|
391
434
|
}));
|
|
392
435
|
|
|
393
436
|
return () => {
|
|
394
|
-
unsubs.forEach(fn => fn());
|
|
437
|
+
unsubs.forEach((fn) => fn());
|
|
395
438
|
controller.destroy();
|
|
396
439
|
controllerRef.current = null;
|
|
397
440
|
setState(initialState);
|
|
@@ -445,7 +488,7 @@ export function usePlayerController(
|
|
|
445
488
|
|
|
446
489
|
const clearError = useCallback(() => {
|
|
447
490
|
controllerRef.current?.clearError();
|
|
448
|
-
setState(prev => ({ ...prev, error: null, isPassiveError: false }));
|
|
491
|
+
setState((prev) => ({ ...prev, error: null, isPassiveError: false }));
|
|
449
492
|
}, []);
|
|
450
493
|
|
|
451
494
|
const jumpToLive = useCallback(() => {
|
|
@@ -484,14 +527,17 @@ export function usePlayerController(
|
|
|
484
527
|
controllerRef.current?.handleTouchStart();
|
|
485
528
|
}, []);
|
|
486
529
|
|
|
487
|
-
const setDevModeOptions = useCallback(
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
530
|
+
const setDevModeOptions = useCallback(
|
|
531
|
+
async (options: {
|
|
532
|
+
forcePlayer?: string;
|
|
533
|
+
forceType?: string;
|
|
534
|
+
forceSource?: number;
|
|
535
|
+
playbackMode?: "auto" | "low-latency" | "quality" | "vod";
|
|
536
|
+
}) => {
|
|
537
|
+
await controllerRef.current?.setDevModeOptions(options);
|
|
538
|
+
},
|
|
539
|
+
[]
|
|
540
|
+
);
|
|
495
541
|
|
|
496
542
|
return {
|
|
497
543
|
containerRef,
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* Uses event-driven updates instead of polling - no render spam.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { useState, useEffect, useCallback } from
|
|
8
|
+
import { useState, useEffect, useCallback } from "react";
|
|
9
9
|
import type {
|
|
10
10
|
PlayerManager,
|
|
11
11
|
PlayerSelection,
|
|
12
12
|
PlayerCombination,
|
|
13
13
|
StreamInfo,
|
|
14
14
|
PlaybackMode,
|
|
15
|
-
} from
|
|
15
|
+
} from "@livepeer-frameworks/player-core";
|
|
16
16
|
|
|
17
17
|
export interface UsePlayerSelectionOptions {
|
|
18
18
|
/** Stream info to compute selections for */
|
|
@@ -67,16 +67,16 @@ export function usePlayerSelection(
|
|
|
67
67
|
|
|
68
68
|
// Subscribe to events
|
|
69
69
|
useEffect(() => {
|
|
70
|
-
const unsubSelection = manager.on(
|
|
70
|
+
const unsubSelection = manager.on("selection-changed", (sel) => {
|
|
71
71
|
if (debug) {
|
|
72
|
-
console.log(
|
|
72
|
+
console.log("[usePlayerSelection] Selection changed:", sel?.player, sel?.source?.type);
|
|
73
73
|
}
|
|
74
74
|
setSelection(sel);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
const unsubCombos = manager.on(
|
|
77
|
+
const unsubCombos = manager.on("combinations-updated", (combos) => {
|
|
78
78
|
if (debug) {
|
|
79
|
-
console.log(
|
|
79
|
+
console.log("[usePlayerSelection] Combinations updated:", combos.length);
|
|
80
80
|
}
|
|
81
81
|
setCombinations(combos);
|
|
82
82
|
setReady(true);
|