@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
@@ -1,46 +1,210 @@
1
1
  import { __decorate } from '../node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js';
2
2
  import { css, LitElement, nothing, html } from 'lit';
3
- import { property, customElement } from 'lit/decorators.js';
3
+ import { property, state, customElement } from 'lit/decorators.js';
4
4
  import { classMap } from 'lit/directives/class-map.js';
5
5
  import { sharedStyles } from '../styles/shared-styles.js';
6
6
  import { utilityStyles } from '../styles/utility-styles.js';
7
+ import { supportsPlaybackRate, SPEED_PRESETS } from '@livepeer-frameworks/player-core';
7
8
 
8
9
  let FwSettingsMenu = class FwSettingsMenu extends LitElement {
9
10
  constructor() {
10
11
  super(...arguments);
11
12
  this.open = false;
13
+ this.playbackMode = "auto";
14
+ this.isContentLive = true;
15
+ this._playbackRate = 1;
16
+ }
17
+ updated() {
18
+ if (!this.open) {
19
+ return;
20
+ }
21
+ if (Number.isFinite(this.playbackRate)) {
22
+ this._playbackRate = this.playbackRate;
23
+ return;
24
+ }
25
+ const video = this.pc?.s.videoElement;
26
+ if (video && Number.isFinite(video.playbackRate)) {
27
+ this._playbackRate = video.playbackRate;
28
+ }
29
+ }
30
+ _close() {
31
+ this.dispatchEvent(new CustomEvent("fw-close", { bubbles: true, composed: true }));
32
+ }
33
+ _handleModeChange(mode) {
34
+ this.pc.setDevModeOptions({ playbackMode: mode });
35
+ this.dispatchEvent(new CustomEvent("fw-mode-change", {
36
+ detail: { mode },
37
+ bubbles: true,
38
+ composed: true,
39
+ }));
40
+ this._close();
41
+ }
42
+ _handleSpeedChange(rate) {
43
+ this._playbackRate = rate;
44
+ this.pc.setPlaybackRate(rate);
45
+ this.dispatchEvent(new CustomEvent("fw-speed-change", {
46
+ detail: { rate },
47
+ bubbles: true,
48
+ composed: true,
49
+ }));
50
+ this._close();
51
+ }
52
+ _handleQualityChange(id) {
53
+ this.pc.selectQuality(id);
54
+ this.dispatchEvent(new CustomEvent("fw-quality-change", {
55
+ detail: { quality: id },
56
+ bubbles: true,
57
+ composed: true,
58
+ }));
59
+ this._close();
60
+ }
61
+ _handleCaptionChange(id) {
62
+ if (id === "none") {
63
+ this.pc.selectTextTrack(null);
64
+ }
65
+ else {
66
+ this.pc.selectTextTrack(id);
67
+ }
68
+ this.dispatchEvent(new CustomEvent("fw-caption-change", {
69
+ detail: { caption: id },
70
+ bubbles: true,
71
+ composed: true,
72
+ }));
73
+ this._close();
74
+ }
75
+ _deriveFallbackQualities() {
76
+ const tracks = this.pc?.s.streamState?.streamInfo?.meta?.tracks;
77
+ if (!tracks) {
78
+ return [];
79
+ }
80
+ return Object.entries(tracks)
81
+ .filter(([, track]) => track?.type === "video")
82
+ .map(([id, track]) => ({
83
+ id,
84
+ label: track.height ? `${track.height}p` : (track.codec ?? id),
85
+ width: track.width,
86
+ height: track.height,
87
+ bitrate: track.bps,
88
+ }))
89
+ .sort((a, b) => (b.height ?? 0) - (a.height ?? 0));
12
90
  }
13
91
  render() {
14
- if (!this.open)
92
+ if (!this.open) {
15
93
  return nothing;
16
- const { qualities } = this.pc.s;
17
- const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2];
94
+ }
95
+ const state = this.pc.s;
96
+ const controllerQualities = state.qualities ?? [];
97
+ const qualities = controllerQualities.length > 0 ? controllerQualities : this._deriveFallbackQualities();
98
+ const textTracks = state.textTracks ?? [];
99
+ const activeQuality = this.qualityValue ?? qualities.find((quality) => quality.active)?.id ?? "auto";
100
+ const activeCaption = this.captionValue ?? textTracks.find((track) => track.active)?.id ?? "none";
101
+ const supportsPlaybackRate$1 = this.supportsPlaybackRate ?? supportsPlaybackRate(state.videoElement);
18
102
  return html `
19
- <div class="menu fw-settings-menu">
103
+ <div class="fw-player-surface fw-settings-menu" role="menu" aria-label="Player settings">
104
+ ${this.isContentLive
105
+ ? html `
106
+ <div class="fw-settings-section">
107
+ <div class="fw-settings-label">Mode</div>
108
+ <div class="fw-settings-options">
109
+ ${["auto", "low-latency", "quality"].map((mode) => html `
110
+ <button
111
+ type="button"
112
+ class=${classMap({
113
+ "fw-settings-btn": true,
114
+ "fw-settings-btn--active": this.playbackMode === mode,
115
+ })}
116
+ @click=${() => this._handleModeChange(mode)}
117
+ >
118
+ ${mode === "low-latency" ? "Fast" : mode === "quality" ? "Stable" : "Auto"}
119
+ </button>
120
+ `)}
121
+ </div>
122
+ </div>
123
+ `
124
+ : nothing}
125
+ ${supportsPlaybackRate$1
126
+ ? html `
127
+ <div class="fw-settings-section">
128
+ <div class="fw-settings-label">Speed</div>
129
+ <div class="fw-settings-options fw-settings-options--wrap">
130
+ ${SPEED_PRESETS.map((rate) => html `
131
+ <button
132
+ type="button"
133
+ class=${classMap({
134
+ "fw-settings-btn": true,
135
+ "fw-settings-btn--active": this._playbackRate === rate,
136
+ })}
137
+ @click=${() => this._handleSpeedChange(rate)}
138
+ >
139
+ ${rate}x
140
+ </button>
141
+ `)}
142
+ </div>
143
+ </div>
144
+ `
145
+ : nothing}
20
146
  ${qualities.length > 0
21
147
  ? html `
22
- <div class="section">
23
- <div class="label">Quality</div>
24
- ${qualities.map((q) => html `
25
- <button
26
- class=${classMap({ option: true, "option--active": !!q.active })}
27
- @click=${() => this.pc.selectQuality(q.id)}
28
- >
29
- <div class=${classMap({ dot: true, "dot--hidden": !q.active })}></div>
30
- ${q.label}
31
- </button>
32
- `)}
148
+ <div class="fw-settings-section">
149
+ <div class="fw-settings-label">Quality</div>
150
+ <div class="fw-settings-list">
151
+ <button
152
+ type="button"
153
+ class=${classMap({
154
+ "fw-settings-list-item": true,
155
+ "fw-settings-list-item--active": activeQuality === "auto",
156
+ })}
157
+ @click=${() => this._handleQualityChange("auto")}
158
+ >
159
+ Auto
160
+ </button>
161
+ ${qualities.map((quality) => html `
162
+ <button
163
+ type="button"
164
+ class=${classMap({
165
+ "fw-settings-list-item": true,
166
+ "fw-settings-list-item--active": activeQuality === quality.id,
167
+ })}
168
+ @click=${() => this._handleQualityChange(quality.id)}
169
+ >
170
+ ${quality.label}
171
+ </button>
172
+ `)}
173
+ </div>
174
+ </div>
175
+ `
176
+ : nothing}
177
+ ${textTracks.length > 0
178
+ ? html `
179
+ <div class="fw-settings-section">
180
+ <div class="fw-settings-label">Captions</div>
181
+ <div class="fw-settings-list">
182
+ <button
183
+ type="button"
184
+ class=${classMap({
185
+ "fw-settings-list-item": true,
186
+ "fw-settings-list-item--active": activeCaption === "none",
187
+ })}
188
+ @click=${() => this._handleCaptionChange("none")}
189
+ >
190
+ Off
191
+ </button>
192
+ ${textTracks.map((track) => html `
193
+ <button
194
+ type="button"
195
+ class=${classMap({
196
+ "fw-settings-list-item": true,
197
+ "fw-settings-list-item--active": activeCaption === track.id,
198
+ })}
199
+ @click=${() => this._handleCaptionChange(track.id)}
200
+ >
201
+ ${track.label || track.id}
202
+ </button>
203
+ `)}
204
+ </div>
33
205
  </div>
34
206
  `
35
207
  : nothing}
36
- <div class="section">
37
- <div class="label">Speed</div>
38
- ${speeds.map((s) => html `
39
- <button class="option" @click=${() => this.pc.getController()?.setPlaybackRate(s)}>
40
- ${s === 1 ? "Normal" : `${s}x`}
41
- </button>
42
- `)}
43
- </div>
44
208
  </div>
45
209
  `;
46
210
  }
