@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
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import React, { useState, useCallback
|
|
1
|
+
import React, { useState, useCallback } from "react";
|
|
2
2
|
import IdleScreen from "./IdleScreen";
|
|
3
|
-
import ThumbnailOverlay from "./ThumbnailOverlay";
|
|
4
3
|
import TitleOverlay from "./TitleOverlay";
|
|
5
4
|
import StatsPanel from "./StatsPanel";
|
|
6
5
|
import PlayerControls from "./PlayerControls";
|
|
@@ -29,7 +28,7 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
29
28
|
thumbnailUrl = null,
|
|
30
29
|
options,
|
|
31
30
|
endpoints: propsEndpoints,
|
|
32
|
-
onStateChange
|
|
31
|
+
onStateChange,
|
|
33
32
|
}) => {
|
|
34
33
|
// ============================================================================
|
|
35
34
|
// UI-only State (stays in wrapper)
|
|
@@ -39,7 +38,9 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
39
38
|
const [skipDirection, setSkipDirection] = useState<SkipDirection>(null);
|
|
40
39
|
|
|
41
40
|
// Playback mode preference (persistent)
|
|
42
|
-
const [devPlaybackMode, setDevPlaybackMode] = useState<PlaybackMode>(
|
|
41
|
+
const [devPlaybackMode, setDevPlaybackMode] = useState<PlaybackMode>(
|
|
42
|
+
options?.playbackMode || "auto"
|
|
43
|
+
);
|
|
43
44
|
|
|
44
45
|
// ============================================================================
|
|
45
46
|
// PlayerController Hook - ALL business logic
|
|
@@ -79,31 +80,33 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
79
80
|
onStateChange?.(playerState);
|
|
80
81
|
},
|
|
81
82
|
onError: (error) => {
|
|
82
|
-
console.warn(
|
|
83
|
+
console.warn("[Player] Error:", error);
|
|
83
84
|
},
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
// ============================================================================
|
|
87
88
|
// Dev Mode Callbacks
|
|
88
89
|
// ============================================================================
|
|
89
|
-
const handleDevSettingsChange = useCallback(
|
|
90
|
-
forcePlayer?: string;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}, [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
|
+
);
|
|
101
101
|
|
|
102
|
-
const handleModeChange = useCallback(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
const handleModeChange = useCallback(
|
|
103
|
+
(mode: PlaybackMode) => {
|
|
104
|
+
setDevPlaybackMode(mode);
|
|
105
|
+
// Mode is a persistent preference
|
|
106
|
+
setDevModeOptions({ playbackMode: mode });
|
|
107
|
+
},
|
|
108
|
+
[setDevModeOptions]
|
|
109
|
+
);
|
|
107
110
|
|
|
108
111
|
const handleReload = useCallback(() => {
|
|
109
112
|
clearError();
|
|
@@ -111,7 +114,7 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
111
114
|
}, [clearError, reload]);
|
|
112
115
|
|
|
113
116
|
const handleStatsToggle = useCallback(() => {
|
|
114
|
-
setIsStatsOpen(prev => !prev);
|
|
117
|
+
setIsStatsOpen((prev) => !prev);
|
|
115
118
|
}, []);
|
|
116
119
|
|
|
117
120
|
// Clear skip indicator after animation
|
|
@@ -123,27 +126,29 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
123
126
|
// Derived Values
|
|
124
127
|
// ============================================================================
|
|
125
128
|
const primaryEndpoint = state.endpoints?.primary as EndpointInfo | undefined;
|
|
126
|
-
const isLegacyPlayer = state.currentPlayerInfo?.shortname ===
|
|
129
|
+
const isLegacyPlayer = state.currentPlayerInfo?.shortname === "mist-legacy";
|
|
127
130
|
const useStockControls = options?.stockControls === true || isLegacyPlayer;
|
|
128
131
|
|
|
129
132
|
// Title overlay visibility: show on hover or when paused
|
|
130
|
-
const showTitleOverlay =
|
|
131
|
-
|
|
133
|
+
const showTitleOverlay =
|
|
134
|
+
(state.isHovering || state.isPaused) &&
|
|
135
|
+
!state.shouldShowIdleScreen &&
|
|
136
|
+
!state.isBuffering &&
|
|
137
|
+
!state.error;
|
|
132
138
|
|
|
133
139
|
// Buffering spinner: only during active playback
|
|
134
|
-
const showBufferingSpinner =
|
|
135
|
-
state.isBuffering && !state.error && state.hasPlaybackStarted;
|
|
136
|
-
|
|
137
|
-
// Click-to-play overlay support
|
|
138
|
-
const supportsOverlay = false;
|
|
140
|
+
const showBufferingSpinner =
|
|
141
|
+
!state.shouldShowIdleScreen && state.isBuffering && !state.error && state.hasPlaybackStarted;
|
|
139
142
|
|
|
140
143
|
// ============================================================================
|
|
141
144
|
// Waiting for Endpoint (shown as overlay, not early return)
|
|
142
145
|
// ============================================================================
|
|
143
|
-
const showWaitingForEndpoint = !state.endpoints?.primary && state.state !==
|
|
146
|
+
const showWaitingForEndpoint = !state.endpoints?.primary && state.state !== "booting";
|
|
144
147
|
const waitingMessage = options?.gatewayUrl
|
|
145
|
-
?
|
|
146
|
-
|
|
148
|
+
? state.state === "gateway_loading"
|
|
149
|
+
? "Resolving viewing endpoint..."
|
|
150
|
+
: "Waiting for endpoint..."
|
|
151
|
+
: "Waiting for endpoint...";
|
|
147
152
|
|
|
148
153
|
// ============================================================================
|
|
149
154
|
// Render
|
|
@@ -163,10 +168,7 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
163
168
|
onMouseMove={handleMouseMove}
|
|
164
169
|
>
|
|
165
170
|
{/* Player area */}
|
|
166
|
-
<div className={cn(
|
|
167
|
-
"relative",
|
|
168
|
-
options?.devMode ? "flex-1 min-w-0" : "w-full h-full"
|
|
169
|
-
)}>
|
|
171
|
+
<div className={cn("relative", options?.devMode ? "flex-1 min-w-0" : "w-full h-full")}>
|
|
170
172
|
{/* Video container - PlayerController attaches here */}
|
|
171
173
|
<div ref={containerRef} className="fw-player-container" />
|
|
172
174
|
|
|
@@ -182,19 +184,7 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
182
184
|
isOpen={isStatsOpen}
|
|
183
185
|
onClose={handleStatsToggle}
|
|
184
186
|
metadata={state.metadata}
|
|
185
|
-
streamState={state.streamState
|
|
186
|
-
status: state.streamState.status,
|
|
187
|
-
viewers: state.metadata?.viewers,
|
|
188
|
-
tracks: state.streamState.streamInfo?.meta?.tracks
|
|
189
|
-
? Object.values(state.streamState.streamInfo.meta.tracks).map(t => ({
|
|
190
|
-
type: t.type,
|
|
191
|
-
codec: t.codec,
|
|
192
|
-
width: t.width,
|
|
193
|
-
height: t.height,
|
|
194
|
-
bps: t.bps,
|
|
195
|
-
}))
|
|
196
|
-
: [],
|
|
197
|
-
} : null}
|
|
187
|
+
streamState={state.streamState}
|
|
198
188
|
quality={state.playbackQuality}
|
|
199
189
|
videoElement={state.videoElement}
|
|
200
190
|
protocol={primaryEndpoint?.protocol}
|
|
@@ -223,9 +213,7 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
223
213
|
)}
|
|
224
214
|
|
|
225
215
|
{/* Speed indicator */}
|
|
226
|
-
{state.isHoldingSpeed &&
|
|
227
|
-
<SpeedIndicator isVisible={true} speed={state.holdSpeed} />
|
|
228
|
-
)}
|
|
216
|
+
{state.isHoldingSpeed && <SpeedIndicator isVisible={true} speed={state.holdSpeed} />}
|
|
229
217
|
|
|
230
218
|
{/* Skip indicator */}
|
|
231
219
|
<SkipIndicator
|
|
@@ -235,15 +223,13 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
235
223
|
/>
|
|
236
224
|
|
|
237
225
|
{/* Waiting for endpoint overlay */}
|
|
238
|
-
{showWaitingForEndpoint &&
|
|
239
|
-
<IdleScreen status="OFFLINE" message={waitingMessage} />
|
|
240
|
-
)}
|
|
226
|
+
{showWaitingForEndpoint && <IdleScreen status="OFFLINE" message={waitingMessage} />}
|
|
241
227
|
|
|
242
228
|
{/* Idle screen */}
|
|
243
229
|
{!showWaitingForEndpoint && state.shouldShowIdleScreen && (
|
|
244
230
|
<IdleScreen
|
|
245
231
|
status={state.isEffectivelyLive ? state.streamState?.status : undefined}
|
|
246
|
-
message={state.isEffectivelyLive ? state.streamState?.message :
|
|
232
|
+
message={state.isEffectivelyLive ? state.streamState?.message : "Loading video..."}
|
|
247
233
|
percentage={state.isEffectivelyLive ? state.streamState?.percentage : undefined}
|
|
248
234
|
/>
|
|
249
235
|
)}
|
|
@@ -269,21 +255,29 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
269
255
|
aria-live="assertive"
|
|
270
256
|
className={cn(
|
|
271
257
|
"fw-error-overlay",
|
|
272
|
-
state.isPassiveError
|
|
258
|
+
state.isPassiveError
|
|
259
|
+
? "fw-error-overlay--passive"
|
|
260
|
+
: "fw-error-overlay--fullscreen"
|
|
273
261
|
)}
|
|
274
262
|
>
|
|
275
|
-
<div
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
"fw-error-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
+
>
|
|
287
281
|
{state.isPassiveError ? "Warning" : "Error"}
|
|
288
282
|
</span>
|
|
289
283
|
<button
|
|
@@ -293,7 +287,12 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
293
287
|
aria-label="Dismiss"
|
|
294
288
|
>
|
|
295
289
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
|
|
296
|
-
<path
|
|
290
|
+
<path
|
|
291
|
+
d="M9 3L3 9M3 3L9 9"
|
|
292
|
+
stroke="currentColor"
|
|
293
|
+
strokeWidth="1.5"
|
|
294
|
+
strokeLinecap="round"
|
|
295
|
+
/>
|
|
297
296
|
</svg>
|
|
298
297
|
</button>
|
|
299
298
|
</div>
|
|
@@ -305,7 +304,10 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
305
304
|
type="button"
|
|
306
305
|
className="fw-error-btn"
|
|
307
306
|
aria-label="Retry playback"
|
|
308
|
-
onClick={() => {
|
|
307
|
+
onClick={() => {
|
|
308
|
+
clearError();
|
|
309
|
+
retry();
|
|
310
|
+
}}
|
|
309
311
|
>
|
|
310
312
|
Retry
|
|
311
313
|
</button>
|
|
@@ -392,7 +394,17 @@ const PlayerInner: React.FC<PlayerProps> = ({
|
|
|
392
394
|
<span>Picture-in-Picture</span>
|
|
393
395
|
</ContextMenuItem>
|
|
394
396
|
<ContextMenuItem onClick={toggleLoop} className="gap-2">
|
|
395
|
-
<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
|
+
>
|
|
396
408
|
<polyline points="17 1 21 5 17 9"></polyline>
|
|
397
409
|
<path d="M3 11V9a4 4 0 0 1 4-4h14"></path>
|
|
398
410
|
<polyline points="7 23 3 19 7 15"></polyline>
|