@livepeer-frameworks/player-react 0.1.1 → 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.
Files changed (158) hide show
  1. package/dist/cjs/_virtual/_rollupPluginBabelHelpers.js +359 -0
  2. package/dist/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
  3. package/dist/cjs/assets/logomark.svg.js +8 -0
  4. package/dist/cjs/assets/logomark.svg.js.map +1 -0
  5. package/dist/cjs/components/DevModePanel.js +826 -0
  6. package/dist/cjs/components/DevModePanel.js.map +1 -0
  7. package/dist/cjs/components/DvdLogo.js +200 -0
  8. package/dist/cjs/components/DvdLogo.js.map +1 -0
  9. package/dist/cjs/components/Icons.js +439 -0
  10. package/dist/cjs/components/Icons.js.map +1 -0
  11. package/dist/cjs/components/IdleScreen.js +587 -0
  12. package/dist/cjs/components/IdleScreen.js.map +1 -0
  13. package/dist/cjs/components/LoadingScreen.js +523 -0
  14. package/dist/cjs/components/LoadingScreen.js.map +1 -0
  15. package/dist/cjs/components/Player.js +420 -0
  16. package/dist/cjs/components/Player.js.map +1 -0
  17. package/dist/cjs/components/PlayerControls.js +798 -0
  18. package/dist/cjs/components/PlayerControls.js.map +1 -0
  19. package/dist/cjs/components/PlayerErrorBoundary.js +80 -0
  20. package/dist/cjs/components/PlayerErrorBoundary.js.map +1 -0
  21. package/dist/cjs/components/SeekBar.js +253 -0
  22. package/dist/cjs/components/SeekBar.js.map +1 -0
  23. package/dist/cjs/components/SkipIndicator.js +92 -0
  24. package/dist/cjs/components/SkipIndicator.js.map +1 -0
  25. package/dist/cjs/components/SpeedIndicator.js +43 -0
  26. package/dist/cjs/components/SpeedIndicator.js.map +1 -0
  27. package/dist/cjs/components/StatsPanel.js +202 -0
  28. package/dist/cjs/components/StatsPanel.js.map +1 -0
  29. package/dist/cjs/components/StreamStateOverlay.js +229 -0
  30. package/dist/cjs/components/StreamStateOverlay.js.map +1 -0
  31. package/dist/cjs/components/ThumbnailOverlay.js +86 -0
  32. package/dist/cjs/components/ThumbnailOverlay.js.map +1 -0
  33. package/dist/cjs/components/TitleOverlay.js +32 -0
  34. package/dist/cjs/components/TitleOverlay.js.map +1 -0
  35. package/dist/cjs/context/PlayerContext.js +46 -0
  36. package/dist/cjs/context/PlayerContext.js.map +1 -0
  37. package/dist/cjs/hooks/useMetaTrack.js +165 -0
  38. package/dist/cjs/hooks/useMetaTrack.js.map +1 -0
  39. package/dist/cjs/hooks/usePlaybackQuality.js +131 -0
  40. package/dist/cjs/hooks/usePlaybackQuality.js.map +1 -0
  41. package/dist/cjs/hooks/usePlayerController.js +518 -0
  42. package/dist/cjs/hooks/usePlayerController.js.map +1 -0
  43. package/dist/cjs/hooks/usePlayerSelection.js +90 -0
  44. package/dist/cjs/hooks/usePlayerSelection.js.map +1 -0
  45. package/dist/cjs/hooks/useStreamState.js +360 -0
  46. package/dist/cjs/hooks/useStreamState.js.map +1 -0
  47. package/dist/cjs/hooks/useTelemetry.js +120 -0
  48. package/dist/cjs/hooks/useTelemetry.js.map +1 -0
  49. package/dist/cjs/hooks/useViewerEndpoints.js +222 -0
  50. package/dist/cjs/hooks/useViewerEndpoints.js.map +1 -0
  51. package/dist/cjs/index.js +97 -1
  52. package/dist/cjs/index.js.map +1 -1
  53. package/dist/cjs/ui/badge.js +34 -0
  54. package/dist/cjs/ui/badge.js.map +1 -0
  55. package/dist/cjs/ui/button.js +74 -0
  56. package/dist/cjs/ui/button.js.map +1 -0
  57. package/dist/cjs/ui/context-menu.js +163 -0
  58. package/dist/cjs/ui/context-menu.js.map +1 -0
  59. package/dist/cjs/ui/slider.js +60 -0
  60. package/dist/cjs/ui/slider.js.map +1 -0
  61. package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +329 -0
  62. package/dist/esm/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
  63. package/dist/esm/assets/logomark.svg.js +4 -0
  64. package/dist/esm/assets/logomark.svg.js.map +1 -0
  65. package/dist/esm/components/DevModePanel.js +822 -0
  66. package/dist/esm/components/DevModePanel.js.map +1 -0
  67. package/dist/esm/components/DvdLogo.js +196 -0
  68. package/dist/esm/components/DvdLogo.js.map +1 -0
  69. package/dist/esm/components/Icons.js +421 -0
  70. package/dist/esm/components/Icons.js.map +1 -0
  71. package/dist/esm/components/IdleScreen.js +582 -0
  72. package/dist/esm/components/IdleScreen.js.map +1 -0
  73. package/dist/esm/components/LoadingScreen.js +519 -0
  74. package/dist/esm/components/LoadingScreen.js.map +1 -0
  75. package/dist/esm/components/Player.js +416 -0
  76. package/dist/esm/components/Player.js.map +1 -0
  77. package/dist/esm/components/PlayerControls.js +794 -0
  78. package/dist/esm/components/PlayerControls.js.map +1 -0
  79. package/dist/esm/components/PlayerErrorBoundary.js +76 -0
  80. package/dist/esm/components/PlayerErrorBoundary.js.map +1 -0
  81. package/dist/esm/components/SeekBar.js +249 -0
  82. package/dist/esm/components/SeekBar.js.map +1 -0
  83. package/dist/esm/components/SkipIndicator.js +88 -0
  84. package/dist/esm/components/SkipIndicator.js.map +1 -0
  85. package/dist/esm/components/SpeedIndicator.js +39 -0
  86. package/dist/esm/components/SpeedIndicator.js.map +1 -0
  87. package/dist/esm/components/StatsPanel.js +198 -0
  88. package/dist/esm/components/StatsPanel.js.map +1 -0
  89. package/dist/esm/components/StreamStateOverlay.js +224 -0
  90. package/dist/esm/components/StreamStateOverlay.js.map +1 -0
  91. package/dist/esm/components/ThumbnailOverlay.js +82 -0
  92. package/dist/esm/components/ThumbnailOverlay.js.map +1 -0
  93. package/dist/esm/components/TitleOverlay.js +28 -0
  94. package/dist/esm/components/TitleOverlay.js.map +1 -0
  95. package/dist/esm/context/PlayerContext.js +41 -0
  96. package/dist/esm/context/PlayerContext.js.map +1 -0
  97. package/dist/esm/hooks/useMetaTrack.js +163 -0
  98. package/dist/esm/hooks/useMetaTrack.js.map +1 -0
  99. package/dist/esm/hooks/usePlaybackQuality.js +129 -0
  100. package/dist/esm/hooks/usePlaybackQuality.js.map +1 -0
  101. package/dist/esm/hooks/usePlayerController.js +516 -0
  102. package/dist/esm/hooks/usePlayerController.js.map +1 -0
  103. package/dist/esm/hooks/usePlayerSelection.js +88 -0
  104. package/dist/esm/hooks/usePlayerSelection.js.map +1 -0
  105. package/dist/esm/hooks/useStreamState.js +358 -0
  106. package/dist/esm/hooks/useStreamState.js.map +1 -0
  107. package/dist/esm/hooks/useTelemetry.js +118 -0
  108. package/dist/esm/hooks/useTelemetry.js.map +1 -0
  109. package/dist/esm/hooks/useViewerEndpoints.js +220 -0
  110. package/dist/esm/hooks/useViewerEndpoints.js.map +1 -0
  111. package/dist/esm/index.js +23 -1
  112. package/dist/esm/index.js.map +1 -1
  113. package/dist/esm/ui/badge.js +31 -0
  114. package/dist/esm/ui/badge.js.map +1 -0
  115. package/dist/esm/ui/button.js +52 -0
  116. package/dist/esm/ui/button.js.map +1 -0
  117. package/dist/esm/ui/context-menu.js +132 -0
  118. package/dist/esm/ui/context-menu.js.map +1 -0
  119. package/dist/esm/ui/slider.js +38 -0
  120. package/dist/esm/ui/slider.js.map +1 -0
  121. package/dist/types/components/DvdLogo.d.ts +1 -1
  122. package/dist/types/components/Icons.d.ts +1 -1
  123. package/dist/types/components/Player.d.ts +1 -1
  124. package/dist/types/components/PlayerErrorBoundary.d.ts +2 -1
  125. package/dist/types/components/StreamStateOverlay.d.ts +2 -2
  126. package/dist/types/components/SubtitleRenderer.d.ts +2 -2
  127. package/dist/types/context/PlayerContext.d.ts +2 -2
  128. package/dist/types/context/index.d.ts +2 -2
  129. package/dist/types/hooks/useMetaTrack.d.ts +3 -3
  130. package/dist/types/hooks/usePlaybackQuality.d.ts +2 -2
  131. package/dist/types/hooks/usePlayerController.d.ts +26 -3
  132. package/dist/types/hooks/usePlayerSelection.d.ts +1 -1
  133. package/dist/types/hooks/useStreamState.d.ts +1 -1
  134. package/dist/types/hooks/useTelemetry.d.ts +1 -1
  135. package/dist/types/hooks/useViewerEndpoints.d.ts +3 -3
  136. package/dist/types/index.d.ts +28 -28
  137. package/dist/types/types.d.ts +3 -3
  138. package/dist/types/ui/select.d.ts +1 -1
  139. package/package.json +22 -14
  140. package/src/components/DvdLogo.tsx +1 -1
  141. package/src/components/Player.tsx +42 -3
  142. package/src/components/PlayerControls.tsx +36 -17
  143. package/src/components/PlayerErrorBoundary.tsx +2 -1
  144. package/src/hooks/usePlayerController.ts +61 -1
  145. package/dist/types/components/players/DashJsPlayer.d.ts +0 -18
  146. package/dist/types/components/players/HlsJsPlayer.d.ts +0 -18
  147. package/dist/types/components/players/MewsWsPlayer/index.d.ts +0 -18
  148. package/dist/types/components/players/MistPlayer.d.ts +0 -20
  149. package/dist/types/components/players/MistWebRTCPlayer/index.d.ts +0 -20
  150. package/dist/types/components/players/NativePlayer.d.ts +0 -19
  151. package/dist/types/components/players/VideoJsPlayer.d.ts +0 -18
  152. package/src/components/players/DashJsPlayer.tsx +0 -58
  153. package/src/components/players/HlsJsPlayer.tsx +0 -58
  154. package/src/components/players/MewsWsPlayer/index.tsx +0 -58
  155. package/src/components/players/MistPlayer.tsx +0 -62
  156. package/src/components/players/MistWebRTCPlayer/index.tsx +0 -68
  157. package/src/components/players/NativePlayer.tsx +0 -56
  158. package/src/components/players/VideoJsPlayer.tsx +0 -58
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livepeer-frameworks/player-react",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "React components for FrameWorks streaming player",
6
6
  "main": "dist/cjs/index.js",
