@livepeer-frameworks/player-wc 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +347 -173
  10. package/dist/cjs/components/fw-player-controls.js.map +1 -1
  11. package/dist/cjs/components/fw-player.js +460 -60
  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 +191 -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 +28 -1
  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 +348 -174
  48. package/dist/esm/components/fw-player-controls.js.map +1 -1
  49. package/dist/esm/components/fw-player.js +460 -60
  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 +192 -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 +28 -1
  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 +2072 -880
  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 +21 -6
  83. package/dist/types/components/fw-player.d.ts +28 -1
  84. package/dist/types/components/fw-seek-bar.d.ts +31 -14
  85. package/dist/types/components/fw-settings-menu.d.ts +15 -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 +435 -176
  102. package/src/components/fw-player.ts +505 -57
  103. package/src/components/fw-seek-bar.ts +336 -143
  104. package/src/components/fw-settings-menu.ts +208 -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 +29 -2
  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
@@ -6,43 +6,190 @@ var decorators_js = require('lit/decorators.js');
6
6
  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
+ var playerCore = require('@livepeer-frameworks/player-core');
9
10
 
10
11
  exports.FwSettingsMenu = class FwSettingsMenu extends lit.LitElement {
11
12
  constructor() {
12
13
  super(...arguments);
13
14
  this.open = false;
15
+ this.playbackMode = "auto";
16
+ this.isContentLive = true;
17
+ this._playbackRate = 1;
18
+ }
19
+ updated() {
20
+ if (!this.open) {
21
+ return;
22
+ }
23
+ if (Number.isFinite(this.playbackRate)) {
24
+ this._playbackRate = this.playbackRate;
25
+ return;
26
+ }
27
+ const video = this.pc?.s.videoElement;
28
+ if (video && Number.isFinite(video.playbackRate)) {
29
+ this._playbackRate = video.playbackRate;
30
+ }
31
+ }
32
+ _close() {
33
+ this.dispatchEvent(new CustomEvent("fw-close", { bubbles: true, composed: true }));
34
+ }
35
+ _handleModeChange(mode) {
36
+ this.pc.setDevModeOptions({ playbackMode: mode });
37
+ this.dispatchEvent(new CustomEvent("fw-mode-change", {
38
+ detail: { mode },
39
+ bubbles: true,
40
+ composed: true,
41
+ }));
42
+ this._close();
43
+ }
44
+ _handleSpeedChange(rate) {
45
+ this._playbackRate = rate;
46
+ this.pc.setPlaybackRate(rate);
47
+ this.dispatchEvent(new CustomEvent("fw-speed-change", {
48
+ detail: { rate },
49
+ bubbles: true,
50
+ composed: true,
51
+ }));
52
+ this._close();
53
+ }
54
+ _handleQualityChange(id) {
55
+ this.pc.selectQuality(id);
56
+ this.dispatchEvent(new CustomEvent("fw-quality-change", {
57
+ detail: { quality: id },
58
+ bubbles: true,
59
+ composed: true,
60
+ }));
61
+ this._close();
62
+ }
63
+ _handleCaptionChange(id) {
64
+ if (id === "none") {
65
+ this.pc.selectTextTrack(null);
66
+ }
67
+ else {
68
+ this.pc.selectTextTrack(id);
69
+ }
70
+ this.dispatchEvent(new CustomEvent("fw-caption-change", {
71
+ detail: { caption: id },
72
+ bubbles: true,
73
+ composed: true,
74
+ }));
75
+ this._close();
14
76
  }
15
77
  render() {
16
- if (!this.open)
78
+ if (!this.open) {
17
79
  return lit.nothing;
18
- const { qualities } = this.pc.s;
19
- const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2];
80
+ }
81
+ const state = this.pc.s;
82
+ const qualities = state.qualities ?? [];
83
+ const textTracks = state.textTracks ?? [];
84
+ const activeQuality = this.qualityValue ?? qualities.find((quality) => quality.active)?.id ?? "auto";
85
+ const activeCaption = this.captionValue ?? textTracks.find((track) => track.active)?.id ?? "none";
86
+ const supportsPlaybackRate = this.supportsPlaybackRate ?? playerCore.supportsPlaybackRate(state.videoElement);
20
87
  return lit.html `
21
- <div class="menu fw-settings-menu">
88
+ <div class="fw-player-surface fw-settings-menu" role="menu" aria-label="Player settings">
89
+ ${this.isContentLive
90
+ ? lit.html `
91
+ <div class="fw-settings-section">
92
+ <div class="fw-settings-label">Mode</div>
93
+ <div class="fw-settings-options">
94
+ ${["auto", "low-latency", "quality"].map((mode) => lit.html `
95
+ <button
96
+ type="button"
97
+ class=${classMap_js.classMap({
98
+ "fw-settings-btn": true,
99
+ "fw-settings-btn--active": this.playbackMode === mode,
100
+ })}
101
+ @click=${() => this._handleModeChange(mode)}
102
+ >
103
+ ${mode === "low-latency" ? "Fast" : mode === "quality" ? "Stable" : "Auto"}
104
+ </button>
105
+ `)}
106
+ </div>
107
+ </div>
108
+ `
109
+ : lit.nothing}
110
+ ${supportsPlaybackRate
111
+ ? lit.html `
112
+ <div class="fw-settings-section">
113
+ <div class="fw-settings-label">Speed</div>
114
+ <div class="fw-settings-options fw-settings-options--wrap">
115
+ ${playerCore.SPEED_PRESETS.map((rate) => lit.html `
116
+ <button
117
+ type="button"
118
+ class=${classMap_js.classMap({
119
+ "fw-settings-btn": true,
120
+ "fw-settings-btn--active": this._playbackRate === rate,
121
+ })}
122
+ @click=${() => this._handleSpeedChange(rate)}
123
+ >
124
+ ${rate}x
125
+ </button>
126
+ `)}
127
+ </div>
128
+ </div>
129
+ `
130
+ : lit.nothing}
22
131
  ${qualities.length > 0
23
132
  ? lit.html `
24
- <div class="section">
25
- <div class="label">Quality</div>
26
- ${qualities.map((q) => lit.html `
27
- <button
28
- class=${classMap_js.classMap({ option: true, "option--active": !!q.active })}
29
- @click=${() => this.pc.selectQuality(q.id)}
30
- >
31
- <div class=${classMap_js.classMap({ dot: true, "dot--hidden": !q.active })}></div>
32
- ${q.label}
33
- </button>
34
- `)}
133
+ <div class="fw-settings-section">
134
+ <div class="fw-settings-label">Quality</div>
135
+ <div class="fw-settings-list">
136
+ <button
137
+ type="button"
138
+ class=${classMap_js.classMap({
139
+ "fw-settings-list-item": true,
140
+ "fw-settings-list-item--active": activeQuality === "auto",
141
+ })}
142
+ @click=${() => this._handleQualityChange("auto")}
143
+ >
144
+ Auto
145
+ </button>
146
+ ${qualities.map((quality) => lit.html `
147
+ <button
148
+ type="button"
149
+ class=${classMap_js.classMap({
150
+ "fw-settings-list-item": true,
151
+ "fw-settings-list-item--active": activeQuality === quality.id,
152
+ })}
153
+ @click=${() => this._handleQualityChange(quality.id)}
154
+ >
155
+ ${quality.label}
156
+ </button>
157
+ `)}
158
+ </div>
159
+ </div>
160
+ `
161
+ : lit.nothing}
162
+ ${textTracks.length > 0
163
+ ? lit.html `
164
+ <div class="fw-settings-section">
165
+ <div class="fw-settings-label">Captions</div>
166
+ <div class="fw-settings-list">
167
+ <button
168
+ type="button"
169
+ class=${classMap_js.classMap({
170
+ "fw-settings-list-item": true,
171
+ "fw-settings-list-item--active": activeCaption === "none",
172
+ })}
173
+ @click=${() => this._handleCaptionChange("none")}
174
+ >
175
+ Off
176
+ </button>
177
+ ${textTracks.map((track) => lit.html `
178
+ <button
179
+ type="button"
180
+ class=${classMap_js.classMap({
181
+ "fw-settings-list-item": true,
182
+ "fw-settings-list-item--active": activeCaption === track.id,
183
+ })}
184
+ @click=${() => this._handleCaptionChange(track.id)}
185
+ >
186
+ ${track.label || track.id}
187
+ </button>
188
+ `)}
189
+ </div>
35
190
  </div>
36
191
  `
37
192
  : lit.nothing}
38
- <div class="section">
39
- <div class="label">Speed</div>
40
- ${speeds.map((s) => lit.html `
41
- <button class="option" @click=${() => this.pc.getController()?.setPlaybackRate(s)}>
42
- ${s === 1 ? "Normal" : `${s}x`}
43
- </button>
44
- `)}
45
- </div>
46
193
  </div>
47
194
  `;
48
195
  }
