@livepeer-frameworks/player-svelte 0.0.3
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/DevModePanel.svelte +650 -0
- package/dist/DevModePanel.svelte.d.ts +31 -0
- package/dist/DvdLogo.svelte +213 -0
- package/dist/DvdLogo.svelte.d.ts +7 -0
- package/dist/Icons.svelte +27 -0
- package/dist/Icons.svelte.d.ts +25 -0
- package/dist/IdleScreen.svelte +752 -0
- package/dist/IdleScreen.svelte.d.ts +11 -0
- package/dist/LoadingScreen.svelte +689 -0
- package/dist/LoadingScreen.svelte.d.ts +7 -0
- package/dist/Player.svelte +482 -0
- package/dist/Player.svelte.d.ts +26 -0
- package/dist/PlayerControls.svelte +739 -0
- package/dist/PlayerControls.svelte.d.ts +20 -0
- package/dist/SeekBar.svelte +274 -0
- package/dist/SeekBar.svelte.d.ts +25 -0
- package/dist/SkipIndicator.svelte +95 -0
- package/dist/SkipIndicator.svelte.d.ts +14 -0
- package/dist/SpeedIndicator.svelte +38 -0
- package/dist/SpeedIndicator.svelte.d.ts +8 -0
- package/dist/StatsPanel.svelte +155 -0
- package/dist/StatsPanel.svelte.d.ts +27 -0
- package/dist/StreamStateOverlay.svelte +266 -0
- package/dist/StreamStateOverlay.svelte.d.ts +18 -0
- package/dist/SubtitleRenderer.svelte +234 -0
- package/dist/SubtitleRenderer.svelte.d.ts +41 -0
- package/dist/ThumbnailOverlay.svelte +96 -0
- package/dist/ThumbnailOverlay.svelte.d.ts +11 -0
- package/dist/TitleOverlay.svelte +47 -0
- package/dist/TitleOverlay.svelte.d.ts +9 -0
- package/dist/assets/logomark.svg +56 -0
- package/dist/components/VolumeIcons.svelte +53 -0
- package/dist/components/VolumeIcons.svelte.d.ts +10 -0
- package/dist/global.d.ts +15 -0
- package/dist/icons/FullscreenExitIcon.svelte +33 -0
- package/dist/icons/FullscreenExitIcon.svelte.d.ts +8 -0
- package/dist/icons/FullscreenIcon.svelte +33 -0
- package/dist/icons/FullscreenIcon.svelte.d.ts +8 -0
- package/dist/icons/PauseIcon.svelte +28 -0
- package/dist/icons/PauseIcon.svelte.d.ts +8 -0
- package/dist/icons/PictureInPictureIcon.svelte +28 -0
- package/dist/icons/PictureInPictureIcon.svelte.d.ts +8 -0
- package/dist/icons/PlayIcon.svelte +27 -0
- package/dist/icons/PlayIcon.svelte.d.ts +8 -0
- package/dist/icons/SeekToLiveIcon.svelte +30 -0
- package/dist/icons/SeekToLiveIcon.svelte.d.ts +8 -0
- package/dist/icons/SettingsIcon.svelte +40 -0
- package/dist/icons/SettingsIcon.svelte.d.ts +8 -0
- package/dist/icons/SkipBackIcon.svelte +32 -0
- package/dist/icons/SkipBackIcon.svelte.d.ts +8 -0
- package/dist/icons/SkipForwardIcon.svelte +32 -0
- package/dist/icons/SkipForwardIcon.svelte.d.ts +8 -0
- package/dist/icons/StatsIcon.svelte +29 -0
- package/dist/icons/StatsIcon.svelte.d.ts +8 -0
- package/dist/icons/VolumeOffIcon.svelte +29 -0
- package/dist/icons/VolumeOffIcon.svelte.d.ts +8 -0
- package/dist/icons/VolumeUpIcon.svelte +34 -0
- package/dist/icons/VolumeUpIcon.svelte.d.ts +8 -0
- package/dist/icons/index.d.ts +17 -0
- package/dist/icons/index.js +17 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +54 -0
- package/dist/player.css +2 -0
- package/dist/stores/index.d.ts +15 -0
- package/dist/stores/index.js +21 -0
- package/dist/stores/playbackQuality.d.ts +43 -0
- package/dist/stores/playbackQuality.js +107 -0
- package/dist/stores/playerContext.d.ts +73 -0
- package/dist/stores/playerContext.js +166 -0
- package/dist/stores/playerController.d.ts +178 -0
- package/dist/stores/playerController.js +358 -0
- package/dist/stores/playerSelection.d.ts +84 -0
- package/dist/stores/playerSelection.js +159 -0
- package/dist/stores/streamState.d.ts +44 -0
- package/dist/stores/streamState.js +314 -0
- package/dist/stores/viewerEndpoints.d.ts +48 -0
- package/dist/stores/viewerEndpoints.js +178 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js +4 -0
- package/dist/ui/Badge.svelte +21 -0
- package/dist/ui/Badge.svelte.d.ts +32 -0
- package/dist/ui/Button.svelte +42 -0
- package/dist/ui/Button.svelte.d.ts +35 -0
- package/dist/ui/Slider.svelte +100 -0
- package/dist/ui/Slider.svelte.d.ts +17 -0
- package/dist/ui/badge.d.ts +6 -0
- package/dist/ui/badge.js +10 -0
- package/dist/ui/button.d.ts +8 -0
- package/dist/ui/button.js +21 -0
- package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte +34 -0
- package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte.d.ts +31 -0
- package/dist/ui/context-menu/ContextMenuContent.svelte +17 -0
- package/dist/ui/context-menu/ContextMenuContent.svelte.d.ts +7 -0
- package/dist/ui/context-menu/ContextMenuItem.svelte +22 -0
- package/dist/ui/context-menu/ContextMenuItem.svelte.d.ts +8 -0
- package/dist/ui/context-menu/ContextMenuLabel.svelte +22 -0
- package/dist/ui/context-menu/ContextMenuLabel.svelte.d.ts +8 -0
- package/dist/ui/context-menu/ContextMenuPortal.svelte +11 -0
- package/dist/ui/context-menu/ContextMenuPortal.svelte.d.ts +6 -0
- package/dist/ui/context-menu/ContextMenuRadioItem.svelte +21 -0
- package/dist/ui/context-menu/ContextMenuRadioItem.svelte.d.ts +31 -0
- package/dist/ui/context-menu/ContextMenuSeparator.svelte +14 -0
- package/dist/ui/context-menu/ContextMenuSeparator.svelte.d.ts +6 -0
- package/dist/ui/context-menu/ContextMenuShortcut.svelte +19 -0
- package/dist/ui/context-menu/ContextMenuShortcut.svelte.d.ts +7 -0
- package/dist/ui/context-menu/ContextMenuSubContent.svelte +20 -0
- package/dist/ui/context-menu/ContextMenuSubContent.svelte.d.ts +7 -0
- package/dist/ui/context-menu/ContextMenuSubTrigger.svelte +34 -0
- package/dist/ui/context-menu/ContextMenuSubTrigger.svelte.d.ts +8 -0
- package/dist/ui/context-menu/index.d.ts +17 -0
- package/dist/ui/context-menu/index.js +17 -0
- package/package.json +51 -0
- package/src/DevModePanel.svelte +650 -0
- package/src/DvdLogo.svelte +213 -0
- package/src/Icons.svelte +27 -0
- package/src/IdleScreen.svelte +739 -0
- package/src/LoadingScreen.svelte +674 -0
- package/src/Player.svelte +483 -0
- package/src/PlayerControls.svelte +752 -0
- package/src/SeekBar.svelte +274 -0
- package/src/SkipIndicator.svelte +95 -0
- package/src/SpeedIndicator.svelte +37 -0
- package/src/StatsPanel.svelte +155 -0
- package/src/StreamStateOverlay.svelte +266 -0
- package/src/SubtitleRenderer.svelte +234 -0
- package/src/ThumbnailOverlay.svelte +96 -0
- package/src/TitleOverlay.svelte +47 -0
- package/src/assets/logomark.svg +56 -0
- package/src/components/VolumeIcons.svelte +53 -0
- package/src/global.d.ts +15 -0
- package/src/icons/FullscreenExitIcon.svelte +33 -0
- package/src/icons/FullscreenIcon.svelte +33 -0
- package/src/icons/PauseIcon.svelte +28 -0
- package/src/icons/PictureInPictureIcon.svelte +28 -0
- package/src/icons/PlayIcon.svelte +27 -0
- package/src/icons/SeekToLiveIcon.svelte +30 -0
- package/src/icons/SettingsIcon.svelte +40 -0
- package/src/icons/SkipBackIcon.svelte +32 -0
- package/src/icons/SkipForwardIcon.svelte +32 -0
- package/src/icons/StatsIcon.svelte +29 -0
- package/src/icons/VolumeOffIcon.svelte +29 -0
- package/src/icons/VolumeUpIcon.svelte +34 -0
- package/src/icons/index.ts +18 -0
- package/src/index.ts +84 -0
- package/src/player.css +2 -0
- package/src/stores/index.ts +88 -0
- package/src/stores/playbackQuality.ts +137 -0
- package/src/stores/playerContext.ts +221 -0
- package/src/stores/playerController.ts +568 -0
- package/src/stores/playerSelection.ts +216 -0
- package/src/stores/streamState.ts +367 -0
- package/src/stores/viewerEndpoints.ts +224 -0
- package/src/types.ts +6 -0
- package/src/ui/Badge.svelte +21 -0
- package/src/ui/Button.svelte +42 -0
- package/src/ui/Slider.svelte +100 -0
- package/src/ui/badge.ts +20 -0
- package/src/ui/button.ts +35 -0
- package/src/ui/context-menu/ContextMenuCheckboxItem.svelte +34 -0
- package/src/ui/context-menu/ContextMenuContent.svelte +17 -0
- package/src/ui/context-menu/ContextMenuItem.svelte +22 -0
- package/src/ui/context-menu/ContextMenuLabel.svelte +22 -0
- package/src/ui/context-menu/ContextMenuPortal.svelte +11 -0
- package/src/ui/context-menu/ContextMenuRadioItem.svelte +21 -0
- package/src/ui/context-menu/ContextMenuSeparator.svelte +14 -0
- package/src/ui/context-menu/ContextMenuShortcut.svelte +19 -0
- package/src/ui/context-menu/ContextMenuSubContent.svelte +20 -0
- package/src/ui/context-menu/ContextMenuSubTrigger.svelte +34 -0
- package/src/ui/context-menu/index.ts +36 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte store for PlayerController - wraps the core PlayerController
|
|
3
|
+
* for declarative usage in Svelte 5 components.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { writable, derived, type Readable, type Writable } from 'svelte/store';
|
|
7
|
+
import {
|
|
8
|
+
PlayerController,
|
|
9
|
+
type PlayerControllerConfig,
|
|
10
|
+
type PlayerState,
|
|
11
|
+
type StreamState,
|
|
12
|
+
type StreamSource,
|
|
13
|
+
type PlaybackQuality,
|
|
14
|
+
type ContentEndpoints,
|
|
15
|
+
type ContentMetadata,
|
|
16
|
+
} from '@livepeer-frameworks/player-core';
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export interface PlayerControllerStoreConfig extends Omit<PlayerControllerConfig, 'playerManager'> {
|
|
23
|
+
/** Enable/disable the store */
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PlayerControllerState {
|
|
28
|
+
/** Current player state */
|
|
29
|
+
state: PlayerState;
|
|
30
|
+
/** Stream state (for live streams) */
|
|
31
|
+
streamState: StreamState | null;
|
|
32
|
+
/** Resolved endpoints */
|
|
33
|
+
endpoints: ContentEndpoints | null;
|
|
34
|
+
/** Content metadata */
|
|
35
|
+
metadata: ContentMetadata | null;
|
|
36
|
+
/** Video element (null if not ready) */
|
|
37
|
+
videoElement: HTMLVideoElement | null;
|
|
38
|
+
/** Current time */
|
|
39
|
+
currentTime: number;
|
|
40
|
+
/** Duration */
|
|
41
|
+
duration: number;
|
|
42
|
+
/** Is playing */
|
|
43
|
+
isPlaying: boolean;
|
|
44
|
+
/** Is paused */
|
|
45
|
+
isPaused: boolean;
|
|
46
|
+
/** Is buffering */
|
|
47
|
+
isBuffering: boolean;
|
|
48
|
+
/** Is muted */
|
|
49
|
+
isMuted: boolean;
|
|
50
|
+
/** Volume (0-1) */
|
|
51
|
+
volume: number;
|
|
52
|
+
/** Error text */
|
|
53
|
+
error: string | null;
|
|
54
|
+
/** Is passive error */
|
|
55
|
+
isPassiveError: boolean;
|
|
56
|
+
/** Has playback ever started */
|
|
57
|
+
hasPlaybackStarted: boolean;
|
|
58
|
+
/** Is holding speed (2x gesture) */
|
|
59
|
+
isHoldingSpeed: boolean;
|
|
60
|
+
/** Current hold speed */
|
|
61
|
+
holdSpeed: number;
|
|
62
|
+
/** Is hovering (controls visible) */
|
|
63
|
+
isHovering: boolean;
|
|
64
|
+
/** Should show controls */
|
|
65
|
+
shouldShowControls: boolean;
|
|
66
|
+
/** Is loop enabled */
|
|
67
|
+
isLoopEnabled: boolean;
|
|
68
|
+
/** Is fullscreen */
|
|
69
|
+
isFullscreen: boolean;
|
|
70
|
+
/** Is PiP active */
|
|
71
|
+
isPiPActive: boolean;
|
|
72
|
+
/** Is effectively live (live or DVR recording) */
|
|
73
|
+
isEffectivelyLive: boolean;
|
|
74
|
+
/** Should show idle screen */
|
|
75
|
+
shouldShowIdleScreen: boolean;
|
|
76
|
+
/** Current player info */
|
|
77
|
+
currentPlayerInfo: { name: string; shortname: string } | null;
|
|
78
|
+
/** Current source info */
|
|
79
|
+
currentSourceInfo: { url: string; type: string } | null;
|
|
80
|
+
/** Playback quality metrics */
|
|
81
|
+
playbackQuality: PlaybackQuality | null;
|
|
82
|
+
/** Subtitles enabled */
|
|
83
|
+
subtitlesEnabled: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface PlayerControllerStore extends Readable<PlayerControllerState> {
|
|
87
|
+
/** Get controller instance */
|
|
88
|
+
getController: () => PlayerController | null;
|
|
89
|
+
/** Attach to a container element */
|
|
90
|
+
attach: (container: HTMLElement) => Promise<void>;
|
|
91
|
+
/** Detach from container */
|
|
92
|
+
detach: () => void;
|
|
93
|
+
/** Destroy the store and controller */
|
|
94
|
+
destroy: () => void;
|
|
95
|
+
/** Play */
|
|
96
|
+
play: () => Promise<void>;
|
|
97
|
+
/** Pause */
|
|
98
|
+
pause: () => void;
|
|
99
|
+
/** Toggle play/pause */
|
|
100
|
+
togglePlay: () => void;
|
|
101
|
+
/** Seek to time */
|
|
102
|
+
seek: (time: number) => void;
|
|
103
|
+
/** Seek by delta */
|
|
104
|
+
seekBy: (delta: number) => void;
|
|
105
|
+
/** Set volume */
|
|
106
|
+
setVolume: (volume: number) => void;
|
|
107
|
+
/** Toggle mute */
|
|
108
|
+
toggleMute: () => void;
|
|
109
|
+
/** Toggle loop */
|
|
110
|
+
toggleLoop: () => void;
|
|
111
|
+
/** Toggle fullscreen */
|
|
112
|
+
toggleFullscreen: () => Promise<void>;
|
|
113
|
+
/** Toggle PiP */
|
|
114
|
+
togglePiP: () => Promise<void>;
|
|
115
|
+
/** Toggle subtitles */
|
|
116
|
+
toggleSubtitles: () => void;
|
|
117
|
+
/** Clear error */
|
|
118
|
+
clearError: () => void;
|
|
119
|
+
/** Retry playback */
|
|
120
|
+
retry: () => Promise<void>;
|
|
121
|
+
/** Reload player */
|
|
122
|
+
reload: () => Promise<void>;
|
|
123
|
+
/** Get qualities */
|
|
124
|
+
getQualities: () => Array<{ id: string; label: string; bitrate?: number }>;
|
|
125
|
+
/** Select quality */
|
|
126
|
+
selectQuality: (id: string) => void;
|
|
127
|
+
/** Handle mouse enter (for controls visibility) */
|
|
128
|
+
handleMouseEnter: () => void;
|
|
129
|
+
/** Handle mouse leave (for controls visibility) */
|
|
130
|
+
handleMouseLeave: () => void;
|
|
131
|
+
/** Handle mouse move (for controls visibility) */
|
|
132
|
+
handleMouseMove: () => void;
|
|
133
|
+
/** Handle touch start (for controls visibility) */
|
|
134
|
+
handleTouchStart: () => void;
|
|
135
|
+
/** Set dev mode options (force player, type, source, playback mode) */
|
|
136
|
+
setDevModeOptions: (options: {
|
|
137
|
+
forcePlayer?: string;
|
|
138
|
+
forceType?: string;
|
|
139
|
+
forceSource?: number;
|
|
140
|
+
playbackMode?: 'auto' | 'low-latency' | 'quality' | 'vod';
|
|
141
|
+
}) => Promise<void>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// Initial State
|
|
146
|
+
// ============================================================================
|
|
147
|
+
|
|
148
|
+
const initialState: PlayerControllerState = {
|
|
149
|
+
state: 'booting',
|
|
150
|
+
streamState: null,
|
|
151
|
+
endpoints: null,
|
|
152
|
+
metadata: null,
|
|
153
|
+
videoElement: null,
|
|
154
|
+
currentTime: 0,
|
|
155
|
+
duration: NaN,
|
|
156
|
+
isPlaying: false,
|
|
157
|
+
isPaused: true,
|
|
158
|
+
isBuffering: false,
|
|
159
|
+
isMuted: true,
|
|
160
|
+
volume: 1,
|
|
161
|
+
error: null,
|
|
162
|
+
isPassiveError: false,
|
|
163
|
+
hasPlaybackStarted: false,
|
|
164
|
+
isHoldingSpeed: false,
|
|
165
|
+
holdSpeed: 2,
|
|
166
|
+
isHovering: false,
|
|
167
|
+
shouldShowControls: false,
|
|
168
|
+
isLoopEnabled: false,
|
|
169
|
+
isFullscreen: false,
|
|
170
|
+
isPiPActive: false,
|
|
171
|
+
isEffectivelyLive: false,
|
|
172
|
+
shouldShowIdleScreen: true,
|
|
173
|
+
currentPlayerInfo: null,
|
|
174
|
+
currentSourceInfo: null,
|
|
175
|
+
playbackQuality: null,
|
|
176
|
+
subtitlesEnabled: false,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// ============================================================================
|
|
180
|
+
// Store Factory
|
|
181
|
+
// ============================================================================
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Create a PlayerController store for managing player lifecycle.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```svelte
|
|
188
|
+
* <script>
|
|
189
|
+
* import { createPlayerControllerStore } from './stores/playerController';
|
|
190
|
+
* import { onMount, onDestroy } from 'svelte';
|
|
191
|
+
*
|
|
192
|
+
* let containerEl: HTMLElement;
|
|
193
|
+
*
|
|
194
|
+
* const playerStore = createPlayerControllerStore({
|
|
195
|
+
* contentId: 'my-stream',
|
|
196
|
+
* contentType: 'live',
|
|
197
|
+
* gatewayUrl: 'https://gateway.example.com/graphql',
|
|
198
|
+
* });
|
|
199
|
+
*
|
|
200
|
+
* onMount(() => {
|
|
201
|
+
* playerStore.attach(containerEl);
|
|
202
|
+
* });
|
|
203
|
+
*
|
|
204
|
+
* onDestroy(() => {
|
|
205
|
+
* playerStore.destroy();
|
|
206
|
+
* });
|
|
207
|
+
*
|
|
208
|
+
* // Access state reactively
|
|
209
|
+
* $: isPlaying = $playerStore.isPlaying;
|
|
210
|
+
* $: currentTime = $playerStore.currentTime;
|
|
211
|
+
* </script>
|
|
212
|
+
*
|
|
213
|
+
* <div bind:this={containerEl}></div>
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
export function createPlayerControllerStore(
|
|
217
|
+
config: PlayerControllerStoreConfig
|
|
218
|
+
): PlayerControllerStore {
|
|
219
|
+
const { enabled = true, ...controllerConfig } = config;
|
|
220
|
+
|
|
221
|
+
// Internal state
|
|
222
|
+
const store = writable<PlayerControllerState>(initialState);
|
|
223
|
+
let controller: PlayerController | null = null;
|
|
224
|
+
let unsubscribers: Array<() => void> = [];
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Sync state from controller to store
|
|
228
|
+
*/
|
|
229
|
+
function syncState() {
|
|
230
|
+
if (!controller) return;
|
|
231
|
+
|
|
232
|
+
store.update(prev => ({
|
|
233
|
+
...prev,
|
|
234
|
+
isPlaying: controller!.isPlaying(),
|
|
235
|
+
isPaused: controller!.isPaused(),
|
|
236
|
+
isBuffering: controller!.isBuffering(),
|
|
237
|
+
isMuted: controller!.isMuted(),
|
|
238
|
+
volume: controller!.getVolume(),
|
|
239
|
+
hasPlaybackStarted: controller!.hasPlaybackStarted(),
|
|
240
|
+
shouldShowControls: controller!.shouldShowControls(),
|
|
241
|
+
shouldShowIdleScreen: controller!.shouldShowIdleScreen(),
|
|
242
|
+
playbackQuality: controller!.getPlaybackQuality(),
|
|
243
|
+
isLoopEnabled: controller!.isLoopEnabled(),
|
|
244
|
+
subtitlesEnabled: controller!.isSubtitlesEnabled(),
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Attach to a container element
|
|
250
|
+
*/
|
|
251
|
+
async function attach(container: HTMLElement): Promise<void> {
|
|
252
|
+
if (!enabled) return;
|
|
253
|
+
|
|
254
|
+
// Clean up existing controller
|
|
255
|
+
if (controller) {
|
|
256
|
+
unsubscribers.forEach(fn => fn());
|
|
257
|
+
unsubscribers = [];
|
|
258
|
+
controller.destroy();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Create new controller
|
|
262
|
+
controller = new PlayerController(controllerConfig);
|
|
263
|
+
|
|
264
|
+
// Subscribe to events
|
|
265
|
+
unsubscribers.push(controller.on('stateChange', ({ state }) => {
|
|
266
|
+
store.update(prev => ({ ...prev, state }));
|
|
267
|
+
}));
|
|
268
|
+
|
|
269
|
+
unsubscribers.push(controller.on('streamStateChange', ({ state: streamState }) => {
|
|
270
|
+
store.update(prev => ({
|
|
271
|
+
...prev,
|
|
272
|
+
streamState,
|
|
273
|
+
isEffectivelyLive: controller!.isEffectivelyLive(),
|
|
274
|
+
shouldShowIdleScreen: controller!.shouldShowIdleScreen(),
|
|
275
|
+
}));
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
unsubscribers.push(controller.on('timeUpdate', ({ currentTime, duration }) => {
|
|
279
|
+
store.update(prev => ({ ...prev, currentTime, duration }));
|
|
280
|
+
}));
|
|
281
|
+
|
|
282
|
+
unsubscribers.push(controller.on('error', ({ error }) => {
|
|
283
|
+
store.update(prev => ({
|
|
284
|
+
...prev,
|
|
285
|
+
error,
|
|
286
|
+
isPassiveError: controller!.isPassiveError(),
|
|
287
|
+
}));
|
|
288
|
+
}));
|
|
289
|
+
|
|
290
|
+
unsubscribers.push(controller.on('errorCleared', () => {
|
|
291
|
+
store.update(prev => ({ ...prev, error: null, isPassiveError: false }));
|
|
292
|
+
}));
|
|
293
|
+
|
|
294
|
+
unsubscribers.push(controller.on('ready', ({ videoElement }) => {
|
|
295
|
+
store.update(prev => ({
|
|
296
|
+
...prev,
|
|
297
|
+
videoElement,
|
|
298
|
+
endpoints: controller!.getEndpoints(),
|
|
299
|
+
metadata: controller!.getMetadata(),
|
|
300
|
+
isEffectivelyLive: controller!.isEffectivelyLive(),
|
|
301
|
+
shouldShowIdleScreen: controller!.shouldShowIdleScreen(),
|
|
302
|
+
currentPlayerInfo: controller!.getCurrentPlayerInfo(),
|
|
303
|
+
currentSourceInfo: controller!.getCurrentSourceInfo(),
|
|
304
|
+
}));
|
|
305
|
+
|
|
306
|
+
// Add video event listeners for state sync
|
|
307
|
+
const video = videoElement;
|
|
308
|
+
const handleVideoEvent = () => {
|
|
309
|
+
if (controller?.shouldSuppressVideoEvents?.()) return;
|
|
310
|
+
syncState();
|
|
311
|
+
};
|
|
312
|
+
video.addEventListener('play', handleVideoEvent);
|
|
313
|
+
video.addEventListener('pause', handleVideoEvent);
|
|
314
|
+
video.addEventListener('waiting', handleVideoEvent);
|
|
315
|
+
video.addEventListener('playing', handleVideoEvent);
|
|
316
|
+
unsubscribers.push(() => {
|
|
317
|
+
video.removeEventListener('play', handleVideoEvent);
|
|
318
|
+
video.removeEventListener('pause', handleVideoEvent);
|
|
319
|
+
video.removeEventListener('waiting', handleVideoEvent);
|
|
320
|
+
video.removeEventListener('playing', handleVideoEvent);
|
|
321
|
+
});
|
|
322
|
+
}));
|
|
323
|
+
|
|
324
|
+
unsubscribers.push(controller.on('playerSelected', ({ player, source }) => {
|
|
325
|
+
store.update(prev => ({
|
|
326
|
+
...prev,
|
|
327
|
+
currentPlayerInfo: controller!.getCurrentPlayerInfo(),
|
|
328
|
+
currentSourceInfo: { url: source.url, type: source.type },
|
|
329
|
+
}));
|
|
330
|
+
}));
|
|
331
|
+
|
|
332
|
+
unsubscribers.push(controller.on('volumeChange', ({ volume, muted }) => {
|
|
333
|
+
store.update(prev => ({ ...prev, volume, isMuted: muted }));
|
|
334
|
+
}));
|
|
335
|
+
|
|
336
|
+
unsubscribers.push(controller.on('loopChange', ({ isLoopEnabled }) => {
|
|
337
|
+
store.update(prev => ({ ...prev, isLoopEnabled }));
|
|
338
|
+
}));
|
|
339
|
+
|
|
340
|
+
unsubscribers.push(controller.on('fullscreenChange', ({ isFullscreen }) => {
|
|
341
|
+
store.update(prev => ({ ...prev, isFullscreen }));
|
|
342
|
+
}));
|
|
343
|
+
|
|
344
|
+
unsubscribers.push(controller.on('pipChange', ({ isPiP }) => {
|
|
345
|
+
store.update(prev => ({ ...prev, isPiPActive: isPiP }));
|
|
346
|
+
}));
|
|
347
|
+
|
|
348
|
+
unsubscribers.push(controller.on('holdSpeedStart', ({ speed }) => {
|
|
349
|
+
store.update(prev => ({ ...prev, isHoldingSpeed: true, holdSpeed: speed }));
|
|
350
|
+
}));
|
|
351
|
+
|
|
352
|
+
unsubscribers.push(controller.on('holdSpeedEnd', () => {
|
|
353
|
+
store.update(prev => ({ ...prev, isHoldingSpeed: false }));
|
|
354
|
+
}));
|
|
355
|
+
|
|
356
|
+
unsubscribers.push(controller.on('hoverStart', () => {
|
|
357
|
+
store.update(prev => ({ ...prev, isHovering: true, shouldShowControls: true }));
|
|
358
|
+
}));
|
|
359
|
+
|
|
360
|
+
unsubscribers.push(controller.on('hoverEnd', () => {
|
|
361
|
+
store.update(prev => ({
|
|
362
|
+
...prev,
|
|
363
|
+
isHovering: false,
|
|
364
|
+
shouldShowControls: controller!.shouldShowControls(),
|
|
365
|
+
}));
|
|
366
|
+
}));
|
|
367
|
+
|
|
368
|
+
unsubscribers.push(controller.on('captionsChange', ({ enabled }) => {
|
|
369
|
+
store.update(prev => ({ ...prev, subtitlesEnabled: enabled }));
|
|
370
|
+
}));
|
|
371
|
+
|
|
372
|
+
// Set initial loop state
|
|
373
|
+
store.update(prev => ({
|
|
374
|
+
...prev,
|
|
375
|
+
isLoopEnabled: controller!.isLoopEnabled(),
|
|
376
|
+
}));
|
|
377
|
+
|
|
378
|
+
// Attach controller to container
|
|
379
|
+
await controller.attach(container);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Detach from container
|
|
384
|
+
*/
|
|
385
|
+
function detach(): void {
|
|
386
|
+
if (controller) {
|
|
387
|
+
controller.detach();
|
|
388
|
+
}
|
|
389
|
+
store.set(initialState);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Destroy the store and controller
|
|
394
|
+
*/
|
|
395
|
+
function destroy(): void {
|
|
396
|
+
unsubscribers.forEach(fn => fn());
|
|
397
|
+
unsubscribers = [];
|
|
398
|
+
|
|
399
|
+
if (controller) {
|
|
400
|
+
controller.destroy();
|
|
401
|
+
controller = null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
store.set(initialState);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Action methods
|
|
408
|
+
async function play(): Promise<void> {
|
|
409
|
+
await controller?.play();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function pause(): void {
|
|
413
|
+
controller?.pause();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function togglePlay(): void {
|
|
417
|
+
controller?.togglePlay();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function seek(time: number): void {
|
|
421
|
+
controller?.seek(time);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function seekBy(delta: number): void {
|
|
425
|
+
controller?.seekBy(delta);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function setVolume(volume: number): void {
|
|
429
|
+
controller?.setVolume(volume);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function toggleMute(): void {
|
|
433
|
+
controller?.toggleMute();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function toggleLoop(): void {
|
|
437
|
+
controller?.toggleLoop();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function toggleFullscreen(): Promise<void> {
|
|
441
|
+
await controller?.toggleFullscreen();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function togglePiP(): Promise<void> {
|
|
445
|
+
await controller?.togglePictureInPicture();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function toggleSubtitles(): void {
|
|
449
|
+
controller?.toggleSubtitles();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function clearError(): void {
|
|
453
|
+
controller?.clearError();
|
|
454
|
+
store.update(prev => ({ ...prev, error: null, isPassiveError: false }));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async function retry(): Promise<void> {
|
|
458
|
+
await controller?.retry();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function reload(): Promise<void> {
|
|
462
|
+
await controller?.reload();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function getQualities() {
|
|
466
|
+
return controller?.getQualities() ?? [];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function selectQuality(id: string): void {
|
|
470
|
+
controller?.selectQuality(id);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function handleMouseEnter(): void {
|
|
474
|
+
controller?.handleMouseEnter();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function handleMouseLeave(): void {
|
|
478
|
+
controller?.handleMouseLeave();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function handleMouseMove(): void {
|
|
482
|
+
controller?.handleMouseMove();
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function handleTouchStart(): void {
|
|
486
|
+
controller?.handleTouchStart();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function setDevModeOptions(options: {
|
|
490
|
+
forcePlayer?: string;
|
|
491
|
+
forceType?: string;
|
|
492
|
+
forceSource?: number;
|
|
493
|
+
playbackMode?: 'auto' | 'low-latency' | 'quality' | 'vod';
|
|
494
|
+
}): Promise<void> {
|
|
495
|
+
await controller?.setDevModeOptions(options);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function getController(): PlayerController | null {
|
|
499
|
+
return controller;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
subscribe: store.subscribe,
|
|
504
|
+
getController,
|
|
505
|
+
attach,
|
|
506
|
+
detach,
|
|
507
|
+
destroy,
|
|
508
|
+
play,
|
|
509
|
+
pause,
|
|
510
|
+
togglePlay,
|
|
511
|
+
seek,
|
|
512
|
+
seekBy,
|
|
513
|
+
setVolume,
|
|
514
|
+
toggleMute,
|
|
515
|
+
toggleLoop,
|
|
516
|
+
toggleFullscreen,
|
|
517
|
+
togglePiP,
|
|
518
|
+
toggleSubtitles,
|
|
519
|
+
clearError,
|
|
520
|
+
retry,
|
|
521
|
+
reload,
|
|
522
|
+
getQualities,
|
|
523
|
+
selectQuality,
|
|
524
|
+
handleMouseEnter,
|
|
525
|
+
handleMouseLeave,
|
|
526
|
+
handleMouseMove,
|
|
527
|
+
handleTouchStart,
|
|
528
|
+
setDevModeOptions,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ============================================================================
|
|
533
|
+
// Derived Stores (convenience)
|
|
534
|
+
// ============================================================================
|
|
535
|
+
|
|
536
|
+
export function createDerivedState(store: PlayerControllerStore) {
|
|
537
|
+
return derived(store, $state => $state.state);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export function createDerivedIsPlaying(store: PlayerControllerStore) {
|
|
541
|
+
return derived(store, $state => $state.isPlaying);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
export function createDerivedCurrentTime(store: PlayerControllerStore) {
|
|
545
|
+
return derived(store, $state => $state.currentTime);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function createDerivedDuration(store: PlayerControllerStore) {
|
|
549
|
+
return derived(store, $state => $state.duration);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function createDerivedError(store: PlayerControllerStore) {
|
|
553
|
+
return derived(store, $state => $state.error);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export function createDerivedVideoElement(store: PlayerControllerStore) {
|
|
557
|
+
return derived(store, $state => $state.videoElement);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export function createDerivedShouldShowControls(store: PlayerControllerStore) {
|
|
561
|
+
return derived(store, $state => $state.shouldShowControls);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export function createDerivedShouldShowIdleScreen(store: PlayerControllerStore) {
|
|
565
|
+
return derived(store, $state => $state.shouldShowIdleScreen);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export default createPlayerControllerStore;
|