@@ -12,16 +12,21 @@
12
12
  ],
13
13
  "exports": {
14
14
  ".": {
15
+ "source": "./src/index.tsx",
16
+ "types": "./dist/types/index.d.ts",
15
17
  "import": "./dist/esm/index.js",
16
- "require": "./dist/cjs/index.js",
17
- "types": "./dist/types/index.d.ts"
18
+ "require": "./dist/cjs/index.js"
18
19
  },
19
20
  "./player.css": "./src/player.css"
20
21
  },
21
22
  "scripts": {
22
- "build": "NODE_ENV=production rollup -c",
23
+ "build": "pnpm run build:types && NODE_ENV=production rollup -c",
23
24
  "build:watch": "NODE_ENV=development rollup -c --watch",
24
- "type-check": "tsc --noEmit"
25
+ "build:types": "tsc --noEmit false --emitDeclarationOnly --outDir dist/types",
26
+ "type-check": "tsc --noEmit",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "test:coverage": "vitest run --coverage"
25
30
  },
26
31
  "dependencies": {
27
32
  "@livepeer-frameworks/player-core": "workspace:*",
@@ -37,7 +42,9 @@
37
42
  "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
38
43
  },
39
44
  "devDependencies": {
40
- "@babel/preset-env": "^7.26.0",
45
+ "@testing-library/react": "^16.1.0",
46
+ "@vitest/coverage-v8": "^4.0.18",
47
+ "@babel/preset-env": "^7.29.0",
41
48
  "@babel/preset-react": "^7.26.3",
42
49
  "@babel/preset-typescript": "^7.27.1",
43
50
  "@radix-ui/react-context-menu": "^2.2.16",
@@ -45,20 +52,21 @@
45
52
  "@radix-ui/react-slider": "^1.1.2",
46
53
  "@radix-ui/react-slot": "^1.1.1",
47
54
  "@rollup/plugin-babel": "^6.0.4",
48
- "@rollup/plugin-commonjs": "^28.0.6",
55
+ "@rollup/plugin-commonjs": "^29.0.0",
49
56
  "@rollup/plugin-node-resolve": "^16.0.1",
50
- "@rollup/plugin-terser": "^0.4.4",
51
57
  "@rollup/plugin-typescript": "^12.1.4",
52
58
  "@rollup/plugin-url": "^8.0.2",
53
- "@types/react": "^19.1.11",
59
+ "@types/react": "^19.2.13",
54
60
  "@types/react-dom": "^19.1.7",
55
- "lucide-react": "^0.468.0",
56
- "react": "19.2.0",
57
- "react-dom": "19.2.0",
58
- "rollup": "^4.48.1",
61
+ "lucide-react": "^0.563.0",
62
+ "react": "19.2.4",
63
+ "react-dom": "19.2.4",
64
+ "rollup": "^4.57.1",
59
65
  "rollup-plugin-peer-deps-external": "^2.2.4",
60
66
  "tslib": "^2.8.1",
61
- "typescript": "^5.9.2"
67
+ "typescript": "^5.9.2",
68
+ "jsdom": "^28.0.0",
69
+ "vitest": "^4.0.18"
62
70
  },
