@livepeer-frameworks/player-wc 0.1.2 → 0.1.4

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 (116) hide show
  1. package/dist/cjs/components/fw-dev-mode-panel.js +845 -212
  2. package/dist/cjs/components/fw-dev-mode-panel.js.map +1 -1
  3. package/dist/cjs/components/fw-dvd-logo.js +211 -0
  4. package/dist/cjs/components/fw-dvd-logo.js.map +1 -0
  5. package/dist/cjs/components/fw-idle-screen.js +641 -97
  6. package/dist/cjs/components/fw-idle-screen.js.map +1 -1
  7. package/dist/cjs/components/fw-loading-screen.js +513 -0
  8. package/dist/cjs/components/fw-loading-screen.js.map +1 -0
  9. package/dist/cjs/components/fw-player-controls.js +390 -173
  10. package/dist/cjs/components/fw-player-controls.js.map +1 -1
  11. package/dist/cjs/components/fw-player.js +506 -63
  12. package/dist/cjs/components/fw-player.js.map +1 -1
  13. package/dist/cjs/components/fw-seek-bar.js +292 -142
  14. package/dist/cjs/components/fw-seek-bar.js.map +1 -1
  15. package/dist/cjs/components/fw-settings-menu.js +208 -81
  16. package/dist/cjs/components/fw-settings-menu.js.map +1 -1
  17. package/dist/cjs/components/fw-stats-panel.js +134 -70
  18. package/dist/cjs/components/fw-stats-panel.js.map +1 -1
  19. package/dist/cjs/components/fw-stream-state-overlay.js +338 -0
  20. package/dist/cjs/components/fw-stream-state-overlay.js.map +1 -0
  21. package/dist/cjs/components/fw-subtitle-renderer.js +174 -27
  22. package/dist/cjs/components/fw-subtitle-renderer.js.map +1 -1
  23. package/dist/cjs/components/fw-thumbnail-overlay.js +161 -0
  24. package/dist/cjs/components/fw-thumbnail-overlay.js.map +1 -0
  25. package/dist/cjs/components/fw-volume-control.js +150 -69
  26. package/dist/cjs/components/fw-volume-control.js.map +1 -1
  27. package/dist/cjs/components/shared/hitmarker-audio.js +76 -0
  28. package/dist/cjs/components/shared/hitmarker-audio.js.map +1 -0
  29. package/dist/cjs/constants/media-assets.js +11 -0
  30. package/dist/cjs/constants/media-assets.js.map +1 -0
  31. package/dist/cjs/controllers/player-controller-host.js +51 -2
  32. package/dist/cjs/controllers/player-controller-host.js.map +1 -1
  33. package/dist/cjs/define.js +8 -0
  34. package/dist/cjs/define.js.map +1 -1
  35. package/dist/cjs/icons/index.js +27 -0
  36. package/dist/cjs/icons/index.js.map +1 -1
  37. package/dist/cjs/index.js +20 -0
  38. package/dist/cjs/index.js.map +1 -1
  39. package/dist/esm/components/fw-dev-mode-panel.js +846 -213
  40. package/dist/esm/components/fw-dev-mode-panel.js.map +1 -1
  41. package/dist/esm/components/fw-dvd-logo.js +211 -0
  42. package/dist/esm/components/fw-dvd-logo.js.map +1 -0
  43. package/dist/esm/components/fw-idle-screen.js +643 -99
  44. package/dist/esm/components/fw-idle-screen.js.map +1 -1
  45. package/dist/esm/components/fw-loading-screen.js +513 -0
  46. package/dist/esm/components/fw-loading-screen.js.map +1 -0
  47. package/dist/esm/components/fw-player-controls.js +391 -174
  48. package/dist/esm/components/fw-player-controls.js.map +1 -1
  49. package/dist/esm/components/fw-player.js +506 -63
  50. package/dist/esm/components/fw-player.js.map +1 -1
  51. package/dist/esm/components/fw-seek-bar.js +293 -143
  52. package/dist/esm/components/fw-seek-bar.js.map +1 -1
  53. package/dist/esm/components/fw-settings-menu.js +209 -82
  54. package/dist/esm/components/fw-settings-menu.js.map +1 -1
  55. package/dist/esm/components/fw-stats-panel.js +135 -71
  56. package/dist/esm/components/fw-stats-panel.js.map +1 -1
  57. package/dist/esm/components/fw-stream-state-overlay.js +338 -0
  58. package/dist/esm/components/fw-stream-state-overlay.js.map +1 -0
  59. package/dist/esm/components/fw-subtitle-renderer.js +175 -28
  60. package/dist/esm/components/fw-subtitle-renderer.js.map +1 -1
  61. package/dist/esm/components/fw-thumbnail-overlay.js +161 -0
  62. package/dist/esm/components/fw-thumbnail-overlay.js.map +1 -0
  63. package/dist/esm/components/fw-volume-control.js +150 -69
  64. package/dist/esm/components/fw-volume-control.js.map +1 -1
  65. package/dist/esm/components/shared/hitmarker-audio.js +74 -0
  66. package/dist/esm/components/shared/hitmarker-audio.js.map +1 -0
  67. package/dist/esm/constants/media-assets.js +8 -0
  68. package/dist/esm/constants/media-assets.js.map +1 -0
  69. package/dist/esm/controllers/player-controller-host.js +51 -2
  70. package/dist/esm/controllers/player-controller-host.js.map +1 -1
  71. package/dist/esm/define.js +8 -0
  72. package/dist/esm/define.js.map +1 -1
  73. package/dist/esm/icons/index.js +26 -2
  74. package/dist/esm/icons/index.js.map +1 -1
  75. package/dist/esm/index.js +4 -0
  76. package/dist/esm/index.js.map +1 -1
  77. package/dist/fw-player.iife.js +2097 -883
  78. package/dist/types/components/fw-dev-mode-panel.d.ts +36 -9
  79. package/dist/types/components/fw-dvd-logo.d.ts +29 -0
  80. package/dist/types/components/fw-idle-screen.d.ts +36 -0
  81. package/dist/types/components/fw-loading-screen.d.ts +36 -0
  82. package/dist/types/components/fw-player-controls.d.ts +23 -6
  83. package/dist/types/components/fw-player.d.ts +32 -1
  84. package/dist/types/components/fw-seek-bar.d.ts +31 -14
  85. package/dist/types/components/fw-settings-menu.d.ts +16 -1
  86. package/dist/types/components/fw-stats-panel.d.ts +4 -4
  87. package/dist/types/components/fw-stream-state-overlay.d.ts +20 -0
  88. package/dist/types/components/fw-subtitle-renderer.d.ts +33 -2
  89. package/dist/types/components/fw-thumbnail-overlay.d.ts +17 -0
  90. package/dist/types/components/fw-volume-control.d.ts +11 -4
  91. package/dist/types/components/shared/hitmarker-audio.d.ts +1 -0
  92. package/dist/types/constants/media-assets.d.ts +5 -0
  93. package/dist/types/controllers/player-controller-host.d.ts +14 -1
  94. package/dist/types/iife-entry.d.ts +4 -0
  95. package/dist/types/index.d.ts +4 -0
  96. package/package.json +2 -2
  97. package/src/components/fw-dev-mode-panel.ts +929 -228
  98. package/src/components/fw-dvd-logo.ts +233 -0
  99. package/src/components/fw-idle-screen.ts +680 -100
  100. package/src/components/fw-loading-screen.ts +540 -0
  101. package/src/components/fw-player-controls.ts +475 -175
  102. package/src/components/fw-player.ts +551 -60
  103. package/src/components/fw-seek-bar.ts +336 -143
  104. package/src/components/fw-settings-menu.ts +248 -85
  105. package/src/components/fw-stats-panel.ts +150 -77
  106. package/src/components/fw-stream-state-overlay.ts +331 -0
  107. package/src/components/fw-subtitle-renderer.ts +216 -28
  108. package/src/components/fw-thumbnail-overlay.ts +148 -0
  109. package/src/components/fw-volume-control.ts +166 -66
  110. package/src/components/shared/hitmarker-audio.ts +92 -0
  111. package/src/constants/media-assets.ts +7 -0
  112. package/src/controllers/player-controller-host.ts +52 -3
  113. package/src/define.ts +8 -0
  114. package/src/iife-entry.ts +4 -0
  115. package/src/index.ts +4 -0
  116. package/dist/fw-player.iife.js.map +0 -1