@@ -54,64 +201,6 @@ exports.FwSettingsMenu.styles = [
54
201
  :host {
55
202
  display: contents;
56
203
  }
57
- .menu {
58
- position: absolute;
59
- bottom: 100%;
60
- right: 0;
61
- margin-bottom: 0.5rem;
62
- min-width: 200px;
63
- border-radius: 0.5rem;
64
- border: 1px solid rgb(255 255 255 / 0.1);
65
- background: rgb(0 0 0 / 0.9);
66
- backdrop-filter: blur(8px);
67
- padding: 0.5rem;
68
- z-index: 50;
69
- box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3);
70
- }
71
- .section {
72
- padding: 0.25rem 0;
73
- }
74
- .section + .section {
75
- border-top: 1px solid rgb(255 255 255 / 0.1);
76
- }
77
- .label {
78
- padding: 0.25rem 0.5rem;
79
- font-size: 0.6875rem;
80
- font-weight: 600;
81
- text-transform: uppercase;
82
- letter-spacing: 0.05em;
83
- color: rgb(255 255 255 / 0.4);
84
- }
85
- .option {
86
- display: flex;
87
- align-items: center;
88
- width: 100%;
89
- padding: 0.375rem 0.5rem;
90
- border: none;
91
- background: none;
92
- color: rgb(255 255 255 / 0.7);
93
- font-size: 0.8125rem;
94
- cursor: pointer;
95
- border-radius: 0.25rem;
96
- text-align: left;
97
- }
98
- .option:hover {
99
- background: rgb(255 255 255 / 0.1);
100
- color: white;
101
- }
102
- .option--active {
103
- color: hsl(var(--tn-blue, 217 89% 61%));
104
- }
105
- .dot {
106
- width: 6px;
107
- height: 6px;
108
- border-radius: 50%;
109
- background: hsl(var(--tn-blue, 217 89% 61%));
110
- margin-right: 0.5rem;
111
- }
112
- .dot--hidden {
113
- visibility: hidden;
114
- }
115
204
  `,
116
205
  ];
117
206
  tslib_es6.__decorate([
@@ -120,6 +209,27 @@ tslib_es6.__decorate([
120
209
  tslib_es6.__decorate([
121
210
  decorators_js.property({ type: Boolean })
122
211
  ], exports.FwSettingsMenu.prototype, "open", void 0);
212
+ tslib_es6.__decorate([
213
+ decorators_js.property({ type: String })
214
+ ], exports.FwSettingsMenu.prototype, "playbackMode", void 0);
215
+ tslib_es6.__decorate([
216
+ decorators_js.property({ type: Boolean, attribute: "is-content-live" })
217
+ ], exports.FwSettingsMenu.prototype, "isContentLive", void 0);
218
+ tslib_es6.__decorate([
219
+ decorators_js.property({ type: Number, attribute: "playback-rate" })
220
+ ], exports.FwSettingsMenu.prototype, "playbackRate", void 0);
221
+ tslib_es6.__decorate([
222
+ decorators_js.property({ type: String, attribute: "quality-value" })
223
+ ], exports.FwSettingsMenu.prototype, "qualityValue", void 0);
224
+ tslib_es6.__decorate([
225
+ decorators_js.property({ type: String, attribute: "caption-value" })
226
+ ], exports.FwSettingsMenu.prototype, "captionValue", void 0);
227
+ tslib_es6.__decorate([
228
+ decorators_js.property({ type: Boolean, attribute: "supports-playback-rate" })
229
+ ], exports.FwSettingsMenu.prototype, "supportsPlaybackRate", void 0);
230
+ tslib_es6.__decorate([
231
+ decorators_js.state()
232
+ ], exports.FwSettingsMenu.prototype, "_playbackRate", void 0);
123
233
  exports.FwSettingsMenu = tslib_es6.__decorate([
124
234
  decorators_js.customElement("fw-settings-menu")
125
235
  ], exports.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":["FwSettingsMenu","LitElement","nothing","html","classMap","sharedStyles","utilityStyles","css","__decorate","property","customElement"],"mappings":";;;;;;;;;AAWaA,sBAAc,GAApB,MAAM,cAAe,SAAQC,cAAU,CAAA;AAAvC,IAAA,WAAA,GAAA;;QAEwB,IAAA,CAAA,IAAI,GAAG,KAAK;IA4G3C;IAtCY,MAAM,GAAA;QACd,IAAI,CAAC,IAAI,CAAC,IAAI;AAAE,YAAA,OAAOC,WAAO;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,OAAOC,QAAI,CAAA;;UAEL,SAAS,CAAC,MAAM,GAAG;cACjBA,QAAI,CAAA;;;kBAGE,SAAS,CAAC,GAAG,CACb,CAAC,CAAC,KAAKA,QAAI,CAAA;;AAEC,4BAAA,EAAAC,oBAAQ,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,EAAAA,oBAAQ,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,cAAEF,WAAO;;;YAGP,MAAM,CAAC,GAAG,CACV,CAAC,CAAC,KAAKC,QAAI,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;;AAzGOH,sBAAA,CAAA,MAAM,GAAG;IACdK,yBAAY;IACZC,2BAAa;AACb,IAAAC,OAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DF,IAAA,CAAA;AACF,CAlEY;AAHmBC,oBAAA,CAAA;AAA/B,IAAAC,sBAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE;AAA4B,CAAA,EAAAT,sBAAA,CAAA,SAAA,EAAA,IAAA,EAAA,MAAA,CAAA;AAC7BQ,oBAAA,CAAA;AAA5B,IAAAC,sBAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;AAAe,CAAA,EAAAT,sBAAA,CAAA,SAAA,EAAA,MAAA,EAAA,MAAA,CAAA;AAF/BA,sBAAc,GAAAQ,oBAAA,CAAA;IAD1BE,2BAAa,CAAC,kBAAkB;AACpB,CAAA,EAAAV,sBAAc,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 protected render() {\n if (!this.open) {\n return nothing;\n }\n\n const state = this.pc.s;\n const qualities = state.qualities ?? [];\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":["FwSettingsMenu","LitElement","nothing","coreSupportsPlaybackRate","html","classMap","SPEED_PRESETS","sharedStyles","utilityStyles","css","__decorate","property","state","customElement"],"mappings":"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"}
@@ -8,60 +8,121 @@ var utilityStyles = require('../styles/utility-styles.js');
8
8
  var index = require('../icons/index.js');
9
9
 
10
10
  exports.FwStatsPanel = class FwStatsPanel extends lit.LitElement {
11
- _resolution() {
12
- const video = this.pc.s.videoElement;
13
- if (!video || !video.videoWidth || !video.videoHeight)
14
- return null;
15
- return `${video.videoWidth}x${video.videoHeight}`;
11
+ _deriveTracksFromMist(mistInfo) {
12
+ const mistTracks = mistInfo?.meta?.tracks;
13
+ if (!mistTracks)
14
+ return undefined;
15
+ return Object.values(mistTracks).map((track) => ({
16
+ type: track.type,
17
+ codec: track.codec,
18
+ width: track.width,
19
+ height: track.height,
20
+ bitrate: typeof track.bps === "number" ? Math.round(track.bps) : undefined,
21
+ fps: typeof track.fpks === "number" ? track.fpks / 1000 : undefined,
22
+ channels: track.channels,
23
+ sampleRate: track.rate,
24
+ }));
16
25
  }
17
- _stat(label, value) {
18
- if (value == null || value === "")
19
- return lit.nothing;
20
- return lit.html `<div class="row">
21
- <span class="label">${label}</span><span class="value">${value}</span>
22
- </div>`;
26
+ _formatTracks(metadata, mistInfo) {
27
+ const tracks = metadata?.tracks ?? this._deriveTracksFromMist(mistInfo);
28
+ if (!tracks?.length)
29
+ return "";
30
+ return tracks
31
+ .map((track) => {
32
+ if (track.type === "video") {
33
+ const resolution = track.width && track.height ? `${track.width}x${track.height}` : "?";
34
+ const bitrate = track.bitrate ? `${Math.round(track.bitrate / 1000)}kbps` : "?";
35
+ return `${track.codec ?? "?"} ${resolution}@${bitrate}`;
36
+ }
37
+ const channels = track.channels ? `${track.channels}ch` : "?";
38
+ return `${track.codec ?? "?"} ${channels}`;
39
+ })
40
+ .join(", ");
23
41
  }
24
- render() {
42
+ _collectStats() {
25
43
  const s = this.pc.s;
26
- const q = s.playbackQuality;
27
- const meta = s.metadata;
28
- const ss = s.streamState;
44
+ const video = s.videoElement;
45
+ const quality = s.playbackQuality;
46
+ const metadata = s.metadata;
47
+ const streamState = s.streamState;
48
+ const primaryEndpoint = s.endpoints?.primary;
49
+ const currentRes = video ? `${video.videoWidth}x${video.videoHeight}` : "—";
50
+ const buffered = video && video.buffered.length > 0
51
+ ? (video.buffered.end(video.buffered.length - 1) - video.currentTime).toFixed(1)
52
+ : "—";
53
+ const playbackRate = video?.playbackRate?.toFixed(2) ?? "1.00";
54
+ const qualityScore = quality?.score?.toFixed(0) ?? "—";
55
+ const bitrateKbps = quality?.bitrate ? `${(quality.bitrate / 1000).toFixed(0)} kbps` : "—";
56
+ const frameDropRate = quality?.frameDropRate?.toFixed(1) ?? "—";
57
+ const stallCount = quality?.stallCount ?? 0;
58
+ const latency = quality?.latency ? `${Math.round(quality.latency)} ms` : "—";
59
+ const viewers = metadata?.viewers ?? "—";
60
+ const streamStatus = streamState?.status ?? metadata?.status ?? "—";
61
+ const mistInfo = metadata?.mist ?? streamState?.streamInfo;
62
+ const mistType = mistInfo?.type ?? "—";
63
+ const mistBufferWindow = mistInfo?.meta?.buffer_window;
64
+ const mistLastMs = mistInfo?.lastms;
65
+ const mistUnixOffset = mistInfo?.unixoffset;
66
+ const stats = [
67
+ { label: "Resolution", value: currentRes },
68
+ { label: "Buffer", value: `${buffered}s` },
69
+ { label: "Latency", value: latency },
70
+ { label: "Bitrate", value: bitrateKbps },
71
+ { label: "Quality Score", value: `${qualityScore}/100` },
72
+ { label: "Frame Drop Rate", value: `${frameDropRate}%` },
73
+ { label: "Stalls", value: String(stallCount) },
74
+ { label: "Playback Rate", value: `${playbackRate}x` },
75
+ { label: "Protocol", value: primaryEndpoint?.protocol ?? "—" },
76
+ { label: "Node", value: primaryEndpoint?.nodeId ?? "—" },
77
+ {
78
+ label: "Geo Distance",
79
+ value: primaryEndpoint?.geoDistance ? `${primaryEndpoint.geoDistance.toFixed(0)} km` : "—",
80
+ },
81
+ { label: "Viewers", value: String(viewers) },
82
+ { label: "Status", value: streamStatus },
83
+ { label: "Tracks", value: this._formatTracks(metadata, mistInfo) },
84
+ { label: "Mist Type", value: mistType },
85
+ {
86
+ label: "Mist Buffer Window",
87
+ value: mistBufferWindow != null ? String(mistBufferWindow) : "—",
88
+ },
89
+ { label: "Mist Lastms", value: mistLastMs != null ? String(mistLastMs) : "—" },
90
+ { label: "Mist Unixoffset", value: mistUnixOffset != null ? String(mistUnixOffset) : "—" },
91
+ ];
92
+ if (metadata?.title) {
93
+ stats.unshift({ label: "Title", value: metadata.title });
94
+ }
95
+ if (metadata?.durationSeconds) {
96
+ const mins = Math.floor(metadata.durationSeconds / 60);
97
+ const secs = metadata.durationSeconds % 60;
98
+ stats.push({ label: "Duration", value: `${mins}:${String(secs).padStart(2, "0")}` });
99
+ }
100
+ if (metadata?.recordingSizeBytes) {
101
+ const mb = (metadata.recordingSizeBytes / (1024 * 1024)).toFixed(1);
102
+ stats.push({ label: "Size", value: `${mb} MB` });
103
+ }
104
+ return stats;
105
+ }
106
+ render() {
107
+ const stats = this._collectStats();
29
108
  return lit.html `
