@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.
Files changed (169) hide show
  1. package/dist/DevModePanel.svelte +650 -0
  2. package/dist/DevModePanel.svelte.d.ts +31 -0
  3. package/dist/DvdLogo.svelte +213 -0
  4. package/dist/DvdLogo.svelte.d.ts +7 -0
  5. package/dist/Icons.svelte +27 -0
  6. package/dist/Icons.svelte.d.ts +25 -0
  7. package/dist/IdleScreen.svelte +752 -0
  8. package/dist/IdleScreen.svelte.d.ts +11 -0
  9. package/dist/LoadingScreen.svelte +689 -0
  10. package/dist/LoadingScreen.svelte.d.ts +7 -0
  11. package/dist/Player.svelte +482 -0
  12. package/dist/Player.svelte.d.ts +26 -0
  13. package/dist/PlayerControls.svelte +739 -0
  14. package/dist/PlayerControls.svelte.d.ts +20 -0
  15. package/dist/SeekBar.svelte +274 -0
  16. package/dist/SeekBar.svelte.d.ts +25 -0
  17. package/dist/SkipIndicator.svelte +95 -0
  18. package/dist/SkipIndicator.svelte.d.ts +14 -0
  19. package/dist/SpeedIndicator.svelte +38 -0
  20. package/dist/SpeedIndicator.svelte.d.ts +8 -0
  21. package/dist/StatsPanel.svelte +155 -0
  22. package/dist/StatsPanel.svelte.d.ts +27 -0
  23. package/dist/StreamStateOverlay.svelte +266 -0
  24. package/dist/StreamStateOverlay.svelte.d.ts +18 -0
  25. package/dist/SubtitleRenderer.svelte +234 -0
  26. package/dist/SubtitleRenderer.svelte.d.ts +41 -0
  27. package/dist/ThumbnailOverlay.svelte +96 -0
  28. package/dist/ThumbnailOverlay.svelte.d.ts +11 -0
  29. package/dist/TitleOverlay.svelte +47 -0
  30. package/dist/TitleOverlay.svelte.d.ts +9 -0
  31. package/dist/assets/logomark.svg +56 -0
  32. package/dist/components/VolumeIcons.svelte +53 -0
  33. package/dist/components/VolumeIcons.svelte.d.ts +10 -0
  34. package/dist/global.d.ts +15 -0
  35. package/dist/icons/FullscreenExitIcon.svelte +33 -0
  36. package/dist/icons/FullscreenExitIcon.svelte.d.ts +8 -0
  37. package/dist/icons/FullscreenIcon.svelte +33 -0
  38. package/dist/icons/FullscreenIcon.svelte.d.ts +8 -0
  39. package/dist/icons/PauseIcon.svelte +28 -0
  40. package/dist/icons/PauseIcon.svelte.d.ts +8 -0
  41. package/dist/icons/PictureInPictureIcon.svelte +28 -0
  42. package/dist/icons/PictureInPictureIcon.svelte.d.ts +8 -0
  43. package/dist/icons/PlayIcon.svelte +27 -0
  44. package/dist/icons/PlayIcon.svelte.d.ts +8 -0
  45. package/dist/icons/SeekToLiveIcon.svelte +30 -0
  46. package/dist/icons/SeekToLiveIcon.svelte.d.ts +8 -0
  47. package/dist/icons/SettingsIcon.svelte +40 -0
  48. package/dist/icons/SettingsIcon.svelte.d.ts +8 -0
  49. package/dist/icons/SkipBackIcon.svelte +32 -0
  50. package/dist/icons/SkipBackIcon.svelte.d.ts +8 -0
  51. package/dist/icons/SkipForwardIcon.svelte +32 -0
  52. package/dist/icons/SkipForwardIcon.svelte.d.ts +8 -0
  53. package/dist/icons/StatsIcon.svelte +29 -0
  54. package/dist/icons/StatsIcon.svelte.d.ts +8 -0
  55. package/dist/icons/VolumeOffIcon.svelte +29 -0
  56. package/dist/icons/VolumeOffIcon.svelte.d.ts +8 -0
  57. package/dist/icons/VolumeUpIcon.svelte +34 -0
  58. package/dist/icons/VolumeUpIcon.svelte.d.ts +8 -0
  59. package/dist/icons/index.d.ts +17 -0
  60. package/dist/icons/index.js +17 -0
  61. package/dist/index.d.ts +50 -0
  62. package/dist/index.js +54 -0
  63. package/dist/player.css +2 -0
  64. package/dist/stores/index.d.ts +15 -0
  65. package/dist/stores/index.js +21 -0
  66. package/dist/stores/playbackQuality.d.ts +43 -0
  67. package/dist/stores/playbackQuality.js +107 -0
  68. package/dist/stores/playerContext.d.ts +73 -0
  69. package/dist/stores/playerContext.js +166 -0
  70. package/dist/stores/playerController.d.ts +178 -0
  71. package/dist/stores/playerController.js +358 -0
  72. package/dist/stores/playerSelection.d.ts +84 -0
  73. package/dist/stores/playerSelection.js +159 -0
  74. package/dist/stores/streamState.d.ts +44 -0
  75. package/dist/stores/streamState.js +314 -0
  76. package/dist/stores/viewerEndpoints.d.ts +48 -0
  77. package/dist/stores/viewerEndpoints.js +178 -0
  78. package/dist/types.d.ts +4 -0
  79. package/dist/types.js +4 -0
  80. package/dist/ui/Badge.svelte +21 -0
  81. package/dist/ui/Badge.svelte.d.ts +32 -0
  82. package/dist/ui/Button.svelte +42 -0
  83. package/dist/ui/Button.svelte.d.ts +35 -0
  84. package/dist/ui/Slider.svelte +100 -0
  85. package/dist/ui/Slider.svelte.d.ts +17 -0
  86. package/dist/ui/badge.d.ts +6 -0
  87. package/dist/ui/badge.js +10 -0
  88. package/dist/ui/button.d.ts +8 -0
  89. package/dist/ui/button.js +21 -0
  90. package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte +34 -0
  91. package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte.d.ts +31 -0
  92. package/dist/ui/context-menu/ContextMenuContent.svelte +17 -0
  93. package/dist/ui/context-menu/ContextMenuContent.svelte.d.ts +7 -0
  94. package/dist/ui/context-menu/ContextMenuItem.svelte +22 -0
  95. package/dist/ui/context-menu/ContextMenuItem.svelte.d.ts +8 -0
  96. package/dist/ui/context-menu/ContextMenuLabel.svelte +22 -0
  97. package/dist/ui/context-menu/ContextMenuLabel.svelte.d.ts +8 -0
  98. package/dist/ui/context-menu/ContextMenuPortal.svelte +11 -0
  99. package/dist/ui/context-menu/ContextMenuPortal.svelte.d.ts +6 -0
  100. package/dist/ui/context-menu/ContextMenuRadioItem.svelte +21 -0
  101. package/dist/ui/context-menu/ContextMenuRadioItem.svelte.d.ts +31 -0
  102. package/dist/ui/context-menu/ContextMenuSeparator.svelte +14 -0
  103. package/dist/ui/context-menu/ContextMenuSeparator.svelte.d.ts +6 -0
  104. package/dist/ui/context-menu/ContextMenuShortcut.svelte +19 -0
  105. package/dist/ui/context-menu/ContextMenuShortcut.svelte.d.ts +7 -0
  106. package/dist/ui/context-menu/ContextMenuSubContent.svelte +20 -0
  107. package/dist/ui/context-menu/ContextMenuSubContent.svelte.d.ts +7 -0
  108. package/dist/ui/context-menu/ContextMenuSubTrigger.svelte +34 -0
  109. package/dist/ui/context-menu/ContextMenuSubTrigger.svelte.d.ts +8 -0
  110. package/dist/ui/context-menu/index.d.ts +17 -0
  111. package/dist/ui/context-menu/index.js +17 -0
  112. package/package.json +51 -0
  113. package/src/DevModePanel.svelte +650 -0
  114. package/src/DvdLogo.svelte +213 -0
  115. package/src/Icons.svelte +27 -0
  116. package/src/IdleScreen.svelte +739 -0
  117. package/src/LoadingScreen.svelte +674 -0
  118. package/src/Player.svelte +483 -0
  119. package/src/PlayerControls.svelte +752 -0
  120. package/src/SeekBar.svelte +274 -0
  121. package/src/SkipIndicator.svelte +95 -0
  122. package/src/SpeedIndicator.svelte +37 -0
  123. package/src/StatsPanel.svelte +155 -0
  124. package/src/StreamStateOverlay.svelte +266 -0
  125. package/src/SubtitleRenderer.svelte +234 -0
  126. package/src/ThumbnailOverlay.svelte +96 -0
  127. package/src/TitleOverlay.svelte +47 -0
  128. package/src/assets/logomark.svg +56 -0
  129. package/src/components/VolumeIcons.svelte +53 -0
  130. package/src/global.d.ts +15 -0
  131. package/src/icons/FullscreenExitIcon.svelte +33 -0
  132. package/src/icons/FullscreenIcon.svelte +33 -0
  133. package/src/icons/PauseIcon.svelte +28 -0
  134. package/src/icons/PictureInPictureIcon.svelte +28 -0
  135. package/src/icons/PlayIcon.svelte +27 -0
  136. package/src/icons/SeekToLiveIcon.svelte +30 -0
  137. package/src/icons/SettingsIcon.svelte +40 -0
  138. package/src/icons/SkipBackIcon.svelte +32 -0
  139. package/src/icons/SkipForwardIcon.svelte +32 -0
  140. package/src/icons/StatsIcon.svelte +29 -0
  141. package/src/icons/VolumeOffIcon.svelte +29 -0
  142. package/src/icons/VolumeUpIcon.svelte +34 -0
  143. package/src/icons/index.ts +18 -0
  144. package/src/index.ts +84 -0
  145. package/src/player.css +2 -0
  146. package/src/stores/index.ts +88 -0
  147. package/src/stores/playbackQuality.ts +137 -0
  148. package/src/stores/playerContext.ts +221 -0
  149. package/src/stores/playerController.ts +568 -0
  150. package/src/stores/playerSelection.ts +216 -0
  151. package/src/stores/streamState.ts +367 -0
  152. package/src/stores/viewerEndpoints.ts +224 -0
  153. package/src/types.ts +6 -0
  154. package/src/ui/Badge.svelte +21 -0
  155. package/src/ui/Button.svelte +42 -0
  156. package/src/ui/Slider.svelte +100 -0
  157. package/src/ui/badge.ts +20 -0
  158. package/src/ui/button.ts +35 -0
  159. package/src/ui/context-menu/ContextMenuCheckboxItem.svelte +34 -0
  160. package/src/ui/context-menu/ContextMenuContent.svelte +17 -0
  161. package/src/ui/context-menu/ContextMenuItem.svelte +22 -0
  162. package/src/ui/context-menu/ContextMenuLabel.svelte +22 -0
  163. package/src/ui/context-menu/ContextMenuPortal.svelte +11 -0
  164. package/src/ui/context-menu/ContextMenuRadioItem.svelte +21 -0
  165. package/src/ui/context-menu/ContextMenuSeparator.svelte +14 -0
  166. package/src/ui/context-menu/ContextMenuShortcut.svelte +19 -0
  167. package/src/ui/context-menu/ContextMenuSubContent.svelte +20 -0
  168. package/src/ui/context-menu/ContextMenuSubTrigger.svelte +34 -0
  169. 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;