@@ -7,129 +7,852 @@ var classMap_js = require('lit/directives/class-map.js');
7
7
  var sharedStyles = require('../styles/shared-styles.js');
8
8
  var utilityStyles = require('../styles/utility-styles.js');
9
9
  var index = require('../icons/index.js');
10
+ var playerCore = require('@livepeer-frameworks/player-core');
10
11
 
12
+ const SOURCE_TYPE_LABELS = {
13
+ "html5/application/vnd.apple.mpegurl": "HLS",
14
+ "dash/video/mp4": "DASH",
15
+ "html5/video/mp4": "MP4",
16
+ "html5/video/webm": "WebM",
17
+ whep: "WHEP",
18
+ "mist/html": "Mist",
19
+ "mist/legacy": "Auto",
20
+ "ws/video/mp4": "MEWS",
21
+ };
11
22
  exports.FwDevModePanel = class FwDevModePanel extends lit.LitElement {
12
23
  constructor() {
13
24
  super(...arguments);
14
25
  this.playbackMode = "auto";
15
26
  this._activeTab = "config";
16
- this._modes = ["auto", "low-latency", "quality"];
27
+ this._hoveredComboIndex = null;
28
+ this._tooltipAbove = false;
29
+ this._showDisabledPlayers = false;
30
+ this._playbackScore = 1;
31
+ this._qualityScore = 100;
32
+ this._stallCount = 0;
33
+ this._frameDropRate = 0;
34
+ this._videoStats = null;
35
+ this._playerStats = null;
36
+ this._qualityMonitor = null;
37
+ this._qualityMonitorVideo = null;
38
+ this._videoStatsInterval = null;
39
+ this._playerStatsInterval = null;
17
40
  }
18
- render() {
19
- const s = this.pc.s;
41
+ disconnectedCallback() {
42
+ super.disconnectedCallback();
43
+ this._stopQualityMonitor();
44
+ this._stopStatsPolling();
45
+ }
46
+ updated(_changed) {
47
+ this._syncQualityMonitor();
48
+ this._syncStatsPolling();
49
+ }
50
+ _getMistStreamInfo() {
51
+ return this.pc.s.streamState?.streamInfo;
52
+ }
53
+ _getAllCombinations() {
54
+ const streamInfo = this.pc.s.streamInfo;
55
+ if (!streamInfo) {
56
+ return [];
57
+ }
58
+ try {
59
+ return playerCore.globalPlayerManager.getAllCombinations(streamInfo, this.playbackMode);
60
+ }
61
+ catch {
62
+ return [];
63
+ }
64
+ }
65
+ _getCompatibleCombinations() {
66
+ return this._getAllCombinations().filter((combo) => combo.compatible);
67
+ }
68
+ _getActiveComboIndex(combinations) {
69
+ const currentPlayer = this.pc.s.currentPlayerInfo;
70
+ const currentSource = this.pc.s.currentSourceInfo;
71
+ if (!currentPlayer || !currentSource || combinations.length === 0) {
72
+ return -1;
73
+ }
74
+ return combinations.findIndex((combo) => combo.player === currentPlayer.shortname && combo.sourceType === currentSource.type);
75
+ }
76
+ _syncQualityMonitor() {
77
+ const video = this.pc.s.videoElement;
78
+ if (!video) {
79
+ this._stopQualityMonitor();
80
+ return;
81
+ }
82
+ if (!this._qualityMonitor) {
83
+ this._qualityMonitor = new playerCore.QualityMonitor({
84
+ sampleInterval: 500,
85
+ onSample: (quality) => {
86
+ this._qualityScore = quality.score;
87
+ this._stallCount = quality.stallCount;
88
+ this._frameDropRate = quality.frameDropRate;
89
+ this._playbackScore = this._qualityMonitor?.getPlaybackScore() ?? 1;
90
+ this.requestUpdate();
91
+ },
92
+ });
93
+ }
94
+ if (this._qualityMonitorVideo !== video) {
95
+ this._qualityMonitor.stop();
96
+ this._qualityMonitor.start(video);
97
+ this._qualityMonitorVideo = video;
98
+ }
99
+ }
100
+ _stopQualityMonitor() {
101
+ this._qualityMonitor?.stop();
102
+ this._qualityMonitorVideo = null;
103
+ }
104
+ _syncStatsPolling() {
105
+ if (this._activeTab !== "stats") {
106
+ this._stopStatsPolling();
107
+ return;
108
+ }
109
+ if (!this._videoStatsInterval) {
110
+ this._updateVideoStats();
111
+ this._videoStatsInterval = setInterval(() => {
112
+ this._updateVideoStats();
113
+ }, 500);
114
+ }
115
+ if (!this._playerStatsInterval) {
116
+ void this._pollPlayerStats();
117
+ this._playerStatsInterval = setInterval(() => {
118
+ void this._pollPlayerStats();
119
+ }, 500);
120
+ }
121
+ }
122
+ _stopStatsPolling() {
123
+ if (this._videoStatsInterval) {
124
+ clearInterval(this._videoStatsInterval);
125
+ this._videoStatsInterval = null;
126
+ }
127
+ if (this._playerStatsInterval) {
128
+ clearInterval(this._playerStatsInterval);
129
+ this._playerStatsInterval = null;
130
+ }
131
+ }
132
+ _updateVideoStats() {
133
+ const video = this.pc.s.videoElement;
134
+ if (!video) {
135
+ this._videoStats = null;
136
+ return;
137
+ }
138
+ this._videoStats = {
139
+ resolution: `${video.videoWidth}x${video.videoHeight}`,
140
+ buffered: video.buffered.length > 0
141
+ ? (video.buffered.end(video.buffered.length - 1) - video.currentTime).toFixed(1)
142
+ : "0",
143
+ playbackRate: video.playbackRate.toFixed(2),
144
+ currentTime: video.currentTime.toFixed(1),
145
+ duration: Number.isFinite(video.duration) ? video.duration.toFixed(1) : "live",
146
+ readyState: video.readyState,
147
+ networkState: video.networkState,
148
+ };
149
+ }
150
+ async _pollPlayerStats() {
151
+ try {
152
+ const stats = await this.pc.getStats();
153
+ if (stats) {
154
+ this._playerStats = stats;
155
+ }
156
+ }
157
+ catch {
158
+ // No-op for optional stats backends.
159
+ }
160
+ }
161
+ _handleComboMouseEnter(index, event) {
162
+ this._hoveredComboIndex = index;
163
+ const container = this.renderRoot.querySelector(".fw-dev-body");
164
+ if (!container) {
165
+ return;
166
+ }
167
+ const row = event.currentTarget;
168
+ const containerRect = container.getBoundingClientRect();
169
+ const rowRect = row.getBoundingClientRect();
170
+ const relativePosition = (rowRect.top - containerRect.top) / containerRect.height;
171
+ this._tooltipAbove = relativePosition > 0.6;
172
+ }
173
+ _handleModeChange(mode) {
174
+ this.playbackMode = mode;
175
+ void this.pc.setDevModeOptions({ playbackMode: mode });
176
+ this.dispatchEvent(new CustomEvent("fw-playback-mode-change", {
177
+ detail: { mode },
178
+ bubbles: true,
179
+ composed: true,
180
+ }));
181
+ }
182
+ _handleReload() {
183
+ this.pc.clearError();
184
+ void this.pc.reload();
185
+ }
186
+ _handleNextCombo() {
187
+ const compatible = this._getCompatibleCombinations();
188
+ if (compatible.length === 0) {
189
+ return;
190
+ }
191
+ const activeCompatibleIndex = this._getActiveComboIndex(compatible);
192
+ const startIndex = activeCompatibleIndex >= 0 ? activeCompatibleIndex : -1;
193
+ const nextIndex = (startIndex + 1) % compatible.length;
194
+ const next = compatible[nextIndex];
195
+ void this.pc.setDevModeOptions({
196
+ forcePlayer: next.player,
197
+ forceType: next.sourceType,
198
+ forceSource: next.sourceIndex,
199
+ });
200
+ }
201
+ _handleSelectCombo(index) {
202
+ const allCombinations = this._getAllCombinations();
203
+ const combo = allCombinations[index];
204
+ if (!combo) {
205
+ return;
206
+ }
207
+ void this.pc.setDevModeOptions({
208
+ forcePlayer: combo.player,
209
+ forceType: combo.sourceType,
210
+ forceSource: combo.sourceIndex,
211
+ });
212
+ }
213
+ _renderStatsTab() {
214
+ const primaryEndpoint = (this.pc.s.endpoints?.primary ?? null);
215
+ const stats = this._videoStats;
216
+ const playerStats = this._playerStats;
217
+ const mistStreamInfo = this._getMistStreamInfo();
218
+ const trackEntries = Object.entries(mistStreamInfo?.meta?.tracks ?? {});
20
219
  return lit.html `
21
- <div class="panel fw-dev-panel">
22
- <div class="header fw-dev-header">
23
- <div class="tabs">
24
- <button
25
- class=${classMap_js.classMap({ tab: true, "tab--active": this._activeTab === "config" })}
26
- @click=${() => {
27
- this._activeTab = "config";
28
- }}
220
+ <div class="fw-dev-body">
221
+ <div class="fw-dev-section">
222
+ <div class="fw-dev-label">Playback Rate</div>
223
+ <div class="fw-dev-rate">
224
+ <div
225
+ class=${classMap_js.classMap({
226
+ "fw-dev-rate-value": true,
227
+ "fw-dev-stat-value--good": this._playbackScore >= 0.95 && this._playbackScore <= 1.05,
228
+ "fw-dev-stat-value--accent": this._playbackScore > 1.05,
229
+ "fw-dev-stat-value--warn": this._playbackScore >= 0.75 && this._playbackScore < 0.95,
230
+ "fw-dev-stat-value--bad": this._playbackScore < 0.75,
231
+ })}
29
232
  >
30
- Config
31
- </button>
32
- <button
33
- class=${classMap_js.classMap({ tab: true, "tab--active": this._activeTab === "stats" })}
34
- @click=${() => {
35
- this._activeTab = "stats";
36
- }}
233
+ ${this._playbackScore.toFixed(2)}x
234
+ </div>
235
+ <div class="fw-dev-rate-status">
236
+ ${this._playbackScore >= 0.95 && this._playbackScore <= 1.05
237
+ ? "realtime"
238
+ : this._playbackScore > 1.05
239
+ ? "catching up"
240
+ : this._playbackScore >= 0.75
241
+ ? "slightly slow"
242
+ : "stalling"}
243
+ </div>
244
+ </div>
245
+ <div class="fw-dev-rate-stats">
246
+ <span
247
+ class=${classMap_js.classMap({
248
+ "fw-dev-stat-value--good": this._qualityScore >= 75,
249
+ "fw-dev-stat-value--bad": this._qualityScore < 75,
250
+ })}
251
+ >
252
+ Quality: ${this._qualityScore}/100
253
+ </span>
254
+ <span
255
+ class=${classMap_js.classMap({
256
+ "fw-dev-stat-value--good": this._stallCount === 0,
257
+ "fw-dev-stat-value--warn": this._stallCount > 0,
258
+ })}
259
+ >
260
+ Stalls: ${this._stallCount}
261
+ </span>
262
+ <span
263
+ class=${classMap_js.classMap({
264
+ "fw-dev-stat-value--good": this._frameDropRate < 1,
265
+ "fw-dev-stat-value--bad": this._frameDropRate >= 1,
266
+ })}
37
267
  >
38
- Stats
39
- </button>
268
+ Drops: ${this._frameDropRate.toFixed(1)}%
269
+ </span>
40
270
  </div>
41
- <button
42
- class="close"
43
- @click=${() => this.dispatchEvent(new CustomEvent("fw-close", { bubbles: true, composed: true }))}
44
- aria-label="Close panel"
45
- >
46
- ${index.closeIcon()}
47
- </button>
48
271
  </div>
49
272
 
50
- <div class="body fw-dev-body">
51
- ${this._activeTab === "config" ? this._renderConfig(s) : this._renderStats(s)}
52
- </div>
273
+ ${stats
274
+ ? lit.html `
275
+ <div class="fw-dev-stat">
276
+ <span class="fw-dev-stat-label">Resolution</span>
277
+ <span class="fw-dev-stat-value">${stats.resolution}</span>
278
+ </div>
279
+ <div class="fw-dev-stat">
280
+ <span class="fw-dev-stat-label">Buffer</span>
281
+ <span class="fw-dev-stat-value">${stats.buffered}s</span>
282
+ </div>
283
+ <div class="fw-dev-stat">
284
+ <span class="fw-dev-stat-label">Playback Rate</span>
285
+ <span class="fw-dev-stat-value">${stats.playbackRate}x</span>
286
+ </div>
287
+ <div class="fw-dev-stat">
288
+ <span class="fw-dev-stat-label">Time</span>
289
+ <span class="fw-dev-stat-value">${stats.currentTime} / ${stats.duration}</span>
290
+ </div>
291
+ <div class="fw-dev-stat">
292
+ <span class="fw-dev-stat-label">Ready State</span>
293
+ <span class="fw-dev-stat-value">${stats.readyState}</span>
294
+ </div>
295
+ <div class="fw-dev-stat">
296
+ <span class="fw-dev-stat-label">Network State</span>
297
+ <span class="fw-dev-stat-value">${stats.networkState}</span>
298
+ </div>
299
+ ${primaryEndpoint?.protocol
300
+ ? lit.html `
301
+ <div class="fw-dev-stat">
302
+ <span class="fw-dev-stat-label">Protocol</span>
303
+ <span class="fw-dev-stat-value">${primaryEndpoint.protocol}</span>
304
+ </div>
305
+ `
306
+ : lit.nothing}
307
+ ${primaryEndpoint?.nodeId
308
+ ? lit.html `
309
+ <div class="fw-dev-stat">
310
+ <span class="fw-dev-stat-label">Node ID</span>
311
+ <span
312
+ class="fw-dev-stat-value"
313
+ style="max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"
314
+ >${primaryEndpoint.nodeId}</span
315
+ >
316
+ </div>
317
+ `
318
+ : lit.nothing}
319
+ `
320
+ : lit.html `<div class="fw-dev-list-empty">No video element available</div>`}
321
+ ${playerStats
322
+ ? lit.html `
323
+ <div class="fw-dev-list-header fw-dev-section-header">
324
+ <span class="fw-dev-list-title"
325
+ >${playerStats.type === "hls"
326
+ ? "HLS.js Stats"
327
+ : playerStats.type === "webrtc"
328
+ ? "WebRTC Stats"
329
+ : "Player Stats"}</span
330
+ >
331
+ </div>
332
+
333
+ ${playerStats.type === "hls"
334
+ ? lit.html `
335
+ <div class="fw-dev-stat">
336
+ <span class="fw-dev-stat-label">Bitrate</span>
337
+ <span class="fw-dev-stat-value--accent"
338
+ >${typeof playerStats.currentBitrate === "number" &&
339
+ playerStats.currentBitrate > 0
340
+ ? `${Math.round(playerStats.currentBitrate / 1000)} kbps`
341
+ : "N/A"}</span
342
+ >
343
+ </div>
344
+ <div class="fw-dev-stat">
345
+ <span class="fw-dev-stat-label">Bandwidth Est.</span>
346
+ <span class="fw-dev-stat-value"
347
+ >${typeof playerStats.bandwidthEstimate === "number" &&
348
+ playerStats.bandwidthEstimate > 0
349
+ ? `${Math.round(playerStats.bandwidthEstimate / 1000)} kbps`
350
+ : "N/A"}</span
351
+ >
352
+ </div>
353
+ <div class="fw-dev-stat">
354
+ <span class="fw-dev-stat-label">Level</span>
355
+ <span class="fw-dev-stat-value"
356
+ >${typeof playerStats.currentLevel === "number" &&
357
+ playerStats.currentLevel >= 0
358
+ ? playerStats.currentLevel
359
+ : "Auto"}
360
+ / ${Array.isArray(playerStats.levels) ? playerStats.levels.length : 0}</span
361
+ >
362
+ </div>
363
+ ${typeof playerStats.latency === "number"
364
+ ? lit.html `
365
+ <div class="fw-dev-stat">
366
+ <span class="fw-dev-stat-label">Latency</span>
367
+ <span
368
+ class=${classMap_js.classMap({
369
+ "fw-dev-stat-value": true,
370
+ "fw-dev-stat-value--warn": playerStats.latency > 5000,
371
+ })}
372
+ >${Math.round(playerStats.latency)} ms</span
373
+ >
374
+ </div>
375
+ `
376
+ : lit.nothing}
377
+ `
378
+ : lit.nothing}
379
+ ${playerStats.type === "webrtc"
380
+ ? lit.html `
381
+ ${playerStats.video
382
+ ? lit.html `
383
+ <div class="fw-dev-stat">
384
+ <span class="fw-dev-stat-label">Video Bitrate</span>
385
+ <span class="fw-dev-stat-value--accent"
386
+ >${typeof playerStats.video.bitrate === "number" &&
387
+ playerStats.video.bitrate > 0
388
+ ? `${Math.round(playerStats.video.bitrate / 1000)} kbps`
389
+ : "N/A"}</span
390
+ >
391
+ </div>
392
+ <div class="fw-dev-stat">
393
+ <span class="fw-dev-stat-label">FPS</span>
394
+ <span class="fw-dev-stat-value"
395
+ >${Math.round(playerStats.video.framesPerSecond || 0)}</span
396
+ >
397
+ </div>
398
+ <div class="fw-dev-stat">
399
+ <span class="fw-dev-stat-label">Frames</span>
400
+ <span class="fw-dev-stat-value"
401
+ >${playerStats.video.framesDecoded} decoded,
402
+ <span
403
+ class=${classMap_js.classMap({
404
+ "fw-dev-stat-value--bad": (playerStats.video.frameDropRate || 0) > 1,
405
+ "fw-dev-stat-value--good": (playerStats.video.frameDropRate || 0) <= 1,
406
+ })}
407
+ >${playerStats.video.framesDropped} dropped</span
408
+ ></span
409
+ >
410
+ </div>
411
+ <div class="fw-dev-stat">
412
+ <span class="fw-dev-stat-label">Packet Loss</span>
413
+ <span
414
+ class=${classMap_js.classMap({
415
+ "fw-dev-stat-value--bad": (playerStats.video.packetLossRate || 0) > 1,
416
+ "fw-dev-stat-value--good": (playerStats.video.packetLossRate || 0) <= 1,
417
+ })}
418
+ >${(playerStats.video.packetLossRate || 0).toFixed(2)}%</span
419
+ >
420
+ </div>
421
+ <div class="fw-dev-stat">
422
+ <span class="fw-dev-stat-label">Jitter</span>
423
+ <span
424
+ class=${classMap_js.classMap({
425
+ "fw-dev-stat-value": true,
426
+ "fw-dev-stat-value--warn": (playerStats.video.jitter || 0) > 30,
427
+ })}
428
+ >${(playerStats.video.jitter || 0).toFixed(1)}
429
+ ms</span
430
+ >
431
+ </div>
432
+ <div class="fw-dev-stat">
433
+ <span class="fw-dev-stat-label">Jitter Buffer</span>
434
+ <span class="fw-dev-stat-value"
435
+ >${(playerStats.video.jitterBufferDelay || 0).toFixed(1)}
436
+ ms</span
437
+ >
438
+ </div>
439
+ `
440
+ : lit.nothing}
441
+ ${playerStats.network
442
+ ? lit.html `
443
+ <div class="fw-dev-stat">
444
+ <span class="fw-dev-stat-label">RTT</span>
445
+ <span
446
+ class=${classMap_js.classMap({
447
+ "fw-dev-stat-value": true,
448
+ "fw-dev-stat-value--warn": (playerStats.network.rtt || 0) > 200,
449
+ })}
450
+ >${Math.round((playerStats.network.rtt || 0))}
451
+ ms</span
452
+ >
453
+ </div>
454
+ `
455
+ : lit.nothing}
456
+ `
457
+ : lit.nothing}
458
+ `
459
+ : lit.nothing}
460
+ ${trackEntries.length > 0
461
+ ? lit.html `
462
+ <div class="fw-dev-list-header fw-dev-section-header">
463
+ <span class="fw-dev-list-title">Tracks (${trackEntries.length})</span>
464
+ </div>
465
+ ${trackEntries.map(([id, track]) => {
466
+ const typedTrack = track;
467
+ return lit.html `
468
+ <div class="fw-dev-track">
469
+ <div class="fw-dev-track-header">
470
+ <span
471
+ class=${classMap_js.classMap({
472
+ "fw-dev-track-badge": true,
473
+ "fw-dev-track-badge--video": typedTrack.type === "video",
474
+ "fw-dev-track-badge--audio": typedTrack.type === "audio",
475
+ "fw-dev-track-badge--other": typedTrack.type !== "video" && typedTrack.type !== "audio",
476
+ })}
477
+ >${typedTrack.type}</span
478
+ >
479
+ <span class="fw-dev-track-codec">${typedTrack.codec}</span>
480
+ <span class="fw-dev-track-id">#${id}</span>
481
+ </div>
482
+ <div class="fw-dev-track-meta">
483
+ ${typedTrack.type === "video" && typedTrack.width && typedTrack.height
484
+ ? lit.html `<span>${typedTrack.width}x${typedTrack.height}</span>`
485
+ : lit.nothing}
486
+ ${typedTrack.bps
487
+ ? lit.html `<span>${Math.round(typedTrack.bps / 1000)} kbps</span>`
488
+ : lit.nothing}
489
+ ${typedTrack.fpks
490
+ ? lit.html `<span>${Math.round(typedTrack.fpks / 1000)} fps</span>`
491
+ : lit.nothing}
492
+ ${typedTrack.type === "audio" && typedTrack.channels
493
+ ? lit.html `<span>${typedTrack.channels}ch</span>`
494
+ : lit.nothing}
495
+ ${typedTrack.type === "audio" && typedTrack.rate
496
+ ? lit.html `<span>${typedTrack.rate} Hz</span>`
497
+ : lit.nothing}
498
+ ${typedTrack.lang ? lit.html `<span>${typedTrack.lang}</span>` : lit.nothing}
499
+ </div>
500
+ </div>
501
+ `;
502
+ })}
503
+ `
504
+ : lit.nothing}
505
+ ${mistStreamInfo && trackEntries.length === 0
506
+ ? lit.html `
507
+ <div class="fw-dev-no-tracks">
508
+ <span class="fw-dev-no-tracks-text"
509
+ >No track data available
510
+ ${mistStreamInfo.type
511
+ ? lit.html `<span class="fw-dev-no-tracks-type">(${mistStreamInfo.type})</span>`
512
+ : lit.nothing}</span
513
+ >
514
+ </div>
515
+ `
516
+ : lit.nothing}
53
517
  </div>
54
518
  `;
55
519
  }
56
- _renderConfig(s) {
520
+ _renderConfigTab() {
521
+ const allCombinations = this._getAllCombinations();
522
+ const compatibleCombinations = allCombinations.filter((combo) => combo.compatible);
523
+ const activeComboIndex = this._getActiveComboIndex(allCombinations);
524
+ const currentPlayer = this.pc.s.currentPlayerInfo;
525
+ const currentSource = this.pc.s.currentSourceInfo;
57
526
  return lit.html `
58
- <div class="section">
59
- <div class="label">Current Player</div>
60
- <div class="value">${s.currentPlayerInfo?.name ?? "—"}</div>
61
- </div>
62
- <div class="section">
63
- <div class="label">Current Source</div>
64
- <div class="value">${s.currentSourceInfo?.type ?? ""}</div>
65
- </div>
66
- <div class="section">
67
- <div class="label">Playback Mode</div>
68
- <div class="mode-group fw-dev-mode-group">
69
- ${this._modes.map((mode) => lit.html `
70
- <button
71
- class=${classMap_js.classMap({
72
- "mode-btn": true,
527
+ <div class="fw-dev-body">
528
+ <div class="fw-dev-section">
529
+ <div class="fw-dev-label">Active</div>
530
+ <div class="fw-dev-value">
531
+ ${currentPlayer?.name || "None"}
532
+ <span class="fw-dev-value-arrow">${"\u2192"}</span>
533
+ ${SOURCE_TYPE_LABELS[currentSource?.type || ""] || currentSource?.type || "-"}
534
+ </div>
535
+ ${this.pc.s.endpoints?.primary?.nodeId
536
+ ? lit.html `
537
+ <div class="fw-dev-value-muted">
538
+ Node: ${(this.pc.s.endpoints?.primary).nodeId}
539
+ </div>
540
+ `
541
+ : lit.nothing}
542
+ </div>
543
+
544
+ <div class="fw-dev-section">
545
+ <div class="fw-dev-label">Playback Mode</div>
546
+ <div class="fw-dev-mode-group">
547
+ ${["auto", "low-latency", "quality"].map((mode) => lit.html `
548
+ <button
549
+ type="button"
550
+ class=${classMap_js.classMap({
73
551
  "fw-dev-mode-btn": true,
74
- "mode-btn--active": this.playbackMode === mode,
75
552
  "fw-dev-mode-btn--active": this.playbackMode === mode,
76
553
  })}
77
- @click=${() => this.pc.setDevModeOptions({ playbackMode: mode })}
78
- >
79
- ${mode}
80
- </button>
81
- `)}
554
+ @click=${() => this._handleModeChange(mode)}
555
+ >
556
+ ${mode === "low-latency"
557
+ ? "Low Lat"
558
+ : `${mode.charAt(0).toUpperCase()}${mode.slice(1)}`}
559
+ </button>
560
+ `)}
561
+ </div>
562
+ <div class="fw-dev-mode-desc">
563
+ ${this.playbackMode === "auto"
564
+ ? "Balanced: MP4/WS \u2192 WHEP \u2192 HLS"
565
+ : this.playbackMode === "low-latency"
566
+ ? "WHEP/WebRTC first (<1s delay)"
567
+ : "MP4/WS first, HLS fallback"}
568
+ </div>
569
+ </div>
570
+
571
+ <div class="fw-dev-actions">
572
+ <button type="button" class="fw-dev-action-btn" @click=${this._handleReload}>
573
+ Reload
574
+ </button>
575
+ <button type="button" class="fw-dev-action-btn" @click=${this._handleNextCombo}>
576
+ Next Option
577
+ </button>
578
+ </div>
579
+
580
+ <div class="fw-dev-section" style="padding:0;border-bottom:0;">
581
+ <div class="fw-dev-list-header">
582
+ <span class="fw-dev-list-title">Player Options (${compatibleCombinations.length})</span>
583
+ ${allCombinations.length > compatibleCombinations.length
584
+ ? lit.html `
585
+ <button
586
+ type="button"
587
+ class="fw-dev-list-toggle"
588
+ @click=${() => {
589
+ this._showDisabledPlayers = !this._showDisabledPlayers;
590
+ }}
591
+ >
592
+ <svg
593
+ width="10"
594
+ height="10"
595
+ viewBox="0 0 24 24"
596
+ fill="none"
597
+ stroke="currentColor"
598
+ stroke-width="2"
599
+ class=${classMap_js.classMap({
600
+ "fw-dev-chevron": true,
601
+ "fw-dev-chevron--open": this._showDisabledPlayers,
602
+ })}
603
+ >
604
+ <path d="M6 9l6 6 6-6"></path>
605
+ </svg>
606
+ ${this._showDisabledPlayers ? "Hide" : "Show"} disabled
607
+ (${allCombinations.length - compatibleCombinations.length})
608
+ </button>
609
+ `
610
+ : lit.nothing}
611
+ </div>
612
+
613
+ ${allCombinations.length === 0
614
+ ? lit.html `<div class="fw-dev-list-empty">No stream info available</div>`
615
+ : lit.html `
616
+ ${allCombinations.map((combo, index) => {
617
+ const isCodecIncompatible = combo.codecIncompatible === true;
618
+ if (!combo.compatible && !isCodecIncompatible && !this._showDisabledPlayers) {
619
+ return lit.nothing;
620
+ }
621
+ const isActive = activeComboIndex === index;
622
+ const typeLabel = SOURCE_TYPE_LABELS[combo.sourceType] || combo.sourceType.split("/").pop();
623
+ const scoreClass = !combo.compatible && !isCodecIncompatible
624
+ ? "fw-dev-combo-score--disabled"
625
+ : isCodecIncompatible
626
+ ? "fw-dev-combo-score--low"
627
+ : combo.score >= 2
628
+ ? "fw-dev-combo-score--high"
629
+ : combo.score >= 1.5
630
+ ? "fw-dev-combo-score--mid"
631
+ : "fw-dev-combo-score--low";
632
+ const rankClass = isActive
633
+ ? "fw-dev-combo-rank--active"
634
+ : !combo.compatible && !isCodecIncompatible
635
+ ? "fw-dev-combo-rank--disabled"
636
+ : isCodecIncompatible
637
+ ? "fw-dev-combo-rank--warn"
638
+ : "";
639
+ const typeClass = !combo.compatible && !isCodecIncompatible
640
+ ? "fw-dev-combo-type--disabled"
641
+ : isCodecIncompatible
642
+ ? "fw-dev-combo-type--warn"
643
+ : "";
644
+ return lit.html `
645
+ <div
646
+ class="fw-dev-combo"
647
+ @mouseenter=${(event) => this._handleComboMouseEnter(index, event)}
648
+ @mouseleave=${() => {
649
+ this._hoveredComboIndex = null;
650
+ }}
651
+ >
652
+ <button
653
+ type="button"
654
+ class=${classMap_js.classMap({
655
+ "fw-dev-combo-btn": true,
656
+ "fw-dev-combo-btn--active": isActive,
657
+ "fw-dev-combo-btn--disabled": !combo.compatible && !isCodecIncompatible,
658
+ "fw-dev-combo-btn--codec-warn": isCodecIncompatible,
659
+ })}
660
+ @click=${() => this._handleSelectCombo(index)}
661
+ >
662
+ <span
663
+ class=${classMap_js.classMap({
664
+ "fw-dev-combo-rank": true,
665
+ [rankClass]: rankClass.length > 0,
666
+ })}
667
+ >${combo.compatible
668
+ ? index + 1
669
+ : isCodecIncompatible
670
+ ? "\u26A0"
671
+ : "\u2014"}</span
672
+ >
673
+
674
+ <span class="fw-dev-combo-name"
675
+ >${combo.playerName} <span class="fw-dev-combo-arrow">${"\u2192"}</span>
676
+ <span
677
+ class=${classMap_js.classMap({
678
+ "fw-dev-combo-type": true,
679
+ [typeClass]: typeClass.length > 0,
680
+ })}
681
+ >${typeLabel}</span
682
+ ></span
683
+ >
684
+
685
+ <span class=${classMap_js.classMap({ "fw-dev-combo-score": true, [scoreClass]: true })}
686
+ >${combo.score.toFixed(2)}</span
687
+ >
688
+ </button>
689
+
690
+ ${this._hoveredComboIndex === index
691
+ ? lit.html `
692
+ <div
693
+ class=${classMap_js.classMap({
694
+ "fw-dev-tooltip": true,
695
+ "fw-dev-tooltip--above": this._tooltipAbove,
696
+ "fw-dev-tooltip--below": !this._tooltipAbove,
697
+ })}
698
+ >
699
+ <div class="fw-dev-tooltip-header">
700
+ <div class="fw-dev-tooltip-title">${combo.playerName}</div>
701
+ <div class="fw-dev-tooltip-subtitle">${combo.sourceType}</div>
702
+ ${combo.scoreBreakdown?.trackTypes &&
703
+ combo.scoreBreakdown.trackTypes.length > 0
704
+ ? lit.html `
705
+ <div class="fw-dev-tooltip-tracks">
706
+ Tracks:
707
+ <span class="fw-dev-tooltip-value"
708
+ >${combo.scoreBreakdown.trackTypes.join(", ")}</span
709
+ >
710
+ </div>
711
+ `
712
+ : lit.nothing}
713
+ </div>
714
+
715
+ ${combo.compatible && combo.scoreBreakdown
716
+ ? lit.html `
717
+ <div class="fw-dev-tooltip-score">
718
+ Score: ${combo.score.toFixed(2)}
719
+ </div>
720
+ <div class="fw-dev-tooltip-row">
721
+ Tracks [${combo.scoreBreakdown.trackTypes.join(", ")}]:
722
+ <span class="fw-dev-tooltip-value"
723
+ >${combo.scoreBreakdown.trackScore.toFixed(2)}</span
724
+ >
725
+ <span class="fw-dev-tooltip-weight"
726
+ >x${combo.scoreBreakdown.weights.tracks}</span
727
+ >
728
+ </div>
729
+ <div class="fw-dev-tooltip-row">
730
+ Priority:
731
+ <span class="fw-dev-tooltip-value"
732
+ >${combo.scoreBreakdown.priorityScore.toFixed(2)}</span
733
+ >
734
+ <span class="fw-dev-tooltip-weight"
735
+ >x${combo.scoreBreakdown.weights.priority}</span
736
+ >
737
+ </div>
738
+ <div class="fw-dev-tooltip-row">
739
+ Source:
740
+ <span class="fw-dev-tooltip-value"
741
+ >${combo.scoreBreakdown.sourceScore.toFixed(2)}</span
742
+ >
743
+ <span class="fw-dev-tooltip-weight"
744
+ >x${combo.scoreBreakdown.weights.source}</span
745
+ >
746
+ </div>
747
+
748
+ ${typeof combo.scoreBreakdown.reliabilityScore === "number"
749
+ ? lit.html `
750
+ <div class="fw-dev-tooltip-row">
751
+ Reliability:
752
+ <span class="fw-dev-tooltip-value"
753
+ >${combo.scoreBreakdown.reliabilityScore.toFixed(2)}</span
754
+ >
755
+ <span class="fw-dev-tooltip-weight"
756
+ >x${combo.scoreBreakdown.weights.reliability ??
757
+ 0}</span
758
+ >
759
+ </div>
760
+ `
761
+ : lit.nothing}
762
+ ${typeof combo.scoreBreakdown.modeBonus === "number" &&
763
+ combo.scoreBreakdown.modeBonus !== 0
764
+ ? lit.html `
765
+ <div class="fw-dev-tooltip-row">
766
+ Mode (${this.playbackMode}):
767
+ <span class="fw-dev-tooltip-bonus"
768
+ >+${combo.scoreBreakdown.modeBonus.toFixed(2)}</span
769
+ >
770
+ <span class="fw-dev-tooltip-weight"
771
+ >x${combo.scoreBreakdown.weights.mode ?? 0}</span
772
+ >
773
+ </div>
774
+ `
775
+ : lit.nothing}
776
+ ${typeof combo.scoreBreakdown.routingBonus === "number" &&
777
+ combo.scoreBreakdown.routingBonus !== 0
778
+ ? lit.html `
779
+ <div class="fw-dev-tooltip-row">
780
+ Routing:
781
+ <span
782
+ class=${classMap_js.classMap({
783
+ "fw-dev-tooltip-bonus": combo.scoreBreakdown.routingBonus > 0,
784
+ "fw-dev-tooltip-penalty": combo.scoreBreakdown.routingBonus < 0,
785
+ })}
786
+ >${combo.scoreBreakdown.routingBonus > 0
787
+ ? "+"
788
+ : ""}${combo.scoreBreakdown.routingBonus.toFixed(2)}</span
789
+ >
790
+ <span class="fw-dev-tooltip-weight"
791
+ >x${combo.scoreBreakdown.weights.routing ?? 0}</span
792
+ >
793
+ </div>
794
+ `
795
+ : lit.nothing}
796
+ `
797
+ : lit.html `
798
+ <div class="fw-dev-tooltip-error">
799
+ ${combo.incompatibleReason || "Incompatible"}
800
+ </div>
801
+ `}
802
+ </div>
803
+ `
804
+ : lit.nothing}
805
+ </div>
806
+ `;
807
+ })}
808
+ `}
82
809
  </div>
83
- </div>
84
- <div class="actions fw-dev-actions">
85
- <button
86
- class="action-btn fw-dev-action-btn"
87
- @click=${() => {
88
- this.pc.clearError();
89
- this.pc.reload();
90
- }}
91
- >
92
- Reload
93
- </button>
94
810
  </div>
95
811
  `;
96
812
  }
97
- _renderStats(s) {
98
- const q = s.playbackQuality;
813
+ render() {
99
814
  return lit.html `
100
- <div class="section">
101
- <div class="label">Playback</div>
102
- ${this._row("State", s.state)}
103
- ${this._row("Time", `${s.currentTime.toFixed(1)}s / ${isFinite(s.duration) ? s.duration.toFixed(1) + "s" : "∞"}`)}
104
- ${this._row("Volume", `${Math.round(s.volume * 100)}%${s.isMuted ? " (muted)" : ""}`)}
815
+ <div class="fw-dev-panel">
816
+ <div class="fw-dev-header">
817
+ <button
818
+ type="button"
819
+ class=${classMap_js.classMap({
820
+ "fw-dev-tab": true,
821
+ "fw-dev-tab--active": this._activeTab === "config",
822
+ })}
823
+ @click=${() => {
824
+ this._activeTab = "config";
825
+ }}
826
+ >
827
+ Config
828
+ </button>
829
+ <button
830
+ type="button"
831
+ class=${classMap_js.classMap({
832
+ "fw-dev-tab": true,
833
+ "fw-dev-tab--active": this._activeTab === "stats",
834
+ })}
835
+ @click=${() => {
836
+ this._activeTab = "stats";
837
+ }}
838
+ >
839
+ Stats
840
+ </button>
841
+ <div class="fw-dev-spacer"></div>
842
+ <button
843
+ type="button"
844
+ class="fw-dev-close"
845
+ aria-label="Close dev mode panel"
846
+ @click=${() => this.dispatchEvent(new CustomEvent("fw-close", { bubbles: true, composed: true }))}
847
+ >
848
+ ${index.closeIcon()}
849
+ </button>
850
+ </div>
851
+
852
+ ${this._activeTab === "config" ? this._renderConfigTab() : this._renderStatsTab()}
105
853
  </div>
106
- ${q
107
- ? lit.html `
108
- <div class="section">
109
- <div class="label">Quality</div>
110
- ${this._row("Resolution", this._resolution())}
111
- ${this._row("Bitrate", q.bitrate ? `${Math.round(q.bitrate / 1000)} kbps` : "—")}
112
- ${this._row("Latency", q.latency != null ? `${q.latency.toFixed(2)}s` : "—")}
113
- ${this._row("Buffer", q.bufferedAhead != null ? `${q.bufferedAhead.toFixed(1)}s` : "—")}
114
- ${this._row("Score", q.score != null ? `${q.score.toFixed(0)}` : "—")}
115
- ${this._row("Drops", `${q.frameDropRate?.toFixed(1) ?? "0"}%`)}
116
- ${this._row("Stalls", `${q.stallCount ?? 0}`)}
117
- </div>
118
- `
119
- : lit.nothing}
120
854
  `;
121
855
  }
122
- _resolution() {
123
- const video = this.pc.s.videoElement;
124
- if (!video || !video.videoWidth || !video.videoHeight)
125
- return "—";
126
- return `${video.videoWidth}×${video.videoHeight}`;
127
- }
128
- _row(label, value) {
129
- return lit.html `<div class="stat-row">
130
- <span class="stat-label">${label}</span><span class="stat-value">${value}</span>
131
- </div>`;
132
- }
133
856
  };
134
857
  exports.FwDevModePanel.styles = [
135
858
  sharedStyles.sharedStyles,
@@ -137,124 +860,7 @@ exports.FwDevModePanel.styles = [
137
860
  lit.css `
138
861
  :host {
139
862
  display: block;
140
- }
141
- .panel {
142
- width: 320px;
143
863
  height: 100%;
144
- border-left: 1px solid rgb(255 255 255 / 0.1);
145
- background: rgb(15 23 42);
146
- overflow: auto;
147
- font-size: 0.75rem;
148
- color: rgb(255 255 255 / 0.7);
149
- }
150
- .header {
151
- display: flex;
152
- align-items: center;
153
- justify-content: space-between;
154
- padding: 0.5rem 0.75rem;
155
- border-bottom: 1px solid rgb(255 255 255 / 0.1);
156
- }
157
- .tabs {
158
- display: flex;
159
- gap: 0.5rem;
160
- }
161
- .tab {
162
- padding: 0.25rem 0.5rem;
163
- border: none;
164
- background: none;
165
- color: rgb(255 255 255 / 0.5);
166
- font-size: 0.6875rem;
167
- font-weight: 600;
168
- cursor: pointer;
169
- border-radius: 0.25rem;
170
- }
171
- .tab--active {
172
- color: white;
173
- background: rgb(255 255 255 / 0.1);
174
- }
175
- .close {
176
- display: flex;
177
- background: none;
178
- border: none;
179
- color: rgb(255 255 255 / 0.5);
180
- cursor: pointer;
181
- padding: 0;
182
- }
183
- .close:hover {
184
- color: white;
185
- }
186
- .body {
187
- padding: 0.75rem;
188
- }
189
- .section {
190
- margin-bottom: 0.75rem;
191
- }
192
- .label {
193
- font-size: 0.625rem;
194
- font-weight: 600;
195
- text-transform: uppercase;
196
- letter-spacing: 0.05em;
197
- color: rgb(255 255 255 / 0.4);
198
- margin-bottom: 0.375rem;
199
- }
200
- .value {
201
- color: rgb(255 255 255 / 0.9);
202
- font-family: ui-monospace, monospace;
203
- }
204
- .mode-group {
205
- display: flex;
206
- gap: 0.25rem;
207
- flex-wrap: wrap;
208
- }
209
- .mode-btn {
210
- padding: 0.25rem 0.5rem;
211
- border: 1px solid rgb(255 255 255 / 0.15);
212
- background: none;
213
- color: rgb(255 255 255 / 0.6);
214
- font-size: 0.6875rem;
215
- cursor: pointer;
216
- border-radius: 0.25rem;
217
- transition: all 150ms;
218
- }
219
- .mode-btn:hover {
220
- border-color: rgb(255 255 255 / 0.3);
221
- color: white;
222
- }
223
- .mode-btn--active {
224
- border-color: hsl(var(--tn-blue, 217 89% 61%));
225
- color: hsl(var(--tn-blue, 217 89% 61%));
226
- background: hsl(var(--tn-blue, 217 89% 61%) / 0.1);
227
- }
228
- .actions {
229
- display: flex;
230
- gap: 0.5rem;
231
- margin-top: 0.5rem;
232
- }
233
- .action-btn {
234
- padding: 0.375rem 0.75rem;
235
- border: 1px solid rgb(255 255 255 / 0.15);
236
- background: none;
237
- color: rgb(255 255 255 / 0.7);
238
- font-size: 0.6875rem;
239
- cursor: pointer;
240
- border-radius: 0.25rem;
241
- }
242
- .action-btn:hover {
243
- border-color: rgb(255 255 255 / 0.3);
244
- color: white;
245
- }
246
- .stat-row {
247
- display: flex;
248
- justify-content: space-between;
249
- padding: 0.125rem 0;
250
- }
251
- .stat-label {
252
- color: rgb(255 255 255 / 0.4);
253
- }
254
- .stat-value {
255
- color: rgb(255 255 255 / 0.8);
256
- font-family: ui-monospace, monospace;
257
- font-variant-numeric: tabular-nums;
258
864
  }
259
865
  `,
260
866
  ];
@@ -267,6 +873,33 @@ tslib_es6.__decorate([
267
873
  tslib_es6.__decorate([
268
874
  decorators_js.state()
269
875
  ], exports.FwDevModePanel.prototype, "_activeTab", void 0);
876
+ tslib_es6.__decorate([
877
+ decorators_js.state()
878
+ ], exports.FwDevModePanel.prototype, "_hoveredComboIndex", void 0);
879
+ tslib_es6.__decorate([
880
+ decorators_js.state()
881
+ ], exports.FwDevModePanel.prototype, "_tooltipAbove", void 0);
882
+ tslib_es6.__decorate([
883
+ decorators_js.state()
884
+ ], exports.FwDevModePanel.prototype, "_showDisabledPlayers", void 0);
885
+ tslib_es6.__decorate([
886
+ decorators_js.state()
887
+ ], exports.FwDevModePanel.prototype, "_playbackScore", void 0);
888
+ tslib_es6.__decorate([
889
+ decorators_js.state()
890
+ ], exports.FwDevModePanel.prototype, "_qualityScore", void 0);
891
+ tslib_es6.__decorate([
892
+ decorators_js.state()
893
+ ], exports.FwDevModePanel.prototype, "_stallCount", void 0);
894
+ tslib_es6.__decorate([
895
+ decorators_js.state()
896
+ ], exports.FwDevModePanel.prototype, "_frameDropRate", void 0);
897
+ tslib_es6.__decorate([
898
+ decorators_js.state()
899
+ ], exports.FwDevModePanel.prototype, "_videoStats", void 0);
900
+ tslib_es6.__decorate([
901
+ decorators_js.state()
902
+ ], exports.FwDevModePanel.prototype, "_playerStats", void 0);
270
903
  exports.FwDevModePanel = tslib_es6.__decorate([
271
904
  decorators_js.customElement("fw-dev-mode-panel")
272
905
  ], exports.FwDevModePanel);