@livepeer-frameworks/player-react 0.0.4 → 0.1.1
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/README.md +16 -5
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/types/components/PlayerControls.d.ts +2 -0
- package/dist/types/components/StatsPanel.d.ts +2 -14
- package/dist/types/hooks/useMetaTrack.d.ts +1 -1
- package/dist/types/hooks/usePlayerController.d.ts +2 -0
- package/dist/types/hooks/useStreamState.d.ts +1 -1
- package/dist/types/hooks/useTelemetry.d.ts +1 -1
- package/dist/types/hooks/useViewerEndpoints.d.ts +2 -2
- package/dist/types/types.d.ts +1 -1
- package/dist/types/ui/button.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/DevModePanel.tsx +249 -170
- package/src/components/Icons.tsx +105 -25
- package/src/components/IdleScreen.tsx +262 -142
- package/src/components/LoadingScreen.tsx +171 -153
- package/src/components/LogoOverlay.tsx +3 -6
- package/src/components/Player.tsx +86 -74
- package/src/components/PlayerControls.tsx +351 -263
- package/src/components/PlayerErrorBoundary.tsx +6 -13
- package/src/components/SeekBar.tsx +96 -88
- package/src/components/SkipIndicator.tsx +2 -12
- package/src/components/SpeedIndicator.tsx +2 -11
- package/src/components/StatsPanel.tsx +65 -34
- package/src/components/StreamStateOverlay.tsx +105 -49
- package/src/components/SubtitleRenderer.tsx +29 -29
- package/src/components/ThumbnailOverlay.tsx +5 -6
- package/src/components/TitleOverlay.tsx +2 -8
- package/src/components/players/DashJsPlayer.tsx +13 -11
- package/src/components/players/HlsJsPlayer.tsx +13 -11
- package/src/components/players/MewsWsPlayer/index.tsx +13 -11
- package/src/components/players/MistPlayer.tsx +13 -11
- package/src/components/players/MistWebRTCPlayer/index.tsx +19 -10
- package/src/components/players/NativePlayer.tsx +10 -12
- package/src/components/players/VideoJsPlayer.tsx +13 -11
- package/src/context/PlayerContext.tsx +4 -8
- package/src/context/index.ts +3 -3
- package/src/hooks/useMetaTrack.ts +28 -28
- package/src/hooks/usePlaybackQuality.ts +3 -3
- package/src/hooks/usePlayerController.ts +186 -140
- package/src/hooks/usePlayerSelection.ts +6 -6
- package/src/hooks/useStreamState.ts +53 -58
- package/src/hooks/useTelemetry.ts +19 -4
- package/src/hooks/useViewerEndpoints.ts +40 -30
- package/src/index.tsx +36 -28
- package/src/types.ts +9 -9
- package/src/ui/badge.tsx +6 -5
- package/src/ui/button.tsx +9 -8
- package/src/ui/context-menu.tsx +42 -61
- package/src/ui/select.tsx +13 -7
- package/src/ui/slider.tsx +18 -29
|
@@ -14,7 +14,7 @@ const SOURCE_TYPE_LABELS: Record<string, string> = {
|
|
|
14
14
|
"dash/video/mp4": "DASH",
|
|
15
15
|
"html5/video/mp4": "MP4",
|
|
16
16
|
"html5/video/webm": "WebM",
|
|
17
|
-
|
|
17
|
+
whep: "WHEP",
|
|
18
18
|
"mist/html": "Mist",
|
|
19
19
|
"mist/legacy": "Auto",
|
|
20
20
|
"ws/video/mp4": "MEWS",
|
|
@@ -66,7 +66,7 @@ export interface DevModePanelProps {
|
|
|
66
66
|
*/
|
|
67
67
|
const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
68
68
|
onSettingsChange,
|
|
69
|
-
playbackMode =
|
|
69
|
+
playbackMode = "auto",
|
|
70
70
|
onModeChange,
|
|
71
71
|
onReload,
|
|
72
72
|
streamInfo,
|
|
@@ -76,22 +76,25 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
76
76
|
videoElement,
|
|
77
77
|
protocol,
|
|
78
78
|
nodeId,
|
|
79
|
-
isVisible = true,
|
|
79
|
+
isVisible: _isVisible = true,
|
|
80
80
|
isOpen: controlledIsOpen,
|
|
81
81
|
onOpenChange,
|
|
82
82
|
}) => {
|
|
83
83
|
// Support both controlled and uncontrolled modes
|
|
84
84
|
const [internalIsOpen, setInternalIsOpen] = useState(false);
|
|
85
85
|
const isOpen = controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen;
|
|
86
|
-
const setIsOpen = useCallback(
|
|
87
|
-
|
|
88
|
-
onOpenChange
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
const setIsOpen = useCallback(
|
|
87
|
+
(value: boolean) => {
|
|
88
|
+
if (onOpenChange) {
|
|
89
|
+
onOpenChange(value);
|
|
90
|
+
} else {
|
|
91
|
+
setInternalIsOpen(value);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
[onOpenChange]
|
|
95
|
+
);
|
|
93
96
|
const [activeTab, setActiveTab] = useState<"config" | "stats">("config");
|
|
94
|
-
const [
|
|
97
|
+
const [, setCurrentComboIndex] = useState(0);
|
|
95
98
|
const [hoveredComboIndex, setHoveredComboIndex] = useState<number | null>(null);
|
|
96
99
|
const [tooltipAbove, setTooltipAbove] = useState(false);
|
|
97
100
|
const [showDisabledPlayers, setShowDisabledPlayers] = useState(false);
|
|
@@ -147,7 +150,7 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
147
150
|
setPlayerStats(stats);
|
|
148
151
|
}
|
|
149
152
|
}
|
|
150
|
-
} catch
|
|
153
|
+
} catch {
|
|
151
154
|
// Ignore errors
|
|
152
155
|
}
|
|
153
156
|
};
|
|
@@ -180,7 +183,7 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
180
183
|
|
|
181
184
|
// For backward compatibility (Next Option only cycles compatible)
|
|
182
185
|
const combinations = useMemo(() => {
|
|
183
|
-
return allCombinations.filter(c => c.compatible);
|
|
186
|
+
return allCombinations.filter((c) => c.compatible);
|
|
184
187
|
}, [allCombinations]);
|
|
185
188
|
|
|
186
189
|
// Find current active combo index based on current player/source (in allCombinations)
|
|
@@ -220,18 +223,21 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
220
223
|
});
|
|
221
224
|
}, [combinations, activeCompatibleIndex, onSettingsChange]);
|
|
222
225
|
|
|
223
|
-
const handleSelectCombo = useCallback(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
226
|
+
const handleSelectCombo = useCallback(
|
|
227
|
+
(index: number) => {
|
|
228
|
+
const combo = allCombinations[index];
|
|
229
|
+
if (!combo) return;
|
|
230
|
+
|
|
231
|
+
// Allow selecting even incompatible combos in dev mode (for testing)
|
|
232
|
+
setCurrentComboIndex(index);
|
|
233
|
+
onSettingsChange({
|
|
234
|
+
forcePlayer: combo.player,
|
|
235
|
+
forceType: combo.sourceType,
|
|
236
|
+
forceSource: combo.sourceIndex,
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
[allCombinations, onSettingsChange]
|
|
240
|
+
);
|
|
235
241
|
|
|
236
242
|
// Video stats - poll periodically when stats tab is open
|
|
237
243
|
const [stats, setStats] = useState<{
|
|
@@ -274,31 +280,9 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
274
280
|
return () => clearInterval(interval);
|
|
275
281
|
}, [isOpen, activeTab, videoElement]);
|
|
276
282
|
|
|
277
|
-
//
|
|
283
|
+
// Panel is only rendered when open (no floating toggle button)
|
|
278
284
|
if (!isOpen) {
|
|
279
|
-
return
|
|
280
|
-
<button
|
|
281
|
-
type="button"
|
|
282
|
-
onClick={() => setIsOpen(true)}
|
|
283
|
-
className={cn(
|
|
284
|
-
"fw-dev-toggle",
|
|
285
|
-
!isVisible && "fw-dev-toggle--hidden"
|
|
286
|
-
)}
|
|
287
|
-
title="Advanced Settings"
|
|
288
|
-
aria-label="Open advanced settings panel"
|
|
289
|
-
>
|
|
290
|
-
<svg
|
|
291
|
-
width="16"
|
|
292
|
-
height="16"
|
|
293
|
-
viewBox="0 0 24 24"
|
|
294
|
-
fill="none"
|
|
295
|
-
stroke="currentColor"
|
|
296
|
-
strokeWidth="2"
|
|
297
|
-
>
|
|
298
|
-
<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" />
|
|
299
|
-
</svg>
|
|
300
|
-
</button>
|
|
301
|
-
);
|
|
285
|
+
return null;
|
|
302
286
|
}
|
|
303
287
|
|
|
304
288
|
return (
|
|
@@ -346,20 +330,17 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
346
330
|
<div className="fw-dev-section">
|
|
347
331
|
<div className="fw-dev-label">Active</div>
|
|
348
332
|
<div className="fw-dev-value">
|
|
349
|
-
{currentPlayer?.name || "None"}{" "}
|
|
350
|
-
<span className="fw-dev-value-arrow">→</span>{" "}
|
|
333
|
+
{currentPlayer?.name || "None"} <span className="fw-dev-value-arrow">→</span>{" "}
|
|
351
334
|
{SOURCE_TYPE_LABELS[currentSource?.type || ""] || currentSource?.type || "—"}
|
|
352
335
|
</div>
|
|
353
|
-
{nodeId &&
|
|
354
|
-
<div className="fw-dev-value-muted">Node: {nodeId}</div>
|
|
355
|
-
)}
|
|
336
|
+
{nodeId && <div className="fw-dev-value-muted">Node: {nodeId}</div>}
|
|
356
337
|
</div>
|
|
357
338
|
|
|
358
339
|
{/* Playback Mode Selector */}
|
|
359
340
|
<div className="fw-dev-section">
|
|
360
341
|
<div className="fw-dev-label">Playback Mode</div>
|
|
361
342
|
<div className="fw-dev-mode-group">
|
|
362
|
-
{([
|
|
343
|
+
{(["auto", "low-latency", "quality"] as const).map((mode) => (
|
|
363
344
|
<button
|
|
364
345
|
key={mode}
|
|
365
346
|
type="button"
|
|
@@ -369,31 +350,25 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
369
350
|
playbackMode === mode && "fw-dev-mode-btn--active"
|
|
370
351
|
)}
|
|
371
352
|
>
|
|
372
|
-
{mode ===
|
|
353
|
+
{mode === "low-latency"
|
|
354
|
+
? "Low Lat"
|
|
355
|
+
: mode.charAt(0).toUpperCase() + mode.slice(1)}
|
|
373
356
|
</button>
|
|
374
357
|
))}
|
|
375
358
|
</div>
|
|
376
359
|
<div className="fw-dev-mode-desc">
|
|
377
|
-
{playbackMode ===
|
|
378
|
-
{playbackMode ===
|
|
379
|
-
{playbackMode ===
|
|
360
|
+
{playbackMode === "auto" && "Balanced: MP4/WS → WHEP → HLS"}
|
|
361
|
+
{playbackMode === "low-latency" && "WHEP/WebRTC first (<1s delay)"}
|
|
362
|
+
{playbackMode === "quality" && "MP4/WS first, HLS fallback"}
|
|
380
363
|
</div>
|
|
381
364
|
</div>
|
|
382
365
|
|
|
383
366
|
{/* Action buttons */}
|
|
384
367
|
<div className="fw-dev-actions">
|
|
385
|
-
<button
|
|
386
|
-
type="button"
|
|
387
|
-
onClick={handleReload}
|
|
388
|
-
className="fw-dev-action-btn"
|
|
389
|
-
>
|
|
368
|
+
<button type="button" onClick={handleReload} className="fw-dev-action-btn">
|
|
390
369
|
Reload
|
|
391
370
|
</button>
|
|
392
|
-
<button
|
|
393
|
-
type="button"
|
|
394
|
-
onClick={handleNextCombo}
|
|
395
|
-
className="fw-dev-action-btn"
|
|
396
|
-
>
|
|
371
|
+
<button type="button" onClick={handleNextCombo} className="fw-dev-action-btn">
|
|
397
372
|
Next Option
|
|
398
373
|
</button>
|
|
399
374
|
</div>
|
|
@@ -401,9 +376,7 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
401
376
|
{/* Combo list */}
|
|
402
377
|
<div className="fw-dev-section" style={{ padding: 0, borderBottom: 0 }}>
|
|
403
378
|
<div className="fw-dev-list-header">
|
|
404
|
-
<span className="fw-dev-list-title">
|
|
405
|
-
Player Options ({combinations.length})
|
|
406
|
-
</span>
|
|
379
|
+
<span className="fw-dev-list-title">Player Options ({combinations.length})</span>
|
|
407
380
|
{allCombinations.length > combinations.length && (
|
|
408
381
|
<button
|
|
409
382
|
type="button"
|
|
@@ -421,14 +394,13 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
421
394
|
>
|
|
422
395
|
<path d="M6 9l6 6 6-6" />
|
|
423
396
|
</svg>
|
|
424
|
-
{showDisabledPlayers ? "Hide" : "Show"} disabled (
|
|
397
|
+
{showDisabledPlayers ? "Hide" : "Show"} disabled (
|
|
398
|
+
{allCombinations.length - combinations.length})
|
|
425
399
|
</button>
|
|
426
400
|
)}
|
|
427
401
|
</div>
|
|
428
402
|
{allCombinations.length === 0 ? (
|
|
429
|
-
<div className="fw-dev-list-empty">
|
|
430
|
-
No stream info available
|
|
431
|
-
</div>
|
|
403
|
+
<div className="fw-dev-list-empty">No stream info available</div>
|
|
432
404
|
) : (
|
|
433
405
|
<div>
|
|
434
406
|
{allCombinations.map((combo, index) => {
|
|
@@ -437,11 +409,13 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
437
409
|
if (!combo.compatible && !isCodecIncompat && !showDisabledPlayers) return null;
|
|
438
410
|
|
|
439
411
|
const isActive = activeComboIndex === index;
|
|
440
|
-
const typeLabel =
|
|
412
|
+
const typeLabel =
|
|
413
|
+
SOURCE_TYPE_LABELS[combo.sourceType] || combo.sourceType.split("/").pop();
|
|
441
414
|
|
|
442
415
|
// Determine score class
|
|
443
416
|
const getScoreClass = () => {
|
|
444
|
-
if (!combo.compatible && !isCodecIncompat)
|
|
417
|
+
if (!combo.compatible && !isCodecIncompat)
|
|
418
|
+
return "fw-dev-combo-score--disabled";
|
|
445
419
|
if (isCodecIncompat) return "fw-dev-combo-score--low";
|
|
446
420
|
if (combo.score >= 2) return "fw-dev-combo-score--high";
|
|
447
421
|
if (combo.score >= 1.5) return "fw-dev-combo-score--mid";
|
|
@@ -473,7 +447,8 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
473
447
|
const row = e.currentTarget;
|
|
474
448
|
const containerRect = container.getBoundingClientRect();
|
|
475
449
|
const rowRect = row.getBoundingClientRect();
|
|
476
|
-
const relativePosition =
|
|
450
|
+
const relativePosition =
|
|
451
|
+
(rowRect.top - containerRect.top) / containerRect.height;
|
|
477
452
|
setTooltipAbove(relativePosition > 0.6);
|
|
478
453
|
}
|
|
479
454
|
}}
|
|
@@ -496,9 +471,10 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
496
471
|
</span>
|
|
497
472
|
{/* Player + Protocol */}
|
|
498
473
|
<span className="fw-dev-combo-name">
|
|
499
|
-
{combo.playerName}{" "}
|
|
500
|
-
<span className="fw-dev-combo-
|
|
501
|
-
|
|
474
|
+
{combo.playerName} <span className="fw-dev-combo-arrow">→</span>{" "}
|
|
475
|
+
<span className={cn("fw-dev-combo-type", getTypeClass())}>
|
|
476
|
+
{typeLabel}
|
|
477
|
+
</span>
|
|
502
478
|
</span>
|
|
503
479
|
{/* Score */}
|
|
504
480
|
<span className={cn("fw-dev-combo-score", getScoreClass())}>
|
|
@@ -508,50 +484,105 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
508
484
|
|
|
509
485
|
{/* Score breakdown tooltip */}
|
|
510
486
|
{hoveredComboIndex === index && (
|
|
511
|
-
<div
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
487
|
+
<div
|
|
488
|
+
className={cn(
|
|
489
|
+
"fw-dev-tooltip",
|
|
490
|
+
tooltipAbove ? "fw-dev-tooltip--above" : "fw-dev-tooltip--below"
|
|
491
|
+
)}
|
|
492
|
+
>
|
|
515
493
|
{/* Full player/source info */}
|
|
516
494
|
<div className="fw-dev-tooltip-header">
|
|
517
495
|
<div className="fw-dev-tooltip-title">{combo.playerName}</div>
|
|
518
496
|
<div className="fw-dev-tooltip-subtitle">{combo.sourceType}</div>
|
|
519
|
-
{combo.scoreBreakdown?.trackTypes &&
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
497
|
+
{combo.scoreBreakdown?.trackTypes &&
|
|
498
|
+
combo.scoreBreakdown.trackTypes.length > 0 && (
|
|
499
|
+
<div className="fw-dev-tooltip-tracks">
|
|
500
|
+
Tracks:{" "}
|
|
501
|
+
<span className="fw-dev-tooltip-value">
|
|
502
|
+
{combo.scoreBreakdown.trackTypes.join(", ")}
|
|
503
|
+
</span>
|
|
504
|
+
</div>
|
|
505
|
+
)}
|
|
524
506
|
</div>
|
|
525
507
|
{combo.compatible && combo.scoreBreakdown ? (
|
|
526
508
|
<>
|
|
527
|
-
<div className="fw-dev-tooltip-score">
|
|
509
|
+
<div className="fw-dev-tooltip-score">
|
|
510
|
+
Score: {combo.score.toFixed(2)}
|
|
511
|
+
</div>
|
|
528
512
|
<div className="fw-dev-tooltip-row">
|
|
529
|
-
Tracks [{combo.scoreBreakdown.trackTypes.join(
|
|
513
|
+
Tracks [{combo.scoreBreakdown.trackTypes.join(", ")}]:{" "}
|
|
514
|
+
<span className="fw-dev-tooltip-value">
|
|
515
|
+
{combo.scoreBreakdown.trackScore.toFixed(2)}
|
|
516
|
+
</span>{" "}
|
|
517
|
+
<span className="fw-dev-tooltip-weight">
|
|
518
|
+
x{combo.scoreBreakdown.weights.tracks}
|
|
519
|
+
</span>
|
|
530
520
|
</div>
|
|
531
521
|
<div className="fw-dev-tooltip-row">
|
|
532
|
-
Priority:
|
|
522
|
+
Priority:{" "}
|
|
523
|
+
<span className="fw-dev-tooltip-value">
|
|
524
|
+
{combo.scoreBreakdown.priorityScore.toFixed(2)}
|
|
525
|
+
</span>{" "}
|
|
526
|
+
<span className="fw-dev-tooltip-weight">
|
|
527
|
+
x{combo.scoreBreakdown.weights.priority}
|
|
528
|
+
</span>
|
|
533
529
|
</div>
|
|
534
530
|
<div className="fw-dev-tooltip-row">
|
|
535
|
-
Source:
|
|
531
|
+
Source:{" "}
|
|
532
|
+
<span className="fw-dev-tooltip-value">
|
|
533
|
+
{combo.scoreBreakdown.sourceScore.toFixed(2)}
|
|
534
|
+
</span>{" "}
|
|
535
|
+
<span className="fw-dev-tooltip-weight">
|
|
536
|
+
x{combo.scoreBreakdown.weights.source}
|
|
537
|
+
</span>
|
|
536
538
|
</div>
|
|
537
539
|
{combo.scoreBreakdown.reliabilityScore !== undefined && (
|
|
538
540
|
<div className="fw-dev-tooltip-row">
|
|
539
|
-
Reliability:
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
)}
|
|
547
|
-
{combo.scoreBreakdown.routingBonus !== undefined && combo.scoreBreakdown.routingBonus !== 0 && (
|
|
548
|
-
<div className="fw-dev-tooltip-row">
|
|
549
|
-
Routing: <span className={combo.scoreBreakdown.routingBonus > 0 ? "fw-dev-tooltip-bonus" : "fw-dev-tooltip-penalty"}>{combo.scoreBreakdown.routingBonus > 0 ? '+' : ''}{combo.scoreBreakdown.routingBonus.toFixed(2)}</span> <span className="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.routing ?? 0}</span>
|
|
541
|
+
Reliability:{" "}
|
|
542
|
+
<span className="fw-dev-tooltip-value">
|
|
543
|
+
{combo.scoreBreakdown.reliabilityScore.toFixed(2)}
|
|
544
|
+
</span>{" "}
|
|
545
|
+
<span className="fw-dev-tooltip-weight">
|
|
546
|
+
x{combo.scoreBreakdown.weights.reliability ?? 0}
|
|
547
|
+
</span>
|
|
550
548
|
</div>
|
|
551
549
|
)}
|
|
550
|
+
{combo.scoreBreakdown.modeBonus !== undefined &&
|
|
551
|
+
combo.scoreBreakdown.modeBonus !== 0 && (
|
|
552
|
+
<div className="fw-dev-tooltip-row">
|
|
553
|
+
Mode ({playbackMode}):{" "}
|
|
554
|
+
<span className="fw-dev-tooltip-bonus">
|
|
555
|
+
+{combo.scoreBreakdown.modeBonus.toFixed(2)}
|
|
556
|
+
</span>{" "}
|
|
557
|
+
<span className="fw-dev-tooltip-weight">
|
|
558
|
+
x{combo.scoreBreakdown.weights.mode ?? 0}
|
|
559
|
+
</span>
|
|
560
|
+
</div>
|
|
561
|
+
)}
|
|
562
|
+
{combo.scoreBreakdown.routingBonus !== undefined &&
|
|
563
|
+
combo.scoreBreakdown.routingBonus !== 0 && (
|
|
564
|
+
<div className="fw-dev-tooltip-row">
|
|
565
|
+
Routing:{" "}
|
|
566
|
+
<span
|
|
567
|
+
className={
|
|
568
|
+
combo.scoreBreakdown.routingBonus > 0
|
|
569
|
+
? "fw-dev-tooltip-bonus"
|
|
570
|
+
: "fw-dev-tooltip-penalty"
|
|
571
|
+
}
|
|
572
|
+
>
|
|
573
|
+
{combo.scoreBreakdown.routingBonus > 0 ? "+" : ""}
|
|
574
|
+
{combo.scoreBreakdown.routingBonus.toFixed(2)}
|
|
575
|
+
</span>{" "}
|
|
576
|
+
<span className="fw-dev-tooltip-weight">
|
|
577
|
+
x{combo.scoreBreakdown.weights.routing ?? 0}
|
|
578
|
+
</span>
|
|
579
|
+
</div>
|
|
580
|
+
)}
|
|
552
581
|
</>
|
|
553
582
|
) : (
|
|
554
|
-
<div className="fw-dev-tooltip-error">
|
|
583
|
+
<div className="fw-dev-tooltip-error">
|
|
584
|
+
{combo.incompatibleReason || "Incompatible"}
|
|
585
|
+
</div>
|
|
555
586
|
)}
|
|
556
587
|
</div>
|
|
557
588
|
)}
|
|
@@ -571,30 +602,46 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
571
602
|
<div className="fw-dev-section">
|
|
572
603
|
<div className="fw-dev-label">Playback Rate</div>
|
|
573
604
|
<div className="fw-dev-rate">
|
|
574
|
-
<div
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
605
|
+
<div
|
|
606
|
+
className={cn(
|
|
607
|
+
"fw-dev-rate-value",
|
|
608
|
+
playbackScore >= 0.95 && playbackScore <= 1.05
|
|
609
|
+
? "fw-dev-stat-value--good"
|
|
610
|
+
: playbackScore > 1.05
|
|
611
|
+
? "fw-dev-stat-value--accent"
|
|
612
|
+
: playbackScore >= 0.75
|
|
613
|
+
? "fw-dev-stat-value--warn"
|
|
614
|
+
: "fw-dev-stat-value--bad"
|
|
615
|
+
)}
|
|
616
|
+
>
|
|
581
617
|
{playbackScore.toFixed(2)}×
|
|
582
618
|
</div>
|
|
583
619
|
<div className="fw-dev-rate-status">
|
|
584
|
-
{playbackScore >= 0.95 && playbackScore <= 1.05
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
620
|
+
{playbackScore >= 0.95 && playbackScore <= 1.05
|
|
621
|
+
? "realtime"
|
|
622
|
+
: playbackScore > 1.05
|
|
623
|
+
? "catching up"
|
|
624
|
+
: playbackScore >= 0.75
|
|
625
|
+
? "slightly slow"
|
|
626
|
+
: "stalling"}
|
|
588
627
|
</div>
|
|
589
628
|
</div>
|
|
590
629
|
<div className="fw-dev-rate-stats">
|
|
591
|
-
<span
|
|
630
|
+
<span
|
|
631
|
+
className={
|
|
632
|
+
qualityScore >= 75 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--bad"
|
|
633
|
+
}
|
|
634
|
+
>
|
|
592
635
|
Quality: {qualityScore}/100
|
|
593
636
|
</span>
|
|
594
|
-
<span
|
|
637
|
+
<span
|
|
638
|
+
className={stallCount === 0 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--warn"}
|
|
639
|
+
>
|
|
595
640
|
Stalls: {stallCount}
|
|
596
641
|
</span>
|
|
597
|
-
<span
|
|
642
|
+
<span
|
|
643
|
+
className={frameDropRate < 1 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--bad"}
|
|
644
|
+
>
|
|
598
645
|
Drops: {frameDropRate.toFixed(1)}%
|
|
599
646
|
</span>
|
|
600
647
|
</div>
|
|
@@ -638,16 +685,14 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
638
685
|
{nodeId && (
|
|
639
686
|
<div className="fw-dev-stat">
|
|
640
687
|
<span className="fw-dev-stat-label">Node ID</span>
|
|
641
|
-
<span className="fw-dev-stat-value truncate" style={{ maxWidth:
|
|
688
|
+
<span className="fw-dev-stat-value truncate" style={{ maxWidth: "150px" }}>
|
|
642
689
|
{nodeId}
|
|
643
690
|
</span>
|
|
644
691
|
</div>
|
|
645
692
|
)}
|
|
646
693
|
</div>
|
|
647
694
|
) : (
|
|
648
|
-
<div className="fw-dev-list-empty">
|
|
649
|
-
No video element available
|
|
650
|
-
</div>
|
|
695
|
+
<div className="fw-dev-list-empty">No video element available</div>
|
|
651
696
|
)}
|
|
652
697
|
|
|
653
698
|
{/* Player-specific Stats (HLS.js / WebRTC) */}
|
|
@@ -655,20 +700,23 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
655
700
|
<div>
|
|
656
701
|
<div className="fw-dev-list-header fw-dev-section-header">
|
|
657
702
|
<span className="fw-dev-list-title">
|
|
658
|
-
{playerStats.type ===
|
|
659
|
-
|
|
703
|
+
{playerStats.type === "hls"
|
|
704
|
+
? "HLS.js Stats"
|
|
705
|
+
: playerStats.type === "webrtc"
|
|
706
|
+
? "WebRTC Stats"
|
|
707
|
+
: "Player Stats"}
|
|
660
708
|
</span>
|
|
661
709
|
</div>
|
|
662
710
|
|
|
663
711
|
{/* HLS-specific stats */}
|
|
664
|
-
{playerStats.type ===
|
|
712
|
+
{playerStats.type === "hls" && (
|
|
665
713
|
<>
|
|
666
714
|
<div className="fw-dev-stat">
|
|
667
715
|
<span className="fw-dev-stat-label">Bitrate</span>
|
|
668
716
|
<span className="fw-dev-stat-value--accent">
|
|
669
717
|
{playerStats.currentBitrate > 0
|
|
670
718
|
? `${Math.round(playerStats.currentBitrate / 1000)} kbps`
|
|
671
|
-
:
|
|
719
|
+
: "N/A"}
|
|
672
720
|
</span>
|
|
673
721
|
</div>
|
|
674
722
|
<div className="fw-dev-stat">
|
|
@@ -676,19 +724,26 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
676
724
|
<span className="fw-dev-stat-value">
|
|
677
725
|
{playerStats.bandwidthEstimate > 0
|
|
678
726
|
? `${Math.round(playerStats.bandwidthEstimate / 1000)} kbps`
|
|
679
|
-
:
|
|
727
|
+
: "N/A"}
|
|
680
728
|
</span>
|
|
681
729
|
</div>
|
|
682
730
|
<div className="fw-dev-stat">
|
|
683
731
|
<span className="fw-dev-stat-label">Level</span>
|
|
684
732
|
<span className="fw-dev-stat-value">
|
|
685
|
-
{playerStats.currentLevel >= 0 ? playerStats.currentLevel :
|
|
733
|
+
{playerStats.currentLevel >= 0 ? playerStats.currentLevel : "Auto"} /{" "}
|
|
734
|
+
{playerStats.levels?.length || 0}
|
|
686
735
|
</span>
|
|
687
736
|
</div>
|
|
688
737
|
{playerStats.latency !== undefined && (
|
|
689
738
|
<div className="fw-dev-stat">
|
|
690
739
|
<span className="fw-dev-stat-label">Latency</span>
|
|
691
|
-
<span
|
|
740
|
+
<span
|
|
741
|
+
className={
|
|
742
|
+
playerStats.latency > 5000
|
|
743
|
+
? "fw-dev-stat-value--warn"
|
|
744
|
+
: "fw-dev-stat-value"
|
|
745
|
+
}
|
|
746
|
+
>
|
|
692
747
|
{Math.round(playerStats.latency)} ms
|
|
693
748
|
</span>
|
|
694
749
|
</div>
|
|
@@ -697,7 +752,7 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
697
752
|
)}
|
|
698
753
|
|
|
699
754
|
{/* WebRTC-specific stats */}
|
|
700
|
-
{playerStats.type ===
|
|
755
|
+
{playerStats.type === "webrtc" && (
|
|
701
756
|
<>
|
|
702
757
|
{playerStats.video && (
|
|
703
758
|
<>
|
|
@@ -706,7 +761,7 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
706
761
|
<span className="fw-dev-stat-value--accent">
|
|
707
762
|
{playerStats.video.bitrate > 0
|
|
708
763
|
? `${Math.round(playerStats.video.bitrate / 1000)} kbps`
|
|
709
|
-
:
|
|
764
|
+
: "N/A"}
|
|
710
765
|
</span>
|
|
711
766
|
</div>
|
|
712
767
|
<div className="fw-dev-stat">
|
|
@@ -718,21 +773,39 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
718
773
|
<div className="fw-dev-stat">
|
|
719
774
|
<span className="fw-dev-stat-label">Frames</span>
|
|
720
775
|
<span className="fw-dev-stat-value">
|
|
721
|
-
{playerStats.video.framesDecoded} decoded,{
|
|
722
|
-
<span
|
|
776
|
+
{playerStats.video.framesDecoded} decoded,{" "}
|
|
777
|
+
<span
|
|
778
|
+
className={
|
|
779
|
+
playerStats.video.frameDropRate > 1
|
|
780
|
+
? "fw-dev-stat-value--bad"
|
|
781
|
+
: "fw-dev-stat-value--good"
|
|
782
|
+
}
|
|
783
|
+
>
|
|
723
784
|
{playerStats.video.framesDropped} dropped
|
|
724
785
|
</span>
|
|
725
786
|
</span>
|
|
726
787
|
</div>
|
|
727
788
|
<div className="fw-dev-stat">
|
|
728
789
|
<span className="fw-dev-stat-label">Packet Loss</span>
|
|
729
|
-
<span
|
|
790
|
+
<span
|
|
791
|
+
className={
|
|
792
|
+
playerStats.video.packetLossRate > 1
|
|
793
|
+
? "fw-dev-stat-value--bad"
|
|
794
|
+
: "fw-dev-stat-value--good"
|
|
795
|
+
}
|
|
796
|
+
>
|
|
730
797
|
{playerStats.video.packetLossRate.toFixed(2)}%
|
|
731
798
|
</span>
|
|
732
799
|
</div>
|
|
733
800
|
<div className="fw-dev-stat">
|
|
734
801
|
<span className="fw-dev-stat-label">Jitter</span>
|
|
735
|
-
<span
|
|
802
|
+
<span
|
|
803
|
+
className={
|
|
804
|
+
playerStats.video.jitter > 30
|
|
805
|
+
? "fw-dev-stat-value--warn"
|
|
806
|
+
: "fw-dev-stat-value"
|
|
807
|
+
}
|
|
808
|
+
>
|
|
736
809
|
{playerStats.video.jitter.toFixed(1)} ms
|
|
737
810
|
</span>
|
|
738
811
|
</div>
|
|
@@ -747,7 +820,13 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
747
820
|
{playerStats.network && (
|
|
748
821
|
<div className="fw-dev-stat">
|
|
749
822
|
<span className="fw-dev-stat-label">RTT</span>
|
|
750
|
-
<span
|
|
823
|
+
<span
|
|
824
|
+
className={
|
|
825
|
+
playerStats.network.rtt > 200
|
|
826
|
+
? "fw-dev-stat-value--warn"
|
|
827
|
+
: "fw-dev-stat-value"
|
|
828
|
+
}
|
|
829
|
+
>
|
|
751
830
|
{Math.round(playerStats.network.rtt)} ms
|
|
752
831
|
</span>
|
|
753
832
|
</div>
|
|
@@ -768,36 +847,32 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
768
847
|
{Object.entries(mistStreamInfo.meta.tracks).map(([id, track]) => (
|
|
769
848
|
<div key={id} className="fw-dev-track">
|
|
770
849
|
<div className="fw-dev-track-header">
|
|
771
|
-
<span
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
850
|
+
<span
|
|
851
|
+
className={cn(
|
|
852
|
+
"fw-dev-track-badge",
|
|
853
|
+
track.type === "video"
|
|
854
|
+
? "fw-dev-track-badge--video"
|
|
855
|
+
: track.type === "audio"
|
|
856
|
+
? "fw-dev-track-badge--audio"
|
|
857
|
+
: "fw-dev-track-badge--other"
|
|
858
|
+
)}
|
|
859
|
+
>
|
|
777
860
|
{track.type}
|
|
778
861
|
</span>
|
|
779
862
|
<span className="fw-dev-track-codec">{track.codec}</span>
|
|
780
863
|
<span className="fw-dev-track-id">#{id}</span>
|
|
781
864
|
</div>
|
|
782
865
|
<div className="fw-dev-track-meta">
|
|
783
|
-
{track.type ===
|
|
784
|
-
<span>
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
<span>{Math.round(track.bps / 1000)} kbps</span>
|
|
788
|
-
)}
|
|
789
|
-
{track.fpks && (
|
|
790
|
-
<span>{Math.round(track.fpks / 1000)} fps</span>
|
|
791
|
-
)}
|
|
792
|
-
{track.type === 'audio' && track.channels && (
|
|
793
|
-
<span>{track.channels}ch</span>
|
|
794
|
-
)}
|
|
795
|
-
{track.type === 'audio' && track.rate && (
|
|
796
|
-
<span>{track.rate} Hz</span>
|
|
797
|
-
)}
|
|
798
|
-
{track.lang && (
|
|
799
|
-
<span>{track.lang}</span>
|
|
866
|
+
{track.type === "video" && track.width && track.height && (
|
|
867
|
+
<span>
|
|
868
|
+
{track.width}×{track.height}
|
|
869
|
+
</span>
|
|
800
870
|
)}
|
|
871
|
+
{track.bps && <span>{Math.round(track.bps / 1000)} kbps</span>}
|
|
872
|
+
{track.fpks && <span>{Math.round(track.fpks / 1000)} fps</span>}
|
|
873
|
+
{track.type === "audio" && track.channels && <span>{track.channels}ch</span>}
|
|
874
|
+
{track.type === "audio" && track.rate && <span>{track.rate} Hz</span>}
|
|
875
|
+
{track.lang && <span>{track.lang}</span>}
|
|
801
876
|
</div>
|
|
802
877
|
</div>
|
|
803
878
|
))}
|
|
@@ -805,14 +880,18 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
|
|
|
805
880
|
)}
|
|
806
881
|
|
|
807
882
|
{/* Debug: Show if mistStreamInfo is missing tracks */}
|
|
808
|
-
{mistStreamInfo &&
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
883
|
+
{mistStreamInfo &&
|
|
884
|
+
(!mistStreamInfo.meta?.tracks ||
|
|
885
|
+
Object.keys(mistStreamInfo.meta.tracks).length === 0) && (
|
|
886
|
+
<div className="fw-dev-no-tracks">
|
|
887
|
+
<span className="fw-dev-no-tracks-text">
|
|
888
|
+
No track data available
|
|
889
|
+
{mistStreamInfo.type && (
|
|
890
|
+
<span className="fw-dev-no-tracks-type">({mistStreamInfo.type})</span>
|
|
891
|
+
)}
|
|
892
|
+
</span>
|
|
893
|
+
</div>
|
|
894
|
+
)}
|
|
816
895
|
</div>
|
|
817
896
|
)}
|
|
818
897
|
</div>
|