@@ -52,64 +216,6 @@ FwSettingsMenu.styles = [
52
216
  :host {
53
217
  display: contents;
54
218
  }
55
- .menu {
56
- position: absolute;
57
- bottom: 100%;
58
- right: 0;
59
- margin-bottom: 0.5rem;
60
- min-width: 200px;
61
- border-radius: 0.5rem;
62
- border: 1px solid rgb(255 255 255 / 0.1);
63
- background: rgb(0 0 0 / 0.9);
64
- backdrop-filter: blur(8px);
65
- padding: 0.5rem;
66
- z-index: 50;
67
- box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3);
68
- }
69
- .section {
70
- padding: 0.25rem 0;
71
- }
72
- .section + .section {
73
- border-top: 1px solid rgb(255 255 255 / 0.1);
74
- }
75
- .label {
76
- padding: 0.25rem 0.5rem;
77
- font-size: 0.6875rem;
78
- font-weight: 600;
79
- text-transform: uppercase;
80
- letter-spacing: 0.05em;
81
- color: rgb(255 255 255 / 0.4);
82
- }
83
- .option {
84
- display: flex;
85
- align-items: center;
86
- width: 100%;
87
- padding: 0.375rem 0.5rem;
88
- border: none;
89
- background: none;
90
- color: rgb(255 255 255 / 0.7);
91
- font-size: 0.8125rem;
92
- cursor: pointer;
93
- border-radius: 0.25rem;
94
- text-align: left;
95
- }
96
- .option:hover {
97
- background: rgb(255 255 255 / 0.1);
98
- color: white;
99
- }
100
- .option--active {
101
- color: hsl(var(--tn-blue, 217 89% 61%));
102
- }
103
- .dot {
104
- width: 6px;
105
- height: 6px;
106
- border-radius: 50%;
107
- background: hsl(var(--tn-blue, 217 89% 61%));
108
- margin-right: 0.5rem;
109
- }
110
- .dot--hidden {
111
- visibility: hidden;
112
- }
113
219
  `,
114
220
  ];
115
221
  __decorate([
@@ -118,6 +224,27 @@ __decorate([
118
224
  __decorate([
119
225
  property({ type: Boolean })
120
226
  ], FwSettingsMenu.prototype, "open", void 0);
227
+ __decorate([
228
+ property({ type: String })
229
+ ], FwSettingsMenu.prototype, "playbackMode", void 0);
230
+ __decorate([
231
+ property({ type: Boolean, attribute: "is-content-live" })
232
+ ], FwSettingsMenu.prototype, "isContentLive", void 0);
233
+ __decorate([
234
+ property({ type: Number, attribute: "playback-rate" })
235
+ ], FwSettingsMenu.prototype, "playbackRate", void 0);
236
+ __decorate([
237
+ property({ type: String, attribute: "quality-value" })
238
+ ], FwSettingsMenu.prototype, "qualityValue", void 0);
239
+ __decorate([
240
+ property({ type: String, attribute: "caption-value" })
241
+ ], FwSettingsMenu.prototype, "captionValue", void 0);
242
+ __decorate([
243
+ property({ type: Boolean, attribute: "supports-playback-rate" })
244
+ ], FwSettingsMenu.prototype, "supportsPlaybackRate", void 0);
245
+ __decorate([
246
+ state()
247
+ ], FwSettingsMenu.prototype, "_playbackRate", void 0);
121
248
  FwSettingsMenu = __decorate([
122
249
  customElement("fw-settings-menu")
123
250
  ], FwSettingsMenu);
@@ -1 +1 @@
1
- {"version":3,"file":"fw-settings-menu.js","sources":["../../../../src/components/fw-settings-menu.ts"],"sourcesContent":["/**\n * <fw-settings-menu> — Quality, speed, and captions settings popup.\n */\nimport { LitElement, html, css, nothing } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { classMap } from \"lit/directives/class-map.js\";\nimport { sharedStyles } from \"../styles/shared-styles.js\";\nimport { utilityStyles } from \"../styles/utility-styles.js\";\nimport type { PlayerControllerHost } from \"../controllers/player-controller-host.js\";\n\n@customElement(\"fw-settings-menu\")\nexport class FwSettingsMenu extends LitElement {\n @property({ attribute: false }) pc!: PlayerControllerHost;\n @property({ type: Boolean }) open = false;\n\n static styles = [\n sharedStyles,\n utilityStyles,\n css`\n :host {\n display: contents;\n }\n .menu {\n position: absolute;\n bottom: 100%;\n right: 0;\n margin-bottom: 0.5rem;\n min-width: 200px;\n border-radius: 0.5rem;\n border: 1px solid rgb(255 255 255 / 0.1);\n background: rgb(0 0 0 / 0.9);\n backdrop-filter: blur(8px);\n padding: 0.5rem;\n z-index: 50;\n box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3);\n }\n .section {\n padding: 0.25rem 0;\n }\n .section + .section {\n border-top: 1px solid rgb(255 255 255 / 0.1);\n }\n .label {\n padding: 0.25rem 0.5rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: rgb(255 255 255 / 0.4);\n }\n .option {\n display: flex;\n align-items: center;\n width: 100%;\n padding: 0.375rem 0.5rem;\n border: none;\n background: none;\n color: rgb(255 255 255 / 0.7);\n font-size: 0.8125rem;\n cursor: pointer;\n border-radius: 0.25rem;\n text-align: left;\n }\n .option:hover {\n background: rgb(255 255 255 / 0.1);\n color: white;\n }\n .option--active {\n color: hsl(var(--tn-blue, 217 89% 61%));\n }\n .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: hsl(var(--tn-blue, 217 89% 61%));\n margin-right: 0.5rem;\n }\n .dot--hidden {\n visibility: hidden;\n }\n `,\n ];\n\n protected render() {\n if (!this.open) return nothing;\n const { qualities } = this.pc.s;\n const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2];\n\n return html`\n <div class=\"menu fw-settings-menu\">\n ${qualities.length > 0\n ? html`\n <div class=\"section\">\n <div class=\"label\">Quality</div>\n ${qualities.map(\n (q) => html`\n <button\n class=${classMap({ option: true, \"option--active\": !!q.active })}\n @click=${() => this.pc.selectQuality(q.id)}\n >\n <div class=${classMap({ dot: true, \"dot--hidden\": !q.active })}></div>\n ${q.label}\n </button>\n `\n )}\n </div>\n `\n : nothing}\n <div class=\"section\">\n <div class=\"label\">Speed</div>\n ${speeds.map(\n (s) => html`\n <button class=\"option\" @click=${() => this.pc.getController()?.setPlaybackRate(s)}>\n ${s === 1 ? \"Normal\" : `${s}x`}\n </button>\n `\n )}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"fw-settings-menu\": FwSettingsMenu;\n }\n}\n"],"names":[],"mappings":";;;;;;;AAWO,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU,CAAA;AAAvC,IAAA,WAAA,GAAA;;QAEwB,IAAA,CAAA,IAAI,GAAG,KAAK;IA4G3C;IAtCY,MAAM,GAAA;QACd,IAAI,CAAC,IAAI,CAAC,IAAI;AAAE,YAAA,OAAO,OAAO;QAC9B,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;AAC/B,QAAA,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;AAEjD,QAAA,OAAO,IAAI,CAAA;;UAEL,SAAS,CAAC,MAAM,GAAG;cACjB,IAAI,CAAA;;;kBAGE,SAAS,CAAC,GAAG,CACb,CAAC,CAAC,KAAK,IAAI,CAAA;;AAEC,4BAAA,EAAA,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;+BACvD,MAAM,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;;AAE7B,iCAAA,EAAA,QAAQ,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;AAC5D,sBAAA,EAAA,CAAC,CAAC,KAAK;;mBAEZ,CACF;;AAEJ,YAAA;AACH,cAAE,OAAO;;;YAGP,MAAM,CAAC,GAAG,CACV,CAAC,CAAC,KAAK,IAAI,CAAA;AACuB,4CAAA,EAAA,MAAM,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,CAAA;kBAC7E,CAAC,KAAK,CAAC,GAAG,QAAQ,GAAG,CAAA,EAAG,CAAC,CAAA,CAAA,CAAG;;aAEjC,CACF;;;KAGN;IACH;;AAzGO,cAAA,CAAA,MAAM,GAAG;IACd,YAAY;IACZ,aAAa;AACb,IAAA,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DF,IAAA,CAAA;AACF,CAlEY;AAHmB,UAAA,CAAA;AAA/B,IAAA,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE;AAA4B,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,IAAA,EAAA,MAAA,CAAA;AAC7B,UAAA,CAAA;AAA5B,IAAA,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;AAAe,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,MAAA,EAAA,MAAA,CAAA;AAF/B,cAAc,GAAA,UAAA,CAAA;IAD1B,aAAa,CAAC,kBAAkB;AACpB,CAAA,EAAA,cAAc,CA8G1B;;;;"}
1
+ {"version":3,"file":"fw-settings-menu.js","sources":["../../../../src/components/fw-settings-menu.ts"],"sourcesContent":["/**\n * <fw-settings-menu> — Mode, speed, quality, and captions settings popup.\n */\nimport { LitElement, html, css, nothing } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { classMap } from \"lit/directives/class-map.js\";\nimport { sharedStyles } from \"../styles/shared-styles.js\";\nimport { utilityStyles } from \"../styles/utility-styles.js\";\nimport {\n SPEED_PRESETS,\n supportsPlaybackRate as coreSupportsPlaybackRate,\n} from \"@livepeer-frameworks/player-core\";\nimport type { PlaybackMode } from \"@livepeer-frameworks/player-core\";\nimport type { PlayerControllerHost } from \"../controllers/player-controller-host.js\";\n\n@customElement(\"fw-settings-menu\")\nexport class FwSettingsMenu extends LitElement {\n @property({ attribute: false }) pc!: PlayerControllerHost;\n @property({ type: Boolean }) open = false;\n @property({ type: String }) playbackMode: PlaybackMode = \"auto\";\n @property({ type: Boolean, attribute: \"is-content-live\" }) isContentLive = true;\n @property({ type: Number, attribute: \"playback-rate\" }) playbackRate?: number;\n @property({ type: String, attribute: \"quality-value\" }) qualityValue?: string;\n @property({ type: String, attribute: \"caption-value\" }) captionValue?: string;\n @property({ type: Boolean, attribute: \"supports-playback-rate\" }) supportsPlaybackRate?: boolean;\n\n @state() private _playbackRate = 1;\n\n static styles = [\n sharedStyles,\n utilityStyles,\n css`\n :host {\n display: contents;\n }\n `,\n ];\n\n protected updated(): void {\n if (!this.open) {\n return;\n }\n\n if (Number.isFinite(this.playbackRate)) {\n this._playbackRate = this.playbackRate as number;\n return;\n }\n\n const video = this.pc?.s.videoElement;\n if (video && Number.isFinite(video.playbackRate)) {\n this._playbackRate = video.playbackRate;\n }\n }\n\n private _close(): void {\n this.dispatchEvent(new CustomEvent(\"fw-close\", { bubbles: true, composed: true }));\n }\n\n private _handleModeChange(mode: \"auto\" | \"low-latency\" | \"quality\"): void {\n this.pc.setDevModeOptions({ playbackMode: mode });\n this.dispatchEvent(\n new CustomEvent(\"fw-mode-change\", {\n detail: { mode },\n bubbles: true,\n composed: true,\n })\n );\n this._close();\n }\n\n private _handleSpeedChange(rate: number): void {\n this._playbackRate = rate;\n this.pc.setPlaybackRate(rate);\n this.dispatchEvent(\n new CustomEvent(\"fw-speed-change\", {\n detail: { rate },\n bubbles: true,\n composed: true,\n })\n );\n this._close();\n }\n\n private _handleQualityChange(id: string): void {\n this.pc.selectQuality(id);\n this.dispatchEvent(\n new CustomEvent(\"fw-quality-change\", {\n detail: { quality: id },\n bubbles: true,\n composed: true,\n })\n );\n this._close();\n }\n\n private _handleCaptionChange(id: string): void {\n if (id === \"none\") {\n this.pc.selectTextTrack(null);\n } else {\n this.pc.selectTextTrack(id);\n }\n this.dispatchEvent(\n new CustomEvent(\"fw-caption-change\", {\n detail: { caption: id },\n bubbles: true,\n composed: true,\n })\n );\n this._close();\n }\n\n private _deriveFallbackQualities(): Array<{\n id: string;\n label: string;\n bitrate?: number;\n width?: number;\n height?: number;\n isAuto?: boolean;\n active?: boolean;\n }> {\n const tracks = (\n this.pc?.s.streamState?.streamInfo as\n | {\n meta?: {\n tracks?: Record<\n string,\n { type?: string; codec?: string; width?: number; height?: number; bps?: number }\n >;\n };\n }\n | undefined\n )?.meta?.tracks;\n\n if (!tracks) {\n return [];\n }\n\n return Object.entries(tracks)\n .filter(([, track]) => track?.type === \"video\")\n .map(([id, track]) => ({\n id,\n label: track.height ? `${track.height}p` : (track.codec ?? id),\n width: track.width,\n height: track.height,\n bitrate: track.bps,\n }))\n .sort((a, b) => (b.height ?? 0) - (a.height ?? 0));\n }\n\n protected render() {\n if (!this.open) {\n return nothing;\n }\n\n const state = this.pc.s;\n const controllerQualities = state.qualities ?? [];\n const qualities =\n controllerQualities.length > 0 ? controllerQualities : this._deriveFallbackQualities();\n const textTracks = state.textTracks ?? [];\n const activeQuality =\n this.qualityValue ?? qualities.find((quality) => quality.active)?.id ?? \"auto\";\n const activeCaption =\n this.captionValue ?? textTracks.find((track) => track.active)?.id ?? \"none\";\n\n const supportsPlaybackRate =\n this.supportsPlaybackRate ?? coreSupportsPlaybackRate(state.videoElement);\n\n return html`\n <div class=\"fw-player-surface fw-settings-menu\" role=\"menu\" aria-label=\"Player settings\">\n ${this.isContentLive\n ? html`\n <div class=\"fw-settings-section\">\n <div class=\"fw-settings-label\">Mode</div>\n <div class=\"fw-settings-options\">\n ${([\"auto\", \"low-latency\", \"quality\"] as const).map(\n (mode) => html`\n <button\n type=\"button\"\n class=${classMap({\n \"fw-settings-btn\": true,\n \"fw-settings-btn--active\": this.playbackMode === mode,\n })}\n @click=${() => this._handleModeChange(mode)}\n >\n ${mode === \"low-latency\" ? \"Fast\" : mode === \"quality\" ? \"Stable\" : \"Auto\"}\n </button>\n `\n )}\n </div>\n </div>\n `\n : nothing}\n ${supportsPlaybackRate\n ? html`\n <div class=\"fw-settings-section\">\n <div class=\"fw-settings-label\">Speed</div>\n <div class=\"fw-settings-options fw-settings-options--wrap\">\n ${SPEED_PRESETS.map(\n (rate) => html`\n <button\n type=\"button\"\n class=${classMap({\n \"fw-settings-btn\": true,\n \"fw-settings-btn--active\": this._playbackRate === rate,\n })}\n @click=${() => this._handleSpeedChange(rate)}\n >\n ${rate}x\n </button>\n `\n )}\n </div>\n </div>\n `\n : nothing}\n ${qualities.length > 0\n ? html`\n <div class=\"fw-settings-section\">\n <div class=\"fw-settings-label\">Quality</div>\n <div class=\"fw-settings-list\">\n <button\n type=\"button\"\n class=${classMap({\n \"fw-settings-list-item\": true,\n \"fw-settings-list-item--active\": activeQuality === \"auto\",\n })}\n @click=${() => this._handleQualityChange(\"auto\")}\n >\n Auto\n </button>\n ${qualities.map(\n (quality) => html`\n <button\n type=\"button\"\n class=${classMap({\n \"fw-settings-list-item\": true,\n \"fw-settings-list-item--active\": activeQuality === quality.id,\n })}\n @click=${() => this._handleQualityChange(quality.id)}\n >\n ${quality.label}\n </button>\n `\n )}\n </div>\n </div>\n `\n : nothing}\n ${textTracks.length > 0\n ? html`\n <div class=\"fw-settings-section\">\n <div class=\"fw-settings-label\">Captions</div>\n <div class=\"fw-settings-list\">\n <button\n type=\"button\"\n class=${classMap({\n \"fw-settings-list-item\": true,\n \"fw-settings-list-item--active\": activeCaption === \"none\",\n })}\n @click=${() => this._handleCaptionChange(\"none\")}\n >\n Off\n </button>\n ${textTracks.map(\n (track) => html`\n <button\n type=\"button\"\n class=${classMap({\n \"fw-settings-list-item\": true,\n \"fw-settings-list-item--active\": activeCaption === track.id,\n })}\n @click=${() => this._handleCaptionChange(track.id)}\n >\n ${track.label || track.id}\n </button>\n `\n )}\n </div>\n </div>\n `\n : nothing}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"fw-settings-menu\": FwSettingsMenu;\n }\n}\n"],"names":["supportsPlaybackRate","coreSupportsPlaybackRate"],"mappings":";;;;;;;;AAgBO,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU,CAAA;AAAvC,IAAA,WAAA,GAAA;;QAEwB,IAAA,CAAA,IAAI,GAAG,KAAK;QACb,IAAA,CAAA,YAAY,GAAiB,MAAM;QACJ,IAAA,CAAA,aAAa,GAAG,IAAI;QAM9D,IAAA,CAAA,aAAa,GAAG,CAAC;IAkQpC;IAtPY,OAAO,GAAA;AACf,QAAA,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YACd;QACF;QAEA,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE;AACtC,YAAA,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,YAAsB;YAChD;QACF;QAEA,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY;QACrC,IAAI,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE;AAChD,YAAA,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,YAAY;QACzC;IACF;IAEQ,MAAM,GAAA;AACZ,QAAA,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpF;AAEQ,IAAA,iBAAiB,CAAC,IAAwC,EAAA;QAChE,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;AACjD,QAAA,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,gBAAgB,EAAE;YAChC,MAAM,EAAE,EAAE,IAAI,EAAE;AAChB,YAAA,OAAO,EAAE,IAAI;AACb,YAAA,QAAQ,EAAE,IAAI;AACf,SAAA,CAAC,CACH;QACD,IAAI,CAAC,MAAM,EAAE;IACf;AAEQ,IAAA,kBAAkB,CAAC,IAAY,EAAA;AACrC,QAAA,IAAI,CAAC,aAAa,GAAG,IAAI;AACzB,QAAA,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC;AAC7B,QAAA,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,iBAAiB,EAAE;YACjC,MAAM,EAAE,EAAE,IAAI,EAAE;AAChB,YAAA,OAAO,EAAE,IAAI;AACb,YAAA,QAAQ,EAAE,IAAI;AACf,SAAA,CAAC,CACH;QACD,IAAI,CAAC,MAAM,EAAE;IACf;AAEQ,IAAA,oBAAoB,CAAC,EAAU,EAAA;AACrC,QAAA,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC;AACzB,QAAA,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,mBAAmB,EAAE;AACnC,YAAA,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;AACvB,YAAA,OAAO,EAAE,IAAI;AACb,YAAA,QAAQ,EAAE,IAAI;AACf,SAAA,CAAC,CACH;QACD,IAAI,CAAC,MAAM,EAAE;IACf;AAEQ,IAAA,oBAAoB,CAAC,EAAU,EAAA;AACrC,QAAA,IAAI,EAAE,KAAK,MAAM,EAAE;AACjB,YAAA,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC;QAC/B;aAAO;AACL,YAAA,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC;QAC7B;AACA,QAAA,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,mBAAmB,EAAE;AACnC,YAAA,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;AACvB,YAAA,OAAO,EAAE,IAAI;AACb,YAAA,QAAQ,EAAE,IAAI;AACf,SAAA,CAAC,CACH;QACD,IAAI,CAAC,MAAM,EAAE;IACf;IAEQ,wBAAwB,GAAA;AAS9B,QAAA,MAAM,MAAM,GACV,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,UAUzB,EAAE,IAAI,EAAE,MAAM;QAEf,IAAI,CAAC,MAAM,EAAE;AACX,YAAA,OAAO,EAAE;QACX;AAEA,QAAA,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM;AACzB,aAAA,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,KAAK,EAAE,IAAI,KAAK,OAAO;aAC7C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,MAAM;YACrB,EAAE;YACF,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAA,CAAA,CAAG,IAAI,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;YAC9D,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,OAAO,EAAE,KAAK,CAAC,GAAG;AACnB,SAAA,CAAC;aACD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IACtD;IAEU,MAAM,GAAA;AACd,QAAA,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;AACd,YAAA,OAAO,OAAO;QAChB;AAEA,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;AACvB,QAAA,MAAM,mBAAmB,GAAG,KAAK,CAAC,SAAS,IAAI,EAAE;AACjD,QAAA,MAAM,SAAS,GACb,mBAAmB,CAAC,MAAM,GAAG,CAAC,GAAG,mBAAmB,GAAG,IAAI,CAAC,wBAAwB,EAAE;AACxF,QAAA,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,EAAE;QACzC,MAAM,aAAa,GACjB,IAAI,CAAC,YAAY,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,MAAM;QAChF,MAAM,aAAa,GACjB,IAAI,CAAC,YAAY,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,MAAM;AAE7E,QAAA,MAAMA,sBAAoB,GACxB,IAAI,CAAC,oBAAoB,IAAIC,oBAAwB,CAAC,KAAK,CAAC,YAAY,CAAC;AAE3E,QAAA,OAAO,IAAI,CAAA;;AAEL,QAAA,EAAA,IAAI,CAAC;cACH,IAAI,CAAA;;;;AAIK,kBAAA,EAAA,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,CAAW,CAAC,GAAG,CACjD,CAAC,IAAI,KAAK,IAAI,CAAA;;;AAGF,8BAAA,EAAA,QAAQ,CAAC;AACf,gBAAA,iBAAiB,EAAE,IAAI;AACvB,gBAAA,yBAAyB,EAAE,IAAI,CAAC,YAAY,KAAK,IAAI;aACtD,CAAC;AACO,+BAAA,EAAA,MAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC;;AAEzC,wBAAA,EAAA,IAAI,KAAK,aAAa,GAAG,MAAM,GAAG,IAAI,KAAK,SAAS,GAAG,QAAQ,GAAG,MAAM;;qBAE7E,CACF;;;AAGN,YAAA;AACH,cAAE,OAAO;UACTD;cACE,IAAI,CAAA;;;;oBAII,aAAa,CAAC,GAAG,CACjB,CAAC,IAAI,KAAK,IAAI,CAAA;;;AAGF,8BAAA,EAAA,QAAQ,CAAC;AACf,gBAAA,iBAAiB,EAAE,IAAI;AACvB,gBAAA,yBAAyB,EAAE,IAAI,CAAC,aAAa,KAAK,IAAI;aACvD,CAAC;AACO,+BAAA,EAAA,MAAM,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC;;0BAE1C,IAAI,CAAA;;qBAET,CACF;;;AAGN,YAAA;AACH,cAAE,OAAO;UACT,SAAS,CAAC,MAAM,GAAG;cACjB,IAAI,CAAA;;;;;;AAMY,0BAAA,EAAA,QAAQ,CAAC;AACf,gBAAA,uBAAuB,EAAE,IAAI;gBAC7B,+BAA+B,EAAE,aAAa,KAAK,MAAM;aAC1D,CAAC;AACO,2BAAA,EAAA,MAAM,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC;;;;oBAIhD,SAAS,CAAC,GAAG,CACb,CAAC,OAAO,KAAK,IAAI,CAAA;;;AAGL,8BAAA,EAAA,QAAQ,CAAC;AACf,gBAAA,uBAAuB,EAAE,IAAI;AAC7B,gBAAA,+BAA+B,EAAE,aAAa,KAAK,OAAO,CAAC,EAAE;aAC9D,CAAC;iCACO,MAAM,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;;AAElD,wBAAA,EAAA,OAAO,CAAC,KAAK;;qBAElB,CACF;;;AAGN,YAAA;AACH,cAAE,OAAO;UACT,UAAU,CAAC,MAAM,GAAG;cAClB,IAAI,CAAA;;;;;;AAMY,0BAAA,EAAA,QAAQ,CAAC;AACf,gBAAA,uBAAuB,EAAE,IAAI;gBAC7B,+BAA+B,EAAE,aAAa,KAAK,MAAM;aAC1D,CAAC;AACO,2BAAA,EAAA,MAAM,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC;;;;oBAIhD,UAAU,CAAC,GAAG,CACd,CAAC,KAAK,KAAK,IAAI,CAAA;;;AAGH,8BAAA,EAAA,QAAQ,CAAC;AACf,gBAAA,uBAAuB,EAAE,IAAI;AAC7B,gBAAA,+BAA+B,EAAE,aAAa,KAAK,KAAK,CAAC,EAAE;aAC5D,CAAC;iCACO,MAAM,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;;AAEhD,wBAAA,EAAA,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,EAAE;;qBAE5B,CACF;;;AAGN,YAAA;AACH,cAAE,OAAO;;KAEd;IACH;;AA/PO,cAAA,CAAA,MAAM,GAAG;IACd,YAAY;IACZ,aAAa;AACb,IAAA,GAAG,CAAA;;;;AAIF,IAAA,CAAA;AACF,CARY;AAXmB,UAAA,CAAA;AAA/B,IAAA,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE;AAA4B,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,IAAA,EAAA,MAAA,CAAA;AAC7B,UAAA,CAAA;AAA5B,IAAA,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;AAAe,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,MAAA,EAAA,MAAA,CAAA;AACd,UAAA,CAAA;AAA3B,IAAA,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;AAAsC,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,cAAA,EAAA,MAAA,CAAA;AACL,UAAA,CAAA;IAA1D,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE;AAAuB,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,eAAA,EAAA,MAAA,CAAA;AACxB,UAAA,CAAA;IAAvD,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE;AAAwB,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,cAAA,EAAA,MAAA,CAAA;AACtB,UAAA,CAAA;IAAvD,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE;AAAwB,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,cAAA,EAAA,MAAA,CAAA;AACtB,UAAA,CAAA;IAAvD,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE;AAAwB,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,cAAA,EAAA,MAAA,CAAA;AACZ,UAAA,CAAA;IAAjE,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,wBAAwB,EAAE;AAAiC,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,sBAAA,EAAA,MAAA,CAAA;AAEhF,UAAA,CAAA;AAAhB,IAAA,KAAK;AAA6B,CAAA,EAAA,cAAA,CAAA,SAAA,EAAA,eAAA,EAAA,MAAA,CAAA;AAVxB,cAAc,GAAA,UAAA,CAAA;IAD1B,aAAa,CAAC,kBAAkB;AACpB,CAAA,EAAA,cAAc,CA4Q1B;;;;"}
@@ -1,65 +1,126 @@
1
1
  import { __decorate } from '../node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js';
