@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.
Files changed (41) hide show
  1. package/README.md +7 -9
  2. package/package.json +1 -1
  3. package/src/components/DevModePanel.tsx +244 -143
  4. package/src/components/Icons.tsx +105 -25
  5. package/src/components/IdleScreen.tsx +262 -128
  6. package/src/components/LoadingScreen.tsx +169 -151
  7. package/src/components/LogoOverlay.tsx +3 -6
  8. package/src/components/Player.tsx +84 -56
  9. package/src/components/PlayerControls.tsx +349 -256
  10. package/src/components/PlayerErrorBoundary.tsx +6 -13
  11. package/src/components/SeekBar.tsx +96 -88
  12. package/src/components/SkipIndicator.tsx +2 -12
  13. package/src/components/SpeedIndicator.tsx +2 -11
  14. package/src/components/StatsPanel.tsx +31 -22
  15. package/src/components/StreamStateOverlay.tsx +105 -49
  16. package/src/components/SubtitleRenderer.tsx +29 -29
  17. package/src/components/ThumbnailOverlay.tsx +5 -6
  18. package/src/components/TitleOverlay.tsx +2 -8
  19. package/src/components/players/DashJsPlayer.tsx +13 -11
  20. package/src/components/players/HlsJsPlayer.tsx +13 -11
  21. package/src/components/players/MewsWsPlayer/index.tsx +13 -11
  22. package/src/components/players/MistPlayer.tsx +13 -11
  23. package/src/components/players/MistWebRTCPlayer/index.tsx +19 -10
  24. package/src/components/players/NativePlayer.tsx +10 -12
  25. package/src/components/players/VideoJsPlayer.tsx +13 -11
  26. package/src/context/PlayerContext.tsx +4 -8
  27. package/src/context/index.ts +3 -3
  28. package/src/hooks/useMetaTrack.ts +27 -27
  29. package/src/hooks/usePlaybackQuality.ts +3 -3
  30. package/src/hooks/usePlayerController.ts +186 -138
  31. package/src/hooks/usePlayerSelection.ts +6 -6
  32. package/src/hooks/useStreamState.ts +51 -56
  33. package/src/hooks/useTelemetry.ts +18 -3
  34. package/src/hooks/useViewerEndpoints.ts +34 -23
  35. package/src/index.tsx +36 -28
  36. package/src/types.ts +8 -8
  37. package/src/ui/badge.tsx +6 -5
  38. package/src/ui/button.tsx +9 -8
  39. package/src/ui/context-menu.tsx +42 -61
  40. package/src/ui/select.tsx +13 -7
  41. 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>(options?.playbackMode || 'auto');
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('[Player] Error:', error);
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((settings: {
89
- forcePlayer?: string;
90
- forceType?: string;
91
- forceSource?: number;
92
- }) => {
93
- // One-shot selection - controller handles the state
94
- setDevModeOptions({
95
- forcePlayer: settings.forcePlayer,
96
- forceType: settings.forceType,
97
- forceSource: settings.forceSource,
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((mode: PlaybackMode) => {
102
- setDevPlaybackMode(mode);
103
- // Mode is a persistent preference
104
- setDevModeOptions({ playbackMode: mode });
105
- }, [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
+ );
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 === 'mist-legacy';
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 = (state.isHovering || state.isPaused) &&
130
- !state.shouldShowIdleScreen && !state.isBuffering && !state.error;
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 = !state.shouldShowIdleScreen &&
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 !== 'booting';
146
+ const showWaitingForEndpoint = !state.endpoints?.primary && state.state !== "booting";
140
147
  const waitingMessage = options?.gatewayUrl
141
- ? (state.state === 'gateway_loading' ? 'Resolving viewing endpoint...' : 'Waiting for endpoint...')
142
- : 'Waiting for endpoint...';
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 : 'Loading video...'}
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 ? "fw-error-overlay--passive" : "fw-error-overlay--fullscreen"
258
+ state.isPassiveError
259
+ ? "fw-error-overlay--passive"
260
+ : "fw-error-overlay--fullscreen"
257
261
  )}
258
262
  >
259
- <div className={cn(
260
- "fw-error-popup",
261
- state.isPassiveError ? "fw-error-popup--passive" : "fw-error-popup--fullscreen"
262
- )}>
263
- <div className={cn(
264
- "fw-error-header",
265
- state.isPassiveError ? "fw-error-header--warning" : "fw-error-header--error"
266
- )}>
267
- <span className={cn(
268
- "fw-error-title",
269
- state.isPassiveError ? "fw-error-title--warning" : "fw-error-title--error"
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 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
+ />
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={() => { clearError(); retry(); }}
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 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
+ >
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>