@livepeer-frameworks/player-react 0.1.0 → 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 +7 -9
- package/package.json +1 -1
- package/src/components/DevModePanel.tsx +244 -143
- 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 +84 -56
- package/src/components/PlayerControls.tsx +349 -256
- 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 +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/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 +27 -27
- package/src/hooks/usePlaybackQuality.ts +3 -3
- package/src/hooks/usePlayerController.ts +186 -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
|
@@ -28,7 +28,7 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
28
28
|
thumbnailUrl = null,
|
|
29
29
|
options,
|
|
30
30
|
endpoints: propsEndpoints,
|
|
31
|
-
onStateChange
|
|
31
|
+
onStateChange,
|
|
32
32
|
}) => {
|
|
33
33
|
// ============================================================================
|
|
34
34
|
// UI-only State (stays in wrapper)
|
|
@@ -38,7 +38,9 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
38
38
|
const [skipDirection, setSkipDirection] = useState<SkipDirection>(null);
|
|
39
39
|
|
|
40
40
|
// Playback mode preference (persistent)
|
|
41
|
-
const [devPlaybackMode, setDevPlaybackMode] = useState<PlaybackMode>(
|
|
41
|
+
const [devPlaybackMode, setDevPlaybackMode] = useState<PlaybackMode>(
|
|
42
|
+
options?.playbackMode || "auto"
|
|
43
|
+
);
|
|
42
44
|
|
|
43
45
|
// ============================================================================
|
|
44
46
|
// PlayerController Hook - ALL business logic
|
|
@@ -78,31 +80,33 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
78
80
|
onStateChange?.(playerState);
|
|
79
81
|
},
|
|
80
82
|
onError: (error) => {
|
|
81
|
-
console.warn(
|
|
83
|
+
console.warn("[Player] Error:", error);
|
|
82
84
|
},
|
|
83
85
|
});
|
|
84
86
|
|
|
85
87
|
// ============================================================================
|
|
86
88
|
// Dev Mode Callbacks
|
|
87
89
|
// ============================================================================
|
|
88
|
-
const handleDevSettingsChange = useCallback(
|
|
89
|
-
forcePlayer?: string;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}, [setDevModeOptions]);
|
|
90
|
+
const handleDevSettingsChange = useCallback(
|
|
91
|
+
(settings: { forcePlayer?: string; forceType?: string; forceSource?: number }) => {
|
|
92
|
+
// One-shot selection - controller handles the state
|
|
93
|
+
setDevModeOptions({
|
|
94
|
+
forcePlayer: settings.forcePlayer,
|
|
95
|
+
forceType: settings.forceType,
|
|
96
|
+
forceSource: settings.forceSource,
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
[setDevModeOptions]
|
|
100
|
+
);
|
|
100
101
|
|
|
101
|
-
const handleModeChange = useCallback(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
const handleModeChange = useCallback(
|
|
103
|
+
(mode: PlaybackMode) => {
|
|
104
|
+
setDevPlaybackMode(mode);
|
|
105
|
+
// Mode is a persistent preference
|
|
106
|
+
setDevModeOptions({ playbackMode: mode });
|
|
107
|
+
},
|
|
108
|
+
[setDevModeOptions]
|
|
109
|
+
);
|
|
106
110
|
|
|
107
111
|
const handleReload = useCallback(() => {
|
|
108
112
|
clearError();
|
|
@@ -110,7 +114,7 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
110
114
|
}, [clearError, reload]);
|
|
111
115
|
|
|
112
116
|
const handleStatsToggle = useCallback(() => {
|
|
113
|
-
setIsStatsOpen(prev => !prev);
|
|
117
|
+
setIsStatsOpen((prev) => !prev);
|
|
114
118
|
}, []);
|
|
115
119
|
|
|
116
120
|
// Clear skip indicator after animation
|
|
@@ -122,24 +126,29 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
122
126
|
// Derived Values
|
|
123
127
|
// ============================================================================
|
|
124
128
|
const primaryEndpoint = state.endpoints?.primary as EndpointInfo | undefined;
|
|
125
|
-
const isLegacyPlayer = state.currentPlayerInfo?.shortname ===
|
|
129
|
+
const isLegacyPlayer = state.currentPlayerInfo?.shortname === "mist-legacy";
|
|
126
130
|
const useStockControls = options?.stockControls === true || isLegacyPlayer;
|
|
127
131
|
|
|
128
132
|
// Title overlay visibility: show on hover or when paused
|
|
129
|
-
const showTitleOverlay =
|
|
130
|
-
|
|
133
|
+
const showTitleOverlay =
|
|
134
|
+
(state.isHovering || state.isPaused) &&
|
|
135
|
+
!state.shouldShowIdleScreen &&
|
|
136
|
+
!state.isBuffering &&
|
|
137
|
+
!state.error;
|
|
131
138
|
|
|
132
139
|
// Buffering spinner: only during active playback
|
|
133
|
-
const showBufferingSpinner =
|
|
134
|
-
state.isBuffering && !state.error && state.hasPlaybackStarted;
|
|
140
|
+
const showBufferingSpinner =
|
|
141
|
+
!state.shouldShowIdleScreen && state.isBuffering && !state.error && state.hasPlaybackStarted;
|
|
135
142
|
|
|
136
143
|
// ============================================================================
|
|
137
144
|
// Waiting for Endpoint (shown as overlay, not early return)
|
|
138
145
|
// ============================================================================
|
|
139
|
-
const showWaitingForEndpoint = !state.endpoints?.primary && state.state !==
|
|
146
|
+
const showWaitingForEndpoint = !state.endpoints?.primary && state.state !== "booting";
|
|
140
147
|
const waitingMessage = options?.gatewayUrl
|
|
141
|
-
?
|
|
142
|
-
|
|
148
|
+
? state.state === "gateway_loading"
|
|
149
|
+
? "Resolving viewing endpoint..."
|
|
150
|
+
: "Waiting for endpoint..."
|
|
151
|
+
: "Waiting for endpoint...";
|
|
143
152
|
|
|
144
153
|
// ============================================================================
|
|
145
154
|
// Render
|
|
@@ -159,10 +168,7 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
159
168
|
onMouseMove={handleMouseMove}
|
|
160
169
|
>
|
|
161
170
|
{/* Player area */}
|
|
162
|
-
<div className={cn(
|
|
163
|
-
"relative",
|
|
164
|
-
options?.devMode ? "flex-1 min-w-0" : "w-full h-full"
|
|
165
|
-
)}>
|
|
171
|
+
<div className={cn("relative", options?.devMode ? "flex-1 min-w-0" : "w-full h-full")}>
|
|
166
172
|
{/* Video container - PlayerController attaches here */}
|
|
167
173
|
<div ref={containerRef} className="fw-player-container" />
|
|
168
174
|
|
|
@@ -207,9 +213,7 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
207
213
|
)}
|
|
208
214
|
|
|
209
215
|
{/* Speed indicator */}
|
|
210
|
-
{state.isHoldingSpeed &&
|
|
211
|
-
<SpeedIndicator isVisible={true} speed={state.holdSpeed} />
|
|
212
|
-
)}
|
|
216
|
+
{state.isHoldingSpeed && <SpeedIndicator isVisible={true} speed={state.holdSpeed} />}
|
|
213
217
|
|
|
214
218
|
{/* Skip indicator */}
|
|
215
219
|
<SkipIndicator
|
|
@@ -219,15 +223,13 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
219
223
|
/>
|
|
220
224
|
|
|
221
225
|
{/* Waiting for endpoint overlay */}
|
|
222
|
-
{showWaitingForEndpoint &&
|
|
223
|
-
<IdleScreen status="OFFLINE" message={waitingMessage} />
|
|
224
|
-
)}
|
|
226
|
+
{showWaitingForEndpoint && <IdleScreen status="OFFLINE" message={waitingMessage} />}
|
|
225
227
|
|
|
226
228
|
{/* Idle screen */}
|
|
227
229
|
{!showWaitingForEndpoint && state.shouldShowIdleScreen && (
|
|
228
230
|
<IdleScreen
|
|
229
231
|
status={state.isEffectivelyLive ? state.streamState?.status : undefined}
|
|
230
|
-
message={state.isEffectivelyLive ? state.streamState?.message :
|
|
232
|
+
message={state.isEffectivelyLive ? state.streamState?.message : "Loading video..."}
|
|
231
233
|
percentage={state.isEffectivelyLive ? state.streamState?.percentage : undefined}
|
|
232
234
|
/>
|
|
233
235
|
)}
|
|
@@ -253,21 +255,29 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
253
255
|
aria-live="assertive"
|
|
254
256
|
className={cn(
|
|
255
257
|
"fw-error-overlay",
|
|
256
|
-
state.isPassiveError
|
|
258
|
+
state.isPassiveError
|
|
259
|
+
? "fw-error-overlay--passive"
|
|
260
|
+
: "fw-error-overlay--fullscreen"
|
|
257
261
|
)}
|
|
258
262
|
>
|
|
259
|
-
<div
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
"fw-error-
|
|
269
|
-
|
|
270
|
-
|
|
263
|
+
<div
|
|
264
|
+
className={cn(
|
|
265
|
+
"fw-error-popup",
|
|
266
|
+
state.isPassiveError ? "fw-error-popup--passive" : "fw-error-popup--fullscreen"
|
|
267
|
+
)}
|
|
268
|
+
>
|
|
269
|
+
<div
|
|
270
|
+
className={cn(
|
|
271
|
+
"fw-error-header",
|
|
272
|
+
state.isPassiveError ? "fw-error-header--warning" : "fw-error-header--error"
|
|
273
|
+
)}
|
|
274
|
+
>
|
|
275
|
+
<span
|
|
276
|
+
className={cn(
|
|
277
|
+
"fw-error-title",
|
|
278
|
+
state.isPassiveError ? "fw-error-title--warning" : "fw-error-title--error"
|
|
279
|
+
)}
|
|
280
|
+
>
|
|
271
281
|
{state.isPassiveError ? "Warning" : "Error"}
|
|
272
282
|
</span>
|
|
273
283
|
<button
|
|
@@ -277,7 +287,12 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
277
287
|
aria-label="Dismiss"
|
|
278
288
|
>
|
|
279
289
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
|
|
280
|
-
<path
|
|
290
|
+
<path
|
|
291
|
+
d="M9 3L3 9M3 3L9 9"
|
|
292
|
+
stroke="currentColor"
|
|
293
|
+
strokeWidth="1.5"
|
|
294
|
+
strokeLinecap="round"
|
|
295
|
+
/>
|
|
281
296
|
</svg>
|
|
282
297
|
</button>
|
|
283
298
|
</div>
|
|
@@ -289,7 +304,10 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
289
304
|
type="button"
|
|
290
305
|
className="fw-error-btn"
|
|
291
306
|
aria-label="Retry playback"
|
|
292
|
-
onClick={() => {
|
|
307
|
+
onClick={() => {
|
|
308
|
+
clearError();
|
|
309
|
+
retry();
|
|
310
|
+
}}
|
|
293
311
|
>
|
|
294
312
|
Retry
|
|
295
313
|
</button>
|
|
@@ -376,7 +394,17 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
376
394
|
<span>Picture-in-Picture</span>
|
|
377
395
|
</ContextMenuItem>
|
|
378
396
|
<ContextMenuItem onClick={toggleLoop} className="gap-2">
|
|
379
|
-
<svg
|
|
397
|
+
<svg
|
|
398
|
+
width="14"
|
|
399
|
+
height="14"
|
|
400
|
+
viewBox="0 0 24 24"
|
|
401
|
+
fill="none"
|
|
402
|
+
stroke="currentColor"
|
|
403
|
+
strokeWidth="2"
|
|
404
|
+
strokeLinecap="round"
|
|
405
|
+
strokeLinejoin="round"
|
|
406
|
+
className="opacity-70 flex-shrink-0"
|
|
407
|
+
>
|
|
380
408
|
<polyline points="17 1 21 5 17 9"></polyline>
|
|
381
409
|
<path d="M3 11V9a4 4 0 0 1 4-4h14"></path>
|
|
382
410
|
<polyline points="7 23 3 19 7 15"></polyline>
|