@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,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte store for PlayerController - wraps the core PlayerController
|
|
3
|
+
* for declarative usage in Svelte 5 components.
|
|
4
|
+
*/
|
|
5
|
+
import { type Readable } from 'svelte/store';
|
|
6
|
+
import { PlayerController, type PlayerControllerConfig, type PlayerState, type StreamState, type PlaybackQuality, type ContentEndpoints, type ContentMetadata } from '@livepeer-frameworks/player-core';
|
|
7
|
+
export interface PlayerControllerStoreConfig extends Omit<PlayerControllerConfig, 'playerManager'> {
|
|
8
|
+
/** Enable/disable the store */
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface PlayerControllerState {
|
|
12
|
+
/** Current player state */
|
|
13
|
+
state: PlayerState;
|
|
14
|
+
/** Stream state (for live streams) */
|
|
15
|
+
streamState: StreamState | null;
|
|
16
|
+
/** Resolved endpoints */
|
|
17
|
+
endpoints: ContentEndpoints | null;
|
|
18
|
+
/** Content metadata */
|
|
19
|
+
metadata: ContentMetadata | null;
|
|
20
|
+
/** Video element (null if not ready) */
|
|
21
|
+
videoElement: HTMLVideoElement | null;
|
|
22
|
+
/** Current time */
|
|
23
|
+
currentTime: number;
|
|
24
|
+
/** Duration */
|
|
25
|
+
duration: number;
|
|
26
|
+
/** Is playing */
|
|
27
|
+
isPlaying: boolean;
|
|
28
|
+
/** Is paused */
|
|
29
|
+
isPaused: boolean;
|
|
30
|
+
/** Is buffering */
|
|
31
|
+
isBuffering: boolean;
|
|
32
|
+
/** Is muted */
|
|
33
|
+
isMuted: boolean;
|
|
34
|
+
/** Volume (0-1) */
|
|
35
|
+
volume: number;
|
|
36
|
+
/** Error text */
|
|
37
|
+
error: string | null;
|
|
38
|
+
/** Is passive error */
|
|
39
|
+
isPassiveError: boolean;
|
|
40
|
+
/** Has playback ever started */
|
|
41
|
+
hasPlaybackStarted: boolean;
|
|
42
|
+
/** Is holding speed (2x gesture) */
|
|
43
|
+
isHoldingSpeed: boolean;
|
|
44
|
+
/** Current hold speed */
|
|
45
|
+
holdSpeed: number;
|
|
46
|
+
/** Is hovering (controls visible) */
|
|
47
|
+
isHovering: boolean;
|
|
48
|
+
/** Should show controls */
|
|
49
|
+
shouldShowControls: boolean;
|
|
50
|
+
/** Is loop enabled */
|
|
51
|
+
isLoopEnabled: boolean;
|
|
52
|
+
/** Is fullscreen */
|
|
53
|
+
isFullscreen: boolean;
|
|
54
|
+
/** Is PiP active */
|
|
55
|
+
isPiPActive: boolean;
|
|
56
|
+
/** Is effectively live (live or DVR recording) */
|
|
57
|
+
isEffectivelyLive: boolean;
|
|
58
|
+
/** Should show idle screen */
|
|
59
|
+
shouldShowIdleScreen: boolean;
|
|
60
|
+
/** Current player info */
|
|
61
|
+
currentPlayerInfo: {
|
|
62
|
+
name: string;
|
|
63
|
+
shortname: string;
|
|
64
|
+
} | null;
|
|
65
|
+
/** Current source info */
|
|
66
|
+
currentSourceInfo: {
|
|
67
|
+
url: string;
|
|
68
|
+
type: string;
|
|
69
|
+
} | null;
|
|
70
|
+
/** Playback quality metrics */
|
|
71
|
+
playbackQuality: PlaybackQuality | null;
|
|
72
|
+
/** Subtitles enabled */
|
|
73
|
+
subtitlesEnabled: boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface PlayerControllerStore extends Readable<PlayerControllerState> {
|
|
76
|
+
/** Get controller instance */
|
|
77
|
+
getController: () => PlayerController | null;
|
|
78
|
+
/** Attach to a container element */
|
|
79
|
+
attach: (container: HTMLElement) => Promise<void>;
|
|
80
|
+
/** Detach from container */
|
|
81
|
+
detach: () => void;
|
|
82
|
+
/** Destroy the store and controller */
|
|
83
|
+
destroy: () => void;
|
|
84
|
+
/** Play */
|
|
85
|
+
play: () => Promise<void>;
|
|
86
|
+
/** Pause */
|
|
87
|
+
pause: () => void;
|
|
88
|
+
/** Toggle play/pause */
|
|
89
|
+
togglePlay: () => void;
|
|
90
|
+
/** Seek to time */
|
|
91
|
+
seek: (time: number) => void;
|
|
92
|
+
/** Seek by delta */
|
|
93
|
+
seekBy: (delta: number) => void;
|
|
94
|
+
/** Set volume */
|
|
95
|
+
setVolume: (volume: number) => void;
|
|
96
|
+
/** Toggle mute */
|
|
97
|
+
toggleMute: () => void;
|
|
98
|
+
/** Toggle loop */
|
|
99
|
+
toggleLoop: () => void;
|
|
100
|
+
/** Toggle fullscreen */
|
|
101
|
+
toggleFullscreen: () => Promise<void>;
|
|
102
|
+
/** Toggle PiP */
|
|
103
|
+
togglePiP: () => Promise<void>;
|
|
104
|
+
/** Toggle subtitles */
|
|
105
|
+
toggleSubtitles: () => void;
|
|
106
|
+
/** Clear error */
|
|
107
|
+
clearError: () => void;
|
|
108
|
+
/** Retry playback */
|
|
109
|
+
retry: () => Promise<void>;
|
|
110
|
+
/** Reload player */
|
|
111
|
+
reload: () => Promise<void>;
|
|
112
|
+
/** Get qualities */
|
|
113
|
+
getQualities: () => Array<{
|
|
114
|
+
id: string;
|
|
115
|
+
label: string;
|
|
116
|
+
bitrate?: number;
|
|
117
|
+
}>;
|
|
118
|
+
/** Select quality */
|
|
119
|
+
selectQuality: (id: string) => void;
|
|
120
|
+
/** Handle mouse enter (for controls visibility) */
|
|
121
|
+
handleMouseEnter: () => void;
|
|
122
|
+
/** Handle mouse leave (for controls visibility) */
|
|
123
|
+
handleMouseLeave: () => void;
|
|
124
|
+
/** Handle mouse move (for controls visibility) */
|
|
125
|
+
handleMouseMove: () => void;
|
|
126
|
+
/** Handle touch start (for controls visibility) */
|
|
127
|
+
handleTouchStart: () => void;
|
|
128
|
+
/** Set dev mode options (force player, type, source, playback mode) */
|
|
129
|
+
setDevModeOptions: (options: {
|
|
130
|
+
forcePlayer?: string;
|
|
131
|
+
forceType?: string;
|
|
132
|
+
forceSource?: number;
|
|
133
|
+
playbackMode?: 'auto' | 'low-latency' | 'quality' | 'vod';
|
|
134
|
+
}) => Promise<void>;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Create a PlayerController store for managing player lifecycle.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```svelte
|
|
141
|
+
* <script>
|
|
142
|
+
* import { createPlayerControllerStore } from './stores/playerController';
|
|
143
|
+
* import { onMount, onDestroy } from 'svelte';
|
|
144
|
+
*
|
|
145
|
+
* let containerEl: HTMLElement;
|
|
146
|
+
*
|
|
147
|
+
* const playerStore = createPlayerControllerStore({
|
|
148
|
+
* contentId: 'my-stream',
|
|
149
|
+
* contentType: 'live',
|
|
150
|
+
* gatewayUrl: 'https://gateway.example.com/graphql',
|
|
151
|
+
* });
|
|
152
|
+
*
|
|
153
|
+
* onMount(() => {
|
|
154
|
+
* playerStore.attach(containerEl);
|
|
155
|
+
* });
|
|
156
|
+
*
|
|
157
|
+
* onDestroy(() => {
|
|
158
|
+
* playerStore.destroy();
|
|
159
|
+
* });
|
|
160
|
+
*
|
|
161
|
+
* // Access state reactively
|
|
162
|
+
* $: isPlaying = $playerStore.isPlaying;
|
|
163
|
+
* $: currentTime = $playerStore.currentTime;
|
|
164
|
+
* </script>
|
|
165
|
+
*
|
|
166
|
+
* <div bind:this={containerEl}></div>
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export declare function createPlayerControllerStore(config: PlayerControllerStoreConfig): PlayerControllerStore;
|
|
170
|
+
export declare function createDerivedState(store: PlayerControllerStore): Readable<PlayerState>;
|
|
171
|
+
export declare function createDerivedIsPlaying(store: PlayerControllerStore): Readable<boolean>;
|
|
172
|
+
export declare function createDerivedCurrentTime(store: PlayerControllerStore): Readable<number>;
|
|
173
|
+
export declare function createDerivedDuration(store: PlayerControllerStore): Readable<number>;
|
|
174
|
+
export declare function createDerivedError(store: PlayerControllerStore): Readable<string | null>;
|
|
175
|
+
export declare function createDerivedVideoElement(store: PlayerControllerStore): Readable<HTMLVideoElement | null>;
|
|
176
|
+
export declare function createDerivedShouldShowControls(store: PlayerControllerStore): Readable<boolean>;
|
|
177
|
+
export declare function createDerivedShouldShowIdleScreen(store: PlayerControllerStore): Readable<boolean>;
|
|
178
|
+
export default createPlayerControllerStore;
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte store for PlayerController - wraps the core PlayerController
|
|
3
|
+
* for declarative usage in Svelte 5 components.
|
|
4
|
+
*/
|
|
5
|
+
import { writable, derived } from 'svelte/store';
|
|
6
|
+
import { PlayerController, } from '@livepeer-frameworks/player-core';
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Initial State
|
|
9
|
+
// ============================================================================
|
|
10
|
+
const initialState = {
|
|
11
|
+
state: 'booting',
|
|
12
|
+
streamState: null,
|
|
13
|
+
endpoints: null,
|
|
14
|
+
metadata: null,
|
|
15
|
+
videoElement: null,
|
|
16
|
+
currentTime: 0,
|
|
17
|
+
duration: NaN,
|
|
18
|
+
isPlaying: false,
|
|
19
|
+
isPaused: true,
|
|
20
|
+
isBuffering: false,
|
|
21
|
+
isMuted: true,
|
|
22
|
+
volume: 1,
|
|
23
|
+
error: null,
|
|
24
|
+
isPassiveError: false,
|
|
25
|
+
hasPlaybackStarted: false,
|
|
26
|
+
isHoldingSpeed: false,
|
|
27
|
+
holdSpeed: 2,
|
|
28
|
+
isHovering: false,
|
|
29
|
+
shouldShowControls: false,
|
|
30
|
+
isLoopEnabled: false,
|
|
31
|
+
isFullscreen: false,
|
|
32
|
+
isPiPActive: false,
|
|
33
|
+
isEffectivelyLive: false,
|
|
34
|
+
shouldShowIdleScreen: true,
|
|
35
|
+
currentPlayerInfo: null,
|
|
36
|
+
currentSourceInfo: null,
|
|
37
|
+
playbackQuality: null,
|
|
38
|
+
subtitlesEnabled: false,
|
|
39
|
+
};
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Store Factory
|
|
42
|
+
// ============================================================================
|
|
43
|
+
/**
|
|
44
|
+
* Create a PlayerController store for managing player lifecycle.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```svelte
|
|
48
|
+
* <script>
|
|
49
|
+
* import { createPlayerControllerStore } from './stores/playerController';
|
|
50
|
+
* import { onMount, onDestroy } from 'svelte';
|
|
51
|
+
*
|
|
52
|
+
* let containerEl: HTMLElement;
|
|
53
|
+
*
|
|
54
|
+
* const playerStore = createPlayerControllerStore({
|
|
55
|
+
* contentId: 'my-stream',
|
|
56
|
+
* contentType: 'live',
|
|
57
|
+
* gatewayUrl: 'https://gateway.example.com/graphql',
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* onMount(() => {
|
|
61
|
+
* playerStore.attach(containerEl);
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* onDestroy(() => {
|
|
65
|
+
* playerStore.destroy();
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* // Access state reactively
|
|
69
|
+
* $: isPlaying = $playerStore.isPlaying;
|
|
70
|
+
* $: currentTime = $playerStore.currentTime;
|
|
71
|
+
* </script>
|
|
72
|
+
*
|
|
73
|
+
* <div bind:this={containerEl}></div>
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function createPlayerControllerStore(config) {
|
|
77
|
+
const { enabled = true, ...controllerConfig } = config;
|
|
78
|
+
// Internal state
|
|
79
|
+
const store = writable(initialState);
|
|
80
|
+
let controller = null;
|
|
81
|
+
let unsubscribers = [];
|
|
82
|
+
/**
|
|
83
|
+
* Sync state from controller to store
|
|
84
|
+
*/
|
|
85
|
+
function syncState() {
|
|
86
|
+
if (!controller)
|
|
87
|
+
return;
|
|
88
|
+
store.update(prev => ({
|
|
89
|
+
...prev,
|
|
90
|
+
isPlaying: controller.isPlaying(),
|
|
91
|
+
isPaused: controller.isPaused(),
|
|
92
|
+
isBuffering: controller.isBuffering(),
|
|
93
|
+
isMuted: controller.isMuted(),
|
|
94
|
+
volume: controller.getVolume(),
|
|
95
|
+
hasPlaybackStarted: controller.hasPlaybackStarted(),
|
|
96
|
+
shouldShowControls: controller.shouldShowControls(),
|
|
97
|
+
shouldShowIdleScreen: controller.shouldShowIdleScreen(),
|
|
98
|
+
playbackQuality: controller.getPlaybackQuality(),
|
|
99
|
+
isLoopEnabled: controller.isLoopEnabled(),
|
|
100
|
+
subtitlesEnabled: controller.isSubtitlesEnabled(),
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Attach to a container element
|
|
105
|
+
*/
|
|
106
|
+
async function attach(container) {
|
|
107
|
+
if (!enabled)
|
|
108
|
+
return;
|
|
109
|
+
// Clean up existing controller
|
|
110
|
+
if (controller) {
|
|
111
|
+
unsubscribers.forEach(fn => fn());
|
|
112
|
+
unsubscribers = [];
|
|
113
|
+
controller.destroy();
|
|
114
|
+
}
|
|
115
|
+
// Create new controller
|
|
116
|
+
controller = new PlayerController(controllerConfig);
|
|
117
|
+
// Subscribe to events
|
|
118
|
+
unsubscribers.push(controller.on('stateChange', ({ state }) => {
|
|
119
|
+
store.update(prev => ({ ...prev, state }));
|
|
120
|
+
}));
|
|
121
|
+
unsubscribers.push(controller.on('streamStateChange', ({ state: streamState }) => {
|
|
122
|
+
store.update(prev => ({
|
|
123
|
+
...prev,
|
|
124
|
+
streamState,
|
|
125
|
+
isEffectivelyLive: controller.isEffectivelyLive(),
|
|
126
|
+
shouldShowIdleScreen: controller.shouldShowIdleScreen(),
|
|
127
|
+
}));
|
|
128
|
+
}));
|
|
129
|
+
unsubscribers.push(controller.on('timeUpdate', ({ currentTime, duration }) => {
|
|
130
|
+
store.update(prev => ({ ...prev, currentTime, duration }));
|
|
131
|
+
}));
|
|
132
|
+
unsubscribers.push(controller.on('error', ({ error }) => {
|
|
133
|
+
store.update(prev => ({
|
|
134
|
+
...prev,
|
|
135
|
+
error,
|
|
136
|
+
isPassiveError: controller.isPassiveError(),
|
|
137
|
+
}));
|
|
138
|
+
}));
|
|
139
|
+
unsubscribers.push(controller.on('errorCleared', () => {
|
|
140
|
+
store.update(prev => ({ ...prev, error: null, isPassiveError: false }));
|
|
141
|
+
}));
|
|
142
|
+
unsubscribers.push(controller.on('ready', ({ videoElement }) => {
|
|
143
|
+
store.update(prev => ({
|
|
144
|
+
...prev,
|
|
145
|
+
videoElement,
|
|
146
|
+
endpoints: controller.getEndpoints(),
|
|
147
|
+
metadata: controller.getMetadata(),
|
|
148
|
+
isEffectivelyLive: controller.isEffectivelyLive(),
|
|
149
|
+
shouldShowIdleScreen: controller.shouldShowIdleScreen(),
|
|
150
|
+
currentPlayerInfo: controller.getCurrentPlayerInfo(),
|
|
151
|
+
currentSourceInfo: controller.getCurrentSourceInfo(),
|
|
152
|
+
}));
|
|
153
|
+
// Add video event listeners for state sync
|
|
154
|
+
const video = videoElement;
|
|
155
|
+
const handleVideoEvent = () => syncState();
|
|
156
|
+
video.addEventListener('play', handleVideoEvent);
|
|
157
|
+
video.addEventListener('pause', handleVideoEvent);
|
|
158
|
+
video.addEventListener('waiting', handleVideoEvent);
|
|
159
|
+
video.addEventListener('playing', handleVideoEvent);
|
|
160
|
+
unsubscribers.push(() => {
|
|
161
|
+
video.removeEventListener('play', handleVideoEvent);
|
|
162
|
+
video.removeEventListener('pause', handleVideoEvent);
|
|
163
|
+
video.removeEventListener('waiting', handleVideoEvent);
|
|
164
|
+
video.removeEventListener('playing', handleVideoEvent);
|
|
165
|
+
});
|
|
166
|
+
}));
|
|
167
|
+
unsubscribers.push(controller.on('playerSelected', ({ player, source }) => {
|
|
168
|
+
store.update(prev => ({
|
|
169
|
+
...prev,
|
|
170
|
+
currentPlayerInfo: controller.getCurrentPlayerInfo(),
|
|
171
|
+
currentSourceInfo: { url: source.url, type: source.type },
|
|
172
|
+
}));
|
|
173
|
+
}));
|
|
174
|
+
unsubscribers.push(controller.on('volumeChange', ({ volume, muted }) => {
|
|
175
|
+
store.update(prev => ({ ...prev, volume, isMuted: muted }));
|
|
176
|
+
}));
|
|
177
|
+
unsubscribers.push(controller.on('loopChange', ({ isLoopEnabled }) => {
|
|
178
|
+
store.update(prev => ({ ...prev, isLoopEnabled }));
|
|
179
|
+
}));
|
|
180
|
+
unsubscribers.push(controller.on('fullscreenChange', ({ isFullscreen }) => {
|
|
181
|
+
store.update(prev => ({ ...prev, isFullscreen }));
|
|
182
|
+
}));
|
|
183
|
+
unsubscribers.push(controller.on('pipChange', ({ isPiP }) => {
|
|
184
|
+
store.update(prev => ({ ...prev, isPiPActive: isPiP }));
|
|
185
|
+
}));
|
|
186
|
+
unsubscribers.push(controller.on('holdSpeedStart', ({ speed }) => {
|
|
187
|
+
store.update(prev => ({ ...prev, isHoldingSpeed: true, holdSpeed: speed }));
|
|
188
|
+
}));
|
|
189
|
+
unsubscribers.push(controller.on('holdSpeedEnd', () => {
|
|
190
|
+
store.update(prev => ({ ...prev, isHoldingSpeed: false }));
|
|
191
|
+
}));
|
|
192
|
+
unsubscribers.push(controller.on('hoverStart', () => {
|
|
193
|
+
store.update(prev => ({ ...prev, isHovering: true, shouldShowControls: true }));
|
|
194
|
+
}));
|
|
195
|
+
unsubscribers.push(controller.on('hoverEnd', () => {
|
|
196
|
+
store.update(prev => ({
|
|
197
|
+
...prev,
|
|
198
|
+
isHovering: false,
|
|
199
|
+
shouldShowControls: controller.shouldShowControls(),
|
|
200
|
+
}));
|
|
201
|
+
}));
|
|
202
|
+
unsubscribers.push(controller.on('captionsChange', ({ enabled }) => {
|
|
203
|
+
store.update(prev => ({ ...prev, subtitlesEnabled: enabled }));
|
|
204
|
+
}));
|
|
205
|
+
// Set initial loop state
|
|
206
|
+
store.update(prev => ({
|
|
207
|
+
...prev,
|
|
208
|
+
isLoopEnabled: controller.isLoopEnabled(),
|
|
209
|
+
}));
|
|
210
|
+
// Attach controller to container
|
|
211
|
+
await controller.attach(container);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Detach from container
|
|
215
|
+
*/
|
|
216
|
+
function detach() {
|
|
217
|
+
if (controller) {
|
|
218
|
+
controller.detach();
|
|
219
|
+
}
|
|
220
|
+
store.set(initialState);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Destroy the store and controller
|
|
224
|
+
*/
|
|
225
|
+
function destroy() {
|
|
226
|
+
unsubscribers.forEach(fn => fn());
|
|
227
|
+
unsubscribers = [];
|
|
228
|
+
if (controller) {
|
|
229
|
+
controller.destroy();
|
|
230
|
+
controller = null;
|
|
231
|
+
}
|
|
232
|
+
store.set(initialState);
|
|
233
|
+
}
|
|
234
|
+
// Action methods
|
|
235
|
+
async function play() {
|
|
236
|
+
await controller?.play();
|
|
237
|
+
}
|
|
238
|
+
function pause() {
|
|
239
|
+
controller?.pause();
|
|
240
|
+
}
|
|
241
|
+
function togglePlay() {
|
|
242
|
+
controller?.togglePlay();
|
|
243
|
+
}
|
|
244
|
+
function seek(time) {
|
|
245
|
+
controller?.seek(time);
|
|
246
|
+
}
|
|
247
|
+
function seekBy(delta) {
|
|
248
|
+
controller?.seekBy(delta);
|
|
249
|
+
}
|
|
250
|
+
function setVolume(volume) {
|
|
251
|
+
controller?.setVolume(volume);
|
|
252
|
+
}
|
|
253
|
+
function toggleMute() {
|
|
254
|
+
controller?.toggleMute();
|
|
255
|
+
}
|
|
256
|
+
function toggleLoop() {
|
|
257
|
+
controller?.toggleLoop();
|
|
258
|
+
}
|
|
259
|
+
async function toggleFullscreen() {
|
|
260
|
+
await controller?.toggleFullscreen();
|
|
261
|
+
}
|
|
262
|
+
async function togglePiP() {
|
|
263
|
+
await controller?.togglePictureInPicture();
|
|
264
|
+
}
|
|
265
|
+
function toggleSubtitles() {
|
|
266
|
+
controller?.toggleSubtitles();
|
|
267
|
+
}
|
|
268
|
+
function clearError() {
|
|
269
|
+
controller?.clearError();
|
|
270
|
+
store.update(prev => ({ ...prev, error: null, isPassiveError: false }));
|
|
271
|
+
}
|
|
272
|
+
async function retry() {
|
|
273
|
+
await controller?.retry();
|
|
274
|
+
}
|
|
275
|
+
async function reload() {
|
|
276
|
+
await controller?.reload();
|
|
277
|
+
}
|
|
278
|
+
function getQualities() {
|
|
279
|
+
return controller?.getQualities() ?? [];
|
|
280
|
+
}
|
|
281
|
+
function selectQuality(id) {
|
|
282
|
+
controller?.selectQuality(id);
|
|
283
|
+
}
|
|
284
|
+
function handleMouseEnter() {
|
|
285
|
+
controller?.handleMouseEnter();
|
|
286
|
+
}
|
|
287
|
+
function handleMouseLeave() {
|
|
288
|
+
controller?.handleMouseLeave();
|
|
289
|
+
}
|
|
290
|
+
function handleMouseMove() {
|
|
291
|
+
controller?.handleMouseMove();
|
|
292
|
+
}
|
|
293
|
+
function handleTouchStart() {
|
|
294
|
+
controller?.handleTouchStart();
|
|
295
|
+
}
|
|
296
|
+
async function setDevModeOptions(options) {
|
|
297
|
+
await controller?.setDevModeOptions(options);
|
|
298
|
+
}
|
|
299
|
+
function getController() {
|
|
300
|
+
return controller;
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
subscribe: store.subscribe,
|
|
304
|
+
getController,
|
|
305
|
+
attach,
|
|
306
|
+
detach,
|
|
307
|
+
destroy,
|
|
308
|
+
play,
|
|
309
|
+
pause,
|
|
310
|
+
togglePlay,
|
|
311
|
+
seek,
|
|
312
|
+
seekBy,
|
|
313
|
+
setVolume,
|
|
314
|
+
toggleMute,
|
|
315
|
+
toggleLoop,
|
|
316
|
+
toggleFullscreen,
|
|
317
|
+
togglePiP,
|
|
318
|
+
toggleSubtitles,
|
|
319
|
+
clearError,
|
|
320
|
+
retry,
|
|
321
|
+
reload,
|
|
322
|
+
getQualities,
|
|
323
|
+
selectQuality,
|
|
324
|
+
handleMouseEnter,
|
|
325
|
+
handleMouseLeave,
|
|
326
|
+
handleMouseMove,
|
|
327
|
+
handleTouchStart,
|
|
328
|
+
setDevModeOptions,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
// ============================================================================
|
|
332
|
+
// Derived Stores (convenience)
|
|
333
|
+
// ============================================================================
|
|
334
|
+
export function createDerivedState(store) {
|
|
335
|
+
return derived(store, $state => $state.state);
|
|
336
|
+
}
|
|
337
|
+
export function createDerivedIsPlaying(store) {
|
|
338
|
+
return derived(store, $state => $state.isPlaying);
|
|
339
|
+
}
|
|
340
|
+
export function createDerivedCurrentTime(store) {
|
|
341
|
+
return derived(store, $state => $state.currentTime);
|
|
342
|
+
}
|
|
343
|
+
export function createDerivedDuration(store) {
|
|
344
|
+
return derived(store, $state => $state.duration);
|
|
345
|
+
}
|
|
346
|
+
export function createDerivedError(store) {
|
|
347
|
+
return derived(store, $state => $state.error);
|
|
348
|
+
}
|
|
349
|
+
export function createDerivedVideoElement(store) {
|
|
350
|
+
return derived(store, $state => $state.videoElement);
|
|
351
|
+
}
|
|
352
|
+
export function createDerivedShouldShowControls(store) {
|
|
353
|
+
return derived(store, $state => $state.shouldShowControls);
|
|
354
|
+
}
|
|
355
|
+
export function createDerivedShouldShowIdleScreen(store) {
|
|
356
|
+
return derived(store, $state => $state.shouldShowIdleScreen);
|
|
357
|
+
}
|
|
358
|
+
export default createPlayerControllerStore;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte store for player selection.
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to PlayerManager events for reactive selection updates.
|
|
5
|
+
* Uses event-driven updates instead of polling - no render spam.
|
|
6
|
+
*/
|
|
7
|
+
import { type Readable } from 'svelte/store';
|
|
8
|
+
import { type PlayerManager, type PlayerSelection, type PlayerCombination, type StreamInfo, type PlaybackMode } from '@livepeer-frameworks/player-core';
|
|
9
|
+
export interface PlayerSelectionOptions {
|
|
10
|
+
/** Enable debug logging */
|
|
11
|
+
debug?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface PlayerSelectionState {
|
|
14
|
+
/** Current best selection (null if no compatible player) */
|
|
15
|
+
selection: PlayerSelection | null;
|
|
16
|
+
/** All player+source combinations with scores */
|
|
17
|
+
combinations: PlayerCombination[];
|
|
18
|
+
/** Whether initial computation has completed */
|
|
19
|
+
ready: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface PlayerSelectionStore extends Readable<PlayerSelectionState> {
|
|
22
|
+
/** Update stream info to compute selections for */
|
|
23
|
+
setStreamInfo: (streamInfo: StreamInfo | null, playbackMode?: PlaybackMode) => void;
|
|
24
|
+
/** Force recomputation (invalidates cache) */
|
|
25
|
+
refresh: () => void;
|
|
26
|
+
/** Cleanup subscriptions */
|
|
27
|
+
destroy: () => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create a player selection store that subscribes to PlayerManager events.
|
|
31
|
+
*
|
|
32
|
+
* This store uses the event system in PlayerManager, which means:
|
|
33
|
+
* - Initial computation happens once when streamInfo is provided
|
|
34
|
+
* - Updates only fire when selection actually changes (different player+source)
|
|
35
|
+
* - No render spam from frequent reactive updates
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```svelte
|
|
39
|
+
* <script>
|
|
40
|
+
* import { createPlayerSelectionStore } from './stores/playerSelection';
|
|
41
|
+
* import { globalPlayerManager } from '@livepeer-frameworks/player-core';
|
|
42
|
+
*
|
|
43
|
+
* const playerSelection = createPlayerSelectionStore(globalPlayerManager);
|
|
44
|
+
*
|
|
45
|
+
* // Set stream info to trigger selection
|
|
46
|
+
* $: if (streamInfo) playerSelection.setStreamInfo(streamInfo, playbackMode);
|
|
47
|
+
*
|
|
48
|
+
* // Access selection state
|
|
49
|
+
* $: selection = $playerSelection.selection;
|
|
50
|
+
* $: combinations = $playerSelection.combinations;
|
|
51
|
+
* $: ready = $playerSelection.ready;
|
|
52
|
+
* </script>
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function createPlayerSelectionStore(manager: PlayerManager, options?: PlayerSelectionOptions): PlayerSelectionStore;
|
|
56
|
+
/**
|
|
57
|
+
* Derive just the current selection from the store.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createDerivedSelection(store: PlayerSelectionStore): Readable<PlayerSelection | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Derive just the combinations array from the store.
|
|
62
|
+
*/
|
|
63
|
+
export declare function createDerivedCombinations(store: PlayerSelectionStore): Readable<PlayerCombination[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Derive the ready state from the store.
|
|
66
|
+
*/
|
|
67
|
+
export declare function createDerivedReady(store: PlayerSelectionStore): Readable<boolean>;
|
|
68
|
+
/**
|
|
69
|
+
* Derive the selected player name.
|
|
70
|
+
*/
|
|
71
|
+
export declare function createDerivedSelectedPlayer(store: PlayerSelectionStore): Readable<string | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Derive the selected source type.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createDerivedSelectedSourceType(store: PlayerSelectionStore): Readable<string | null>;
|
|
76
|
+
/**
|
|
77
|
+
* Derive only compatible combinations (filtered from all).
|
|
78
|
+
*/
|
|
79
|
+
export declare function createDerivedCompatibleCombinations(store: PlayerSelectionStore): Readable<PlayerCombination[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Derive only incompatible combinations.
|
|
82
|
+
*/
|
|
83
|
+
export declare function createDerivedIncompatibleCombinations(store: PlayerSelectionStore): Readable<PlayerCombination[]>;
|
|
84
|
+
export default createPlayerSelectionStore;
|