@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.
Files changed (54) hide show
  1. package/README.md +16 -5
  2. package/dist/cjs/index.js +1 -1
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/esm/index.js +1 -1
  5. package/dist/esm/index.js.map +1 -1
  6. package/dist/types/components/PlayerControls.d.ts +2 -0
  7. package/dist/types/components/StatsPanel.d.ts +2 -14
  8. package/dist/types/hooks/useMetaTrack.d.ts +1 -1
  9. package/dist/types/hooks/usePlayerController.d.ts +2 -0
  10. package/dist/types/hooks/useStreamState.d.ts +1 -1
  11. package/dist/types/hooks/useTelemetry.d.ts +1 -1
  12. package/dist/types/hooks/useViewerEndpoints.d.ts +2 -2
  13. package/dist/types/types.d.ts +1 -1
  14. package/dist/types/ui/button.d.ts +1 -1
  15. package/package.json +1 -1
  16. package/src/components/DevModePanel.tsx +249 -170
  17. package/src/components/Icons.tsx +105 -25
  18. package/src/components/IdleScreen.tsx +262 -142
  19. package/src/components/LoadingScreen.tsx +171 -153
  20. package/src/components/LogoOverlay.tsx +3 -6
  21. package/src/components/Player.tsx +86 -74
  22. package/src/components/PlayerControls.tsx +351 -263
  23. package/src/components/PlayerErrorBoundary.tsx +6 -13
  24. package/src/components/SeekBar.tsx +96 -88
  25. package/src/components/SkipIndicator.tsx +2 -12
  26. package/src/components/SpeedIndicator.tsx +2 -11
  27. package/src/components/StatsPanel.tsx +65 -34
  28. package/src/components/StreamStateOverlay.tsx +105 -49
  29. package/src/components/SubtitleRenderer.tsx +29 -29
  30. package/src/components/ThumbnailOverlay.tsx +5 -6
  31. package/src/components/TitleOverlay.tsx +2 -8
  32. package/src/components/players/DashJsPlayer.tsx +13 -11
  33. package/src/components/players/HlsJsPlayer.tsx +13 -11
  34. package/src/components/players/MewsWsPlayer/index.tsx +13 -11
  35. package/src/components/players/MistPlayer.tsx +13 -11
  36. package/src/components/players/MistWebRTCPlayer/index.tsx +19 -10
  37. package/src/components/players/NativePlayer.tsx +10 -12
  38. package/src/components/players/VideoJsPlayer.tsx +13 -11
  39. package/src/context/PlayerContext.tsx +4 -8
  40. package/src/context/index.ts +3 -3
  41. package/src/hooks/useMetaTrack.ts +28 -28
  42. package/src/hooks/usePlaybackQuality.ts +3 -3
  43. package/src/hooks/usePlayerController.ts +186 -140
  44. package/src/hooks/usePlayerSelection.ts +6 -6
  45. package/src/hooks/useStreamState.ts +53 -58
  46. package/src/hooks/useTelemetry.ts +19 -4
  47. package/src/hooks/useViewerEndpoints.ts +40 -30
  48. package/src/index.tsx +36 -28
  49. package/src/types.ts +9 -9
  50. package/src/ui/badge.tsx +6 -5
  51. package/src/ui/button.tsx +9 -8
  52. package/src/ui/context-menu.tsx +42 -61
  53. package/src/ui/select.tsx +13 -7
  54. package/src/ui/slider.tsx +18 -29
@@ -1,6 +1,5 @@
1
- import React, { useState, useCallback, useMemo } from "react";
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>(options?.playbackMode || 'auto');
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('[Player] Error:', error);
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((settings: {
90
- forcePlayer?: string;
91
- forceType?: string;
92
- forceSource?: number;
93
- }) => {
94
- // One-shot selection - controller handles the state
95
- setDevModeOptions({
96
- forcePlayer: settings.forcePlayer,
97
- forceType: settings.forceType,
98
- forceSource: settings.forceSource,
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((mode: PlaybackMode) => {
103
- setDevPlaybackMode(mode);
104
- // Mode is a persistent preference
105
- setDevModeOptions({ playbackMode: mode });
106
- }, [setDevModeOptions]);
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 === 'mist-legacy';
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 = (state.isHovering || state.isPaused) &&
131
- !state.shouldShowIdleScreen && !state.isBuffering && !state.error;
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 = !state.shouldShowIdleScreen &&
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 !== 'booting';
146
+ const showWaitingForEndpoint = !state.endpoints?.primary && state.state !== "booting";
144
147
  const waitingMessage = options?.gatewayUrl
145
- ? (state.state === 'gateway_loading' ? 'Resolving viewing endpoint...' : 'Waiting for endpoint...')
146
- : 'Waiting for endpoint...';
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?.isOnline ? {
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 : 'Loading video...'}
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 ? "fw-error-overlay--passive" : "fw-error-overlay--fullscreen"
258
+ state.isPassiveError
259
+ ? "fw-error-overlay--passive"
260
+ : "fw-error-overlay--fullscreen"
273
261
  )}
274
262
  >
275
- <div className={cn(
276
- "fw-error-popup",
277
- state.isPassiveError ? "fw-error-popup--passive" : "fw-error-popup--fullscreen"
278
- )}>
279
- <div className={cn(
280
- "fw-error-header",
281
- state.isPassiveError ? "fw-error-header--warning" : "fw-error-header--error"
282
- )}>
283
- <span className={cn(
284
- "fw-error-title",
285
- state.isPassiveError ? "fw-error-title--warning" : "fw-error-title--error"
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 d="M9 3L3 9M3 3L9 9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/>
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={() => { clearError(); retry(); }}
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 width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="opacity-70 flex-shrink-0">
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>