@livepeer-frameworks/player-wc 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/fw-dev-mode-panel.js +1 -0
- package/dist/cjs/components/fw-dev-mode-panel.js.map +1 -1
- package/dist/cjs/components/fw-player-controls.js +12 -3
- package/dist/cjs/components/fw-player-controls.js.map +1 -1
- package/dist/cjs/components/fw-player.js +50 -10
- package/dist/cjs/components/fw-player.js.map +1 -1
- package/dist/cjs/components/fw-settings-menu.js +18 -1
- package/dist/cjs/components/fw-settings-menu.js.map +1 -1
- package/dist/cjs/controllers/player-controller-host.js +23 -1
- package/dist/cjs/controllers/player-controller-host.js.map +1 -1
- package/dist/cjs/styles/shared-styles.js +18 -0
- package/dist/cjs/styles/shared-styles.js.map +1 -1
- package/dist/esm/components/fw-dev-mode-panel.js +1 -0
- package/dist/esm/components/fw-dev-mode-panel.js.map +1 -1
- package/dist/esm/components/fw-player-controls.js +12 -3
- package/dist/esm/components/fw-player-controls.js.map +1 -1
- package/dist/esm/components/fw-player.js +50 -10
- package/dist/esm/components/fw-player.js.map +1 -1
- package/dist/esm/components/fw-settings-menu.js +18 -1
- package/dist/esm/components/fw-settings-menu.js.map +1 -1
- package/dist/esm/controllers/player-controller-host.js +23 -1
- package/dist/esm/controllers/player-controller-host.js.map +1 -1
- package/dist/esm/styles/shared-styles.js +18 -0
- package/dist/esm/styles/shared-styles.js.map +1 -1
- package/dist/fw-player.iife.js +40 -20
- package/dist/types/components/fw-player-controls.d.ts +1 -0
- package/dist/types/components/fw-player.d.ts +4 -0
- package/dist/types/components/fw-settings-menu.d.ts +1 -0
- package/package.json +2 -2
- package/src/components/fw-dev-mode-panel.ts +1 -0
- package/src/components/fw-player-controls.ts +10 -3
- package/src/components/fw-player.ts +50 -10
- package/src/components/fw-settings-menu.ts +41 -1
- package/src/controllers/player-controller-host.ts +23 -1
- package/src/styles/shared-styles.ts +18 -0
|
@@ -14,6 +14,8 @@ export declare class FwPlayer extends LitElement {
|
|
|
14
14
|
autoplay: boolean;
|
|
15
15
|
muted: boolean;
|
|
16
16
|
controls: boolean;
|
|
17
|
+
stockControls: boolean;
|
|
18
|
+
nativeControls: boolean;
|
|
17
19
|
debug: boolean;
|
|
18
20
|
devMode: boolean;
|
|
19
21
|
thumbnailUrl?: string;
|
|
@@ -45,6 +47,8 @@ export declare class FwPlayer extends LitElement {
|
|
|
45
47
|
private _closeContextMenu;
|
|
46
48
|
private _getQueryRoot;
|
|
47
49
|
private _getContextMenuElement;
|
|
50
|
+
private _getContextMenuBounds;
|
|
51
|
+
private _toLocalContextMenuPoint;
|
|
48
52
|
private _getContextMenuItems;
|
|
49
53
|
private _focusFirstContextMenuItem;
|
|
50
54
|
private _focusPlaybackModeTrigger;
|
|
@@ -21,6 +21,7 @@ export declare class FwSettingsMenu extends LitElement {
|
|
|
21
21
|
private _handleSpeedChange;
|
|
22
22
|
private _handleQualityChange;
|
|
23
23
|
private _handleCaptionChange;
|
|
24
|
+
private _deriveFallbackQualities;
|
|
24
25
|
protected render(): import("lit").TemplateResult<1> | typeof nothing;
|
|
25
26
|
}
|
|
26
27
|
declare global {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livepeer-frameworks/player-wc",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lit Web Components for FrameWorks streaming player — <fw-player> custom element with full UI",
|
|
6
6
|
"main": "dist/cjs/index.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"lit": "^3.2.0",
|
|
32
|
-
"@livepeer-frameworks/player-core": "0.1.
|
|
32
|
+
"@livepeer-frameworks/player-core": "0.1.4"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
@@ -48,6 +48,7 @@ export class FwPlayerControls extends LitElement {
|
|
|
48
48
|
@property({ attribute: false }) pc!: PlayerControllerHost;
|
|
49
49
|
@property({ type: String }) playbackMode: PlaybackMode = "auto";
|
|
50
50
|
@property({ type: Boolean, attribute: "is-content-live" }) isContentLive = false;
|
|
51
|
+
@property({ type: Boolean, attribute: "dev-mode" }) devMode = false;
|
|
51
52
|
@property({ type: Boolean, attribute: "show-stats-button" }) showStatsButton = false;
|
|
52
53
|
@property({ type: Boolean, attribute: "is-stats-open" }) isStatsOpen = false;
|
|
53
54
|
|
|
@@ -314,6 +315,13 @@ export class FwPlayerControls extends LitElement {
|
|
|
314
315
|
const state = this.pc.s;
|
|
315
316
|
const disabled = !state.videoElement;
|
|
316
317
|
const context = this._getSeekingContext();
|
|
318
|
+
const shouldShowControls =
|
|
319
|
+
state.shouldShowControls ||
|
|
320
|
+
state.isPaused ||
|
|
321
|
+
!state.hasPlaybackStarted ||
|
|
322
|
+
state.shouldShowIdleScreen ||
|
|
323
|
+
!!state.error ||
|
|
324
|
+
this._settingsOpen;
|
|
317
325
|
|
|
318
326
|
const timeDisplay = formatTimeDisplay({
|
|
319
327
|
isLive: context.isLive,
|
|
@@ -331,8 +339,8 @@ export class FwPlayerControls extends LitElement {
|
|
|
331
339
|
class=${classMap({
|
|
332
340
|
"fw-player-surface": true,
|
|
333
341
|
"fw-controls-wrapper": true,
|
|
334
|
-
"fw-controls-wrapper--visible":
|
|
335
|
-
"fw-controls-wrapper--hidden": !
|
|
342
|
+
"fw-controls-wrapper--visible": shouldShowControls,
|
|
343
|
+
"fw-controls-wrapper--hidden": !shouldShowControls,
|
|
336
344
|
})}
|
|
337
345
|
>
|
|
338
346
|
<div class="fw-control-bar" @click=${(event: Event) => event.stopPropagation()}>
|
|
@@ -453,7 +461,6 @@ export class FwPlayerControls extends LitElement {
|
|
|
453
461
|
</div>
|
|
454
462
|
`
|
|
455
463
|
: nothing}
|
|
456
|
-
|
|
457
464
|
<div class="fw-control-group fw-settings-anchor">
|
|
458
465
|
<button
|
|
459
466
|
type="button"
|
|
@@ -27,7 +27,11 @@ export class FwPlayer extends LitElement {
|
|
|
27
27
|
@property({ attribute: "auth-token" }) authToken?: string;
|
|
28
28
|
@property({ type: Boolean }) autoplay = true;
|
|
29
29
|
@property({ type: Boolean }) muted = true;
|
|
30
|
+
// React/Svelte use `stockControls` for native controls. Keep `controls` as a
|
|
31
|
+
// compatibility no-op so WC parity does not hide custom controls/seekbar.
|
|
30
32
|
@property({ type: Boolean }) controls = false;
|
|
33
|
+
@property({ type: Boolean, attribute: "stock-controls" }) stockControls = false;
|
|
34
|
+
@property({ type: Boolean, attribute: "native-controls" }) nativeControls = false;
|
|
31
35
|
@property({ type: Boolean }) debug = false;
|
|
32
36
|
@property({ type: Boolean, attribute: "dev-mode" }) devMode = false;
|
|
33
37
|
@property({ attribute: "thumbnail-url" }) thumbnailUrl?: string;
|
|
@@ -78,6 +82,7 @@ export class FwPlayer extends LitElement {
|
|
|
78
82
|
.player-area--dev {
|
|
79
83
|
flex: 1;
|
|
80
84
|
min-width: 0;
|
|
85
|
+
min-height: 0;
|
|
81
86
|
}
|
|
82
87
|
`,
|
|
83
88
|
];
|
|
@@ -93,7 +98,8 @@ export class FwPlayer extends LitElement {
|
|
|
93
98
|
changed.has("authToken") ||
|
|
94
99
|
changed.has("autoplay") ||
|
|
95
100
|
changed.has("muted") ||
|
|
96
|
-
changed.has("
|
|
101
|
+
changed.has("stockControls") ||
|
|
102
|
+
changed.has("nativeControls") ||
|
|
97
103
|
changed.has("debug") ||
|
|
98
104
|
changed.has("thumbnailUrl") ||
|
|
99
105
|
changed.has("endpoints")
|
|
@@ -107,7 +113,7 @@ export class FwPlayer extends LitElement {
|
|
|
107
113
|
authToken: this.authToken,
|
|
108
114
|
autoplay: this.autoplay,
|
|
109
115
|
muted: this.muted,
|
|
110
|
-
controls: this.
|
|
116
|
+
controls: this.stockControls || this.nativeControls,
|
|
111
117
|
poster: this.thumbnailUrl,
|
|
112
118
|
debug: this.debug,
|
|
113
119
|
});
|
|
@@ -199,6 +205,31 @@ export class FwPlayer extends LitElement {
|
|
|
199
205
|
private _getContextMenuElement = () =>
|
|
200
206
|
this._getQueryRoot()?.querySelector<HTMLElement>('[data-context-menu="true"]') ?? null;
|
|
201
207
|
|
|
208
|
+
private _getContextMenuBounds = () => {
|
|
209
|
+
const root = this._getQueryRoot()?.querySelector<HTMLElement>('[part="root"]');
|
|
210
|
+
const rect = root?.getBoundingClientRect() ?? this.getBoundingClientRect();
|
|
211
|
+
|
|
212
|
+
const width = rect.width > 0 ? rect.width : window.innerWidth;
|
|
213
|
+
const height = rect.height > 0 ? rect.height : window.innerHeight;
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
left: rect.left,
|
|
217
|
+
top: rect.top,
|
|
218
|
+
right: rect.left + width,
|
|
219
|
+
bottom: rect.top + height,
|
|
220
|
+
width,
|
|
221
|
+
height,
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
private _toLocalContextMenuPoint = (clientX: number, clientY: number) => {
|
|
226
|
+
const bounds = this._getContextMenuBounds();
|
|
227
|
+
return {
|
|
228
|
+
x: clientX - bounds.left,
|
|
229
|
+
y: clientY - bounds.top,
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
|
|
202
233
|
private _getContextMenuItems = (level: "root" | "submenu" = this._contextMenuActiveLevel) =>
|
|
203
234
|
Array.from(
|
|
204
235
|
this._getQueryRoot()?.querySelectorAll<HTMLButtonElement>(
|
|
@@ -241,8 +272,9 @@ export class FwPlayer extends LitElement {
|
|
|
241
272
|
|
|
242
273
|
private _clampContextMenuPosition = (x: number, y: number, width: number, height: number) => {
|
|
243
274
|
const viewportPadding = 8;
|
|
244
|
-
const
|
|
245
|
-
const
|
|
275
|
+
const bounds = this._getContextMenuBounds();
|
|
276
|
+
const maxX = Math.max(viewportPadding, bounds.width - width - viewportPadding);
|
|
277
|
+
const maxY = Math.max(viewportPadding, bounds.height - height - viewportPadding);
|
|
246
278
|
|
|
247
279
|
return {
|
|
248
280
|
x: Math.max(viewportPadding, Math.min(x, maxX)),
|
|
@@ -278,15 +310,17 @@ export class FwPlayer extends LitElement {
|
|
|
278
310
|
if (this._contextMenuOpenSubmenu !== "playback-mode") return;
|
|
279
311
|
const menu = this._getContextMenuElement();
|
|
280
312
|
if (!menu) return;
|
|
313
|
+
const bounds = this._getContextMenuBounds();
|
|
281
314
|
const rect = menu.getBoundingClientRect();
|
|
282
315
|
const estimatedSubmenuWidth = 190;
|
|
283
316
|
this._contextMenuSubmenuSide =
|
|
284
|
-
rect.right + estimatedSubmenuWidth >
|
|
317
|
+
rect.right + estimatedSubmenuWidth > bounds.right - 8 ? "left" : "right";
|
|
285
318
|
};
|
|
286
319
|
|
|
287
|
-
private _openContextMenu = (
|
|
288
|
-
const
|
|
289
|
-
|
|
320
|
+
private _openContextMenu = (clientX: number, clientY: number) => {
|
|
321
|
+
const local = this._toLocalContextMenuPoint(clientX, clientY);
|
|
322
|
+
const next = this._clampContextMenuPosition(local.x, local.y, 220, 200);
|
|
323
|
+
this._contextMenuSide = this._resolveContextMenuSide(local.x, local.y, next.x, next.y);
|
|
290
324
|
this._contextMenuX = next.x;
|
|
291
325
|
this._contextMenuY = next.y;
|
|
292
326
|
this._contextMenuMounted = true;
|
|
@@ -488,7 +522,11 @@ export class FwPlayer extends LitElement {
|
|
|
488
522
|
}
|
|
489
523
|
|
|
490
524
|
private get _useStockControls() {
|
|
491
|
-
return
|
|
525
|
+
return (
|
|
526
|
+
this.stockControls ||
|
|
527
|
+
this.nativeControls ||
|
|
528
|
+
this.pc.s.currentPlayerInfo?.shortname === "mist-legacy"
|
|
529
|
+
);
|
|
492
530
|
}
|
|
493
531
|
|
|
494
532
|
// ---- Public API methods ----
|
|
@@ -773,6 +811,7 @@ export class FwPlayer extends LitElement {
|
|
|
773
811
|
.pc=${this.pc}
|
|
774
812
|
.playbackMode=${this.playbackMode}
|
|
775
813
|
.isContentLive=${s.isEffectivelyLive}
|
|
814
|
+
.devMode=${this.devMode}
|
|
776
815
|
.isStatsOpen=${this._isStatsOpen}
|
|
777
816
|
@fw-stats-toggle=${() => {
|
|
778
817
|
this._isStatsOpen = !this._isStatsOpen;
|
|
@@ -814,7 +853,8 @@ export class FwPlayer extends LitElement {
|
|
|
814
853
|
role="menu"
|
|
815
854
|
aria-label="Player options"
|
|
816
855
|
tabindex="-1"
|
|
817
|
-
style="position:
|
|
856
|
+
style="position: absolute; left: ${this._contextMenuX}px; top: ${this
|
|
857
|
+
._contextMenuY}px;"
|
|
818
858
|
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
|
|
819
859
|
@keydown=${this._handleContextMenuKeyDown}
|
|
820
860
|
>
|
|
@@ -109,13 +109,53 @@ export class FwSettingsMenu extends LitElement {
|
|
|
109
109
|
this._close();
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
private _deriveFallbackQualities(): Array<{
|
|
113
|
+
id: string;
|
|
114
|
+
label: string;
|
|
115
|
+
bitrate?: number;
|
|
116
|
+
width?: number;
|
|
117
|
+
height?: number;
|
|
118
|
+
isAuto?: boolean;
|
|
119
|
+
active?: boolean;
|
|
120
|
+
}> {
|
|
121
|
+
const tracks = (
|
|
122
|
+
this.pc?.s.streamState?.streamInfo as
|
|
123
|
+
| {
|
|
124
|
+
meta?: {
|
|
125
|
+
tracks?: Record<
|
|
126
|
+
string,
|
|
127
|
+
{ type?: string; codec?: string; width?: number; height?: number; bps?: number }
|
|
128
|
+
>;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
| undefined
|
|
132
|
+
)?.meta?.tracks;
|
|
133
|
+
|
|
134
|
+
if (!tracks) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return Object.entries(tracks)
|
|
139
|
+
.filter(([, track]) => track?.type === "video")
|
|
140
|
+
.map(([id, track]) => ({
|
|
141
|
+
id,
|
|
142
|
+
label: track.height ? `${track.height}p` : (track.codec ?? id),
|
|
143
|
+
width: track.width,
|
|
144
|
+
height: track.height,
|
|
145
|
+
bitrate: track.bps,
|
|
146
|
+
}))
|
|
147
|
+
.sort((a, b) => (b.height ?? 0) - (a.height ?? 0));
|
|
148
|
+
}
|
|
149
|
+
|
|
112
150
|
protected render() {
|
|
113
151
|
if (!this.open) {
|
|
114
152
|
return nothing;
|
|
115
153
|
}
|
|
116
154
|
|
|
117
155
|
const state = this.pc.s;
|
|
118
|
-
const
|
|
156
|
+
const controllerQualities = state.qualities ?? [];
|
|
157
|
+
const qualities =
|
|
158
|
+
controllerQualities.length > 0 ? controllerQualities : this._deriveFallbackQualities();
|
|
119
159
|
const textTracks = state.textTracks ?? [];
|
|
120
160
|
const activeQuality =
|
|
121
161
|
this.qualityValue ?? qualities.find((quality) => quality.active)?.id ?? "auto";
|
|
@@ -216,7 +216,18 @@ export class PlayerControllerHost implements ReactiveController {
|
|
|
216
216
|
|
|
217
217
|
u.push(
|
|
218
218
|
controller.on("timeUpdate", ({ currentTime, duration }) => {
|
|
219
|
-
|
|
219
|
+
const next: Partial<PlayerControllerHostState> = {
|
|
220
|
+
currentTime,
|
|
221
|
+
duration,
|
|
222
|
+
shouldShowControls: controller.shouldShowControls(),
|
|
223
|
+
};
|
|
224
|
+
if (this.s.qualities.length === 0) {
|
|
225
|
+
const qualities = controller.getQualities();
|
|
226
|
+
if (qualities.length > 0) {
|
|
227
|
+
next.qualities = qualities;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
this.update(next);
|
|
220
231
|
this.dispatchEvent("fw-time-update", { currentTime, duration });
|
|
221
232
|
})
|
|
222
233
|
);
|
|
@@ -252,6 +263,7 @@ export class PlayerControllerHost implements ReactiveController {
|
|
|
252
263
|
textTracks: controller.getTextTracks(),
|
|
253
264
|
});
|
|
254
265
|
this.dispatchEvent("fw-ready", { videoElement });
|
|
266
|
+
this.syncState();
|
|
255
267
|
|
|
256
268
|
const handleVideoEvent = () => {
|
|
257
269
|
if (this.controller?.shouldSuppressVideoEvents?.()) return;
|
|
@@ -278,6 +290,7 @@ export class PlayerControllerHost implements ReactiveController {
|
|
|
278
290
|
qualities: controller.getQualities(),
|
|
279
291
|
textTracks: controller.getTextTracks(),
|
|
280
292
|
});
|
|
293
|
+
this.syncState();
|
|
281
294
|
})
|
|
282
295
|
);
|
|
283
296
|
|
|
@@ -458,15 +471,24 @@ export class PlayerControllerHost implements ReactiveController {
|
|
|
458
471
|
|
|
459
472
|
handleMouseEnter() {
|
|
460
473
|
this.controller?.handleMouseEnter();
|
|
474
|
+
this.update({ isHovering: true, shouldShowControls: true });
|
|
461
475
|
}
|
|
462
476
|
handleMouseLeave() {
|
|
463
477
|
this.controller?.handleMouseLeave();
|
|
478
|
+
this.update({
|
|
479
|
+
isHovering: false,
|
|
480
|
+
shouldShowControls: this.controller?.shouldShowControls() ?? false,
|
|
481
|
+
});
|
|
464
482
|
}
|
|
465
483
|
handleMouseMove() {
|
|
466
484
|
this.controller?.handleMouseMove();
|
|
485
|
+
if (this.controller) {
|
|
486
|
+
this.update({ shouldShowControls: this.controller.shouldShowControls() });
|
|
487
|
+
}
|
|
467
488
|
}
|
|
468
489
|
handleTouchStart() {
|
|
469
490
|
this.controller?.handleTouchStart();
|
|
491
|
+
this.update({ shouldShowControls: true });
|
|
470
492
|
}
|
|
471
493
|
|
|
472
494
|
async setDevModeOptions(options: {
|
|
@@ -442,11 +442,20 @@ export const sharedStyles = css`
|
|
|
442
442
|
.fw-context-menu-item {
|
|
443
443
|
position: relative;
|
|
444
444
|
display: flex;
|
|
445
|
+
width: 100%;
|
|
446
|
+
justify-content: flex-start;
|
|
447
|
+
gap: 0.5rem;
|
|
445
448
|
cursor: pointer;
|
|
446
449
|
user-select: none;
|
|
447
450
|
align-items: center;
|
|
448
451
|
padding: 0.5rem 0.75rem;
|
|
449
452
|
font-size: 0.875rem;
|
|
453
|
+
text-align: left;
|
|
454
|
+
font: inherit;
|
|
455
|
+
border: none;
|
|
456
|
+
background: transparent;
|
|
457
|
+
appearance: none;
|
|
458
|
+
-webkit-appearance: none;
|
|
450
459
|
outline: none;
|
|
451
460
|
color: hsl(var(--tn-fg));
|
|
452
461
|
transition:
|
|
@@ -485,11 +494,20 @@ export const sharedStyles = css`
|
|
|
485
494
|
.fw-context-menu-checkbox {
|
|
486
495
|
position: relative;
|
|
487
496
|
display: flex;
|
|
497
|
+
width: 100%;
|
|
498
|
+
justify-content: flex-start;
|
|
499
|
+
gap: 0.5rem;
|
|
488
500
|
cursor: pointer;
|
|
489
501
|
user-select: none;
|
|
490
502
|
align-items: center;
|
|
491
503
|
padding: 0.5rem 0.5rem 0.5rem 2rem;
|
|
492
504
|
font-size: 0.875rem;
|
|
505
|
+
text-align: left;
|
|
506
|
+
font: inherit;
|
|
507
|
+
border: none;
|
|
508
|
+
background: transparent;
|
|
509
|
+
appearance: none;
|
|
510
|
+
-webkit-appearance: none;
|
|
493
511
|
outline: none;
|
|
494
512
|
color: hsl(var(--tn-fg));
|
|
495
513
|
transition:
|