63
71
  "keywords": [
64
72
  "react",
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useRef, useState } from "react";
2
- import { DvdLogoProps } from "../types";
2
+ import type { DvdLogoProps } from "../types";
3
3
 
4
4
  type Point = { top: number; left: number };
5
5
  type Velocity = { x: number; y: number };
@@ -1,13 +1,14 @@
1
- import React, { useState, useCallback } from "react";
1
+ import React, { useState, useCallback, useEffect } from "react";
2
2
  import IdleScreen from "./IdleScreen";
3
3
  import TitleOverlay from "./TitleOverlay";
4
4
  import StatsPanel from "./StatsPanel";
5
5
  import PlayerControls from "./PlayerControls";
6
6
  import DevModePanel from "./DevModePanel";
7
7
  import SpeedIndicator from "./SpeedIndicator";
8
- import SkipIndicator, { SkipDirection } from "./SkipIndicator";
8
+ import type { SkipDirection } from "./SkipIndicator";
9
+ import SkipIndicator from "./SkipIndicator";
9
10
  import { StatsIcon, SettingsIcon, PictureInPictureIcon } from "./Icons";
10
- import { PlayerProps } from "../types";
11
+ import type { PlayerProps } from "../types";
11
12
  import { usePlayerController } from "../hooks/usePlayerController";
