@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,159 @@
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 { writable, derived } from 'svelte/store';
8
+ const initialState = {
9
+ selection: null,
10
+ combinations: [],
11
+ ready: false,
12
+ };
13
+ /**
14
+ * Create a player selection store that subscribes to PlayerManager events.
15
+ *
16
+ * This store uses the event system in PlayerManager, which means:
17
+ * - Initial computation happens once when streamInfo is provided
18
+ * - Updates only fire when selection actually changes (different player+source)
19
+ * - No render spam from frequent reactive updates
20
+ *
21
+ * @example
22
+ * ```svelte
23
+ * <script>
24
+ * import { createPlayerSelectionStore } from './stores/playerSelection';
25
+ * import { globalPlayerManager } from '@livepeer-frameworks/player-core';
26
+ *
27
+ * const playerSelection = createPlayerSelectionStore(globalPlayerManager);
28
+ *
29
+ * // Set stream info to trigger selection
30
+ * $: if (streamInfo) playerSelection.setStreamInfo(streamInfo, playbackMode);
31
+ *
32
+ * // Access selection state
33
+ * $: selection = $playerSelection.selection;
34
+ * $: combinations = $playerSelection.combinations;
35
+ * $: ready = $playerSelection.ready;
36
+ * </script>
37
+ * ```
38
+ */
39
+ export function createPlayerSelectionStore(manager, options = {}) {
40
+ const { debug = false } = options;
41
+ const store = writable({ ...initialState });
42
+ let currentStreamInfo = null;
43
+ let currentPlaybackMode = 'auto';
44
+ let unsubSelection = null;
45
+ let unsubCombos = null;
46
+ // Subscribe to PlayerManager events
47
+ function subscribe() {
48
+ // Clean up existing subscriptions
49
+ unsubscribe();
50
+ unsubSelection = manager.on('selection-changed', (sel) => {
51
+ if (debug) {
52
+ console.log('[playerSelection store] Selection changed:', sel?.player, sel?.source?.type);
53
+ }
54
+ store.update((state) => ({
55
+ ...state,
56
+ selection: sel,
57
+ }));
58
+ });
59
+ unsubCombos = manager.on('combinations-updated', (combos) => {
60
+ if (debug) {
61
+ console.log('[playerSelection store] Combinations updated:', combos.length);
62
+ }
63
+ store.update((state) => ({
64
+ ...state,
65
+ combinations: combos,
66
+ ready: true,
67
+ }));
68
+ });
69
+ }
70
+ function unsubscribe() {
71
+ unsubSelection?.();
72
+ unsubCombos?.();
73
+ unsubSelection = null;
74
+ unsubCombos = null;
75
+ }
76
+ // Initialize subscriptions
77
+ subscribe();
78
+ /**
79
+ * Set stream info to compute selections for.
80
+ * This triggers computation (using cache if available).
81
+ */
82
+ function setStreamInfo(streamInfo, playbackMode) {
83
+ currentStreamInfo = streamInfo;
84
+ currentPlaybackMode = playbackMode ?? 'auto';
85
+ if (!streamInfo) {
86
+ store.set({ ...initialState });
87
+ return;
88
+ }
89
+ // This will use cache if available, or compute + emit events if not
90
+ manager.getAllCombinations(streamInfo, currentPlaybackMode);
91
+ }
92
+ /**
93
+ * Force recomputation (invalidates cache).
94
+ */
95
+ function refresh() {
96
+ if (!currentStreamInfo)
97
+ return;
98
+ manager.invalidateCache();
99
+ manager.getAllCombinations(currentStreamInfo, currentPlaybackMode);
100
+ }
101
+ /**
102
+ * Cleanup subscriptions.
103
+ */
104
+ function destroy() {
105
+ unsubscribe();
106
+ store.set({ ...initialState });
107
+ currentStreamInfo = null;
108
+ }
109
+ return {
110
+ subscribe: store.subscribe,
111
+ setStreamInfo,
112
+ refresh,
113
+ destroy,
114
+ };
115
+ }
116
+ // Convenience derived stores
117
+ /**
118
+ * Derive just the current selection from the store.
119
+ */
120
+ export function createDerivedSelection(store) {
121
+ return derived(store, ($state) => $state.selection);
122
+ }
123
+ /**
124
+ * Derive just the combinations array from the store.
125
+ */
126
+ export function createDerivedCombinations(store) {
127
+ return derived(store, ($state) => $state.combinations);
128
+ }
129
+ /**
130
+ * Derive the ready state from the store.
131
+ */
132
+ export function createDerivedReady(store) {
133
+ return derived(store, ($state) => $state.ready);
134
+ }
135
+ /**
136
+ * Derive the selected player name.
137
+ */
138
+ export function createDerivedSelectedPlayer(store) {
139
+ return derived(store, ($state) => $state.selection?.player ?? null);
140
+ }
141
+ /**
142
+ * Derive the selected source type.
143
+ */
144
+ export function createDerivedSelectedSourceType(store) {
145
+ return derived(store, ($state) => $state.selection?.source?.type ?? null);
146
+ }
147
+ /**
148
+ * Derive only compatible combinations (filtered from all).
149
+ */
150
+ export function createDerivedCompatibleCombinations(store) {
151
+ return derived(store, ($state) => $state.combinations.filter((c) => c.compatible));
152
+ }
153
+ /**
154
+ * Derive only incompatible combinations.
155
+ */
156
+ export function createDerivedIncompatibleCombinations(store) {
157
+ return derived(store, ($state) => $state.combinations.filter((c) => !c.compatible));
158
+ }
159
+ export default createPlayerSelectionStore;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Svelte store for MistServer stream state polling via WebSocket/HTTP.
3
+ *
4
+ * Port of useStreamState.ts React hook to Svelte 5 stores.
5
+ */
6
+ import { type Readable } from 'svelte/store';
7
+ import type { StreamStatus, MistStreamInfo, StreamState } from '@livepeer-frameworks/player-core';
8
+ export interface StreamStateOptions {
9
+ mistBaseUrl: string;
10
+ streamName: string;
11
+ pollInterval?: number;
12
+ enabled?: boolean;
13
+ useWebSocket?: boolean;
14
+ }
15
+ export interface StreamStateStore extends Readable<StreamState> {
16
+ refetch: () => void;
17
+ getSocket: () => WebSocket | null;
18
+ isSocketReady: Readable<boolean>;
19
+ destroy: () => void;
20
+ }
21
+ /**
22
+ * Create a stream state manager store for MistServer polling.
23
+ *
24
+ * @example
25
+ * ```svelte
26
+ * <script>
27
+ * import { createStreamStateManager } from './stores/streamState';
28
+ *
29
+ * const streamState = createStreamStateManager({
30
+ * mistBaseUrl: 'https://mist.example.com',
31
+ * streamName: 'my-stream',
32
+ * });
33
+ *
34
+ * // Access values
35
+ * $: status = $streamState.status;
36
+ * $: isOnline = $streamState.isOnline;
37
+ * </script>
38
+ * ```
39
+ */
40
+ export declare function createStreamStateManager(options: StreamStateOptions): StreamStateStore;
41
+ export declare function createDerivedStreamStatus(store: StreamStateStore): Readable<StreamStatus>;
42
+ export declare function createDerivedIsOnline(store: StreamStateStore): Readable<boolean>;
43
+ export declare function createDerivedStreamInfo(store: StreamStateStore): Readable<MistStreamInfo | undefined>;
44
+ export default createStreamStateManager;
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Svelte store for MistServer stream state polling via WebSocket/HTTP.
3
+ *
4
+ * Port of useStreamState.ts React hook to Svelte 5 stores.
5
+ */
6
+ import { writable, derived } from 'svelte/store';
7
+ /**
8
+ * Parse MistServer error string into StreamStatus enum
9
+ */
10
+ function parseErrorToStatus(error) {
11
+ const lowerError = error.toLowerCase();
12
+ if (lowerError.includes('offline'))
13
+ return 'OFFLINE';
14
+ if (lowerError.includes('initializing'))
15
+ return 'INITIALIZING';
16
+ if (lowerError.includes('booting'))
17
+ return 'BOOTING';
18
+ if (lowerError.includes('waiting for data'))
19
+ return 'WAITING_FOR_DATA';
20
+ if (lowerError.includes('shutting down'))
21
+ return 'SHUTTING_DOWN';
22
+ if (lowerError.includes('invalid'))
23
+ return 'INVALID';
24
+ return 'ERROR';
25
+ }
26
+ /**
27
+ * Get human-readable message for stream status
28
+ */
29
+ function getStatusMessage(status, percentage) {
30
+ switch (status) {
31
+ case 'ONLINE':
32
+ return 'Stream is online';
33
+ case 'OFFLINE':
34
+ return 'Stream is offline';
35
+ case 'INITIALIZING':
36
+ return percentage !== undefined
37
+ ? `Initializing... ${Math.round(percentage * 10) / 10}%`
38
+ : 'Stream is initializing';
39
+ case 'BOOTING':
40
+ return 'Stream is starting up';
41
+ case 'WAITING_FOR_DATA':
42
+ return 'Waiting for stream data';
43
+ case 'SHUTTING_DOWN':
44
+ return 'Stream is shutting down';
45
+ case 'INVALID':
46
+ return 'Stream status is invalid';
47
+ case 'ERROR':
48
+ default:
49
+ return 'Stream error';
50
+ }
51
+ }
52
+ const initialState = {
53
+ status: 'OFFLINE',
54
+ isOnline: false,
55
+ message: 'Connecting...',
56
+ lastUpdate: 0,
57
+ };
58
+ /**
59
+ * Create a stream state manager store for MistServer polling.
60
+ *
61
+ * @example
62
+ * ```svelte
63
+ * <script>
64
+ * import { createStreamStateManager } from './stores/streamState';
65
+ *
66
+ * const streamState = createStreamStateManager({
67
+ * mistBaseUrl: 'https://mist.example.com',
68
+ * streamName: 'my-stream',
69
+ * });
70
+ *
71
+ * // Access values
72
+ * $: status = $streamState.status;
73
+ * $: isOnline = $streamState.isOnline;
74
+ * </script>
75
+ * ```
76
+ */
77
+ export function createStreamStateManager(options) {
78
+ const { mistBaseUrl, streamName, pollInterval = 3000, enabled = true, useWebSocket = true, } = options;
79
+ const WS_TIMEOUT_MS = 5000;
80
+ // Internal state
81
+ const store = writable(initialState);
82
+ const socketReady = writable(false);
83
+ let ws = null;
84
+ let pollTimeout = null;
85
+ let wsTimeout = null;
86
+ let mounted = true;
87
+ /**
88
+ * Process MistServer response data
89
+ */
90
+ function processStreamInfo(data) {
91
+ if (!mounted)
92
+ return;
93
+ if (data.error) {
94
+ const status = parseErrorToStatus(data.error);
95
+ const message = data.on_error || getStatusMessage(status, data.perc);
96
+ store.update(prev => ({
97
+ status,
98
+ isOnline: false,
99
+ message,
100
+ percentage: data.perc,
101
+ lastUpdate: Date.now(),
102
+ error: data.error,
103
+ streamInfo: prev.streamInfo, // Preserve track data through error states
104
+ }));
105
+ }
106
+ else {
107
+ // Stream is online with valid metadata
108
+ store.update(prev => {
109
+ const mergedStreamInfo = {
110
+ ...prev.streamInfo,
111
+ ...data,
112
+ source: data.source || prev.streamInfo?.source,
113
+ meta: {
114
+ ...prev.streamInfo?.meta,
115
+ ...data.meta,
116
+ tracks: data.meta?.tracks || prev.streamInfo?.meta?.tracks,
117
+ },
118
+ };
119
+ return {
120
+ status: 'ONLINE',
121
+ isOnline: true,
122
+ message: 'Stream is online',
123
+ lastUpdate: Date.now(),
124
+ streamInfo: mergedStreamInfo,
125
+ };
126
+ });
127
+ }
128
+ }
129
+ /**
130
+ * HTTP polling fallback
131
+ */
132
+ async function pollHttp() {
133
+ if (!mounted || !enabled)
134
+ return;
135
+ try {
136
+ const baseUrl = `${mistBaseUrl.replace(/\/$/, '')}/json_${encodeURIComponent(streamName)}.js`;
137
+ const url = `${baseUrl}?metaeverywhere=1&inclzero=1`;
138
+ const response = await fetch(url, {
139
+ method: 'GET',
140
+ headers: { 'Accept': 'application/json' },
141
+ });
142
+ if (!response.ok) {
143
+ throw new Error(`HTTP ${response.status}`);
144
+ }
145
+ let text = await response.text();
146
+ // Strip JSONP callback if present
147
+ const jsonpMatch = text.match(/^[^(]+\(([\s\S]*)\);?$/);
148
+ if (jsonpMatch) {
149
+ text = jsonpMatch[1];
150
+ }
151
+ const data = JSON.parse(text);
152
+ processStreamInfo(data);
153
+ }
154
+ catch (error) {
155
+ if (!mounted)
156
+ return;
157
+ store.update(prev => ({
158
+ ...prev,
159
+ status: 'ERROR',
160
+ isOnline: false,
161
+ message: error instanceof Error ? error.message : 'Connection failed',
162
+ lastUpdate: Date.now(),
163
+ error: error instanceof Error ? error.message : 'Unknown error',
164
+ }));
165
+ }
166
+ // Schedule next poll
167
+ if (mounted && enabled && !useWebSocket) {
168
+ pollTimeout = setTimeout(pollHttp, pollInterval);
169
+ }
170
+ }
171
+ /**
172
+ * WebSocket connection with timeout fallback
173
+ */
174
+ function connectWebSocket() {
175
+ if (!mounted || !enabled || !useWebSocket)
176
+ return;
177
+ // Clean up existing
178
+ if (wsTimeout) {
179
+ clearTimeout(wsTimeout);
180
+ wsTimeout = null;
181
+ }
182
+ if (ws) {
183
+ ws.close();
184
+ ws = null;
185
+ }
186
+ try {
187
+ const wsUrl = mistBaseUrl
188
+ .replace(/^http:/, 'ws:')
189
+ .replace(/^https:/, 'wss:')
190
+ .replace(/\/$/, '');
191
+ const url = `${wsUrl}/json_${encodeURIComponent(streamName)}.js?metaeverywhere=1&inclzero=1`;
192
+ const socket = new WebSocket(url);
193
+ ws = socket;
194
+ // Timeout: if no message within 5 seconds, fall back to HTTP
195
+ wsTimeout = setTimeout(() => {
196
+ if (socket.readyState <= WebSocket.OPEN) {
197
+ console.debug('[streamState] WebSocket timeout (5s), falling back to HTTP');
198
+ socket.close();
199
+ pollHttp();
200
+ }
201
+ }, WS_TIMEOUT_MS);
202
+ socket.onopen = () => {
203
+ console.debug('[streamState] WebSocket connected');
204
+ socketReady.set(true);
205
+ };
206
+ socket.onmessage = (event) => {
207
+ if (wsTimeout) {
208
+ clearTimeout(wsTimeout);
209
+ wsTimeout = null;
210
+ }
211
+ try {
212
+ const data = JSON.parse(event.data);
213
+ processStreamInfo(data);
214
+ }
215
+ catch (e) {
216
+ console.warn('[streamState] Failed to parse WebSocket message:', e);
217
+ }
218
+ };
219
+ socket.onerror = () => {
220
+ console.warn('[streamState] WebSocket error, falling back to HTTP');
221
+ if (wsTimeout) {
222
+ clearTimeout(wsTimeout);
223
+ wsTimeout = null;
224
+ }
225
+ socket.close();
226
+ };
227
+ socket.onclose = () => {
228
+ ws = null;
229
+ socketReady.set(false);
230
+ if (!mounted || !enabled)
231
+ return;
232
+ console.debug('[streamState] WebSocket closed, starting HTTP polling');
233
+ pollHttp();
234
+ };
235
+ }
236
+ catch (error) {
237
+ console.warn('[streamState] WebSocket connection failed:', error);
238
+ pollHttp();
239
+ }
240
+ }
241
+ /**
242
+ * Manual refetch
243
+ */
244
+ function refetch() {
245
+ if (useWebSocket && ws?.readyState === WebSocket.OPEN) {
246
+ return; // WebSocket will receive updates automatically
247
+ }
248
+ pollHttp();
249
+ }
250
+ /**
251
+ * Get current WebSocket reference
252
+ */
253
+ function getSocket() {
254
+ return ws;
255
+ }
256
+ /**
257
+ * Cleanup and destroy the store
258
+ */
259
+ function destroy() {
260
+ mounted = false;
261
+ if (wsTimeout) {
262
+ clearTimeout(wsTimeout);
263
+ wsTimeout = null;
264
+ }
265
+ if (ws) {
266
+ ws.onclose = null;
267
+ ws.onerror = null;
268
+ ws.onmessage = null;
269
+ ws.onopen = null;
270
+ ws.close();
271
+ ws = null;
272
+ }
273
+ if (pollTimeout) {
274
+ clearTimeout(pollTimeout);
275
+ pollTimeout = null;
276
+ }
277
+ socketReady.set(false);
278
+ store.set(initialState);
279
+ }
280
+ // Initialize connection
281
+ if (enabled && mistBaseUrl && streamName) {
282
+ store.set({
283
+ ...initialState,
284
+ message: 'Connecting...',
285
+ lastUpdate: Date.now(),
286
+ });
287
+ // Initial HTTP poll then WebSocket
288
+ const init = async () => {
289
+ await pollHttp();
290
+ if (useWebSocket && mounted) {
291
+ connectWebSocket();
292
+ }
293
+ };
294
+ init();
295
+ }
296
+ return {
297
+ subscribe: store.subscribe,
298
+ refetch,
299
+ getSocket,
300
+ isSocketReady: { subscribe: socketReady.subscribe },
301
+ destroy,
302
+ };
303
+ }
304
+ // Convenience derived stores for common values
305
+ export function createDerivedStreamStatus(store) {
306
+ return derived(store, $state => $state.status);
307
+ }
308
+ export function createDerivedIsOnline(store) {
309
+ return derived(store, $state => $state.isOnline);
310
+ }
311
+ export function createDerivedStreamInfo(store) {
312
+ return derived(store, $state => $state.streamInfo);
313
+ }
314
+ export default createStreamStateManager;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Svelte store for Gateway GraphQL endpoint resolution with retry logic.
3
+ *
4
+ * Port of useViewerEndpoints.ts React hook to Svelte 5 stores.
5
+ */
6
+ import { type Readable } from 'svelte/store';
7
+ import type { ContentEndpoints, EndpointInfo, ContentMetadata, ContentType } from '@livepeer-frameworks/player-core';
8
+ export interface ViewerEndpointsOptions {
9
+ gatewayUrl: string;
10
+ contentType: ContentType;
11
+ contentId: string;
12
+ authToken?: string;
13
+ }
14
+ export type EndpointStatus = 'idle' | 'loading' | 'ready' | 'error';
15
+ export interface ViewerEndpointsState {
16
+ endpoints: ContentEndpoints | null;
17
+ status: EndpointStatus;
18
+ error: string | null;
19
+ }
20
+ export interface ViewerEndpointsStore extends Readable<ViewerEndpointsState> {
21
+ refetch: () => void;
22
+ destroy: () => void;
23
+ }
24
+ /**
25
+ * Create a viewer endpoints resolver store for Gateway GraphQL queries.
26
+ *
27
+ * @example
28
+ * ```svelte
29
+ * <script>
30
+ * import { createEndpointResolver } from './stores/viewerEndpoints';
31
+ *
32
+ * const resolver = createEndpointResolver({
33
+ * gatewayUrl: 'https://gateway.example.com/graphql',
34
+ * contentType: 'live',
35
+ * contentId: 'my-stream',
36
+ * });
37
+ *
38
+ * $: endpoints = $resolver.endpoints;
39
+ * $: status = $resolver.status;
40
+ * </script>
41
+ * ```
42
+ */
43
+ export declare function createEndpointResolver(options: ViewerEndpointsOptions): ViewerEndpointsStore;
44
+ export declare function createDerivedEndpoints(store: ViewerEndpointsStore): Readable<ContentEndpoints | null>;
45
+ export declare function createDerivedPrimaryEndpoint(store: ViewerEndpointsStore): Readable<EndpointInfo | null>;
46
+ export declare function createDerivedMetadata(store: ViewerEndpointsStore): Readable<ContentMetadata | null>;
47
+ export declare function createDerivedStatus(store: ViewerEndpointsStore): Readable<EndpointStatus>;
48
+ export default createEndpointResolver;