@livepeer-frameworks/player-react 0.1.0 → 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.
Files changed (41) hide show
  1. package/README.md +7 -9
  2. package/package.json +1 -1
  3. package/src/components/DevModePanel.tsx +244 -143
  4. package/src/components/Icons.tsx +105 -25
  5. package/src/components/IdleScreen.tsx +262 -128
  6. package/src/components/LoadingScreen.tsx +169 -151
  7. package/src/components/LogoOverlay.tsx +3 -6
  8. package/src/components/Player.tsx +84 -56
  9. package/src/components/PlayerControls.tsx +349 -256
  10. package/src/components/PlayerErrorBoundary.tsx +6 -13
  11. package/src/components/SeekBar.tsx +96 -88
  12. package/src/components/SkipIndicator.tsx +2 -12
  13. package/src/components/SpeedIndicator.tsx +2 -11
  14. package/src/components/StatsPanel.tsx +31 -22
  15. package/src/components/StreamStateOverlay.tsx +105 -49
  16. package/src/components/SubtitleRenderer.tsx +29 -29
  17. package/src/components/ThumbnailOverlay.tsx +5 -6
  18. package/src/components/TitleOverlay.tsx +2 -8
  19. package/src/components/players/DashJsPlayer.tsx +13 -11
  20. package/src/components/players/HlsJsPlayer.tsx +13 -11
  21. package/src/components/players/MewsWsPlayer/index.tsx +13 -11
  22. package/src/components/players/MistPlayer.tsx +13 -11
  23. package/src/components/players/MistWebRTCPlayer/index.tsx +19 -10
  24. package/src/components/players/NativePlayer.tsx +10 -12
  25. package/src/components/players/VideoJsPlayer.tsx +13 -11
  26. package/src/context/PlayerContext.tsx +4 -8
  27. package/src/context/index.ts +3 -3
  28. package/src/hooks/useMetaTrack.ts +27 -27
  29. package/src/hooks/usePlaybackQuality.ts +3 -3
  30. package/src/hooks/usePlayerController.ts +186 -138
  31. package/src/hooks/usePlayerSelection.ts +6 -6
  32. package/src/hooks/useStreamState.ts +51 -56
  33. package/src/hooks/useTelemetry.ts +18 -3
  34. package/src/hooks/useViewerEndpoints.ts +34 -23
  35. package/src/index.tsx +36 -28
  36. package/src/types.ts +8 -8
  37. package/src/ui/badge.tsx +6 -5
  38. package/src/ui/button.tsx +9 -8
  39. package/src/ui/context-menu.tsx +42 -61
  40. package/src/ui/select.tsx +13 -7
  41. 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
- "whep": "WHEP",
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 = 'auto',
69
+ playbackMode = "auto",
70
70
  onModeChange,
71
71
  onReload,
72
72
  streamInfo,
@@ -83,13 +83,16 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
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((value: boolean) => {
87
- if (onOpenChange) {
88
- onOpenChange(value);
89
- } else {
90
- setInternalIsOpen(value);
91
- }
92
- }, [onOpenChange]);
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
97
  const [, setCurrentComboIndex] = useState(0);
95
98
  const [hoveredComboIndex, setHoveredComboIndex] = useState<number | null>(null);