30
109
  <div class="panel fw-stats-panel">
31
- <div class="header">
32
- <span class="title">Stats</span>
110
+ <div class="header fw-stats-header">
111
+ <span class="title">Stats Overlay</span>
33
112
  <button
34
113
  class="close"
35
114
  @click=${() => this.dispatchEvent(new CustomEvent("fw-close", { bubbles: true, composed: true }))}
36
- aria-label="Close stats"
115
+ aria-label="Close stats panel"
37
116
  >
38
117
  ${index.closeIcon()}
39
118
  </button>
40
119
  </div>
41
-
42
- ${this._stat("State", s.state)} ${this._stat("Player", s.currentPlayerInfo?.name)}
43
- ${this._stat("Source", s.currentSourceInfo?.type)}
44
-
45
- <div class="sep"></div>
46
-
47
- ${q
48
- ? lit.html `
49
- ${this._stat("Resolution", this._resolution())}
50
- ${this._stat("Bitrate", q.bitrate ? `${Math.round(q.bitrate / 1000)} kbps` : null)}
51
- ${this._stat("Latency", q.latency != null ? `${q.latency.toFixed(1)}s` : null)}
52
- ${this._stat("Buffer", q.bufferedAhead != null ? `${q.bufferedAhead.toFixed(1)}s` : null)}
53
- ${this._stat("Quality", q.score != null ? `${q.score.toFixed(0)}` : null)}
54
- ${this._stat("Frame drops", q.frameDropRate != null ? `${q.frameDropRate.toFixed(1)}%` : null)}
55
- ${this._stat("Stalls", q.stallCount ?? null)}
56
- `
57
- : lit.nothing}
58
- ${meta || ss
59
- ? lit.html `
60
- <div class="sep"></div>
61
- ${this._stat("Viewers", meta?.viewers ?? null)}
62
- ${this._stat("Stream status", ss?.status ?? null)}
63
- `
64
- : lit.nothing}
120
+ <div class="rows">
121
+ ${stats.map((stat) => lit.html `<div class="row fw-stats-row">
122
+ <span class="label">${stat.label}</span>
123
+ <span class="value fw-stats-value">${stat.value}</span>
124
+ </div>`)}
125
+ </div>
65
126
  </div>