2
- import { css, LitElement, nothing, html } from 'lit';
2
+ import { css, LitElement, html } from 'lit';
3
3
  import { property, customElement } from 'lit/decorators.js';
4
4
  import { sharedStyles } from '../styles/shared-styles.js';
5
5
  import { utilityStyles } from '../styles/utility-styles.js';
6
6
  import { closeIcon } from '../icons/index.js';
7
7
 
8
8
  let FwStatsPanel = class FwStatsPanel extends LitElement {
9
- _resolution() {
10
- const video = this.pc.s.videoElement;
11
- if (!video || !video.videoWidth || !video.videoHeight)
12
- return null;
13
- return `${video.videoWidth}x${video.videoHeight}`;
9
+ _deriveTracksFromMist(mistInfo) {
10
+ const mistTracks = mistInfo?.meta?.tracks;
11
+ if (!mistTracks)
12
+ return undefined;
13
+ return Object.values(mistTracks).map((track) => ({
14
+ type: track.type,
15
+ codec: track.codec,
16
+ width: track.width,
17
+ height: track.height,
18
+ bitrate: typeof track.bps === "number" ? Math.round(track.bps) : undefined,
19
+ fps: typeof track.fpks === "number" ? track.fpks / 1000 : undefined,
20
+ channels: track.channels,
21
+ sampleRate: track.rate,
22
+ }));
14
23
  }
15
- _stat(label, value) {
16
- if (value == null || value === "")
17
- return nothing;
18
- return html `<div class="row">
19
- <span class="label">${label}</span><span class="value">${value}</span>
20
- </div>`;
24
+ _formatTracks(metadata, mistInfo) {
25
+ const tracks = metadata?.tracks ?? this._deriveTracksFromMist(mistInfo);
26
+ if (!tracks?.length)
27
+ return "";
28
+ return tracks
29
+ .map((track) => {
30
+ if (track.type === "video") {
31
+ const resolution = track.width && track.height ? `${track.width}x${track.height}` : "?";
32
+ const bitrate = track.bitrate ? `${Math.round(track.bitrate / 1000)}kbps` : "?";
33
+ return `${track.codec ?? "?"} ${resolution}@${bitrate}`;
34
+ }
35
+ const channels = track.channels ? `${track.channels}ch` : "?";
36
+ return `${track.codec ?? "?"} ${channels}`;
37
+ })
38
+ .join(", ");
21
39
  }
22
- render() {
40
+ _collectStats() {
23
41
  const s = this.pc.s;
24
- const q = s.playbackQuality;
25
- const meta = s.metadata;
26
- const ss = s.streamState;
42
+ const video = s.videoElement;
43
+ const quality = s.playbackQuality;
44
+ const metadata = s.metadata;
45
+ const streamState = s.streamState;
46
+ const primaryEndpoint = s.endpoints?.primary;
47
+ const currentRes = video ? `${video.videoWidth}x${video.videoHeight}` : "—";
48
+ const buffered = video && video.buffered.length > 0
49
+ ? (video.buffered.end(video.buffered.length - 1) - video.currentTime).toFixed(1)
50
+ : "—";
51
+ const playbackRate = video?.playbackRate?.toFixed(2) ?? "1.00";
52
+ const qualityScore = quality?.score?.toFixed(0) ?? "—";
53
+ const bitrateKbps = quality?.bitrate ? `${(quality.bitrate / 1000).toFixed(0)} kbps` : "—";
54
+ const frameDropRate = quality?.frameDropRate?.toFixed(1) ?? "—";
55
+ const stallCount = quality?.stallCount ?? 0;
56
+ const latency = quality?.latency ? `${Math.round(quality.latency)} ms` : "—";
57
+ const viewers = metadata?.viewers ?? "—";
58
+ const streamStatus = streamState?.status ?? metadata?.status ?? "—";
59
+ const mistInfo = metadata?.mist ?? streamState?.streamInfo;
60
+ const mistType = mistInfo?.type ?? "—";
61
+ const mistBufferWindow = mistInfo?.meta?.buffer_window;
62
+ const mistLastMs = mistInfo?.lastms;
63
+ const mistUnixOffset = mistInfo?.unixoffset;
64
+ const stats = [
65
+ { label: "Resolution", value: currentRes },
66
+ { label: "Buffer", value: `${buffered}s` },
67
+ { label: "Latency", value: latency },
68
+ { label: "Bitrate", value: bitrateKbps },
69
+ { label: "Quality Score", value: `${qualityScore}/100` },
70
+ { label: "Frame Drop Rate", value: `${frameDropRate}%` },
71
+ { label: "Stalls", value: String(stallCount) },
72
+ { label: "Playback Rate", value: `${playbackRate}x` },
73
+ { label: "Protocol", value: primaryEndpoint?.protocol ?? "—" },
74
+ { label: "Node", value: primaryEndpoint?.nodeId ?? "—" },
75
+ {
76
+ label: "Geo Distance",
77
+ value: primaryEndpoint?.geoDistance ? `${primaryEndpoint.geoDistance.toFixed(0)} km` : "—",
78
+ },
79
+ { label: "Viewers", value: String(viewers) },
80
+ { label: "Status", value: streamStatus },
81
+ { label: "Tracks", value: this._formatTracks(metadata, mistInfo) },
82
+ { label: "Mist Type", value: mistType },
83
+ {
84
+ label: "Mist Buffer Window",
85
+ value: mistBufferWindow != null ? String(mistBufferWindow) : "—",
86
+ },
87
+ { label: "Mist Lastms", value: mistLastMs != null ? String(mistLastMs) : "—" },
88
+ { label: "Mist Unixoffset", value: mistUnixOffset != null ? String(mistUnixOffset) : "—" },
89
+ ];
90
+ if (metadata?.title) {
91
+ stats.unshift({ label: "Title", value: metadata.title });
92
+ }
93
+ if (metadata?.durationSeconds) {
94
+ const mins = Math.floor(metadata.durationSeconds / 60);
95
+ const secs = metadata.durationSeconds % 60;
96
+ stats.push({ label: "Duration", value: `${mins}:${String(secs).padStart(2, "0")}` });
97
+ }
98
+ if (metadata?.recordingSizeBytes) {
99
+ const mb = (metadata.recordingSizeBytes / (1024 * 1024)).toFixed(1);
100
+ stats.push({ label: "Size", value: `${mb} MB` });
101
+ }
102
+ return stats;
103
+ }
104
+ render() {
105
+ const stats = this._collectStats();
27
106
  return html `
28
107
  <div class="panel fw-stats-panel">
29
- <div class="header">
30
- <span class="title">Stats</span>
108
+ <div class="header fw-stats-header">
109
+ <span class="title">Stats Overlay</span>
31
110
  <button
32
111
  class="close"
33
112
  @click=${() => this.dispatchEvent(new CustomEvent("fw-close", { bubbles: true, composed: true }))}
34
- aria-label="Close stats"
113
+ aria-label="Close stats panel"
35
114
  >
36
115
  ${closeIcon()}
37
116
  </button>
38
117
  </div>
39
-
40
- ${this._stat("State", s.state)} ${this._stat("Player", s.currentPlayerInfo?.name)}
41
- ${this._stat("Source", s.currentSourceInfo?.type)}
42
-
43
- <div class="sep"></div>
44
-
45
- ${q
46
- ? html `
47
- ${this._stat("Resolution", this._resolution())}
48
- ${this._stat("Bitrate", q.bitrate ? `${Math.round(q.bitrate / 1000)} kbps` : null)}
49
- ${this._stat("Latency", q.latency != null ? `${q.latency.toFixed(1)}s` : null)}
50
- ${this._stat("Buffer", q.bufferedAhead != null ? `${q.bufferedAhead.toFixed(1)}s` : null)}
51
- ${this._stat("Quality", q.score != null ? `${q.score.toFixed(0)}` : null)}
52
- ${this._stat("Frame drops", q.frameDropRate != null ? `${q.frameDropRate.toFixed(1)}%` : null)}
53
- ${this._stat("Stalls", q.stallCount ?? null)}
54
- `
55
- : nothing}
56
- ${meta || ss
57
- ? html `
58
- <div class="sep"></div>
59
- ${this._stat("Viewers", meta?.viewers ?? null)}
60
- ${this._stat("Stream status", ss?.status ?? null)}
61
- `
62
- : nothing}
118
+ <div class="rows">
119
+ ${stats.map((stat) => html `<div class="row fw-stats-row">
120
+ <span class="label">${stat.label}</span>
121
+ <span class="value fw-stats-value">${stat.value}</span>
122
+ </div>`)}
123
+ </div>
63
124
  </div>
64
125
  `;
65
126
  }
@@ -73,60 +134,63 @@ FwStatsPanel.styles = [
73
134
  }
74
135
  .panel {
75
136
  position: absolute;
76
- top: 0.75rem;
77
- left: 0.75rem;
137
+ top: 0.5rem;
138
+ right: 0.5rem;
78
139
  z-index: 30;
79
- min-width: 240px;
80
- max-width: 320px;
140
+ width: 18rem;
81
141
  max-height: 80%;
82
- overflow: auto;
83
- border-radius: 0.5rem;
84
- border: 1px solid rgb(255 255 255 / 0.1);
85
- background: rgb(0 0 0 / 0.85);
86
- backdrop-filter: blur(8px);
87
- padding: 0.5rem 0.75rem;
88
- font-size: 0.6875rem;
89
- color: rgb(255 255 255 / 0.7);
142
+ overflow-y: auto;
143
+ background: hsl(var(--tn-bg-dark) / 0.9);
144
+ backdrop-filter: blur(4px);
145
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
146
+ font-family: ui-monospace, monospace;
147
+ font-size: 0.75rem;
148
+ color: hsl(var(--tn-fg));
90
149
  }
91
150
  .header {
92
151
  display: flex;
93
152
  align-items: center;
94
153
  justify-content: space-between;
95
- margin-bottom: 0.5rem;
154
+ padding: 0.5rem;
155
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
96
156
  }
97
157
  .title {
98
- font-size: 0.75rem;
158
+ font-size: 10px;
99
159
  font-weight: 600;
100
- color: white;
160
+ text-transform: uppercase;
161
+ letter-spacing: 0.05em;
162
+ color: hsl(var(--tn-fg-dark));
101
163
  }
102
164
  .close {
103
165
  display: flex;
104
- background: none;
166
+ align-items: center;
167
+ justify-content: center;
168
+ width: 1.5rem;
169
+ height: 1.5rem;
105
170
  border: none;
106
- color: rgb(255 255 255 / 0.5);
171
+ background: transparent;
172
+ color: hsl(var(--tn-fg-dark));
107
173
  cursor: pointer;
108
- padding: 0;
109
174
  }
110
175
  .close:hover {
111
- color: white;
176
+ color: hsl(var(--tn-fg));
177
+ }
178
+ .rows {
179
+ padding: 0.5rem;
112
180
  }
113
181
  .row {
114
182
  display: flex;
115
183
  justify-content: space-between;
184
+ gap: 0.5rem;
116
185
  padding: 0.125rem 0;
117
186
  }
118
187
  .label {
119
- color: rgb(255 255 255 / 0.5);
188
+ color: hsl(var(--tn-fg-dark));
120
189
  }
121
190
  .value {
122
- color: rgb(255 255 255 / 0.9);
123
- font-variant-numeric: tabular-nums;
124
- font-family: ui-monospace, monospace;
125
- }
126
- .sep {
127
- height: 1px;
128
- background: rgb(255 255 255 / 0.08);
129
- margin: 0.375rem 0;
191
+ color: hsl(var(--tn-fg));
192
+ text-align: right;
193
+ word-break: break-word;
130
194
  }
131
195
  `,
132
196
  ];