12
13
  import { cn } from "@livepeer-frameworks/player-core";
13
14
  import type { PlaybackMode, EndpointInfo } from "@livepeer-frameworks/player-core";
@@ -57,6 +58,7 @@ const PlayerInner: React.FC<PlayerProps> = ({
57
58
  setVolume,
58
59
  selectQuality,
59
60
  clearError,
61
+ dismissToast,
60
62
  retry,
61
63
  reload,
62
64
  jumpToLive,
@@ -122,6 +124,15 @@ const PlayerInner: React.FC<PlayerProps> = ({
122
124
  setSkipDirection(null);
123
125
  }, []);
124
126
 
127
+ // Auto-dismiss toast after 3 seconds
128
+ useEffect(() => {
129
+ if (!state.toast) return;
130
+ const timer = setTimeout(() => {
131
+ dismissToast();
132
+ }, 3000);
133
+ return () => clearTimeout(timer);
134
+ }, [state.toast, dismissToast]);
135
+
125
136
  // ============================================================================
126
137
  // Derived Values
127
138
  // ============================================================================
@@ -316,6 +327,34 @@ const PlayerInner: React.FC<PlayerProps> = ({
316
327
  </div>
317
328
  )}
318
329
 
330
+ {/* Toast notification */}
331
+ {state.toast && (
332
+ <div
333
+ className="absolute bottom-20 left-1/2 -translate-x-1/2 z-30 animate-in fade-in slide-in-from-bottom-2 duration-200"
334
+ role="status"
335
+ aria-live="polite"
336
+ >
337
+ <div className="flex items-center gap-2 rounded-lg border border-white/10 bg-black/80 px-4 py-2 text-sm text-white shadow-lg backdrop-blur-sm">
338
+ <span>{state.toast.message}</span>
339
+ <button
340
+ type="button"
341
+ onClick={dismissToast}
342
+ className="ml-2 text-white/60 hover:text-white"
343
+ aria-label="Dismiss"
344
+ >
345
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
346
+ <path
347
+ d="M9 3L3 9M3 3L9 9"
348
+ stroke="currentColor"
349
+ strokeWidth="1.5"
350
+ strokeLinecap="round"
351
+ />
352
+ </svg>
353
+ </button>
354
+ </div>
355
+ </div>
356
+ )}
357
+
319
358
  {/* Player controls */}
320
359
  {!useStockControls && (
321
360
  <PlayerControls
@@ -90,7 +90,7 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({
90
90
  currentTime,
91
91
  duration,
92
92
  isVisible = true,
93
- className,
93
+ className: _className,
94
94
  onSeek,
95
95
  mistStreamInfo,
96
96
  disabled = false,
@@ -509,22 +509,25 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({
509
509
  else setInternalVolume(Math.round(v.volume * 100));
510
510
  };
511
511
 
512
- const handleVolumeChange = (value: number[]) => {
513
- if (disabled) return;
514
- const next = Math.max(0, Math.min(100, value[0] ?? 0));
515
- // Prefer prop callback from usePlayerController
516
- if (onVolumeChange) {
517
- onVolumeChange(next / 100);
518
- return;
519
- }
520
- // Fallback: direct video manipulation
521
- const v = video ?? (document.querySelector(".fw-player-video") as HTMLVideoElement | null);
522
- if (!v) return;
523
- v.volume = next / 100;
524
- v.muted = next === 0;
525
- setInternalVolume(next);
526
- setInternalIsMuted(next === 0);
527
- };
512
+ const handleVolumeChange = useCallback(
513
+ (value: number[]) => {
514
+ if (disabled) return;
515
+ const next = Math.max(0, Math.min(100, value[0] ?? 0));
516
+ // Prefer prop callback from usePlayerController
517
+ if (onVolumeChange) {
518
+ onVolumeChange(next / 100);
519
+ return;
520
+ }
521
+ // Fallback: direct video manipulation
522
+ const v = video ?? (document.querySelector(".fw-player-video") as HTMLVideoElement | null);
523
+ if (!v) return;
524
+ v.volume = next / 100;
525
+ v.muted = next === 0;
526
+ setInternalVolume(next);
527
+ setInternalIsMuted(next === 0);
528
+ },
529
+ [disabled, onVolumeChange, video]
530
+ );
528
531
 
529
532
  const handleFullscreen = () => {
530
533
  if (disabled) return;
@@ -601,6 +604,21 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({
601
604
  const [isVolumeHovered, setIsVolumeHovered] = useState(false);
602
605
  const [isVolumeFocused, setIsVolumeFocused] = useState(false);
603
606
  const isVolumeExpanded = isVolumeHovered || isVolumeFocused;
607
+ const volumeGroupRef = useRef<HTMLDivElement>(null);
608
+
609
+ // Non-passive wheel listener for volume control
610
+ useEffect(() => {
611
+ const el = volumeGroupRef.current;
612
+ if (!el) return;
613
+ const handler = (e: WheelEvent) => {
614
+ if (disabled || !hasAudio) return;
615
+ e.preventDefault();
616
+ const delta = e.deltaY < 0 ? 5 : -5;
617
+ handleVolumeChange([actualVolume + delta]);
618
+ };
619
+ el.addEventListener("wheel", handler, { passive: false });
620
+ return () => el.removeEventListener("wheel", handler);
621
+ }, [disabled, hasAudio, actualVolume, handleVolumeChange]);
604
622
 
605
623
  return (
606
624
  <div
@@ -671,6 +689,7 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({
671
689
 
672
690
  {/* Volume pill - cohesive hover element (slab style) */}
673
691
  <div
692
+ ref={volumeGroupRef}
674
693
  className={cn(
675
694
  "fw-volume-group",
676
695
  isVolumeExpanded && "fw-volume-group--expanded",
@@ -1,4 +1,5 @@
1
- import React, { Component, ErrorInfo, ReactNode } from "react";
1
+ import type { ErrorInfo, ReactNode } from "react";
2
+ import React, { Component } from "react";
2
3
  import { Button } from "../ui/button";
3
4
 
4
5
  interface Props {
@@ -15,6 +15,7 @@ import {
15
15
  type PlaybackQuality,
16
16
  type ContentEndpoints,
17
17
  type ContentMetadata,
18
+ type ClassifiedError,
18
19
  } from "@livepeer-frameworks/player-core";
19
20
 
20
21
  // ============================================================================
@@ -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 */
@@ -105,6 +122,8 @@ export interface PlayerControllerState {
105
122
  textTracks: Array<{ id: string; label: string; language?: string; active: boolean }>;
106
123
  /** Stream info for player selection (sources + tracks) */
107
124
  streamInfo: StreamInfo | null;
125
+ /** Toast message to display (auto-dismisses) */
126
+ toast: { message: string; timestamp: number } | null;
108
127
  }
109
128
 
110
129
  export interface UsePlayerControllerReturn {
@@ -140,6 +159,8 @@ export interface UsePlayerControllerReturn {
140
159
  toggleSubtitles: () => void;
141
160
  /** Clear error */
142
161
  clearError: () => void;
162
+ /** Dismiss toast notification */
163
+ dismissToast: () => void;
143
164
  /** Retry playback */
144
165
  retry: () => Promise<void>;
145
166
  /** Reload player */
@@ -183,6 +204,7 @@ const initialState: PlayerControllerState = {
183
204
  isMuted: true,
184
205
  volume: 1,
185
206
  error: null,
207
+ errorDetails: null,
186
208
  isPassiveError: false,
187
209
  hasPlaybackStarted: false,
188
210
  isHoldingSpeed: false,
@@ -201,6 +223,7 @@ const initialState: PlayerControllerState = {
201
223
  qualities: [],
202
224
  textTracks: [],
203
225
  streamInfo: null,
226
+ toast: null,
204
227
  };
205
228
 
206
229
  // ============================================================================
@@ -214,6 +237,8 @@ export function usePlayerController(config: UsePlayerControllerConfig): UsePlaye
214
237
  onStreamStateChange,
215
238
  onError,
216
239
  onReady,
240
+ onProtocolSwapped,
241
+ onPlaybackFailed,
217
242
  ...controllerConfig
218
243
  } = config;
219
244
 
@@ -421,6 +446,31 @@ export function usePlayerController(config: UsePlayerControllerConfig): UsePlaye
421
446
  })
422
447
  );
423
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
+ );
473
+
424
474
  // Attach controller to container
425
475
  // Note: Video event listeners are set up in the 'ready' handler above
426
476
  controller.attach(container).catch((err) => {
@@ -488,7 +538,16 @@ export function usePlayerController(config: UsePlayerControllerConfig): UsePlaye
488
538
 
489
539
  const clearError = useCallback(() => {
490
540
  controllerRef.current?.clearError();
491
- setState((prev) => ({ ...prev, error: null, isPassiveError: false }));
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 }));
492
551
  }, []);
493
552
 
494
553
  const jumpToLive = useCallback(() => {
@@ -556,6 +615,7 @@ export function usePlayerController(config: UsePlayerControllerConfig): UsePlaye
556
615
  togglePiP,
557
616
  toggleSubtitles,
558
617
  clearError,
618
+ dismissToast,
559
619
  retry,
560
620
  reload,
561
621
  getQualities,
@@ -1,18 +0,0 @@
1
- /**
2
- * DASH.js Player - React Wrapper
3
- *
4
- * MPEG-DASH streaming via dash.js library.
5
- * The implementation is in @livepeer-frameworks/player-core.
6
- */
7
- import React from 'react';
8
- import { DashJsPlayerImpl } from '@livepeer-frameworks/player-core';
9
- export { DashJsPlayerImpl };
10
- type Props = {
11
- src: string;
12
- muted?: boolean;
13
- autoPlay?: boolean;
14
- controls?: boolean;
15
- onError?: (e: Error) => void;
16
- };
17
- declare const DashJsPlayer: React.FC<Props>;
18
- export default DashJsPlayer;
@@ -1,18 +0,0 @@
1
- /**
2
- * HLS.js Player - React Wrapper
3
- *
4
- * Adaptive HLS streaming via hls.js library.
5
- * The implementation is in @livepeer-frameworks/player-core.
6
- */
7
- import React from 'react';
8
- import { HlsJsPlayerImpl } from '@livepeer-frameworks/player-core';
9
- export { HlsJsPlayerImpl };
10
- type Props = {
11
- src: string;
12
- muted?: boolean;
13
- autoPlay?: boolean;
14
- controls?: boolean;
15
- onError?: (e: Error) => void;
16
- };
17
- declare const HlsJsPlayer: React.FC<Props>;
18
- export default HlsJsPlayer;
@@ -1,18 +0,0 @@
1
- /**
2
- * MEWS WebSocket Player - React Wrapper
3
- *
4
- * Low-latency WebSocket MP4 streaming using MediaSource Extensions.
5
- * The implementation is in @livepeer-frameworks/player-core.
6
- */
7
- import React from 'react';
8
- import { MewsWsPlayerImpl } from '@livepeer-frameworks/player-core';
9
- export { MewsWsPlayerImpl };
10
- type Props = {
11
- wsUrl: string;
12
- muted?: boolean;
13
- autoPlay?: boolean;
14
- controls?: boolean;
15
- onError?: (e: Error) => void;
16
- };
17
- declare const MewsWsPlayer: React.FC<Props>;
18
- export default MewsWsPlayer;
@@ -1,20 +0,0 @@
1
- /**
2
- * MistServer Legacy Player - React Wrapper
3
- *
4
- * Fallback player using MistServer's native player.js.
5
- * The implementation is in @livepeer-frameworks/player-core.
6
- */
7
- import React from 'react';
8
- import { MistPlayerImpl } from '@livepeer-frameworks/player-core';
9
- export { MistPlayerImpl };
10
- type Props = {
11
- src: string;
12
- streamName?: string;
13
- muted?: boolean;
14
- autoPlay?: boolean;
15
- controls?: boolean;
16
- devMode?: boolean;
17
- onError?: (e: Error) => void;
18
- };
19
- declare const MistPlayer: React.FC<Props>;
20
- export default MistPlayer;
@@ -1,20 +0,0 @@
1
- /**
2
- * MistWebRTC Player - React Wrapper
3
- *
4
- * MistServer native WebRTC with signaling for DVR support.
5
- * The implementation is in @livepeer-frameworks/player-core.
6
- */
7
- import React from 'react';
8
- import { MistWebRTCPlayerImpl } from '@livepeer-frameworks/player-core';
9
- export { MistWebRTCPlayerImpl };
10
- interface Props {
11
- src: string;
12
- autoPlay?: boolean;
13
- muted?: boolean;
14
- controls?: boolean;
15
- poster?: string;
16
- onReady?: (video: HTMLVideoElement) => void;
17
- onError?: (error: Error) => void;
18
- }
19
- export declare const MistWebRTCPlayer: React.FC<Props>;
20
- export default MistWebRTCPlayerImpl;
@@ -1,19 +0,0 @@
1
- /**
2
- * Native Player - React Wrapper
3
- *
4
- * HTML5 video and WHEP WebRTC playback.
5
- * The implementation is in @livepeer-frameworks/player-core.
6
- */
7
- import React from 'react';
8
- import { NativePlayerImpl, DirectPlaybackPlayerImpl } from '@livepeer-frameworks/player-core';
9
- export { NativePlayerImpl, DirectPlaybackPlayerImpl };
10
- type Props = {
11
- src: string;
12
- type?: string;
13
- muted?: boolean;
14
- autoPlay?: boolean;
15
- controls?: boolean;
16
- onError?: (e: Error) => void;
17
- };
18
- declare const NativePlayer: React.FC<Props>;
19
- export default NativePlayer;
@@ -1,18 +0,0 @@
1
- /**
2
- * Video.js Player - React Wrapper
3
- *
4
- * HLS streaming via Video.js with VHS (videojs-http-streaming).
5
- * The implementation is in @livepeer-frameworks/player-core.
6
- */
7
- import React from 'react';
8
- import { VideoJsPlayerImpl } from '@livepeer-frameworks/player-core';
9
- export { VideoJsPlayerImpl };
10
- type Props = {
11
- src: string;
12
- muted?: boolean;
13
- autoPlay?: boolean;
14
- controls?: boolean;
15
- onError?: (e: Error) => void;
16
- };
17
- declare const VideoJsPlayer: React.FC<Props>;
18
- export default VideoJsPlayer;
@@ -1,58 +0,0 @@
1
- /**
2
- * DASH.js Player - React Wrapper
3
- *
4
- * MPEG-DASH streaming via dash.js library.
5
- * The implementation is in @livepeer-frameworks/player-core.
6
- */
7
-
8
- import React, { useEffect, useRef } from "react";
9
- import { DashJsPlayerImpl } from "@livepeer-frameworks/player-core";
10
-
11
- // Re-export the implementation from core for backwards compatibility
12
- export { DashJsPlayerImpl };
13
-
14
- type Props = {
15
- src: string;
16
- muted?: boolean;
17
- autoPlay?: boolean;
18
- controls?: boolean;
19
- onError?: (e: Error) => void;
20
- };
21
-
22
- // React component wrapper
23
- const DashJsPlayer: React.FC<Props> = ({
24
- src,
25
- muted = true,
26
- autoPlay = true,
27
- controls = true,
28
- onError,
29
- }) => {
30
- const containerRef = useRef<HTMLDivElement>(null);
31
- const playerRef = useRef<DashJsPlayerImpl | null>(null);
32
-
33
- useEffect(() => {
34
- if (!containerRef.current) return;
35
-
36
- const player = new DashJsPlayerImpl();
37
- playerRef.current = player;
38
-
39
- player
40
- .initialize(
41
- containerRef.current,
42
- { url: src, type: "dash/video/mp4" },
43
- { autoplay: autoPlay, muted, controls }
44
- )
45
- .catch((e) => {
46
- onError?.(e instanceof Error ? e : new Error(String(e)));
47
- });
48
-
49
- return () => {
50
- player.destroy();
51
- playerRef.current = null;
52
- };
53
- }, [src, muted, autoPlay, controls, onError]);
54
-
55
- return <div ref={containerRef} style={{ width: "100%", height: "100%" }} />;
56
- };
57
-
58
- export default DashJsPlayer;
@@ -1,58 +0,0 @@
1
- /**
2
- * HLS.js Player - React Wrapper
3
- *
4
- * Adaptive HLS streaming via hls.js library.
5
- * The implementation is in @livepeer-frameworks/player-core.
6
- */
7
-
8
- import React, { useEffect, useRef } from "react";
9
- import { HlsJsPlayerImpl } from "@livepeer-frameworks/player-core";
10
-
11
- // Re-export the implementation from core for backwards compatibility
12
- export { HlsJsPlayerImpl };
13
-
14
- type Props = {
15
- src: string;
16
- muted?: boolean;
17
- autoPlay?: boolean;
18
- controls?: boolean;
19
- onError?: (e: Error) => void;
20
- };
21
-
22
- // React component wrapper
23
- const HlsJsPlayer: React.FC<Props> = ({
24
- src,
25
- muted = true,
26
- autoPlay = true,
27
- controls = true,
28
- onError,
29
- }) => {
30
- const containerRef = useRef<HTMLDivElement>(null);
31
- const playerRef = useRef<HlsJsPlayerImpl | null>(null);
32
-
33
- useEffect(() => {
34
- if (!containerRef.current) return;
35
-
36
- const player = new HlsJsPlayerImpl();
37
- playerRef.current = player;
38
-
39
- player
40
- .initialize(
41
- containerRef.current,
42
- { url: src, type: "html5/application/vnd.apple.mpegurl" },
43
- { autoplay: autoPlay, muted, controls }
44
- )
45
- .catch((e) => {
46
- onError?.(e instanceof Error ? e : new Error(String(e)));
47
- });
48
-
49
- return () => {
50
- player.destroy();
51
- playerRef.current = null;
52
- };
53
- }, [src, muted, autoPlay, controls, onError]);
54
-
55
- return <div ref={containerRef} style={{ width: "100%", height: "100%" }} />;
56
- };
57
-
58
- export default HlsJsPlayer;