@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.
- package/dist/cjs/_virtual/_rollupPluginBabelHelpers.js +359 -0
- package/dist/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
- package/dist/cjs/assets/logomark.svg.js +8 -0
- package/dist/cjs/assets/logomark.svg.js.map +1 -0
- package/dist/cjs/components/DevModePanel.js +826 -0
- package/dist/cjs/components/DevModePanel.js.map +1 -0
- package/dist/cjs/components/DvdLogo.js +200 -0
- package/dist/cjs/components/DvdLogo.js.map +1 -0
- package/dist/cjs/components/Icons.js +439 -0
- package/dist/cjs/components/Icons.js.map +1 -0
- package/dist/cjs/components/IdleScreen.js +587 -0
- package/dist/cjs/components/IdleScreen.js.map +1 -0
- package/dist/cjs/components/LoadingScreen.js +523 -0
- package/dist/cjs/components/LoadingScreen.js.map +1 -0
- package/dist/cjs/components/Player.js +420 -0
- package/dist/cjs/components/Player.js.map +1 -0
- package/dist/cjs/components/PlayerControls.js +798 -0
- package/dist/cjs/components/PlayerControls.js.map +1 -0
- package/dist/cjs/components/PlayerErrorBoundary.js +80 -0
- package/dist/cjs/components/PlayerErrorBoundary.js.map +1 -0
- package/dist/cjs/components/SeekBar.js +253 -0
- package/dist/cjs/components/SeekBar.js.map +1 -0
- package/dist/cjs/components/SkipIndicator.js +92 -0
- package/dist/cjs/components/SkipIndicator.js.map +1 -0
- package/dist/cjs/components/SpeedIndicator.js +43 -0
- package/dist/cjs/components/SpeedIndicator.js.map +1 -0
- package/dist/cjs/components/StatsPanel.js +202 -0
- package/dist/cjs/components/StatsPanel.js.map +1 -0
- package/dist/cjs/components/StreamStateOverlay.js +229 -0
- package/dist/cjs/components/StreamStateOverlay.js.map +1 -0
- package/dist/cjs/components/ThumbnailOverlay.js +86 -0
- package/dist/cjs/components/ThumbnailOverlay.js.map +1 -0
- package/dist/cjs/components/TitleOverlay.js +32 -0
- package/dist/cjs/components/TitleOverlay.js.map +1 -0
- package/dist/cjs/context/PlayerContext.js +46 -0
- package/dist/cjs/context/PlayerContext.js.map +1 -0
- package/dist/cjs/hooks/useMetaTrack.js +165 -0
- package/dist/cjs/hooks/useMetaTrack.js.map +1 -0
- package/dist/cjs/hooks/usePlaybackQuality.js +131 -0
- package/dist/cjs/hooks/usePlaybackQuality.js.map +1 -0
- package/dist/cjs/hooks/usePlayerController.js +518 -0
- package/dist/cjs/hooks/usePlayerController.js.map +1 -0
- package/dist/cjs/hooks/usePlayerSelection.js +90 -0
- package/dist/cjs/hooks/usePlayerSelection.js.map +1 -0
- package/dist/cjs/hooks/useStreamState.js +360 -0
- package/dist/cjs/hooks/useStreamState.js.map +1 -0
- package/dist/cjs/hooks/useTelemetry.js +120 -0
- package/dist/cjs/hooks/useTelemetry.js.map +1 -0
- package/dist/cjs/hooks/useViewerEndpoints.js +222 -0
- package/dist/cjs/hooks/useViewerEndpoints.js.map +1 -0
- package/dist/cjs/index.js +97 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/ui/badge.js +34 -0
- package/dist/cjs/ui/badge.js.map +1 -0
- package/dist/cjs/ui/button.js +74 -0
- package/dist/cjs/ui/button.js.map +1 -0
- package/dist/cjs/ui/context-menu.js +163 -0
- package/dist/cjs/ui/context-menu.js.map +1 -0
- package/dist/cjs/ui/slider.js +60 -0
- package/dist/cjs/ui/slider.js.map +1 -0
- package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +329 -0
- package/dist/esm/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
- package/dist/esm/assets/logomark.svg.js +4 -0
- package/dist/esm/assets/logomark.svg.js.map +1 -0
- package/dist/esm/components/DevModePanel.js +822 -0
- package/dist/esm/components/DevModePanel.js.map +1 -0
- package/dist/esm/components/DvdLogo.js +196 -0
- package/dist/esm/components/DvdLogo.js.map +1 -0
- package/dist/esm/components/Icons.js +421 -0
- package/dist/esm/components/Icons.js.map +1 -0
- package/dist/esm/components/IdleScreen.js +582 -0
- package/dist/esm/components/IdleScreen.js.map +1 -0
- package/dist/esm/components/LoadingScreen.js +519 -0
- package/dist/esm/components/LoadingScreen.js.map +1 -0
- package/dist/esm/components/Player.js +416 -0
- package/dist/esm/components/Player.js.map +1 -0
- package/dist/esm/components/PlayerControls.js +794 -0
- package/dist/esm/components/PlayerControls.js.map +1 -0
- package/dist/esm/components/PlayerErrorBoundary.js +76 -0
- package/dist/esm/components/PlayerErrorBoundary.js.map +1 -0
- package/dist/esm/components/SeekBar.js +249 -0
- package/dist/esm/components/SeekBar.js.map +1 -0
- package/dist/esm/components/SkipIndicator.js +88 -0
- package/dist/esm/components/SkipIndicator.js.map +1 -0
- package/dist/esm/components/SpeedIndicator.js +39 -0
- package/dist/esm/components/SpeedIndicator.js.map +1 -0
- package/dist/esm/components/StatsPanel.js +198 -0
- package/dist/esm/components/StatsPanel.js.map +1 -0
- package/dist/esm/components/StreamStateOverlay.js +224 -0
- package/dist/esm/components/StreamStateOverlay.js.map +1 -0
- package/dist/esm/components/ThumbnailOverlay.js +82 -0
- package/dist/esm/components/ThumbnailOverlay.js.map +1 -0
- package/dist/esm/components/TitleOverlay.js +28 -0
- package/dist/esm/components/TitleOverlay.js.map +1 -0
- package/dist/esm/context/PlayerContext.js +41 -0
- package/dist/esm/context/PlayerContext.js.map +1 -0
- package/dist/esm/hooks/useMetaTrack.js +163 -0
- package/dist/esm/hooks/useMetaTrack.js.map +1 -0
- package/dist/esm/hooks/usePlaybackQuality.js +129 -0
- package/dist/esm/hooks/usePlaybackQuality.js.map +1 -0
- package/dist/esm/hooks/usePlayerController.js +516 -0
- package/dist/esm/hooks/usePlayerController.js.map +1 -0
- package/dist/esm/hooks/usePlayerSelection.js +88 -0
- package/dist/esm/hooks/usePlayerSelection.js.map +1 -0
- package/dist/esm/hooks/useStreamState.js +358 -0
- package/dist/esm/hooks/useStreamState.js.map +1 -0
- package/dist/esm/hooks/useTelemetry.js +118 -0
- package/dist/esm/hooks/useTelemetry.js.map +1 -0
- package/dist/esm/hooks/useViewerEndpoints.js +220 -0
- package/dist/esm/hooks/useViewerEndpoints.js.map +1 -0
- package/dist/esm/index.js +23 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/ui/badge.js +31 -0
- package/dist/esm/ui/badge.js.map +1 -0
- package/dist/esm/ui/button.js +52 -0
- package/dist/esm/ui/button.js.map +1 -0
- package/dist/esm/ui/context-menu.js +132 -0
- package/dist/esm/ui/context-menu.js.map +1 -0
- package/dist/esm/ui/slider.js +38 -0
- package/dist/esm/ui/slider.js.map +1 -0
- package/dist/types/components/DvdLogo.d.ts +1 -1
- package/dist/types/components/Icons.d.ts +1 -1
- package/dist/types/components/Player.d.ts +1 -1
- package/dist/types/components/PlayerErrorBoundary.d.ts +2 -1
- package/dist/types/components/StreamStateOverlay.d.ts +2 -2
- package/dist/types/components/SubtitleRenderer.d.ts +2 -2
- package/dist/types/context/PlayerContext.d.ts +2 -2
- package/dist/types/context/index.d.ts +2 -2
- package/dist/types/hooks/useMetaTrack.d.ts +3 -3
- package/dist/types/hooks/usePlaybackQuality.d.ts +2 -2
- package/dist/types/hooks/usePlayerController.d.ts +26 -3
- package/dist/types/hooks/usePlayerSelection.d.ts +1 -1
- 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 +3 -3
- package/dist/types/index.d.ts +28 -28
- package/dist/types/types.d.ts +3 -3
- package/dist/types/ui/select.d.ts +1 -1
- package/package.json +22 -14
- package/src/components/DvdLogo.tsx +1 -1
- package/src/components/Player.tsx +42 -3
- package/src/components/PlayerControls.tsx +36 -17
- package/src/components/PlayerErrorBoundary.tsx +2 -1
- package/src/hooks/usePlayerController.ts +61 -1
- package/dist/types/components/players/DashJsPlayer.d.ts +0 -18
- package/dist/types/components/players/HlsJsPlayer.d.ts +0 -18
- package/dist/types/components/players/MewsWsPlayer/index.d.ts +0 -18
- package/dist/types/components/players/MistPlayer.d.ts +0 -20
- package/dist/types/components/players/MistWebRTCPlayer/index.d.ts +0 -20
- package/dist/types/components/players/NativePlayer.d.ts +0 -19
- package/dist/types/components/players/VideoJsPlayer.d.ts +0 -18
- package/src/components/players/DashJsPlayer.tsx +0 -58
- package/src/components/players/HlsJsPlayer.tsx +0 -58
- package/src/components/players/MewsWsPlayer/index.tsx +0 -58
- package/src/components/players/MistPlayer.tsx +0 -62
- package/src/components/players/MistWebRTCPlayer/index.tsx +0 -68
- package/src/components/players/NativePlayer.tsx +0 -56
- 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.
|
|
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
|
-
"
|
|
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
|
-
"@
|
|
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": "^
|
|
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.
|
|
59
|
+
"@types/react": "^19.2.13",
|
|
54
60
|
"@types/react-dom": "^19.1.7",
|
|
55
|
-
"lucide-react": "^0.
|
|
56
|
-
"react": "19.2.
|
|
57
|
-
"react-dom": "19.2.
|
|
58
|
-
"rollup": "^4.
|
|
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,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
|
|
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 = (
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
onVolumeChange
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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",
|
|
@@ -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) => ({
|
|
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;
|