@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,650 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
DevModePanel.svelte - Advanced settings overlay for testing player configurations
|
|
3
|
+
Port of src/components/DevModePanel.tsx
|
|
4
|
+
-->
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import {
|
|
7
|
+
cn,
|
|
8
|
+
globalPlayerManager,
|
|
9
|
+
QualityMonitor,
|
|
10
|
+
type StreamInfo,
|
|
11
|
+
type MistStreamInfo,
|
|
12
|
+
type PlaybackMode,
|
|
13
|
+
} from '@livepeer-frameworks/player-core';
|
|
14
|
+
import Button from './ui/Button.svelte';
|
|
15
|
+
import Badge from './ui/Badge.svelte';
|
|
16
|
+
|
|
17
|
+
/** Short labels for source types */
|
|
18
|
+
const SOURCE_TYPE_LABELS: Record<string, string> = {
|
|
19
|
+
'html5/application/vnd.apple.mpegurl': 'HLS',
|
|
20
|
+
'dash/video/mp4': 'DASH',
|
|
21
|
+
'html5/video/mp4': 'MP4',
|
|
22
|
+
'html5/video/webm': 'WebM',
|
|
23
|
+
'whep': 'WHEP',
|
|
24
|
+
'mist/html': 'Mist',
|
|
25
|
+
'mist/legacy': 'Auto',
|
|
26
|
+
'ws/video/mp4': 'MEWS',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
interface Props {
|
|
30
|
+
/** Callback when user selects a combo (one-shot selection) */
|
|
31
|
+
onSettingsChange: (settings: { forcePlayer?: string; forceType?: string; forceSource?: number }) => void;
|
|
32
|
+
playbackMode?: PlaybackMode;
|
|
33
|
+
onModeChange?: (mode: PlaybackMode) => void;
|
|
34
|
+
onReload?: () => void;
|
|
35
|
+
streamInfo?: StreamInfo | null;
|
|
36
|
+
mistStreamInfo?: MistStreamInfo | null;
|
|
37
|
+
currentPlayer?: { name: string; shortname: string } | null;
|
|
38
|
+
currentSource?: { url: string; type: string } | null;
|
|
39
|
+
videoElement?: HTMLVideoElement | null;
|
|
40
|
+
protocol?: string;
|
|
41
|
+
nodeId?: string;
|
|
42
|
+
isVisible?: boolean;
|
|
43
|
+
isOpen?: boolean;
|
|
44
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let {
|
|
48
|
+
onSettingsChange,
|
|
49
|
+
playbackMode = 'auto',
|
|
50
|
+
onModeChange = undefined,
|
|
51
|
+
onReload = undefined,
|
|
52
|
+
streamInfo = null,
|
|
53
|
+
mistStreamInfo = null,
|
|
54
|
+
currentPlayer = null,
|
|
55
|
+
currentSource = null,
|
|
56
|
+
videoElement = null,
|
|
57
|
+
protocol = undefined,
|
|
58
|
+
nodeId = undefined,
|
|
59
|
+
isVisible = true,
|
|
60
|
+
isOpen: controlledIsOpen = undefined,
|
|
61
|
+
onOpenChange = undefined,
|
|
62
|
+
}: Props = $props();
|
|
63
|
+
|
|
64
|
+
// Internal state
|
|
65
|
+
let internalIsOpen = $state(false);
|
|
66
|
+
let activeTab = $state<'config' | 'stats'>('config');
|
|
67
|
+
let hoveredComboIndex = $state<number | null>(null);
|
|
68
|
+
let tooltipAbove = $state(false);
|
|
69
|
+
let showDisabledPlayers = $state(false);
|
|
70
|
+
let comboListRef: HTMLDivElement | undefined = $state();
|
|
71
|
+
|
|
72
|
+
// Quality monitoring state
|
|
73
|
+
let playbackScore = $state(1.0);
|
|
74
|
+
let qualityScore = $state(100);
|
|
75
|
+
let stallCount = $state(0);
|
|
76
|
+
let frameDropRate = $state(0);
|
|
77
|
+
let qualityMonitor: QualityMonitor | null = null;
|
|
78
|
+
|
|
79
|
+
// Video stats
|
|
80
|
+
let stats = $state<{
|
|
81
|
+
resolution: string;
|
|
82
|
+
buffered: string;
|
|
83
|
+
playbackRate: string;
|
|
84
|
+
currentTime: string;
|
|
85
|
+
duration: string;
|
|
86
|
+
readyState: number;
|
|
87
|
+
networkState: number;
|
|
88
|
+
} | null>(null);
|
|
89
|
+
|
|
90
|
+
// Player-specific stats (HLS.js / WebRTC)
|
|
91
|
+
let playerStats = $state<any>(null);
|
|
92
|
+
let statsIntervalRef: ReturnType<typeof setInterval> | null = null;
|
|
93
|
+
|
|
94
|
+
// Controlled/uncontrolled state
|
|
95
|
+
let isOpen = $derived(controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen);
|
|
96
|
+
|
|
97
|
+
function setIsOpen(value: boolean) {
|
|
98
|
+
if (onOpenChange) {
|
|
99
|
+
onOpenChange(value);
|
|
100
|
+
} else {
|
|
101
|
+
internalIsOpen = value;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Get all player-source combinations with scores
|
|
106
|
+
// getAllCombinations now includes all combos (compatible + incompatible)
|
|
107
|
+
// and uses content-based caching - won't spam on every MistServer update
|
|
108
|
+
let allCombinations = $derived.by(() => {
|
|
109
|
+
if (!streamInfo) return [];
|
|
110
|
+
try {
|
|
111
|
+
return globalPlayerManager.getAllCombinations(streamInfo, playbackMode);
|
|
112
|
+
} catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
let combinations = $derived(allCombinations.filter(c => c.compatible));
|
|
118
|
+
|
|
119
|
+
// Find active combo index
|
|
120
|
+
let activeComboIndex = $derived.by(() => {
|
|
121
|
+
if (!currentPlayer || !currentSource || allCombinations.length === 0) return -1;
|
|
122
|
+
return allCombinations.findIndex(
|
|
123
|
+
c => c.player === currentPlayer.shortname && c.sourceType === currentSource.type
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
let activeCompatibleIndex = $derived.by(() => {
|
|
128
|
+
if (!currentPlayer || !currentSource || combinations.length === 0) return -1;
|
|
129
|
+
return combinations.findIndex(
|
|
130
|
+
c => c.player === currentPlayer.shortname && c.sourceType === currentSource.type
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Handlers
|
|
135
|
+
function handleReload() {
|
|
136
|
+
// Just trigger reload - controller manages the state
|
|
137
|
+
onReload?.();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function handleNextCombo() {
|
|
141
|
+
if (combinations.length === 0) return;
|
|
142
|
+
const startIdx = activeCompatibleIndex >= 0 ? activeCompatibleIndex : -1;
|
|
143
|
+
const nextIdx = (startIdx + 1) % combinations.length;
|
|
144
|
+
const combo = combinations[nextIdx];
|
|
145
|
+
onSettingsChange({
|
|
146
|
+
forcePlayer: combo.player,
|
|
147
|
+
forceType: combo.sourceType,
|
|
148
|
+
forceSource: combo.sourceIndex,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function handleSelectCombo(index: number) {
|
|
153
|
+
const combo = allCombinations[index];
|
|
154
|
+
if (!combo) return;
|
|
155
|
+
onSettingsChange({
|
|
156
|
+
forcePlayer: combo.player,
|
|
157
|
+
forceType: combo.sourceType,
|
|
158
|
+
forceSource: combo.sourceIndex,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function handleComboHover(index: number, e: MouseEvent) {
|
|
163
|
+
hoveredComboIndex = index;
|
|
164
|
+
if (comboListRef) {
|
|
165
|
+
const container = comboListRef;
|
|
166
|
+
const row = e.currentTarget as HTMLElement;
|
|
167
|
+
const containerRect = container.getBoundingClientRect();
|
|
168
|
+
const rowRect = row.getBoundingClientRect();
|
|
169
|
+
const relativePosition = (rowRect.top - containerRect.top) / containerRect.height;
|
|
170
|
+
tooltipAbove = relativePosition > 0.6;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Quality monitoring
|
|
175
|
+
$effect(() => {
|
|
176
|
+
if (videoElement && isOpen) {
|
|
177
|
+
if (!qualityMonitor) {
|
|
178
|
+
qualityMonitor = new QualityMonitor({
|
|
179
|
+
sampleInterval: 500,
|
|
180
|
+
onSample: (quality) => {
|
|
181
|
+
qualityScore = quality.score;
|
|
182
|
+
stallCount = quality.stallCount;
|
|
183
|
+
frameDropRate = quality.frameDropRate;
|
|
184
|
+
if (qualityMonitor) {
|
|
185
|
+
playbackScore = qualityMonitor.getPlaybackScore();
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
qualityMonitor.start(videoElement);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return () => {
|
|
194
|
+
qualityMonitor?.stop();
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Video stats polling
|
|
199
|
+
$effect(() => {
|
|
200
|
+
if (!isOpen || activeTab !== 'stats') return;
|
|
201
|
+
|
|
202
|
+
function updateStats() {
|
|
203
|
+
const player = globalPlayerManager.getCurrentPlayer();
|
|
204
|
+
const v = player?.getVideoElement() || videoElement;
|
|
205
|
+
if (!v) {
|
|
206
|
+
stats = null;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
stats = {
|
|
210
|
+
resolution: `${v.videoWidth}x${v.videoHeight}`,
|
|
211
|
+
buffered: v.buffered.length > 0
|
|
212
|
+
? (v.buffered.end(v.buffered.length - 1) - v.currentTime).toFixed(1)
|
|
213
|
+
: '0',
|
|
214
|
+
playbackRate: v.playbackRate.toFixed(2),
|
|
215
|
+
currentTime: v.currentTime.toFixed(1),
|
|
216
|
+
duration: isFinite(v.duration) ? v.duration.toFixed(1) : 'live',
|
|
217
|
+
readyState: v.readyState,
|
|
218
|
+
networkState: v.networkState,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
updateStats();
|
|
223
|
+
const interval = setInterval(updateStats, 500);
|
|
224
|
+
return () => clearInterval(interval);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Poll player-specific stats when stats tab is open
|
|
228
|
+
$effect(() => {
|
|
229
|
+
if (!isOpen || activeTab !== 'stats') {
|
|
230
|
+
playerStats = null;
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function pollStats() {
|
|
235
|
+
try {
|
|
236
|
+
const player = globalPlayerManager.getCurrentPlayer();
|
|
237
|
+
if (player && typeof player.getStats === 'function') {
|
|
238
|
+
const stats = await player.getStats();
|
|
239
|
+
if (stats) {
|
|
240
|
+
playerStats = stats;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
// Ignore errors
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Poll immediately and then every 500ms
|
|
249
|
+
pollStats();
|
|
250
|
+
statsIntervalRef = setInterval(pollStats, 500);
|
|
251
|
+
|
|
252
|
+
return () => {
|
|
253
|
+
if (statsIntervalRef) {
|
|
254
|
+
clearInterval(statsIntervalRef);
|
|
255
|
+
statsIntervalRef = null;
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
</script>
|
|
260
|
+
|
|
261
|
+
{#if !isOpen}
|
|
262
|
+
<button
|
|
263
|
+
type="button"
|
|
264
|
+
onclick={() => setIsOpen(true)}
|
|
265
|
+
class={cn(
|
|
266
|
+
'fw-dev-toggle',
|
|
267
|
+
isVisible ? '' : 'fw-dev-toggle--hidden'
|
|
268
|
+
)}
|
|
269
|
+
title="Advanced Settings"
|
|
270
|
+
aria-label="Open advanced settings panel"
|
|
271
|
+
>
|
|
272
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
273
|
+
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
|
|
274
|
+
</svg>
|
|
275
|
+
</button>
|
|
276
|
+
{:else}
|
|
277
|
+
<div class="fw-dev-panel">
|
|
278
|
+
<!-- Header with tabs -->
|
|
279
|
+
<div class="fw-dev-header">
|
|
280
|
+
<button
|
|
281
|
+
type="button"
|
|
282
|
+
onclick={() => activeTab = 'config'}
|
|
283
|
+
class={cn('fw-dev-tab', activeTab === 'config' && 'fw-dev-tab--active')}
|
|
284
|
+
>
|
|
285
|
+
Config
|
|
286
|
+
</button>
|
|
287
|
+
<button
|
|
288
|
+
type="button"
|
|
289
|
+
onclick={() => activeTab = 'stats'}
|
|
290
|
+
class={cn('fw-dev-tab', activeTab === 'stats' && 'fw-dev-tab--active')}
|
|
291
|
+
>
|
|
292
|
+
Stats
|
|
293
|
+
</button>
|
|
294
|
+
<div class="fw-dev-spacer"></div>
|
|
295
|
+
<button
|
|
296
|
+
type="button"
|
|
297
|
+
onclick={() => setIsOpen(false)}
|
|
298
|
+
class="fw-dev-close"
|
|
299
|
+
aria-label="Close dev mode panel"
|
|
300
|
+
>
|
|
301
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
302
|
+
<path d="M2 2l8 8M10 2l-8 8" />
|
|
303
|
+
</svg>
|
|
304
|
+
</button>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
{#if activeTab === 'config'}
|
|
308
|
+
<div bind:this={comboListRef} class="fw-dev-body">
|
|
309
|
+
<!-- Current State -->
|
|
310
|
+
<div class="fw-dev-section">
|
|
311
|
+
<div class="fw-dev-label">Active</div>
|
|
312
|
+
<div class="fw-dev-value">
|
|
313
|
+
{currentPlayer?.name || 'None'}{' '}
|
|
314
|
+
<span class="fw-dev-value-arrow">→</span>{' '}
|
|
315
|
+
{SOURCE_TYPE_LABELS[currentSource?.type || ''] || currentSource?.type || '—'}
|
|
316
|
+
</div>
|
|
317
|
+
{#if nodeId}
|
|
318
|
+
<div class="fw-dev-value-muted">Node: {nodeId}</div>
|
|
319
|
+
{/if}
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
<!-- Playback Mode Selector -->
|
|
323
|
+
<div class="fw-dev-section">
|
|
324
|
+
<div class="fw-dev-label">Playback Mode</div>
|
|
325
|
+
<div class="fw-dev-mode-group">
|
|
326
|
+
{#each ['auto', 'low-latency', 'quality'] as mode}
|
|
327
|
+
<button
|
|
328
|
+
type="button"
|
|
329
|
+
onclick={() => onModeChange?.(mode as PlaybackMode)}
|
|
330
|
+
class={cn('fw-dev-mode-btn', playbackMode === mode && 'fw-dev-mode-btn--active')}
|
|
331
|
+
>
|
|
332
|
+
{mode === 'low-latency' ? 'Low Lat' : mode.charAt(0).toUpperCase() + mode.slice(1)}
|
|
333
|
+
</button>
|
|
334
|
+
{/each}
|
|
335
|
+
</div>
|
|
336
|
+
<div class="fw-dev-mode-desc">
|
|
337
|
+
{#if playbackMode === 'auto'}Balanced: MP4/WS → WHEP → HLS{/if}
|
|
338
|
+
{#if playbackMode === 'low-latency'}WHEP/WebRTC first (sub-1s delay){/if}
|
|
339
|
+
{#if playbackMode === 'quality'}MP4/WS first, HLS fallback{/if}
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<!-- Action buttons -->
|
|
344
|
+
<div class="fw-dev-actions">
|
|
345
|
+
<button type="button" onclick={handleReload} class="fw-dev-action-btn">
|
|
346
|
+
Reload
|
|
347
|
+
</button>
|
|
348
|
+
<button type="button" onclick={handleNextCombo} class="fw-dev-action-btn">
|
|
349
|
+
Next Option
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
<!-- Combo list -->
|
|
354
|
+
<div class="fw-dev-section fw-dev-section-header">
|
|
355
|
+
<div class="fw-dev-list-header">
|
|
356
|
+
<span class="fw-dev-list-title">
|
|
357
|
+
Player Options ({combinations.length})
|
|
358
|
+
</span>
|
|
359
|
+
{#if allCombinations.length > combinations.length}
|
|
360
|
+
<button
|
|
361
|
+
type="button"
|
|
362
|
+
onclick={() => showDisabledPlayers = !showDisabledPlayers}
|
|
363
|
+
class="fw-dev-list-toggle"
|
|
364
|
+
>
|
|
365
|
+
<svg
|
|
366
|
+
width="10"
|
|
367
|
+
height="10"
|
|
368
|
+
viewBox="0 0 24 24"
|
|
369
|
+
fill="none"
|
|
370
|
+
stroke="currentColor"
|
|
371
|
+
stroke-width="2"
|
|
372
|
+
class={cn('fw-dev-chevron', showDisabledPlayers && 'fw-dev-chevron--open')}
|
|
373
|
+
>
|
|
374
|
+
<path d="M6 9l6 6 6-6" />
|
|
375
|
+
</svg>
|
|
376
|
+
{showDisabledPlayers ? 'Hide' : 'Show'} disabled ({allCombinations.length - combinations.length})
|
|
377
|
+
</button>
|
|
378
|
+
{/if}
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
{#if allCombinations.length === 0}
|
|
382
|
+
<div class="fw-dev-list-empty">No stream info available</div>
|
|
383
|
+
{:else}
|
|
384
|
+
{#each allCombinations as combo, index}
|
|
385
|
+
{@const isCodecIncompat = (combo as any).codecIncompatible === true}
|
|
386
|
+
{@const shouldShow = combo.compatible || isCodecIncompat || showDisabledPlayers}
|
|
387
|
+
{@const isActive = activeComboIndex === index}
|
|
388
|
+
{@const typeLabel = SOURCE_TYPE_LABELS[combo.sourceType] || combo.sourceType.split('/').pop()}
|
|
389
|
+
|
|
390
|
+
{#if shouldShow}
|
|
391
|
+
<div
|
|
392
|
+
class="fw-dev-combo"
|
|
393
|
+
onmouseenter={(e) => handleComboHover(index, e)}
|
|
394
|
+
onmouseleave={() => hoveredComboIndex = null}
|
|
395
|
+
>
|
|
396
|
+
<button
|
|
397
|
+
type="button"
|
|
398
|
+
onclick={() => handleSelectCombo(index)}
|
|
399
|
+
class={cn(
|
|
400
|
+
'fw-dev-combo-btn',
|
|
401
|
+
isActive && 'fw-dev-combo-btn--active',
|
|
402
|
+
!combo.compatible && !isCodecIncompat && 'fw-dev-combo-btn--disabled',
|
|
403
|
+
isCodecIncompat && 'fw-dev-combo-btn--codec-warn'
|
|
404
|
+
)}
|
|
405
|
+
>
|
|
406
|
+
<!-- Rank -->
|
|
407
|
+
<span class={cn(
|
|
408
|
+
'fw-dev-combo-rank',
|
|
409
|
+
isActive ? 'fw-dev-combo-rank--active' :
|
|
410
|
+
!combo.compatible && !isCodecIncompat ? 'fw-dev-combo-rank--disabled' :
|
|
411
|
+
isCodecIncompat ? 'fw-dev-combo-rank--warn' : ''
|
|
412
|
+
)}>
|
|
413
|
+
{combo.compatible ? index + 1 : isCodecIncompat ? '⚠' : '—'}
|
|
414
|
+
</span>
|
|
415
|
+
<!-- Player + Protocol -->
|
|
416
|
+
<span class="fw-dev-combo-name">
|
|
417
|
+
{combo.playerName}{' '}
|
|
418
|
+
<span class="fw-dev-combo-arrow">→</span>{' '}
|
|
419
|
+
<span class={cn(
|
|
420
|
+
'fw-dev-combo-type',
|
|
421
|
+
isCodecIncompat && 'fw-dev-combo-type--warn',
|
|
422
|
+
!combo.compatible && !isCodecIncompat && 'fw-dev-combo-type--disabled'
|
|
423
|
+
)}>{typeLabel}</span>
|
|
424
|
+
</span>
|
|
425
|
+
<!-- Score -->
|
|
426
|
+
<span class={cn(
|
|
427
|
+
'fw-dev-combo-score',
|
|
428
|
+
!combo.compatible && !isCodecIncompat ? 'fw-dev-combo-score--disabled' :
|
|
429
|
+
isCodecIncompat ? 'fw-dev-combo-score--low' :
|
|
430
|
+
combo.score >= 2 ? 'fw-dev-combo-score--high' :
|
|
431
|
+
combo.score >= 1.5 ? 'fw-dev-combo-score--mid' :
|
|
432
|
+
'fw-dev-combo-score--low'
|
|
433
|
+
)}>
|
|
434
|
+
{combo.score.toFixed(2)}
|
|
435
|
+
</span>
|
|
436
|
+
</button>
|
|
437
|
+
|
|
438
|
+
<!-- Tooltip -->
|
|
439
|
+
{#if hoveredComboIndex === index}
|
|
440
|
+
<div class={cn(
|
|
441
|
+
'fw-dev-tooltip',
|
|
442
|
+
tooltipAbove ? 'fw-dev-tooltip--above' : 'fw-dev-tooltip--below'
|
|
443
|
+
)}>
|
|
444
|
+
<div class="fw-dev-tooltip-header">
|
|
445
|
+
<div class="fw-dev-tooltip-title">{combo.playerName}</div>
|
|
446
|
+
<div class="fw-dev-tooltip-subtitle">{combo.sourceType}</div>
|
|
447
|
+
{#if combo.scoreBreakdown?.trackTypes?.length}
|
|
448
|
+
<div class="fw-dev-tooltip-tracks">
|
|
449
|
+
Tracks: <span class="fw-dev-tooltip-value">{combo.scoreBreakdown.trackTypes.join(', ')}</span>
|
|
450
|
+
</div>
|
|
451
|
+
{/if}
|
|
452
|
+
</div>
|
|
453
|
+
{#if combo.compatible && combo.scoreBreakdown}
|
|
454
|
+
<div class="fw-dev-tooltip-score">Score: {combo.score.toFixed(2)}</div>
|
|
455
|
+
<div class="fw-dev-tooltip-row">Tracks: <span class="fw-dev-tooltip-value">{combo.scoreBreakdown.trackScore.toFixed(2)}</span> <span class="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.tracks}</span></div>
|
|
456
|
+
<div class="fw-dev-tooltip-row">Priority: <span class="fw-dev-tooltip-value">{combo.scoreBreakdown.priorityScore.toFixed(2)}</span> <span class="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.priority}</span></div>
|
|
457
|
+
<div class="fw-dev-tooltip-row">Source: <span class="fw-dev-tooltip-value">{combo.scoreBreakdown.sourceScore.toFixed(2)}</span> <span class="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.source}</span></div>
|
|
458
|
+
{:else}
|
|
459
|
+
<div class="fw-dev-tooltip-error">{combo.incompatibleReason || 'Incompatible'}</div>
|
|
460
|
+
{/if}
|
|
461
|
+
</div>
|
|
462
|
+
{/if}
|
|
463
|
+
</div>
|
|
464
|
+
{/if}
|
|
465
|
+
{/each}
|
|
466
|
+
{/if}
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
{:else if activeTab === 'stats'}
|
|
470
|
+
<div class="fw-dev-body">
|
|
471
|
+
<!-- Playback Rate -->
|
|
472
|
+
<div class="fw-dev-section">
|
|
473
|
+
<div class="fw-dev-label">Playback Rate</div>
|
|
474
|
+
<div class="fw-dev-rate">
|
|
475
|
+
<div class={cn(
|
|
476
|
+
'fw-dev-rate-value',
|
|
477
|
+
playbackScore >= 0.95 && playbackScore <= 1.05 ? 'fw-dev-stat-value--good' :
|
|
478
|
+
playbackScore > 1.05 ? 'fw-dev-stat-value--accent' :
|
|
479
|
+
playbackScore >= 0.75 ? 'fw-dev-stat-value--warn' :
|
|
480
|
+
'fw-dev-stat-value--bad'
|
|
481
|
+
)}>
|
|
482
|
+
{playbackScore.toFixed(2)}×
|
|
483
|
+
</div>
|
|
484
|
+
<div class="fw-dev-rate-status">
|
|
485
|
+
{playbackScore >= 0.95 && playbackScore <= 1.05 ? 'realtime' :
|
|
486
|
+
playbackScore > 1.05 ? 'catching up' :
|
|
487
|
+
playbackScore >= 0.75 ? 'slightly slow' : 'stalling'}
|
|
488
|
+
</div>
|
|
489
|
+
</div>
|
|
490
|
+
<div class="fw-dev-rate-stats">
|
|
491
|
+
<span class={qualityScore >= 75 ? 'fw-dev-stat-value--good' : 'fw-dev-stat-value--bad'}>Quality: {qualityScore}/100</span>
|
|
492
|
+
<span class={stallCount === 0 ? 'fw-dev-stat-value--good' : 'fw-dev-stat-value--warn'}>Stalls: {stallCount}</span>
|
|
493
|
+
<span class={frameDropRate < 1 ? 'fw-dev-stat-value--good' : 'fw-dev-stat-value--bad'}>Drops: {frameDropRate.toFixed(1)}%</span>
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
|
|
497
|
+
<!-- Video Stats -->
|
|
498
|
+
{#if stats}
|
|
499
|
+
<div class="fw-dev-stat"><span class="fw-dev-stat-label">Resolution</span><span class="fw-dev-stat-value">{stats.resolution}</span></div>
|
|
500
|
+
<div class="fw-dev-stat"><span class="fw-dev-stat-label">Buffer</span><span class="fw-dev-stat-value">{stats.buffered}s</span></div>
|
|
501
|
+
<div class="fw-dev-stat"><span class="fw-dev-stat-label">Playback Rate</span><span class="fw-dev-stat-value">{stats.playbackRate}x</span></div>
|
|
502
|
+
<div class="fw-dev-stat"><span class="fw-dev-stat-label">Time</span><span class="fw-dev-stat-value">{stats.currentTime} / {stats.duration}</span></div>
|
|
503
|
+
<div class="fw-dev-stat"><span class="fw-dev-stat-label">Ready State</span><span class="fw-dev-stat-value">{stats.readyState}</span></div>
|
|
504
|
+
<div class="fw-dev-stat"><span class="fw-dev-stat-label">Network State</span><span class="fw-dev-stat-value">{stats.networkState}</span></div>
|
|
505
|
+
{#if protocol}
|
|
506
|
+
<div class="fw-dev-stat"><span class="fw-dev-stat-label">Protocol</span><span class="fw-dev-stat-value">{protocol}</span></div>
|
|
507
|
+
{/if}
|
|
508
|
+
{#if nodeId}
|
|
509
|
+
<div class="fw-dev-stat"><span class="fw-dev-stat-label">Node ID</span><span class="fw-dev-stat-value">{nodeId}</span></div>
|
|
510
|
+
{/if}
|
|
511
|
+
{:else}
|
|
512
|
+
<div class="fw-dev-list-empty">No video element available</div>
|
|
513
|
+
{/if}
|
|
514
|
+
|
|
515
|
+
<!-- Player-specific Stats (HLS.js / WebRTC) -->
|
|
516
|
+
{#if playerStats}
|
|
517
|
+
<div class="fw-dev-section fw-dev-section-header">
|
|
518
|
+
<div class="fw-dev-label">
|
|
519
|
+
{playerStats.type === 'hls' ? 'HLS.js Stats' :
|
|
520
|
+
playerStats.type === 'webrtc' ? 'WebRTC Stats' : 'Player Stats'}
|
|
521
|
+
</div>
|
|
522
|
+
</div>
|
|
523
|
+
<!-- HLS-specific stats -->
|
|
524
|
+
{#if playerStats.type === 'hls'}
|
|
525
|
+
<div class="fw-dev-stat">
|
|
526
|
+
<span class="fw-dev-stat-label">Bitrate</span>
|
|
527
|
+
<span class="fw-dev-stat-value fw-dev-stat-value--accent">
|
|
528
|
+
{playerStats.currentBitrate > 0 ? `${Math.round(playerStats.currentBitrate / 1000)} kbps` : 'N/A'}
|
|
529
|
+
</span>
|
|
530
|
+
</div>
|
|
531
|
+
<div class="fw-dev-stat">
|
|
532
|
+
<span class="fw-dev-stat-label">Bandwidth Est.</span>
|
|
533
|
+
<span class="fw-dev-stat-value">
|
|
534
|
+
{playerStats.bandwidthEstimate > 0 ? `${Math.round(playerStats.bandwidthEstimate / 1000)} kbps` : 'N/A'}
|
|
535
|
+
</span>
|
|
536
|
+
</div>
|
|
537
|
+
<div class="fw-dev-stat">
|
|
538
|
+
<span class="fw-dev-stat-label">Level</span>
|
|
539
|
+
<span class="fw-dev-stat-value">
|
|
540
|
+
{playerStats.currentLevel >= 0 ? playerStats.currentLevel : 'Auto'} / {playerStats.levels?.length || 0}
|
|
541
|
+
</span>
|
|
542
|
+
</div>
|
|
543
|
+
{#if playerStats.latency !== undefined}
|
|
544
|
+
<div class="fw-dev-stat">
|
|
545
|
+
<span class="fw-dev-stat-label">Latency</span>
|
|
546
|
+
<span class={playerStats.latency > 5000 ? 'fw-dev-stat-value fw-dev-stat-value--warn' : 'fw-dev-stat-value'}>
|
|
547
|
+
{Math.round(playerStats.latency)} ms
|
|
548
|
+
</span>
|
|
549
|
+
</div>
|
|
550
|
+
{/if}
|
|
551
|
+
{/if}
|
|
552
|
+
|
|
553
|
+
<!-- WebRTC-specific stats -->
|
|
554
|
+
{#if playerStats.type === 'webrtc'}
|
|
555
|
+
{#if playerStats.video}
|
|
556
|
+
<div class="fw-dev-stat">
|
|
557
|
+
<span class="fw-dev-stat-label">Video Bitrate</span>
|
|
558
|
+
<span class="fw-dev-stat-value fw-dev-stat-value--accent">
|
|
559
|
+
{playerStats.video.bitrate > 0 ? `${Math.round(playerStats.video.bitrate / 1000)} kbps` : 'N/A'}
|
|
560
|
+
</span>
|
|
561
|
+
</div>
|
|
562
|
+
<div class="fw-dev-stat">
|
|
563
|
+
<span class="fw-dev-stat-label">FPS</span>
|
|
564
|
+
<span class="fw-dev-stat-value">{Math.round(playerStats.video.framesPerSecond || 0)}</span>
|
|
565
|
+
</div>
|
|
566
|
+
<div class="fw-dev-stat">
|
|
567
|
+
<span class="fw-dev-stat-label">Frames</span>
|
|
568
|
+
<span class="fw-dev-stat-value">
|
|
569
|
+
{playerStats.video.framesDecoded} decoded,{' '}
|
|
570
|
+
<span class={playerStats.video.frameDropRate > 1 ? 'fw-dev-stat-value--bad' : 'fw-dev-stat-value--good'}>
|
|
571
|
+
{playerStats.video.framesDropped} dropped
|
|
572
|
+
</span>
|
|
573
|
+
</span>
|
|
574
|
+
</div>
|
|
575
|
+
<div class="fw-dev-stat">
|
|
576
|
+
<span class="fw-dev-stat-label">Packet Loss</span>
|
|
577
|
+
<span class={playerStats.video.packetLossRate > 1 ? 'fw-dev-stat-value fw-dev-stat-value--bad' : 'fw-dev-stat-value fw-dev-stat-value--good'}>
|
|
578
|
+
{playerStats.video.packetLossRate?.toFixed(2) || 0}%
|
|
579
|
+
</span>
|
|
580
|
+
</div>
|
|
581
|
+
<div class="fw-dev-stat">
|
|
582
|
+
<span class="fw-dev-stat-label">Jitter</span>
|
|
583
|
+
<span class={playerStats.video.jitter > 30 ? 'fw-dev-stat-value fw-dev-stat-value--warn' : 'fw-dev-stat-value'}>
|
|
584
|
+
{playerStats.video.jitter?.toFixed(1) || 0} ms
|
|
585
|
+
</span>
|
|
586
|
+
</div>
|
|
587
|
+
<div class="fw-dev-stat">
|
|
588
|
+
<span class="fw-dev-stat-label">Jitter Buffer</span>
|
|
589
|
+
<span class="fw-dev-stat-value">{playerStats.video.jitterBufferDelay?.toFixed(1) || 0} ms</span>
|
|
590
|
+
</div>
|
|
591
|
+
{/if}
|
|
592
|
+
{#if playerStats.network}
|
|
593
|
+
<div class="fw-dev-stat">
|
|
594
|
+
<span class="fw-dev-stat-label">RTT</span>
|
|
595
|
+
<span class={playerStats.network.rtt > 200 ? 'fw-dev-stat-value fw-dev-stat-value--warn' : 'fw-dev-stat-value'}>
|
|
596
|
+
{Math.round(playerStats.network.rtt || 0)} ms
|
|
597
|
+
</span>
|
|
598
|
+
</div>
|
|
599
|
+
{/if}
|
|
600
|
+
{/if}
|
|
601
|
+
{/if}
|
|
602
|
+
|
|
603
|
+
<!-- MistServer Track Info -->
|
|
604
|
+
{#if mistStreamInfo?.meta?.tracks && Object.keys(mistStreamInfo.meta.tracks).length > 0}
|
|
605
|
+
<div class="fw-dev-section fw-dev-section-header">
|
|
606
|
+
<div class="fw-dev-label">
|
|
607
|
+
Tracks ({Object.keys(mistStreamInfo.meta.tracks).length})
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
{#each Object.entries(mistStreamInfo.meta.tracks) as [id, track]}
|
|
611
|
+
<div class="fw-dev-track">
|
|
612
|
+
<div class="fw-dev-track-header">
|
|
613
|
+
<span class={cn(
|
|
614
|
+
'fw-dev-track-badge',
|
|
615
|
+
track.type === 'video' ? 'fw-dev-track-badge--video' :
|
|
616
|
+
track.type === 'audio' ? 'fw-dev-track-badge--audio' :
|
|
617
|
+
'fw-dev-track-badge--other'
|
|
618
|
+
)}>
|
|
619
|
+
{track.type}
|
|
620
|
+
</span>
|
|
621
|
+
<span class="fw-dev-track-codec">{track.codec}</span>
|
|
622
|
+
<span class="fw-dev-track-id">#{id}</span>
|
|
623
|
+
</div>
|
|
624
|
+
<div class="fw-dev-track-meta">
|
|
625
|
+
{#if track.type === 'video' && track.width && track.height}
|
|
626
|
+
<span>{track.width}×{track.height}</span>
|
|
627
|
+
{/if}
|
|
628
|
+
{#if track.bps}
|
|
629
|
+
<span>{Math.round(track.bps / 1000)} kbps</span>
|
|
630
|
+
{/if}
|
|
631
|
+
{#if track.fpks}
|
|
632
|
+
<span>{Math.round(track.fpks / 1000)} fps</span>
|
|
633
|
+
{/if}
|
|
634
|
+
{#if track.type === 'audio' && track.channels}
|
|
635
|
+
<span>{track.channels}ch</span>
|
|
636
|
+
{/if}
|
|
637
|
+
{#if track.type === 'audio' && track.rate}
|
|
638
|
+
<span>{track.rate} Hz</span>
|
|
639
|
+
{/if}
|
|
640
|
+
{#if track.lang}
|
|
641
|
+
<span>{track.lang}</span>
|
|
642
|
+
{/if}
|
|
643
|
+
</div>
|
|
644
|
+
</div>
|
|
645
|
+
{/each}
|
|
646
|
+
{/if}
|
|
647
|
+
</div>
|
|
648
|
+
{/if}
|
|
649
|
+
</div>
|
|
650
|
+
{/if}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type StreamInfo, type MistStreamInfo, type PlaybackMode } from '@livepeer-frameworks/player-core';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Callback when user selects a combo (one-shot selection) */
|
|
4
|
+
onSettingsChange: (settings: {
|
|
5
|
+
forcePlayer?: string;
|
|
6
|
+
forceType?: string;
|
|
7
|
+
forceSource?: number;
|
|
8
|
+
}) => void;
|
|
9
|
+
playbackMode?: PlaybackMode;
|
|
10
|
+
onModeChange?: (mode: PlaybackMode) => void;
|
|
11
|
+
onReload?: () => void;
|
|
12
|
+
streamInfo?: StreamInfo | null;
|
|
13
|
+
mistStreamInfo?: MistStreamInfo | null;
|
|
14
|
+
currentPlayer?: {
|
|
15
|
+
name: string;
|
|
16
|
+
shortname: string;
|
|
17
|
+
} | null;
|
|
18
|
+
currentSource?: {
|
|
19
|
+
url: string;
|
|
20
|
+
type: string;
|
|
21
|
+
} | null;
|
|
22
|
+
videoElement?: HTMLVideoElement | null;
|
|
23
|
+
protocol?: string;
|
|
24
|
+
nodeId?: string;
|
|
25
|
+
isVisible?: boolean;
|
|
26
|
+
isOpen?: boolean;
|
|
27
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
28
|
+
}
|
|
29
|
+
declare const DevModePanel: import("svelte").Component<Props, {}, "">;
|
|
30
|
+
type DevModePanel = ReturnType<typeof DevModePanel>;
|
|
31
|
+
export default DevModePanel;
|