@livepeer-frameworks/player-react 0.1.0 → 0.1.2
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 +7 -9
- package/dist/cjs/_virtual/_rollupPluginBabelHelpers.js +359 -0
- package/dist/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
- package/dist/cjs/assets/logomark.svg.js +8 -0
- package/dist/cjs/assets/logomark.svg.js.map +1 -0
- package/dist/cjs/components/DevModePanel.js +826 -0
- package/dist/cjs/components/DevModePanel.js.map +1 -0
- package/dist/cjs/components/DvdLogo.js +200 -0
- package/dist/cjs/components/DvdLogo.js.map +1 -0
- package/dist/cjs/components/Icons.js +439 -0
- package/dist/cjs/components/Icons.js.map +1 -0
- package/dist/cjs/components/IdleScreen.js +587 -0
- package/dist/cjs/components/IdleScreen.js.map +1 -0
- package/dist/cjs/components/LoadingScreen.js +523 -0
- package/dist/cjs/components/LoadingScreen.js.map +1 -0
- package/dist/cjs/components/Player.js +420 -0
- package/dist/cjs/components/Player.js.map +1 -0
- package/dist/cjs/components/PlayerControls.js +798 -0
- package/dist/cjs/components/PlayerControls.js.map +1 -0
- package/dist/cjs/components/PlayerErrorBoundary.js +80 -0
- package/dist/cjs/components/PlayerErrorBoundary.js.map +1 -0
- package/dist/cjs/components/SeekBar.js +253 -0
- package/dist/cjs/components/SeekBar.js.map +1 -0
- package/dist/cjs/components/SkipIndicator.js +92 -0
- package/dist/cjs/components/SkipIndicator.js.map +1 -0
- package/dist/cjs/components/SpeedIndicator.js +43 -0
- package/dist/cjs/components/SpeedIndicator.js.map +1 -0
- package/dist/cjs/components/StatsPanel.js +202 -0
- package/dist/cjs/components/StatsPanel.js.map +1 -0
- package/dist/cjs/components/StreamStateOverlay.js +229 -0
- package/dist/cjs/components/StreamStateOverlay.js.map +1 -0
- package/dist/cjs/components/ThumbnailOverlay.js +86 -0
- package/dist/cjs/components/ThumbnailOverlay.js.map +1 -0
- package/dist/cjs/components/TitleOverlay.js +32 -0
- package/dist/cjs/components/TitleOverlay.js.map +1 -0
- package/dist/cjs/context/PlayerContext.js +46 -0
- package/dist/cjs/context/PlayerContext.js.map +1 -0
- package/dist/cjs/hooks/useMetaTrack.js +165 -0
- package/dist/cjs/hooks/useMetaTrack.js.map +1 -0
- package/dist/cjs/hooks/usePlaybackQuality.js +131 -0
- package/dist/cjs/hooks/usePlaybackQuality.js.map +1 -0
- package/dist/cjs/hooks/usePlayerController.js +518 -0
- package/dist/cjs/hooks/usePlayerController.js.map +1 -0
- package/dist/cjs/hooks/usePlayerSelection.js +90 -0
- package/dist/cjs/hooks/usePlayerSelection.js.map +1 -0
- package/dist/cjs/hooks/useStreamState.js +360 -0
- package/dist/cjs/hooks/useStreamState.js.map +1 -0
- package/dist/cjs/hooks/useTelemetry.js +120 -0
- package/dist/cjs/hooks/useTelemetry.js.map +1 -0
- package/dist/cjs/hooks/useViewerEndpoints.js +222 -0
- package/dist/cjs/hooks/useViewerEndpoints.js.map +1 -0
- package/dist/cjs/index.js +97 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/ui/badge.js +34 -0
- package/dist/cjs/ui/badge.js.map +1 -0
- package/dist/cjs/ui/button.js +74 -0
- package/dist/cjs/ui/button.js.map +1 -0
- package/dist/cjs/ui/context-menu.js +163 -0
- package/dist/cjs/ui/context-menu.js.map +1 -0
- package/dist/cjs/ui/slider.js +60 -0
- package/dist/cjs/ui/slider.js.map +1 -0
- package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +329 -0
- package/dist/esm/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
- package/dist/esm/assets/logomark.svg.js +4 -0
- package/dist/esm/assets/logomark.svg.js.map +1 -0
- package/dist/esm/components/DevModePanel.js +822 -0
- package/dist/esm/components/DevModePanel.js.map +1 -0
- package/dist/esm/components/DvdLogo.js +196 -0
- package/dist/esm/components/DvdLogo.js.map +1 -0
- package/dist/esm/components/Icons.js +421 -0
- package/dist/esm/components/Icons.js.map +1 -0
- package/dist/esm/components/IdleScreen.js +582 -0
- package/dist/esm/components/IdleScreen.js.map +1 -0
- package/dist/esm/components/LoadingScreen.js +519 -0
- package/dist/esm/components/LoadingScreen.js.map +1 -0
- package/dist/esm/components/Player.js +416 -0
- package/dist/esm/components/Player.js.map +1 -0
- package/dist/esm/components/PlayerControls.js +794 -0
- package/dist/esm/components/PlayerControls.js.map +1 -0
- package/dist/esm/components/PlayerErrorBoundary.js +76 -0
- package/dist/esm/components/PlayerErrorBoundary.js.map +1 -0
- package/dist/esm/components/SeekBar.js +249 -0
- package/dist/esm/components/SeekBar.js.map +1 -0
- package/dist/esm/components/SkipIndicator.js +88 -0
- package/dist/esm/components/SkipIndicator.js.map +1 -0
- package/dist/esm/components/SpeedIndicator.js +39 -0
- package/dist/esm/components/SpeedIndicator.js.map +1 -0
- package/dist/esm/components/StatsPanel.js +198 -0
- package/dist/esm/components/StatsPanel.js.map +1 -0
- package/dist/esm/components/StreamStateOverlay.js +224 -0
- package/dist/esm/components/StreamStateOverlay.js.map +1 -0
- package/dist/esm/components/ThumbnailOverlay.js +82 -0
- package/dist/esm/components/ThumbnailOverlay.js.map +1 -0
- package/dist/esm/components/TitleOverlay.js +28 -0
- package/dist/esm/components/TitleOverlay.js.map +1 -0
- package/dist/esm/context/PlayerContext.js +41 -0
- package/dist/esm/context/PlayerContext.js.map +1 -0
- package/dist/esm/hooks/useMetaTrack.js +163 -0
- package/dist/esm/hooks/useMetaTrack.js.map +1 -0
- package/dist/esm/hooks/usePlaybackQuality.js +129 -0
- package/dist/esm/hooks/usePlaybackQuality.js.map +1 -0
- package/dist/esm/hooks/usePlayerController.js +516 -0
- package/dist/esm/hooks/usePlayerController.js.map +1 -0
- package/dist/esm/hooks/usePlayerSelection.js +88 -0
- package/dist/esm/hooks/usePlayerSelection.js.map +1 -0
- package/dist/esm/hooks/useStreamState.js +358 -0
- package/dist/esm/hooks/useStreamState.js.map +1 -0
- package/dist/esm/hooks/useTelemetry.js +118 -0
- package/dist/esm/hooks/useTelemetry.js.map +1 -0
- package/dist/esm/hooks/useViewerEndpoints.js +220 -0
- package/dist/esm/hooks/useViewerEndpoints.js.map +1 -0
- package/dist/esm/index.js +23 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/ui/badge.js +31 -0
- package/dist/esm/ui/badge.js.map +1 -0
- package/dist/esm/ui/button.js +52 -0
- package/dist/esm/ui/button.js.map +1 -0
- package/dist/esm/ui/context-menu.js +132 -0
- package/dist/esm/ui/context-menu.js.map +1 -0
- package/dist/esm/ui/slider.js +38 -0
- package/dist/esm/ui/slider.js.map +1 -0
- package/dist/types/components/DvdLogo.d.ts +1 -1
- package/dist/types/components/Icons.d.ts +1 -1
- package/dist/types/components/Player.d.ts +1 -1
- package/dist/types/components/PlayerErrorBoundary.d.ts +2 -1
- package/dist/types/components/StreamStateOverlay.d.ts +2 -2
- package/dist/types/components/SubtitleRenderer.d.ts +2 -2
- package/dist/types/context/PlayerContext.d.ts +2 -2
- package/dist/types/context/index.d.ts +2 -2
- package/dist/types/hooks/useMetaTrack.d.ts +3 -3
- package/dist/types/hooks/usePlaybackQuality.d.ts +2 -2
- package/dist/types/hooks/usePlayerController.d.ts +26 -3
- package/dist/types/hooks/usePlayerSelection.d.ts +1 -1
- 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 +3 -3
- package/dist/types/index.d.ts +28 -28
- package/dist/types/types.d.ts +3 -3
- package/dist/types/ui/select.d.ts +1 -1
- package/package.json +22 -14
- package/src/components/DevModePanel.tsx +244 -143
- package/src/components/DvdLogo.tsx +1 -1
- package/src/components/Icons.tsx +105 -25
- package/src/components/IdleScreen.tsx +262 -128
- package/src/components/LoadingScreen.tsx +169 -151
- package/src/components/LogoOverlay.tsx +3 -6
- package/src/components/Player.tsx +126 -59
- package/src/components/PlayerControls.tsx +384 -272
- package/src/components/PlayerErrorBoundary.tsx +7 -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 +31 -22
- 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/context/PlayerContext.tsx +4 -8
- package/src/context/index.ts +3 -3
- package/src/hooks/useMetaTrack.ts +27 -27
- package/src/hooks/usePlaybackQuality.ts +3 -3
- package/src/hooks/usePlayerController.ts +246 -138
- package/src/hooks/usePlayerSelection.ts +6 -6
- package/src/hooks/useStreamState.ts +51 -56
- package/src/hooks/useTelemetry.ts +18 -3
- package/src/hooks/useViewerEndpoints.ts +34 -23
- package/src/index.tsx +36 -28
- package/src/types.ts +8 -8
- 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
- package/dist/types/components/players/DashJsPlayer.d.ts +0 -18
- package/dist/types/components/players/HlsJsPlayer.d.ts +0 -18
- package/dist/types/components/players/MewsWsPlayer/index.d.ts +0 -18
- package/dist/types/components/players/MistPlayer.d.ts +0 -20
- package/dist/types/components/players/MistWebRTCPlayer/index.d.ts +0 -20
- package/dist/types/components/players/NativePlayer.d.ts +0 -19
- package/dist/types/components/players/VideoJsPlayer.d.ts +0 -18
- package/src/components/players/DashJsPlayer.tsx +0 -56
- package/src/components/players/HlsJsPlayer.tsx +0 -56
- package/src/components/players/MewsWsPlayer/index.tsx +0 -56
- package/src/components/players/MistPlayer.tsx +0 -60
- package/src/components/players/MistWebRTCPlayer/index.tsx +0 -59
- package/src/components/players/NativePlayer.tsx +0 -58
- package/src/components/players/VideoJsPlayer.tsx +0 -56
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Manages the complete player lifecycle and provides reactive state.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { useState, useEffect, useRef, useCallback } from
|
|
8
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
9
9
|
import {
|
|
10
10
|
PlayerController,
|
|
11
11
|
type PlayerControllerConfig,
|
|
@@ -15,13 +15,14 @@ import {
|
|
|
15
15
|
type PlaybackQuality,
|
|
16
16
|
type ContentEndpoints,
|
|
17
17
|
type ContentMetadata,
|
|
18
|
-
|
|
18
|
+
type ClassifiedError,
|
|
19
|
+
} from "@livepeer-frameworks/player-core";
|
|
19
20
|
|
|
20
21
|
// ============================================================================
|
|
21
22
|
// Types
|
|
22
23
|
// ============================================================================
|
|
23
24
|
|
|
24
|
-
export interface UsePlayerControllerConfig extends Omit<PlayerControllerConfig,
|
|
25
|
+
export interface UsePlayerControllerConfig extends Omit<PlayerControllerConfig, "playerManager"> {
|
|
25
26
|
/** Enable/disable the hook */
|
|
26
27
|
enabled?: boolean;
|
|
27
28
|
/** Callback when state changes */
|
|
@@ -32,6 +33,20 @@ export interface UsePlayerControllerConfig extends Omit<PlayerControllerConfig,
|
|
|
32
33
|
onError?: (error: string) => void;
|
|
33
34
|
/** Callback when ready */
|
|
34
35
|
onReady?: (videoElement: HTMLVideoElement) => void;
|
|
36
|
+
/** Callback when protocol is swapped (for toast notification) */
|
|
37
|
+
onProtocolSwapped?: (data: {
|
|
38
|
+
fromPlayer: string;
|
|
39
|
+
toPlayer: string;
|
|
40
|
+
fromProtocol: string;
|
|
41
|
+
toProtocol: string;
|
|
42
|
+
reason: string;
|
|
43
|
+
}) => void;
|
|
44
|
+
/** Callback when playback fails after all recovery attempts (for error modal) */
|
|
45
|
+
onPlaybackFailed?: (error: {
|
|
46
|
+
code: string;
|
|
47
|
+
message: string;
|
|
48
|
+
details?: ClassifiedError["details"];
|
|
49
|
+
}) => void;
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
export interface PlayerControllerState {
|
|
@@ -61,6 +76,8 @@ export interface PlayerControllerState {
|
|
|
61
76
|
volume: number;
|
|
62
77
|
/** Error text */
|
|
63
78
|
error: string | null;
|
|
79
|
+
/** Error details for debugging */
|
|
80
|
+
errorDetails: ClassifiedError["details"] | null;
|
|
64
81
|
/** Is passive error */
|
|
65
82
|
isPassiveError: boolean;
|
|
66
83
|
/** Has playback ever started */
|
|
@@ -92,11 +109,21 @@ export interface PlayerControllerState {
|
|
|
92
109
|
/** Subtitles enabled */
|
|
93
110
|
subtitlesEnabled: boolean;
|
|
94
111
|
/** Available quality levels */
|
|
95
|
-
qualities: Array<{
|
|
112
|
+
qualities: Array<{
|
|
113
|
+
id: string;
|
|
114
|
+
label: string;
|
|
115
|
+
bitrate?: number;
|
|
116
|
+
width?: number;
|
|
117
|
+
height?: number;
|
|
118
|
+
isAuto?: boolean;
|
|
119
|
+
active?: boolean;
|
|
120
|
+
}>;
|
|
96
121
|
/** Available text/caption tracks */
|
|
97
122
|
textTracks: Array<{ id: string; label: string; language?: string; active: boolean }>;
|
|
98
123
|
/** Stream info for player selection (sources + tracks) */
|
|
99
124
|
streamInfo: StreamInfo | null;
|
|
125
|
+
/** Toast message to display (auto-dismisses) */
|
|
126
|
+
toast: { message: string; timestamp: number } | null;
|
|
100
127
|
}
|
|
101
128
|
|
|
102
129
|
export interface UsePlayerControllerReturn {
|
|
@@ -132,6 +159,8 @@ export interface UsePlayerControllerReturn {
|
|
|
132
159
|
toggleSubtitles: () => void;
|
|
133
160
|
/** Clear error */
|
|
134
161
|
clearError: () => void;
|
|
162
|
+
/** Dismiss toast notification */
|
|
163
|
+
dismissToast: () => void;
|
|
135
164
|
/** Retry playback */
|
|
136
165
|
retry: () => Promise<void>;
|
|
137
166
|
/** Reload player */
|
|
@@ -153,7 +182,7 @@ export interface UsePlayerControllerReturn {
|
|
|
153
182
|
forcePlayer?: string;
|
|
154
183
|
forceType?: string;
|
|
155
184
|
forceSource?: number;
|
|
156
|
-
playbackMode?:
|
|
185
|
+
playbackMode?: "auto" | "low-latency" | "quality" | "vod";
|
|
157
186
|
}) => Promise<void>;
|
|
158
187
|
}
|
|
159
188
|
|
|
@@ -162,7 +191,7 @@ export interface UsePlayerControllerReturn {
|
|
|
162
191
|
// ============================================================================
|
|
163
192
|
|
|
164
193
|
const initialState: PlayerControllerState = {
|
|
165
|
-
state:
|
|
194
|
+
state: "booting",
|
|
166
195
|
streamState: null,
|
|
167
196
|
endpoints: null,
|
|
168
197
|
metadata: null,
|
|
@@ -175,6 +204,7 @@ const initialState: PlayerControllerState = {
|
|
|
175
204
|
isMuted: true,
|
|
176
205
|
volume: 1,
|
|
177
206
|
error: null,
|
|
207
|
+
errorDetails: null,
|
|
178
208
|
isPassiveError: false,
|
|
179
209
|
hasPlaybackStarted: false,
|
|
180
210
|
isHoldingSpeed: false,
|
|
@@ -193,16 +223,24 @@ const initialState: PlayerControllerState = {
|
|
|
193
223
|
qualities: [],
|
|
194
224
|
textTracks: [],
|
|
195
225
|
streamInfo: null,
|
|
226
|
+
toast: null,
|
|
196
227
|
};
|
|
197
228
|
|
|
198
229
|
// ============================================================================
|
|
199
230
|
// Hook
|
|
200
231
|
// ============================================================================
|
|
201
232
|
|
|
202
|
-
export function usePlayerController(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
233
|
+
export function usePlayerController(config: UsePlayerControllerConfig): UsePlayerControllerReturn {
|
|
234
|
+
const {
|
|
235
|
+
enabled = true,
|
|
236
|
+
onStateChange,
|
|
237
|
+
onStreamStateChange,
|
|
238
|
+
onError,
|
|
239
|
+
onReady,
|
|
240
|
+
onProtocolSwapped,
|
|
241
|
+
onPlaybackFailed,
|
|
242
|
+
...controllerConfig
|
|
243
|
+
} = config;
|
|
206
244
|
|
|
207
245
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
208
246
|
const controllerRef = useRef<PlayerController | null>(null);
|
|
@@ -243,7 +281,7 @@ export function usePlayerController(
|
|
|
243
281
|
const syncState = () => {
|
|
244
282
|
if (!controllerRef.current) return;
|
|
245
283
|
const c = controllerRef.current;
|
|
246
|
-
setState(prev => ({
|
|
284
|
+
setState((prev) => ({
|
|
247
285
|
...prev,
|
|
248
286
|
isPlaying: c.isPlaying(),
|
|
249
287
|
isPaused: c.isPaused(),
|
|
@@ -261,135 +299,192 @@ export function usePlayerController(
|
|
|
261
299
|
}));
|
|
262
300
|
};
|
|
263
301
|
|
|
264
|
-
unsubs.push(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
unsubs.push(
|
|
285
|
-
|
|
286
|
-
...prev,
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
unsubs.push(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
})
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
302
|
+
unsubs.push(
|
|
303
|
+
controller.on("stateChange", ({ state: newState }) => {
|
|
304
|
+
setState((prev) => ({ ...prev, state: newState }));
|
|
305
|
+
onStateChange?.(newState);
|
|
306
|
+
})
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
unsubs.push(
|
|
310
|
+
controller.on("streamStateChange", ({ state: streamState }) => {
|
|
311
|
+
setState((prev) => ({
|
|
312
|
+
...prev,
|
|
313
|
+
streamState,
|
|
314
|
+
metadata: controller.getMetadata(),
|
|
315
|
+
isEffectivelyLive: controller.isEffectivelyLive(),
|
|
316
|
+
shouldShowIdleScreen: controller.shouldShowIdleScreen(),
|
|
317
|
+
}));
|
|
318
|
+
onStreamStateChange?.(streamState);
|
|
319
|
+
})
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
unsubs.push(
|
|
323
|
+
controller.on("timeUpdate", ({ currentTime, duration }) => {
|
|
324
|
+
setState((prev) => ({ ...prev, currentTime, duration }));
|
|
325
|
+
})
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
unsubs.push(
|
|
329
|
+
controller.on("error", ({ error }) => {
|
|
330
|
+
setState((prev) => ({
|
|
331
|
+
...prev,
|
|
332
|
+
error,
|
|
333
|
+
isPassiveError: controller.isPassiveError(),
|
|
334
|
+
}));
|
|
335
|
+
onError?.(error);
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
unsubs.push(
|
|
340
|
+
controller.on("errorCleared", () => {
|
|
341
|
+
setState((prev) => ({ ...prev, error: null, isPassiveError: false }));
|
|
342
|
+
})
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
unsubs.push(
|
|
346
|
+
controller.on("ready", ({ videoElement }) => {
|
|
347
|
+
setState((prev) => ({
|
|
348
|
+
...prev,
|
|
349
|
+
videoElement,
|
|
350
|
+
endpoints: controller.getEndpoints(),
|
|
351
|
+
metadata: controller.getMetadata(),
|
|
352
|
+
streamInfo: controller.getStreamInfo(),
|
|
353
|
+
isEffectivelyLive: controller.isEffectivelyLive(),
|
|
354
|
+
shouldShowIdleScreen: controller.shouldShowIdleScreen(),
|
|
355
|
+
currentPlayerInfo: controller.getCurrentPlayerInfo(),
|
|
356
|
+
currentSourceInfo: controller.getCurrentSourceInfo(),
|
|
357
|
+
qualities: controller.getQualities(),
|
|
358
|
+
}));
|
|
359
|
+
onReady?.(videoElement);
|
|
360
|
+
|
|
361
|
+
// Set up video event listeners AFTER video is ready
|
|
362
|
+
// syncState is defined below - this closure captures it
|
|
363
|
+
const handleVideoEvent = () => {
|
|
364
|
+
if (controllerRef.current?.shouldSuppressVideoEvents?.()) return;
|
|
365
|
+
syncState();
|
|
366
|
+
};
|
|
367
|
+
videoElement.addEventListener("play", handleVideoEvent);
|
|
368
|
+
videoElement.addEventListener("pause", handleVideoEvent);
|
|
369
|
+
videoElement.addEventListener("waiting", handleVideoEvent);
|
|
370
|
+
videoElement.addEventListener("playing", handleVideoEvent);
|
|
371
|
+
unsubs.push(() => {
|
|
372
|
+
videoElement.removeEventListener("play", handleVideoEvent);
|
|
373
|
+
videoElement.removeEventListener("pause", handleVideoEvent);
|
|
374
|
+
videoElement.removeEventListener("waiting", handleVideoEvent);
|
|
375
|
+
videoElement.removeEventListener("playing", handleVideoEvent);
|
|
376
|
+
});
|
|
377
|
+
})
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
unsubs.push(
|
|
381
|
+
controller.on("playerSelected", ({ player: _player, source }) => {
|
|
382
|
+
setState((prev) => ({
|
|
383
|
+
...prev,
|
|
384
|
+
currentPlayerInfo: controller.getCurrentPlayerInfo(),
|
|
385
|
+
currentSourceInfo: { url: source.url, type: source.type },
|
|
386
|
+
qualities: controller.getQualities(),
|
|
387
|
+
}));
|
|
388
|
+
})
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
unsubs.push(
|
|
392
|
+
controller.on("volumeChange", ({ volume, muted }) => {
|
|
393
|
+
setState((prev) => ({ ...prev, volume, isMuted: muted }));
|
|
394
|
+
})
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
unsubs.push(
|
|
398
|
+
controller.on("loopChange", ({ isLoopEnabled }) => {
|
|
399
|
+
setState((prev) => ({ ...prev, isLoopEnabled }));
|
|
400
|
+
})
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
unsubs.push(
|
|
404
|
+
controller.on("fullscreenChange", ({ isFullscreen }) => {
|
|
405
|
+
setState((prev) => ({ ...prev, isFullscreen }));
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
unsubs.push(
|
|
410
|
+
controller.on("pipChange", ({ isPiP }) => {
|
|
411
|
+
setState((prev) => ({ ...prev, isPiPActive: isPiP }));
|
|
412
|
+
})
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
unsubs.push(
|
|
416
|
+
controller.on("holdSpeedStart", ({ speed }) => {
|
|
417
|
+
setState((prev) => ({ ...prev, isHoldingSpeed: true, holdSpeed: speed }));
|
|
418
|
+
})
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
unsubs.push(
|
|
422
|
+
controller.on("holdSpeedEnd", () => {
|
|
423
|
+
setState((prev) => ({ ...prev, isHoldingSpeed: false }));
|
|
424
|
+
})
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
unsubs.push(
|
|
428
|
+
controller.on("hoverStart", () => {
|
|
429
|
+
setState((prev) => ({ ...prev, isHovering: true, shouldShowControls: true }));
|
|
430
|
+
})
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
unsubs.push(
|
|
434
|
+
controller.on("hoverEnd", () => {
|
|
435
|
+
setState((prev) => ({
|
|
436
|
+
...prev,
|
|
437
|
+
isHovering: false,
|
|
438
|
+
shouldShowControls: controller.shouldShowControls(),
|
|
439
|
+
}));
|
|
440
|
+
})
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
unsubs.push(
|
|
444
|
+
controller.on("captionsChange", ({ enabled }) => {
|
|
445
|
+
setState((prev) => ({ ...prev, subtitlesEnabled: enabled }));
|
|
446
|
+
})
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
// Error handling events - show toasts/modals
|
|
450
|
+
unsubs.push(
|
|
451
|
+
controller.on("protocolSwapped", (data) => {
|
|
452
|
+
const message = `Switched to ${data.toProtocol}`;
|
|
453
|
+
setState((prev) => ({ ...prev, toast: { message, timestamp: Date.now() } }));
|
|
454
|
+
onProtocolSwapped?.(data);
|
|
455
|
+
})
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
unsubs.push(
|
|
459
|
+
controller.on("playbackFailed", (data) => {
|
|
460
|
+
setState((prev) => ({
|
|
461
|
+
...prev,
|
|
462
|
+
error: data.message,
|
|
463
|
+
errorDetails: data.details ?? null,
|
|
464
|
+
isPassiveError: false,
|
|
465
|
+
}));
|
|
466
|
+
onPlaybackFailed?.({
|
|
467
|
+
code: data.code,
|
|
468
|
+
message: data.message,
|
|
469
|
+
details: data.details,
|
|
470
|
+
});
|
|
471
|
+
})
|
|
472
|
+
);
|
|
378
473
|
|
|
379
474
|
// Attach controller to container
|
|
380
475
|
// Note: Video event listeners are set up in the 'ready' handler above
|
|
381
|
-
controller.attach(container).catch(err => {
|
|
382
|
-
console.warn(
|
|
476
|
+
controller.attach(container).catch((err) => {
|
|
477
|
+
console.warn("[usePlayerController] Attach failed:", err);
|
|
383
478
|
});
|
|
384
479
|
|
|
385
480
|
// Set initial state
|
|
386
|
-
setState(prev => ({
|
|
481
|
+
setState((prev) => ({
|
|
387
482
|
...prev,
|
|
388
483
|
isLoopEnabled: controller.isLoopEnabled(),
|
|
389
484
|
}));
|
|
390
485
|
|
|
391
486
|
return () => {
|
|
392
|
-
unsubs.forEach(fn => fn());
|
|
487
|
+
unsubs.forEach((fn) => fn());
|
|
393
488
|
controller.destroy();
|
|
394
489
|
controllerRef.current = null;
|
|
395
490
|
setState(initialState);
|
|
@@ -443,7 +538,16 @@ export function usePlayerController(
|
|
|
443
538
|
|
|
444
539
|
const clearError = useCallback(() => {
|
|
445
540
|
controllerRef.current?.clearError();
|
|
446
|
-
setState(prev => ({
|
|
541
|
+
setState((prev) => ({
|
|
542
|
+
...prev,
|
|
543
|
+
error: null,
|
|
544
|
+
errorDetails: null,
|
|
545
|
+
isPassiveError: false,
|
|
546
|
+
}));
|
|
547
|
+
}, []);
|
|
548
|
+
|
|
549
|
+
const dismissToast = useCallback(() => {
|
|
550
|
+
setState((prev) => ({ ...prev, toast: null }));
|
|
447
551
|
}, []);
|
|
448
552
|
|
|
449
553
|
const jumpToLive = useCallback(() => {
|
|
@@ -482,14 +586,17 @@ export function usePlayerController(
|
|
|
482
586
|
controllerRef.current?.handleTouchStart();
|
|
483
587
|
}, []);
|
|
484
588
|
|
|
485
|
-
const setDevModeOptions = useCallback(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
589
|
+
const setDevModeOptions = useCallback(
|
|
590
|
+
async (options: {
|
|
591
|
+
forcePlayer?: string;
|
|
592
|
+
forceType?: string;
|
|
593
|
+
forceSource?: number;
|
|
594
|
+
playbackMode?: "auto" | "low-latency" | "quality" | "vod";
|
|
595
|
+
}) => {
|
|
596
|
+
await controllerRef.current?.setDevModeOptions(options);
|
|
597
|
+
},
|
|
598
|
+
[]
|
|
599
|
+
);
|
|
493
600
|
|
|
494
601
|
return {
|
|
495
602
|
containerRef,
|
|
@@ -508,6 +615,7 @@ export function usePlayerController(
|
|
|
508
615
|
togglePiP,
|
|
509
616
|
toggleSubtitles,
|
|
510
617
|
clearError,
|
|
618
|
+
dismissToast,
|
|
511
619
|
retry,
|
|
512
620
|
reload,
|
|
513
621
|
getQualities,
|
|
@@ -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);
|