@livepeer-frameworks/player-svelte 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DevModePanel.svelte +266 -127
- package/dist/DevModePanel.svelte.d.ts +1 -1
- package/dist/DvdLogo.svelte +17 -21
- package/dist/Icons.svelte +5 -3
- package/dist/Icons.svelte.d.ts +6 -19
- package/dist/IdleScreen.svelte +277 -186
- package/dist/IdleScreen.svelte.d.ts +1 -1
- package/dist/LoadingScreen.svelte +190 -162
- package/dist/Player.svelte +244 -111
- package/dist/Player.svelte.d.ts +1 -1
- package/dist/PlayerControls.svelte +263 -168
- package/dist/PlayerControls.svelte.d.ts +1 -1
- package/dist/SeekBar.svelte +61 -35
- package/dist/SkipIndicator.svelte +4 -4
- package/dist/SkipIndicator.svelte.d.ts +1 -1
- package/dist/SpeedIndicator.svelte +1 -1
- package/dist/StatsPanel.svelte +76 -57
- package/dist/StatsPanel.svelte.d.ts +1 -1
- package/dist/StreamStateOverlay.svelte +143 -107
- package/dist/StreamStateOverlay.svelte.d.ts +1 -1
- package/dist/SubtitleRenderer.svelte +46 -43
- package/dist/ThumbnailOverlay.svelte +22 -19
- package/dist/TitleOverlay.svelte +6 -11
- package/dist/components/VolumeIcons.svelte +12 -6
- package/dist/global.d.ts +3 -3
- package/dist/icons/FullscreenExitIcon.svelte +1 -5
- package/dist/icons/FullscreenIcon.svelte +1 -5
- package/dist/icons/PauseIcon.svelte +1 -5
- package/dist/icons/PictureInPictureIcon.svelte +12 -6
- package/dist/icons/PlayIcon.svelte +1 -5
- package/dist/icons/SeekToLiveIcon.svelte +1 -5
- package/dist/icons/SettingsIcon.svelte +1 -5
- package/dist/icons/SkipBackIcon.svelte +1 -5
- package/dist/icons/SkipForwardIcon.svelte +1 -5
- package/dist/icons/StatsIcon.svelte +1 -5
- package/dist/icons/VolumeOffIcon.svelte +1 -5
- package/dist/icons/VolumeUpIcon.svelte +1 -5
- package/dist/icons/index.d.ts +12 -12
- package/dist/icons/index.js +12 -12
- package/dist/index.d.ts +24 -24
- package/dist/index.js +21 -21
- package/dist/stores/index.d.ts +6 -6
- package/dist/stores/index.js +6 -6
- package/dist/stores/playbackQuality.d.ts +2 -2
- package/dist/stores/playbackQuality.js +7 -7
- package/dist/stores/playerContext.d.ts +2 -2
- package/dist/stores/playerContext.js +17 -17
- package/dist/stores/playerController.d.ts +13 -4
- package/dist/stores/playerController.js +80 -56
- package/dist/stores/playerSelection.d.ts +2 -2
- package/dist/stores/playerSelection.js +7 -7
- package/dist/stores/streamState.d.ts +2 -2
- package/dist/stores/streamState.js +56 -56
- package/dist/stores/viewerEndpoints.d.ts +3 -3
- package/dist/stores/viewerEndpoints.js +21 -21
- package/dist/types.d.ts +1 -1
- package/dist/ui/Badge.svelte +9 -10
- package/dist/ui/Badge.svelte.d.ts +8 -29
- package/dist/ui/Button.svelte +16 -16
- package/dist/ui/Button.svelte.d.ts +8 -29
- package/dist/ui/Slider.svelte +21 -55
- package/dist/ui/badge.js +1 -1
- package/dist/ui/button.js +2 -2
- package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte +5 -7
- package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte.d.ts +6 -27
- package/dist/ui/context-menu/ContextMenuContent.svelte +2 -9
- package/dist/ui/context-menu/ContextMenuItem.svelte +1 -5
- package/dist/ui/context-menu/ContextMenuLabel.svelte +1 -5
- package/dist/ui/context-menu/ContextMenuRadioItem.svelte +5 -7
- package/dist/ui/context-menu/ContextMenuRadioItem.svelte.d.ts +6 -27
- package/dist/ui/context-menu/ContextMenuSeparator.svelte +2 -8
- package/dist/ui/context-menu/ContextMenuShortcut.svelte +2 -12
- package/dist/ui/context-menu/ContextMenuSubContent.svelte +1 -5
- package/package.json +15 -7
- package/src/DevModePanel.svelte +1 -0
- package/src/Icons.svelte +5 -3
- package/src/IdleScreen.svelte +21 -14
- package/src/LoadingScreen.svelte +20 -13
- package/src/Player.svelte +48 -2
- package/src/PlayerControls.svelte +36 -17
- package/src/SeekBar.svelte +33 -0
- package/src/StreamStateOverlay.svelte +2 -2
- package/src/stores/playerController.ts +39 -1
- package/src/stores/viewerEndpoints.ts +1 -1
- package/src/ui/Badge.svelte +7 -4
- package/src/ui/Button.svelte +13 -13
- package/src/ui/context-menu/ContextMenuCheckboxItem.svelte +4 -2
- package/src/ui/context-menu/ContextMenuRadioItem.svelte +4 -2
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Svelte store for PlayerController - wraps the core PlayerController
|
|
3
3
|
* for declarative usage in Svelte 5 components.
|
|
4
4
|
*/
|
|
5
|
-
import { writable, derived } from
|
|
6
|
-
import { PlayerController, } from
|
|
5
|
+
import { writable, derived } from "svelte/store";
|
|
6
|
+
import { PlayerController, } from "@livepeer-frameworks/player-core";
|
|
7
7
|
// ============================================================================
|
|
8
8
|
// Initial State
|
|
9
9
|
// ============================================================================
|
|
10
10
|
const initialState = {
|
|
11
|
-
state:
|
|
11
|
+
state: "booting",
|
|
12
12
|
streamState: null,
|
|
13
13
|
endpoints: null,
|
|
14
14
|
metadata: null,
|
|
@@ -21,6 +21,7 @@ const initialState = {
|
|
|
21
21
|
isMuted: true,
|
|
22
22
|
volume: 1,
|
|
23
23
|
error: null,
|
|
24
|
+
errorDetails: null,
|
|
24
25
|
isPassiveError: false,
|
|
25
26
|
hasPlaybackStarted: false,
|
|
26
27
|
isHoldingSpeed: false,
|
|
@@ -36,6 +37,7 @@ const initialState = {
|
|
|
36
37
|
currentSourceInfo: null,
|
|
37
38
|
playbackQuality: null,
|
|
38
39
|
subtitlesEnabled: false,
|
|
40
|
+
toast: null,
|
|
39
41
|
};
|
|
40
42
|
// ============================================================================
|
|
41
43
|
// Store Factory
|
|
@@ -85,7 +87,7 @@ export function createPlayerControllerStore(config) {
|
|
|
85
87
|
function syncState() {
|
|
86
88
|
if (!controller)
|
|
87
89
|
return;
|
|
88
|
-
store.update(prev => ({
|
|
90
|
+
store.update((prev) => ({
|
|
89
91
|
...prev,
|
|
90
92
|
isPlaying: controller.isPlaying(),
|
|
91
93
|
isPaused: controller.isPaused(),
|
|
@@ -108,18 +110,18 @@ export function createPlayerControllerStore(config) {
|
|
|
108
110
|
return;
|
|
109
111
|
// Clean up existing controller
|
|
110
112
|
if (controller) {
|
|
111
|
-
unsubscribers.forEach(fn => fn());
|
|
113
|
+
unsubscribers.forEach((fn) => fn());
|
|
112
114
|
unsubscribers = [];
|
|
113
115
|
controller.destroy();
|
|
114
116
|
}
|
|
115
117
|
// Create new controller
|
|
116
118
|
controller = new PlayerController(controllerConfig);
|
|
117
119
|
// Subscribe to events
|
|
118
|
-
unsubscribers.push(controller.on(
|
|
119
|
-
store.update(prev => ({ ...prev, state }));
|
|
120
|
+
unsubscribers.push(controller.on("stateChange", ({ state }) => {
|
|
121
|
+
store.update((prev) => ({ ...prev, state }));
|
|
120
122
|
}));
|
|
121
|
-
unsubscribers.push(controller.on(
|
|
122
|
-
store.update(prev => ({
|
|
123
|
+
unsubscribers.push(controller.on("streamStateChange", ({ state: streamState }) => {
|
|
124
|
+
store.update((prev) => ({
|
|
123
125
|
...prev,
|
|
124
126
|
streamState,
|
|
125
127
|
metadata: controller.getMetadata(),
|
|
@@ -127,21 +129,21 @@ export function createPlayerControllerStore(config) {
|
|
|
127
129
|
shouldShowIdleScreen: controller.shouldShowIdleScreen(),
|
|
128
130
|
}));
|
|
129
131
|
}));
|
|
130
|
-
unsubscribers.push(controller.on(
|
|
131
|
-
store.update(prev => ({ ...prev, currentTime, duration }));
|
|
132
|
+
unsubscribers.push(controller.on("timeUpdate", ({ currentTime, duration }) => {
|
|
133
|
+
store.update((prev) => ({ ...prev, currentTime, duration }));
|
|
132
134
|
}));
|
|
133
|
-
unsubscribers.push(controller.on(
|
|
134
|
-
store.update(prev => ({
|
|
135
|
+
unsubscribers.push(controller.on("error", ({ error }) => {
|
|
136
|
+
store.update((prev) => ({
|
|
135
137
|
...prev,
|
|
136
138
|
error,
|
|
137
139
|
isPassiveError: controller.isPassiveError(),
|
|
138
140
|
}));
|
|
139
141
|
}));
|
|
140
|
-
unsubscribers.push(controller.on(
|
|
141
|
-
store.update(prev => ({ ...prev, error: null, isPassiveError: false }));
|
|
142
|
+
unsubscribers.push(controller.on("errorCleared", () => {
|
|
143
|
+
store.update((prev) => ({ ...prev, error: null, isPassiveError: false }));
|
|
142
144
|
}));
|
|
143
|
-
unsubscribers.push(controller.on(
|
|
144
|
-
store.update(prev => ({
|
|
145
|
+
unsubscribers.push(controller.on("ready", ({ videoElement }) => {
|
|
146
|
+
store.update((prev) => ({
|
|
145
147
|
...prev,
|
|
146
148
|
videoElement,
|
|
147
149
|
endpoints: controller.getEndpoints(),
|
|
@@ -158,57 +160,70 @@ export function createPlayerControllerStore(config) {
|
|
|
158
160
|
return;
|
|
159
161
|
syncState();
|
|
160
162
|
};
|
|
161
|
-
video.addEventListener(
|
|
162
|
-
video.addEventListener(
|
|
163
|
-
video.addEventListener(
|
|
164
|
-
video.addEventListener(
|
|
163
|
+
video.addEventListener("play", handleVideoEvent);
|
|
164
|
+
video.addEventListener("pause", handleVideoEvent);
|
|
165
|
+
video.addEventListener("waiting", handleVideoEvent);
|
|
166
|
+
video.addEventListener("playing", handleVideoEvent);
|
|
165
167
|
unsubscribers.push(() => {
|
|
166
|
-
video.removeEventListener(
|
|
167
|
-
video.removeEventListener(
|
|
168
|
-
video.removeEventListener(
|
|
169
|
-
video.removeEventListener(
|
|
168
|
+
video.removeEventListener("play", handleVideoEvent);
|
|
169
|
+
video.removeEventListener("pause", handleVideoEvent);
|
|
170
|
+
video.removeEventListener("waiting", handleVideoEvent);
|
|
171
|
+
video.removeEventListener("playing", handleVideoEvent);
|
|
170
172
|
});
|
|
171
173
|
}));
|
|
172
|
-
unsubscribers.push(controller.on(
|
|
173
|
-
store.update(prev => ({
|
|
174
|
+
unsubscribers.push(controller.on("playerSelected", ({ player: _player, source }) => {
|
|
175
|
+
store.update((prev) => ({
|
|
174
176
|
...prev,
|
|
175
177
|
currentPlayerInfo: controller.getCurrentPlayerInfo(),
|
|
176
178
|
currentSourceInfo: { url: source.url, type: source.type },
|
|
177
179
|
}));
|
|
178
180
|
}));
|
|
179
|
-
unsubscribers.push(controller.on(
|
|
180
|
-
store.update(prev => ({ ...prev, volume, isMuted: muted }));
|
|
181
|
+
unsubscribers.push(controller.on("volumeChange", ({ volume, muted }) => {
|
|
182
|
+
store.update((prev) => ({ ...prev, volume, isMuted: muted }));
|
|
181
183
|
}));
|
|
182
|
-
unsubscribers.push(controller.on(
|
|
183
|
-
store.update(prev => ({ ...prev, isLoopEnabled }));
|
|
184
|
+
unsubscribers.push(controller.on("loopChange", ({ isLoopEnabled }) => {
|
|
185
|
+
store.update((prev) => ({ ...prev, isLoopEnabled }));
|
|
184
186
|
}));
|
|
185
|
-
unsubscribers.push(controller.on(
|
|
186
|
-
store.update(prev => ({ ...prev, isFullscreen }));
|
|
187
|
+
unsubscribers.push(controller.on("fullscreenChange", ({ isFullscreen }) => {
|
|
188
|
+
store.update((prev) => ({ ...prev, isFullscreen }));
|
|
187
189
|
}));
|
|
188
|
-
unsubscribers.push(controller.on(
|
|
189
|
-
store.update(prev => ({ ...prev, isPiPActive: isPiP }));
|
|
190
|
+
unsubscribers.push(controller.on("pipChange", ({ isPiP }) => {
|
|
191
|
+
store.update((prev) => ({ ...prev, isPiPActive: isPiP }));
|
|
190
192
|
}));
|
|
191
|
-
unsubscribers.push(controller.on(
|
|
192
|
-
store.update(prev => ({ ...prev, isHoldingSpeed: true, holdSpeed: speed }));
|
|
193
|
+
unsubscribers.push(controller.on("holdSpeedStart", ({ speed }) => {
|
|
194
|
+
store.update((prev) => ({ ...prev, isHoldingSpeed: true, holdSpeed: speed }));
|
|
193
195
|
}));
|
|
194
|
-
unsubscribers.push(controller.on(
|
|
195
|
-
store.update(prev => ({ ...prev, isHoldingSpeed: false }));
|
|
196
|
+
unsubscribers.push(controller.on("holdSpeedEnd", () => {
|
|
197
|
+
store.update((prev) => ({ ...prev, isHoldingSpeed: false }));
|
|
196
198
|
}));
|
|
197
|
-
unsubscribers.push(controller.on(
|
|
198
|
-
store.update(prev => ({ ...prev, isHovering: true, shouldShowControls: true }));
|
|
199
|
+
unsubscribers.push(controller.on("hoverStart", () => {
|
|
200
|
+
store.update((prev) => ({ ...prev, isHovering: true, shouldShowControls: true }));
|
|
199
201
|
}));
|
|
200
|
-
unsubscribers.push(controller.on(
|
|
201
|
-
store.update(prev => ({
|
|
202
|
+
unsubscribers.push(controller.on("hoverEnd", () => {
|
|
203
|
+
store.update((prev) => ({
|
|
202
204
|
...prev,
|
|
203
205
|
isHovering: false,
|
|
204
206
|
shouldShowControls: controller.shouldShowControls(),
|
|
205
207
|
}));
|
|
206
208
|
}));
|
|
207
|
-
unsubscribers.push(controller.on(
|
|
208
|
-
store.update(prev => ({ ...prev, subtitlesEnabled: enabled }));
|
|
209
|
+
unsubscribers.push(controller.on("captionsChange", ({ enabled }) => {
|
|
210
|
+
store.update((prev) => ({ ...prev, subtitlesEnabled: enabled }));
|
|
211
|
+
}));
|
|
212
|
+
// Error handling events - show toasts/modals
|
|
213
|
+
unsubscribers.push(controller.on("protocolSwapped", (data) => {
|
|
214
|
+
const message = `Switched to ${data.toProtocol}`;
|
|
215
|
+
store.update((prev) => ({ ...prev, toast: { message, timestamp: Date.now() } }));
|
|
216
|
+
}));
|
|
217
|
+
unsubscribers.push(controller.on("playbackFailed", (data) => {
|
|
218
|
+
store.update((prev) => ({
|
|
219
|
+
...prev,
|
|
220
|
+
error: data.message,
|
|
221
|
+
errorDetails: data.details ?? null,
|
|
222
|
+
isPassiveError: false,
|
|
223
|
+
}));
|
|
209
224
|
}));
|
|
210
225
|
// Set initial loop state
|
|
211
|
-
store.update(prev => ({
|
|
226
|
+
store.update((prev) => ({
|
|
212
227
|
...prev,
|
|
213
228
|
isLoopEnabled: controller.isLoopEnabled(),
|
|
214
229
|
}));
|
|
@@ -228,7 +243,7 @@ export function createPlayerControllerStore(config) {
|
|
|
228
243
|
* Destroy the store and controller
|
|
229
244
|
*/
|
|
230
245
|
function destroy() {
|
|
231
|
-
unsubscribers.forEach(fn => fn());
|
|
246
|
+
unsubscribers.forEach((fn) => fn());
|
|
232
247
|
unsubscribers = [];
|
|
233
248
|
if (controller) {
|
|
234
249
|
controller.destroy();
|
|
@@ -272,7 +287,15 @@ export function createPlayerControllerStore(config) {
|
|
|
272
287
|
}
|
|
273
288
|
function clearError() {
|
|
274
289
|
controller?.clearError();
|
|
275
|
-
store.update(prev => ({
|
|
290
|
+
store.update((prev) => ({
|
|
291
|
+
...prev,
|
|
292
|
+
error: null,
|
|
293
|
+
errorDetails: null,
|
|
294
|
+
isPassiveError: false,
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
function dismissToast() {
|
|
298
|
+
store.update((prev) => ({ ...prev, toast: null }));
|
|
276
299
|
}
|
|
277
300
|
async function retry() {
|
|
278
301
|
await controller?.retry();
|
|
@@ -322,6 +345,7 @@ export function createPlayerControllerStore(config) {
|
|
|
322
345
|
togglePiP,
|
|
323
346
|
toggleSubtitles,
|
|
324
347
|
clearError,
|
|
348
|
+
dismissToast,
|
|
325
349
|
retry,
|
|
326
350
|
reload,
|
|
327
351
|
getQualities,
|
|
@@ -337,27 +361,27 @@ export function createPlayerControllerStore(config) {
|
|
|
337
361
|
// Derived Stores (convenience)
|
|
338
362
|
// ============================================================================
|
|
339
363
|
export function createDerivedState(store) {
|
|
340
|
-
return derived(store, $state => $state.state);
|
|
364
|
+
return derived(store, ($state) => $state.state);
|
|
341
365
|
}
|
|
342
366
|
export function createDerivedIsPlaying(store) {
|
|
343
|
-
return derived(store, $state => $state.isPlaying);
|
|
367
|
+
return derived(store, ($state) => $state.isPlaying);
|
|
344
368
|
}
|
|
345
369
|
export function createDerivedCurrentTime(store) {
|
|
346
|
-
return derived(store, $state => $state.currentTime);
|
|
370
|
+
return derived(store, ($state) => $state.currentTime);
|
|
347
371
|
}
|
|
348
372
|
export function createDerivedDuration(store) {
|
|
349
|
-
return derived(store, $state => $state.duration);
|
|
373
|
+
return derived(store, ($state) => $state.duration);
|
|
350
374
|
}
|
|
351
375
|
export function createDerivedError(store) {
|
|
352
|
-
return derived(store, $state => $state.error);
|
|
376
|
+
return derived(store, ($state) => $state.error);
|
|
353
377
|
}
|
|
354
378
|
export function createDerivedVideoElement(store) {
|
|
355
|
-
return derived(store, $state => $state.videoElement);
|
|
379
|
+
return derived(store, ($state) => $state.videoElement);
|
|
356
380
|
}
|
|
357
381
|
export function createDerivedShouldShowControls(store) {
|
|
358
|
-
return derived(store, $state => $state.shouldShowControls);
|
|
382
|
+
return derived(store, ($state) => $state.shouldShowControls);
|
|
359
383
|
}
|
|
360
384
|
export function createDerivedShouldShowIdleScreen(store) {
|
|
361
|
-
return derived(store, $state => $state.shouldShowIdleScreen);
|
|
385
|
+
return derived(store, ($state) => $state.shouldShowIdleScreen);
|
|
362
386
|
}
|
|
363
387
|
export default createPlayerControllerStore;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Subscribes to PlayerManager events for reactive selection updates.
|
|
5
5
|
* Uses event-driven updates instead of polling - no render spam.
|
|
6
6
|
*/
|
|
7
|
-
import { type Readable } from
|
|
8
|
-
import { type PlayerManager, type PlayerSelection, type PlayerCombination, type StreamInfo, type PlaybackMode } from
|
|
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
9
|
export interface PlayerSelectionOptions {
|
|
10
10
|
/** Enable debug logging */
|
|
11
11
|
debug?: boolean;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Subscribes to PlayerManager events for reactive selection updates.
|
|
5
5
|
* Uses event-driven updates instead of polling - no render spam.
|
|
6
6
|
*/
|
|
7
|
-
import { writable, derived } from
|
|
7
|
+
import { writable, derived } from "svelte/store";
|
|
8
8
|
const initialState = {
|
|
9
9
|
selection: null,
|
|
10
10
|
combinations: [],
|
|
@@ -40,25 +40,25 @@ export function createPlayerSelectionStore(manager, options = {}) {
|
|
|
40
40
|
const { debug = false } = options;
|
|
41
41
|
const store = writable({ ...initialState });
|
|
42
42
|
let currentStreamInfo = null;
|
|
43
|
-
let currentPlaybackMode =
|
|
43
|
+
let currentPlaybackMode = "auto";
|
|
44
44
|
let unsubSelection = null;
|
|
45
45
|
let unsubCombos = null;
|
|
46
46
|
// Subscribe to PlayerManager events
|
|
47
47
|
function subscribe() {
|
|
48
48
|
// Clean up existing subscriptions
|
|
49
49
|
unsubscribe();
|
|
50
|
-
unsubSelection = manager.on(
|
|
50
|
+
unsubSelection = manager.on("selection-changed", (sel) => {
|
|
51
51
|
if (debug) {
|
|
52
|
-
console.log(
|
|
52
|
+
console.log("[playerSelection store] Selection changed:", sel?.player, sel?.source?.type);
|
|
53
53
|
}
|
|
54
54
|
store.update((state) => ({
|
|
55
55
|
...state,
|
|
56
56
|
selection: sel,
|
|
57
57
|
}));
|
|
58
58
|
});
|
|
59
|
-
unsubCombos = manager.on(
|
|
59
|
+
unsubCombos = manager.on("combinations-updated", (combos) => {
|
|
60
60
|
if (debug) {
|
|
61
|
-
console.log(
|
|
61
|
+
console.log("[playerSelection store] Combinations updated:", combos.length);
|
|
62
62
|
}
|
|
63
63
|
store.update((state) => ({
|
|
64
64
|
...state,
|
|
@@ -81,7 +81,7 @@ export function createPlayerSelectionStore(manager, options = {}) {
|
|
|
81
81
|
*/
|
|
82
82
|
function setStreamInfo(streamInfo, playbackMode) {
|
|
83
83
|
currentStreamInfo = streamInfo;
|
|
84
|
-
currentPlaybackMode = playbackMode ??
|
|
84
|
+
currentPlaybackMode = playbackMode ?? "auto";
|
|
85
85
|
if (!streamInfo) {
|
|
86
86
|
store.set({ ...initialState });
|
|
87
87
|
return;
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Port of useStreamState.ts React hook to Svelte 5 stores.
|
|
5
5
|
*/
|
|
6
|
-
import { type Readable } from
|
|
7
|
-
import type { StreamStatus, MistStreamInfo, StreamState } from
|
|
6
|
+
import { type Readable } from "svelte/store";
|
|
7
|
+
import type { StreamStatus, MistStreamInfo, StreamState } from "@livepeer-frameworks/player-core";
|
|
8
8
|
export interface StreamStateOptions {
|
|
9
9
|
mistBaseUrl: string;
|
|
10
10
|
streamName: string;
|
|
@@ -3,56 +3,56 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Port of useStreamState.ts React hook to Svelte 5 stores.
|
|
5
5
|
*/
|
|
6
|
-
import { writable, derived } from
|
|
6
|
+
import { writable, derived } from "svelte/store";
|
|
7
7
|
/**
|
|
8
8
|
* Parse MistServer error string into StreamStatus enum
|
|
9
9
|
*/
|
|
10
10
|
function parseErrorToStatus(error) {
|
|
11
11
|
const lowerError = error.toLowerCase();
|
|
12
|
-
if (lowerError.includes(
|
|
13
|
-
return
|
|
14
|
-
if (lowerError.includes(
|
|
15
|
-
return
|
|
16
|
-
if (lowerError.includes(
|
|
17
|
-
return
|
|
18
|
-
if (lowerError.includes(
|
|
19
|
-
return
|
|
20
|
-
if (lowerError.includes(
|
|
21
|
-
return
|
|
22
|
-
if (lowerError.includes(
|
|
23
|
-
return
|
|
24
|
-
return
|
|
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
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Get human-readable message for stream status
|
|
28
28
|
*/
|
|
29
29
|
function getStatusMessage(status, percentage) {
|
|
30
30
|
switch (status) {
|
|
31
|
-
case
|
|
32
|
-
return
|
|
33
|
-
case
|
|
34
|
-
return
|
|
35
|
-
case
|
|
31
|
+
case "ONLINE":
|
|
32
|
+
return "Stream is online";
|
|
33
|
+
case "OFFLINE":
|
|
34
|
+
return "Stream is offline";
|
|
35
|
+
case "INITIALIZING":
|
|
36
36
|
return percentage !== undefined
|
|
37
37
|
? `Initializing... ${Math.round(percentage * 10) / 10}%`
|
|
38
|
-
:
|
|
39
|
-
case
|
|
40
|
-
return
|
|
41
|
-
case
|
|
42
|
-
return
|
|
43
|
-
case
|
|
44
|
-
return
|
|
45
|
-
case
|
|
46
|
-
return
|
|
47
|
-
case
|
|
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
48
|
default:
|
|
49
|
-
return
|
|
49
|
+
return "Stream error";
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
const initialState = {
|
|
53
|
-
status:
|
|
53
|
+
status: "OFFLINE",
|
|
54
54
|
isOnline: false,
|
|
55
|
-
message:
|
|
55
|
+
message: "Connecting...",
|
|
56
56
|
lastUpdate: 0,
|
|
57
57
|
};
|
|
58
58
|
/**
|
|
@@ -93,7 +93,7 @@ export function createStreamStateManager(options) {
|
|
|
93
93
|
if (data.error) {
|
|
94
94
|
const status = parseErrorToStatus(data.error);
|
|
95
95
|
const message = data.on_error || getStatusMessage(status, data.perc);
|
|
96
|
-
store.update(prev => ({
|
|
96
|
+
store.update((prev) => ({
|
|
97
97
|
status,
|
|
98
98
|
isOnline: false,
|
|
99
99
|
message,
|
|
@@ -105,7 +105,7 @@ export function createStreamStateManager(options) {
|
|
|
105
105
|
}
|
|
106
106
|
else {
|
|
107
107
|
// Stream is online with valid metadata
|
|
108
|
-
store.update(prev => {
|
|
108
|
+
store.update((prev) => {
|
|
109
109
|
const mergedStreamInfo = {
|
|
110
110
|
...prev.streamInfo,
|
|
111
111
|
...data,
|
|
@@ -117,9 +117,9 @@ export function createStreamStateManager(options) {
|
|
|
117
117
|
},
|
|
118
118
|
};
|
|
119
119
|
return {
|
|
120
|
-
status:
|
|
120
|
+
status: "ONLINE",
|
|
121
121
|
isOnline: true,
|
|
122
|
-
message:
|
|
122
|
+
message: "Stream is online",
|
|
123
123
|
lastUpdate: Date.now(),
|
|
124
124
|
streamInfo: mergedStreamInfo,
|
|
125
125
|
};
|
|
@@ -133,11 +133,11 @@ export function createStreamStateManager(options) {
|
|
|
133
133
|
if (!mounted || !enabled)
|
|
134
134
|
return;
|
|
135
135
|
try {
|
|
136
|
-
const baseUrl = `${mistBaseUrl.replace(/\/$/,
|
|
136
|
+
const baseUrl = `${mistBaseUrl.replace(/\/$/, "")}/json_${encodeURIComponent(streamName)}.js`;
|
|
137
137
|
const url = `${baseUrl}?metaeverywhere=1&inclzero=1`;
|
|
138
138
|
const response = await fetch(url, {
|
|
139
|
-
method:
|
|
140
|
-
headers: {
|
|
139
|
+
method: "GET",
|
|
140
|
+
headers: { Accept: "application/json" },
|
|
141
141
|
});
|
|
142
142
|
if (!response.ok) {
|
|
143
143
|
throw new Error(`HTTP ${response.status}`);
|
|
@@ -154,13 +154,13 @@ export function createStreamStateManager(options) {
|
|
|
154
154
|
catch (error) {
|
|
155
155
|
if (!mounted)
|
|
156
156
|
return;
|
|
157
|
-
store.update(prev => ({
|
|
157
|
+
store.update((prev) => ({
|
|
158
158
|
...prev,
|
|
159
|
-
status:
|
|
159
|
+
status: "ERROR",
|
|
160
160
|
isOnline: false,
|
|
161
|
-
message: error instanceof Error ? error.message :
|
|
161
|
+
message: error instanceof Error ? error.message : "Connection failed",
|
|
162
162
|
lastUpdate: Date.now(),
|
|
163
|
-
error: error instanceof Error ? error.message :
|
|
163
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
164
164
|
}));
|
|
165
165
|
}
|
|
166
166
|
// Schedule next poll
|
|
@@ -185,22 +185,22 @@ export function createStreamStateManager(options) {
|
|
|
185
185
|
}
|
|
186
186
|
try {
|
|
187
187
|
const wsUrl = mistBaseUrl
|
|
188
|
-
.replace(/^http:/,
|
|
189
|
-
.replace(/^https:/,
|
|
190
|
-
.replace(/\/$/,
|
|
188
|
+
.replace(/^http:/, "ws:")
|
|
189
|
+
.replace(/^https:/, "wss:")
|
|
190
|
+
.replace(/\/$/, "");
|
|
191
191
|
const url = `${wsUrl}/json_${encodeURIComponent(streamName)}.js?metaeverywhere=1&inclzero=1`;
|
|
192
192
|
const socket = new WebSocket(url);
|
|
193
193
|
ws = socket;
|
|
194
194
|
// Timeout: if no message within 5 seconds, fall back to HTTP
|
|
195
195
|
wsTimeout = setTimeout(() => {
|
|
196
196
|
if (socket.readyState <= WebSocket.OPEN) {
|
|
197
|
-
console.debug(
|
|
197
|
+
console.debug("[streamState] WebSocket timeout (5s), falling back to HTTP");
|
|
198
198
|
socket.close();
|
|
199
199
|
pollHttp();
|
|
200
200
|
}
|
|
201
201
|
}, WS_TIMEOUT_MS);
|
|
202
202
|
socket.onopen = () => {
|
|
203
|
-
console.debug(
|
|
203
|
+
console.debug("[streamState] WebSocket connected");
|
|
204
204
|
socketReady.set(true);
|
|
205
205
|
};
|
|
206
206
|
socket.onmessage = (event) => {
|
|
@@ -213,11 +213,11 @@ export function createStreamStateManager(options) {
|
|
|
213
213
|
processStreamInfo(data);
|
|
214
214
|
}
|
|
215
215
|
catch (e) {
|
|
216
|
-
console.warn(
|
|
216
|
+
console.warn("[streamState] Failed to parse WebSocket message:", e);
|
|
217
217
|
}
|
|
218
218
|
};
|
|
219
219
|
socket.onerror = () => {
|
|
220
|
-
console.warn(
|
|
220
|
+
console.warn("[streamState] WebSocket error, falling back to HTTP");
|
|
221
221
|
if (wsTimeout) {
|
|
222
222
|
clearTimeout(wsTimeout);
|
|
223
223
|
wsTimeout = null;
|
|
@@ -229,12 +229,12 @@ export function createStreamStateManager(options) {
|
|
|
229
229
|
socketReady.set(false);
|
|
230
230
|
if (!mounted || !enabled)
|
|
231
231
|
return;
|
|
232
|
-
console.debug(
|
|
232
|
+
console.debug("[streamState] WebSocket closed, starting HTTP polling");
|
|
233
233
|
pollHttp();
|
|
234
234
|
};
|
|
235
235
|
}
|
|
236
236
|
catch (error) {
|
|
237
|
-
console.warn(
|
|
237
|
+
console.warn("[streamState] WebSocket connection failed:", error);
|
|
238
238
|
pollHttp();
|
|
239
239
|
}
|
|
240
240
|
}
|
|
@@ -281,7 +281,7 @@ export function createStreamStateManager(options) {
|
|
|
281
281
|
if (enabled && mistBaseUrl && streamName) {
|
|
282
282
|
store.set({
|
|
283
283
|
...initialState,
|
|
284
|
-
message:
|
|
284
|
+
message: "Connecting...",
|
|
285
285
|
lastUpdate: Date.now(),
|
|
286
286
|
});
|
|
287
287
|
// Initial HTTP poll then WebSocket
|
|
@@ -303,12 +303,12 @@ export function createStreamStateManager(options) {
|
|
|
303
303
|
}
|
|
304
304
|
// Convenience derived stores for common values
|
|
305
305
|
export function createDerivedStreamStatus(store) {
|
|
306
|
-
return derived(store, $state => $state.status);
|
|
306
|
+
return derived(store, ($state) => $state.status);
|
|
307
307
|
}
|
|
308
308
|
export function createDerivedIsOnline(store) {
|
|
309
|
-
return derived(store, $state => $state.isOnline);
|
|
309
|
+
return derived(store, ($state) => $state.isOnline);
|
|
310
310
|
}
|
|
311
311
|
export function createDerivedStreamInfo(store) {
|
|
312
|
-
return derived(store, $state => $state.streamInfo);
|
|
312
|
+
return derived(store, ($state) => $state.streamInfo);
|
|
313
313
|
}
|
|
314
314
|
export default createStreamStateManager;
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Port of useViewerEndpoints.ts React hook to Svelte 5 stores.
|
|
5
5
|
*/
|
|
6
|
-
import { type Readable } from
|
|
7
|
-
import type { ContentEndpoints, ContentType } from
|
|
6
|
+
import { type Readable } from "svelte/store";
|
|
7
|
+
import type { ContentEndpoints, ContentType } from "@livepeer-frameworks/player-core";
|
|
8
8
|
export interface ViewerEndpointsOptions {
|
|
9
9
|
gatewayUrl: string;
|
|
10
10
|
contentId: string;
|
|
11
11
|
contentType?: ContentType;
|
|
12
12
|
authToken?: string;
|
|
13
13
|
}
|
|
14
|
-
export type EndpointStatus =
|
|
14
|
+
export type EndpointStatus = "idle" | "loading" | "ready" | "error";
|
|
15
15
|
export interface ViewerEndpointsState {
|
|
16
16
|
endpoints: ContentEndpoints | null;
|
|
17
17
|
status: EndpointStatus;
|