66
127
  `;
67
128
  }
@@ -75,60 +136,63 @@ exports.FwStatsPanel.styles = [
75
136
  }
76
137
  .panel {
77
138
  position: absolute;
78
- top: 0.75rem;
79
- left: 0.75rem;
139
+ top: 0.5rem;
140
+ right: 0.5rem;
80
141
  z-index: 30;
81
- min-width: 240px;
82
- max-width: 320px;
142
+ width: 18rem;
83
143
  max-height: 80%;
84
- overflow: auto;
85
- border-radius: 0.5rem;
86
- border: 1px solid rgb(255 255 255 / 0.1);
87
- background: rgb(0 0 0 / 0.85);
88
- backdrop-filter: blur(8px);
89
- padding: 0.5rem 0.75rem;
90
- font-size: 0.6875rem;
91
- color: rgb(255 255 255 / 0.7);
144
+ overflow-y: auto;
145
+ background: hsl(var(--tn-bg-dark) / 0.9);
146
+ backdrop-filter: blur(4px);
147
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
148
+ font-family: ui-monospace, monospace;
149
+ font-size: 0.75rem;
150
+ color: hsl(var(--tn-fg));
92
151
  }
93
152
  .header {
94
153
  display: flex;
95
154
  align-items: center;
96
155
  justify-content: space-between;
97
- margin-bottom: 0.5rem;
156
+ padding: 0.5rem;
157
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
98
158
  }
99
159
  .title {
100
- font-size: 0.75rem;
160
+ font-size: 10px;
101
161
  font-weight: 600;
102
- color: white;
162
+ text-transform: uppercase;
163
+ letter-spacing: 0.05em;
164
+ color: hsl(var(--tn-fg-dark));
103
165
  }
104
166
  .close {
105
167
  display: flex;
106
- background: none;
168
+ align-items: center;
169
+ justify-content: center;
170
+ width: 1.5rem;
171
+ height: 1.5rem;
107
172
  border: none;
108
- color: rgb(255 255 255 / 0.5);
173
+ background: transparent;
174
+ color: hsl(var(--tn-fg-dark));
109
175
  cursor: pointer;
110
- padding: 0;
111
176
  }
112
177
  .close:hover {
113
- color: white;
178
+ color: hsl(var(--tn-fg));
179
+ }
180
+ .rows {
181
+ padding: 0.5rem;
114
182
  }
115
183
  .row {
116
184
  display: flex;
117
185
  justify-content: space-between;
186
+ gap: 0.5rem;
118
187
  padding: 0.125rem 0;
119
188
  }
120
189
  .label {
121
- color: rgb(255 255 255 / 0.5);
190
+ color: hsl(var(--tn-fg-dark));
122
191
  }
123
192
  .value {
124
- color: rgb(255 255 255 / 0.9);
125
- font-variant-numeric: tabular-nums;
126
- font-family: ui-monospace, monospace;
127
- }
128
- .sep {
129
- height: 1px;
130
- background: rgb(255 255 255 / 0.08);
131
- margin: 0.375rem 0;
193
+ color: hsl(var(--tn-fg));
194
+ text-align: right;
195
+ word-break: break-word;
132
196
  }
133
197
  `,
134
198
  ];