@@ -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((index: number) => {
224
- const combo = allCombinations[index];
225
- if (!combo) return;
226
-
227
- // Allow selecting even incompatible combos in dev mode (for testing)
228
- setCurrentComboIndex(index);
229
- onSettingsChange({
230
- forcePlayer: combo.player,
231
- forceType: combo.sourceType,
232
- forceSource: combo.sourceIndex,
233
- });
234
- }, [allCombinations, onSettingsChange]);
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<{
@@ -324,20 +330,17 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
324
330
  <div className="fw-dev-section">
325
331
  <div className="fw-dev-label">Active</div>
326
332
  <div className="fw-dev-value">
327
- {currentPlayer?.name || "None"}{" "}
328
- <span className="fw-dev-value-arrow">→</span>{" "}
333
+ {currentPlayer?.name || "None"} <span className="fw-dev-value-arrow">→</span>{" "}
329
334
  {SOURCE_TYPE_LABELS[currentSource?.type || ""] || currentSource?.type || "—"}
330
335
  </div>
331
- {nodeId && (
332
- <div className="fw-dev-value-muted">Node: {nodeId}</div>
333
- )}
336
+ {nodeId && <div className="fw-dev-value-muted">Node: {nodeId}</div>}
334
337
  </div>
335
338
 
336
339
  {/* Playback Mode Selector */}
337
340
  <div className="fw-dev-section">
338
341
  <div className="fw-dev-label">Playback Mode</div>
339
342
  <div className="fw-dev-mode-group">
340
- {(['auto', 'low-latency', 'quality'] as const).map((mode) => (
343
+ {(["auto", "low-latency", "quality"] as const).map((mode) => (
341
344
  <button
342
345
  key={mode}
343
346
  type="button"
@@ -347,31 +350,25 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
347
350
  playbackMode === mode && "fw-dev-mode-btn--active"
348
351
  )}
349
352
  >
350
- {mode === 'low-latency' ? 'Low Lat' : mode.charAt(0).toUpperCase() + mode.slice(1)}
353
+ {mode === "low-latency"
354
+ ? "Low Lat"
355
+ : mode.charAt(0).toUpperCase() + mode.slice(1)}
351
356
  </button>
352
357
  ))}
353
358
  </div>
354
359
  <div className="fw-dev-mode-desc">
355
- {playbackMode === 'auto' && 'Balanced: MP4/WS → WHEP → HLS'}
356
- {playbackMode === 'low-latency' && 'WHEP/WebRTC first (<1s delay)'}
357
- {playbackMode === 'quality' && 'MP4/WS first, HLS fallback'}
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"}
358
363
  </div>
359
364
  </div>
360
365
 
361
366
  {/* Action buttons */}
362
367
  <div className="fw-dev-actions">
363
- <button
364
- type="button"
365
- onClick={handleReload}
366
- className="fw-dev-action-btn"
367
- >
368
+ <button type="button" onClick={handleReload} className="fw-dev-action-btn">
368
369
  Reload
369
370
  </button>
370
- <button
371
- type="button"
372
- onClick={handleNextCombo}
373
- className="fw-dev-action-btn"
374
- >
371
+ <button type="button" onClick={handleNextCombo} className="fw-dev-action-btn">
375
372
  Next Option
376
373
  </button>
377
374
  </div>
@@ -379,9 +376,7 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
379
376
  {/* Combo list */}
380
377
  <div className="fw-dev-section" style={{ padding: 0, borderBottom: 0 }}>
381
378
  <div className="fw-dev-list-header">
382
- <span className="fw-dev-list-title">
383
- Player Options ({combinations.length})
384
- </span>
379
+ <span className="fw-dev-list-title">Player Options ({combinations.length})</span>
385
380
  {allCombinations.length > combinations.length && (
386
381
  <button
387
382
  type="button"
@@ -399,14 +394,13 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
399
394
  >
400
395
  <path d="M6 9l6 6 6-6" />
401
396
  </svg>
402
- {showDisabledPlayers ? "Hide" : "Show"} disabled ({allCombinations.length - combinations.length})
397
+ {showDisabledPlayers ? "Hide" : "Show"} disabled (
398
+ {allCombinations.length - combinations.length})
403
399
  </button>
404
400
  )}
405
401
  </div>
406
402
  {allCombinations.length === 0 ? (
407
- <div className="fw-dev-list-empty">
408
- No stream info available
409
- </div>
403
+ <div className="fw-dev-list-empty">No stream info available</div>
410
404
  ) : (
411
405
  <div>
412
406
  {allCombinations.map((combo, index) => {
@@ -415,11 +409,13 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
415
409
  if (!combo.compatible && !isCodecIncompat && !showDisabledPlayers) return null;
416
410
 
417
411
  const isActive = activeComboIndex === index;
418
- const typeLabel = SOURCE_TYPE_LABELS[combo.sourceType] || combo.sourceType.split("/").pop();
412
+ const typeLabel =
413
+ SOURCE_TYPE_LABELS[combo.sourceType] || combo.sourceType.split("/").pop();
419
414
 
420
415
  // Determine score class
421
416
  const getScoreClass = () => {
422
- if (!combo.compatible && !isCodecIncompat) return "fw-dev-combo-score--disabled";
417
+ if (!combo.compatible && !isCodecIncompat)
418
+ return "fw-dev-combo-score--disabled";
423
419
  if (isCodecIncompat) return "fw-dev-combo-score--low";
424
420
  if (combo.score >= 2) return "fw-dev-combo-score--high";
425
421
  if (combo.score >= 1.5) return "fw-dev-combo-score--mid";
@@ -451,7 +447,8 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
451
447
  const row = e.currentTarget;
452
448
  const containerRect = container.getBoundingClientRect();
453
449
  const rowRect = row.getBoundingClientRect();
454
- const relativePosition = (rowRect.top - containerRect.top) / containerRect.height;
450
+ const relativePosition =
451
+ (rowRect.top - containerRect.top) / containerRect.height;
455
452
  setTooltipAbove(relativePosition > 0.6);
456
453
  }
457
454
  }}
@@ -474,9 +471,10 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
474
471
  </span>
475
472
  {/* Player + Protocol */}
476
473
  <span className="fw-dev-combo-name">
477
- {combo.playerName}{" "}
478
- <span className="fw-dev-combo-arrow">→</span>{" "}
479
- <span className={cn("fw-dev-combo-type", getTypeClass())}>{typeLabel}</span>
474
+ {combo.playerName} <span className="fw-dev-combo-arrow">→</span>{" "}
475
+ <span className={cn("fw-dev-combo-type", getTypeClass())}>
476
+ {typeLabel}
477
+ </span>
480
478
  </span>
481
479
  {/* Score */}
482
480
  <span className={cn("fw-dev-combo-score", getScoreClass())}>
@@ -486,50 +484,105 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
486
484
 
487
485
  {/* Score breakdown tooltip */}
488
486
  {hoveredComboIndex === index && (
489
- <div className={cn(
490
- "fw-dev-tooltip",
491
- tooltipAbove ? "fw-dev-tooltip--above" : "fw-dev-tooltip--below"
492
- )}>
487
+ <div
488
+ className={cn(
489
+ "fw-dev-tooltip",
490
+ tooltipAbove ? "fw-dev-tooltip--above" : "fw-dev-tooltip--below"
491
+ )}
492
+ >
493
493
  {/* Full player/source info */}
494
494
  <div className="fw-dev-tooltip-header">
495
495
  <div className="fw-dev-tooltip-title">{combo.playerName}</div>
496
496
  <div className="fw-dev-tooltip-subtitle">{combo.sourceType}</div>
497
- {combo.scoreBreakdown?.trackTypes && combo.scoreBreakdown.trackTypes.length > 0 && (
498
- <div className="fw-dev-tooltip-tracks">
499
- Tracks: <span className="fw-dev-tooltip-value">{combo.scoreBreakdown.trackTypes.join(', ')}</span>
500
- </div>
501
- )}
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
+ )}
502
506
  </div>
503
507
  {combo.compatible && combo.scoreBreakdown ? (
504
508
  <>
505
- <div className="fw-dev-tooltip-score">Score: {combo.score.toFixed(2)}</div>
509
+ <div className="fw-dev-tooltip-score">
510
+ Score: {combo.score.toFixed(2)}
511
+ </div>
506
512
  <div className="fw-dev-tooltip-row">
507
- Tracks [{combo.scoreBreakdown.trackTypes.join(', ')}]: <span className="fw-dev-tooltip-value">{combo.scoreBreakdown.trackScore.toFixed(2)}</span> <span className="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.tracks}</span>
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>
508
520
  </div>
509
521
  <div className="fw-dev-tooltip-row">
510
- Priority: <span className="fw-dev-tooltip-value">{combo.scoreBreakdown.priorityScore.toFixed(2)}</span> <span className="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.priority}</span>
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>
511
529
  </div>
512
530
  <div className="fw-dev-tooltip-row">
513
- Source: <span className="fw-dev-tooltip-value">{combo.scoreBreakdown.sourceScore.toFixed(2)}</span> <span className="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.source}</span>
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>
514
538
  </div>
515
539
  {combo.scoreBreakdown.reliabilityScore !== undefined && (
516
540
  <div className="fw-dev-tooltip-row">
517
- Reliability: <span className="fw-dev-tooltip-value">{combo.scoreBreakdown.reliabilityScore.toFixed(2)}</span> <span className="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.reliability ?? 0}</span>
518
- </div>
519
- )}
520
- {combo.scoreBreakdown.modeBonus !== undefined && combo.scoreBreakdown.modeBonus !== 0 && (
521
- <div className="fw-dev-tooltip-row">
522
- Mode ({playbackMode}): <span className="fw-dev-tooltip-bonus">+{combo.scoreBreakdown.modeBonus.toFixed(2)}</span> <span className="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.mode ?? 0}</span>
523
- </div>
524
- )}
525
- {combo.scoreBreakdown.routingBonus !== undefined && combo.scoreBreakdown.routingBonus !== 0 && (
526
- <div className="fw-dev-tooltip-row">
527
- 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>
528
548
  </div>
529
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
+ )}
530
581
  </>
531
582
  ) : (
532
- <div className="fw-dev-tooltip-error">{combo.incompatibleReason || 'Incompatible'}</div>
583
+ <div className="fw-dev-tooltip-error">
584
+ {combo.incompatibleReason || "Incompatible"}
585
+ </div>
533
586
  )}
534
587
  </div>
535
588
  )}
@@ -549,30 +602,46 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
549
602
  <div className="fw-dev-section">
550
603
  <div className="fw-dev-label">Playback Rate</div>
551
604
  <div className="fw-dev-rate">
552
- <div className={cn(
553
- "fw-dev-rate-value",
554
- playbackScore >= 0.95 && playbackScore <= 1.05 ? "fw-dev-stat-value--good" :
555
- playbackScore > 1.05 ? "fw-dev-stat-value--accent" :
556
- playbackScore >= 0.75 ? "fw-dev-stat-value--warn" :
557
- "fw-dev-stat-value--bad"
558
- )}>
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
+ >
559
617
  {playbackScore.toFixed(2)}×
560
618
  </div>
561
619
  <div className="fw-dev-rate-status">
562
- {playbackScore >= 0.95 && playbackScore <= 1.05 ? "realtime" :
563
- playbackScore > 1.05 ? "catching up" :
564
- playbackScore >= 0.75 ? "slightly slow" :
565
- "stalling"}
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"}
566
627
  </div>
567
628
  </div>
568
629
  <div className="fw-dev-rate-stats">
569
- <span className={qualityScore >= 75 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--bad"}>
630
+ <span
631
+ className={
632
+ qualityScore >= 75 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--bad"
633
+ }
634
+ >
570
635
  Quality: {qualityScore}/100
571
636
  </span>
572
- <span className={stallCount === 0 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--warn"}>
637
+ <span
638
+ className={stallCount === 0 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--warn"}
639
+ >
573
640
  Stalls: {stallCount}
574
641
  </span>
575
- <span className={frameDropRate < 1 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--bad"}>
642
+ <span
643
+ className={frameDropRate < 1 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--bad"}
644
+ >
576
645
  Drops: {frameDropRate.toFixed(1)}%
577
646
  </span>
578
647
  </div>
@@ -616,16 +685,14 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
616
685
  {nodeId && (
617
686
  <div className="fw-dev-stat">
618
687
  <span className="fw-dev-stat-label">Node ID</span>
619
- <span className="fw-dev-stat-value truncate" style={{ maxWidth: '150px' }}>
688
+ <span className="fw-dev-stat-value truncate" style={{ maxWidth: "150px" }}>
620
689
  {nodeId}
621
690
  </span>
622
691
  </div>
623
692
  )}
624
693
  </div>
625
694
  ) : (
626
- <div className="fw-dev-list-empty">
627
- No video element available
628
- </div>
695
+ <div className="fw-dev-list-empty">No video element available</div>
629
696
  )}
630
697
 
631
698
  {/* Player-specific Stats (HLS.js / WebRTC) */}
@@ -633,20 +700,23 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
633
700
  <div>
634
701
  <div className="fw-dev-list-header fw-dev-section-header">
635
702
  <span className="fw-dev-list-title">
636
- {playerStats.type === 'hls' ? 'HLS.js Stats' :
637
- playerStats.type === 'webrtc' ? 'WebRTC Stats' : 'Player Stats'}
703
+ {playerStats.type === "hls"
704
+ ? "HLS.js Stats"
705
+ : playerStats.type === "webrtc"
706
+ ? "WebRTC Stats"
707
+ : "Player Stats"}
638
708
  </span>
639
709
  </div>
640
710
 
641
711
  {/* HLS-specific stats */}
642
- {playerStats.type === 'hls' && (
712
+ {playerStats.type === "hls" && (
643
713
  <>
644
714
  <div className="fw-dev-stat">
645
715
  <span className="fw-dev-stat-label">Bitrate</span>
646
716
  <span className="fw-dev-stat-value--accent">
647
717
  {playerStats.currentBitrate > 0
648
718
  ? `${Math.round(playerStats.currentBitrate / 1000)} kbps`
649
- : 'N/A'}
719
+ : "N/A"}
650
720
  </span>
651
721
  </div>
652
722
  <div className="fw-dev-stat">
@@ -654,19 +724,26 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
654
724
  <span className="fw-dev-stat-value">
655
725
  {playerStats.bandwidthEstimate > 0
656
726
  ? `${Math.round(playerStats.bandwidthEstimate / 1000)} kbps`
657
- : 'N/A'}
727
+ : "N/A"}
658
728
  </span>
659
729
  </div>
660
730
  <div className="fw-dev-stat">
661
731
  <span className="fw-dev-stat-label">Level</span>
662
732
  <span className="fw-dev-stat-value">
663
- {playerStats.currentLevel >= 0 ? playerStats.currentLevel : 'Auto'} / {playerStats.levels?.length || 0}
733
+ {playerStats.currentLevel >= 0 ? playerStats.currentLevel : "Auto"} /{" "}
734
+ {playerStats.levels?.length || 0}
664
735
  </span>
665
736
  </div>
666
737
  {playerStats.latency !== undefined && (
667
738
  <div className="fw-dev-stat">
668
739
  <span className="fw-dev-stat-label">Latency</span>
669
- <span className={playerStats.latency > 5000 ? "fw-dev-stat-value--warn" : "fw-dev-stat-value"}>
740
+ <span
741
+ className={
742
+ playerStats.latency > 5000
743
+ ? "fw-dev-stat-value--warn"
744
+ : "fw-dev-stat-value"
745
+ }
746
+ >
670
747
  {Math.round(playerStats.latency)} ms
671
748
  </span>
672
749
  </div>
@@ -675,7 +752,7 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
675
752
  )}
676
753
 
677
754
  {/* WebRTC-specific stats */}
678
- {playerStats.type === 'webrtc' && (
755
+ {playerStats.type === "webrtc" && (
679
756
  <>
680
757
  {playerStats.video && (
681
758
  <>
@@ -684,7 +761,7 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
684
761
  <span className="fw-dev-stat-value--accent">
685
762
  {playerStats.video.bitrate > 0
686
763
  ? `${Math.round(playerStats.video.bitrate / 1000)} kbps`
687
- : 'N/A'}
764
+ : "N/A"}
688
765
  </span>
689
766
  </div>
690
767
  <div className="fw-dev-stat">
@@ -696,21 +773,39 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
696
773
  <div className="fw-dev-stat">
697
774
  <span className="fw-dev-stat-label">Frames</span>
698
775
  <span className="fw-dev-stat-value">
699
- {playerStats.video.framesDecoded} decoded,{' '}
700
- <span className={playerStats.video.frameDropRate > 1 ? "fw-dev-stat-value--bad" : "fw-dev-stat-value--good"}>
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
+ >
701
784
  {playerStats.video.framesDropped} dropped
702
785
  </span>
703
786
  </span>
704
787
  </div>
705
788
  <div className="fw-dev-stat">
706
789
  <span className="fw-dev-stat-label">Packet Loss</span>
707
- <span className={playerStats.video.packetLossRate > 1 ? "fw-dev-stat-value--bad" : "fw-dev-stat-value--good"}>
790
+ <span
791
+ className={
792
+ playerStats.video.packetLossRate > 1
793
+ ? "fw-dev-stat-value--bad"
794
+ : "fw-dev-stat-value--good"
795
+ }
796
+ >
708
797
  {playerStats.video.packetLossRate.toFixed(2)}%
709
798
  </span>
710
799
  </div>
711
800
  <div className="fw-dev-stat">
712
801
  <span className="fw-dev-stat-label">Jitter</span>
713
- <span className={playerStats.video.jitter > 30 ? "fw-dev-stat-value--warn" : "fw-dev-stat-value"}>
802
+ <span
803
+ className={
804
+ playerStats.video.jitter > 30
805
+ ? "fw-dev-stat-value--warn"
806
+ : "fw-dev-stat-value"
807
+ }
808
+ >
714
809
  {playerStats.video.jitter.toFixed(1)} ms
715
810
  </span>
716
811
  </div>
@@ -725,7 +820,13 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
725
820
  {playerStats.network && (
726
821
  <div className="fw-dev-stat">
727
822
  <span className="fw-dev-stat-label">RTT</span>
728
- <span className={playerStats.network.rtt > 200 ? "fw-dev-stat-value--warn" : "fw-dev-stat-value"}>
823
+ <span
824
+ className={
825
+ playerStats.network.rtt > 200
826
+ ? "fw-dev-stat-value--warn"
827
+ : "fw-dev-stat-value"
828
+ }
829
+ >
729
830
  {Math.round(playerStats.network.rtt)} ms
730
831
  </span>
731
832
  </div>
@@ -746,36 +847,32 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
746
847
  {Object.entries(mistStreamInfo.meta.tracks).map(([id, track]) => (
747
848
  <div key={id} className="fw-dev-track">
748
849
  <div className="fw-dev-track-header">
749
- <span className={cn(
750
- "fw-dev-track-badge",
751
- track.type === 'video' ? "fw-dev-track-badge--video" :
752
- track.type === 'audio' ? "fw-dev-track-badge--audio" :
753
- "fw-dev-track-badge--other"
754
- )}>
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
+ >
755
860
  {track.type}
756
861
  </span>
757
862
  <span className="fw-dev-track-codec">{track.codec}</span>
758
863
  <span className="fw-dev-track-id">#{id}</span>
759
864
  </div>
760
865
  <div className="fw-dev-track-meta">
761
- {track.type === 'video' && track.width && track.height && (
762
- <span>{track.width}×{track.height}</span>
763
- )}
764
- {track.bps && (
765
- <span>{Math.round(track.bps / 1000)} kbps</span>
766
- )}
767
- {track.fpks && (
768
- <span>{Math.round(track.fpks / 1000)} fps</span>
769
- )}
770
- {track.type === 'audio' && track.channels && (
771
- <span>{track.channels}ch</span>
772
- )}
773
- {track.type === 'audio' && track.rate && (
774
- <span>{track.rate} Hz</span>
775
- )}
776
- {track.lang && (
777
- <span>{track.lang}</span>
866
+ {track.type === "video" && track.width && track.height && (
867
+ <span>
868
+ {track.width}×{track.height}
869
+ </span>
778
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>}
779
876
  </div>
780
877
  </div>
781
878
  ))}
@@ -783,14 +880,18 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
783
880
  )}
784
881
 
785
882
  {/* Debug: Show if mistStreamInfo is missing tracks */}
786
- {mistStreamInfo && (!mistStreamInfo.meta?.tracks || Object.keys(mistStreamInfo.meta.tracks).length === 0) && (
787
- <div className="fw-dev-no-tracks">
788
- <span className="fw-dev-no-tracks-text">
789
- No track data available
790
- {mistStreamInfo.type && <span className="fw-dev-no-tracks-type">({mistStreamInfo.type})</span>}
791
- </span>
792
- </div>
793
- )}
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
+ )}
794
895
  </div>
795
896
  )}
796
897
  </div>