@livepeer-frameworks/player-wc 0.1.7 → 0.1.9

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.
@@ -3,7 +3,7 @@
3
3
  * Parity port of React/Svelte control behavior.
4
4
  */
5
5
  import { LitElement, html, css, nothing, type PropertyValues } from "lit";
6
- import { customElement, property, state } from "lit/decorators.js";
6
+ import { customElement, property, query, state } from "lit/decorators.js";
7
7
  import { classMap } from "lit/directives/class-map.js";
8
8
  import { sharedStyles } from "../styles/shared-styles.js";
9
9
  import { utilityStyles } from "../styles/utility-styles.js";
@@ -55,6 +55,7 @@ export class FwPlayerControls extends LitElement {
55
55
  @state() private _settingsOpen = false;
56
56
  @state() private _isNearLiveState = true;
57
57
  @state() private _buffered: TimeRanges | null = null;
58
+ @query(".fw-settings-anchor") private _settingsAnchorEl!: HTMLElement | null;
58
59
 
59
60
  private _boundVideo: HTMLVideoElement | null = null;
60
61
  private _onBufferedUpdate: (() => void) | null = null;
@@ -150,15 +151,15 @@ export class FwPlayerControls extends LitElement {
150
151
 
151
152
  private _onWindowClick = (event: MouseEvent): void => {
152
153
  const path = event.composedPath();
153
- const insideControls = path.some((entry) => {
154
- if (!(entry instanceof HTMLElement)) {
155
- return false;
156
- }
157
- return (
158
- entry.classList.contains("fw-settings-anchor") ||
159
- entry.classList.contains("fw-settings-menu")
160
- );
161
- });
154
+ const anchor = this._settingsAnchorEl;
155
+ const insideControls =
156
+ anchor !== null &&
157
+ path.some((entry) => {
158
+ if (!(entry instanceof Node)) {
159
+ return false;
160
+ }
161
+ return anchor.contains(entry);
162
+ });
162
163
 
163
164
  if (!insideControls) {
164
165
  this._settingsOpen = false;
@@ -331,6 +332,7 @@ export class FwPlayerControls extends LitElement {
331
332
  seekableStart: context.seekableStart,
332
333
  unixoffset: context.mistStreamInfo?.unixoffset,
333
334
  });
335
+ const showTimeDisplay = !(context.isLive && timeDisplay === "LIVE");
334
336
 
335
337
  const liveButtonDisabled = !context.hasDvrWindow || this._isNearLiveState;
336
338
 
@@ -404,10 +406,13 @@ export class FwPlayerControls extends LitElement {
404
406
  <fw-volume-control .pc=${this.pc}></fw-volume-control>
405
407
  </div>
406
408
 
407
- <div class="fw-control-group">
408
- <span class="fw-time-display">${timeDisplay}</span>
409
- </div>
410
-
409
+ ${showTimeDisplay
410
+ ? html`
411
+ <div class="fw-control-group">
412
+ <span class="fw-time-display">${timeDisplay}</span>
413
+ </div>
414
+ `
415
+ : nothing}
411
416
  ${context.isLive
412
417
  ? html`
413
418
  <div class="fw-control-group">
@@ -472,7 +477,8 @@ export class FwPlayerControls extends LitElement {
472
477
  aria-label="Settings"
473
478
  title="Settings"
474
479
  ?disabled=${disabled}
475
- @click=${() => {
480
+ @click=${(event: MouseEvent) => {
481
+ event.stopPropagation();
476
482
  if (disabled) {
477
483
  return;
478
484
  }
@@ -489,6 +495,7 @@ export class FwPlayerControls extends LitElement {
489
495
  .open=${this._settingsOpen}
490
496
  .playbackMode=${this.playbackMode}
491
497
  .isContentLive=${this.isContentLive}
498
+ @click=${(event: MouseEvent) => event.stopPropagation()}
492
499
  @fw-close=${() => {
493
500
  this._settingsOpen = false;
494
501
  }}
@@ -15,9 +15,11 @@ export class FwVolumeControl extends LitElement {
15
15
 
16
16
  @state() private _hovered = false;
17
17
  @state() private _focused = false;
18
+ @state() private _dragging = false;
18
19
  @state() private _hasAudio = true;
19
20
 
20
21
  private _activePointerId: number | null = null;
22
+ private _activeSliderTarget: HTMLElement | null = null;
21
23
 
22
24
  static styles = [
23
25
  sharedStyles,
@@ -34,6 +36,8 @@ export class FwVolumeControl extends LitElement {
34
36
  background: rgb(255 255 255 / 0.2);
35
37
  border-radius: 9999px;
36
38
  cursor: pointer;
39
+ touch-action: none;
40
+ user-select: none;
37
41
  }
38
42
 
39
43
  .slider-fill {
@@ -59,7 +63,12 @@ export class FwVolumeControl extends LitElement {
59
63
  ];
60
64
 
61
65
  private get _expanded(): boolean {
62
- return this._hovered || this._focused;
66
+ return this._hovered || this._focused || this._dragging;
67
+ }
68
+
69
+ disconnectedCallback(): void {
70
+ super.disconnectedCallback();
71
+ this._endDragInteraction();
63
72
  }
64
73
 
65
74
  protected updated(): void {
@@ -106,6 +115,89 @@ export class FwVolumeControl extends LitElement {
106
115
  }
107
116
  }
108
117
 
118
+ private _beginDragInteraction(target: HTMLElement, pointerId: number): void {
119
+ this._activePointerId = pointerId;
120
+ this._activeSliderTarget = target;
121
+ this._dragging = true;
122
+ this._hovered = true;
123
+ this._focused = true;
124
+
125
+ try {
126
+ target.setPointerCapture(pointerId);
127
+ } catch {
128
+ // Non-fatal: we still listen on window as a fallback.
129
+ }
130
+
131
+ window.addEventListener("pointermove", this._onGlobalPointerMove);
132
+ window.addEventListener("pointerup", this._onGlobalPointerUp);
133
+ window.addEventListener("pointercancel", this._onGlobalPointerUp);
134
+ }
135
+
136
+ private _endDragInteraction(): void {
137
+ const pointerId = this._activePointerId;
138
+ const target = this._activeSliderTarget;
139
+ if (pointerId != null && target) {
140
+ try {
141
+ if (target.hasPointerCapture(pointerId)) {
142
+ target.releasePointerCapture(pointerId);
143
+ }
144
+ } catch {
145
+ // Ignore pointer-capture release errors.
146
+ }
147
+ }
148
+
149
+ this._activePointerId = null;
150
+ this._activeSliderTarget = null;
151
+ this._dragging = false;
152
+ window.removeEventListener("pointermove", this._onGlobalPointerMove);
153
+ window.removeEventListener("pointerup", this._onGlobalPointerUp);
154
+ window.removeEventListener("pointercancel", this._onGlobalPointerUp);
155
+ }
156
+
157
+ private _onGlobalPointerMove = (event: PointerEvent): void => {
158
+ if (!this._dragging || this._activePointerId !== event.pointerId) {
159
+ return;
160
+ }
161
+ const target = this._activeSliderTarget;
162
+ if (!target) {
163
+ return;
164
+ }
165
+ this._setVolumeFromClientX(event.clientX, target);
166
+ };
167
+
168
+ private _onGlobalPointerUp = (event: PointerEvent): void => {
169
+ if (!this._dragging || this._activePointerId !== event.pointerId) {
170
+ return;
171
+ }
172
+ this._endDragInteraction();
173
+ };
174
+
175
+ private _handleMouseEnter = (): void => {
176
+ this._hovered = true;
177
+ };
178
+
179
+ private _handleMouseLeave = (): void => {
180
+ if (this._dragging) {
181
+ return;
182
+ }
183
+ this._hovered = false;
184
+ this._focused = false;
185
+ };
186
+
187
+ private _handleFocusIn = (): void => {
188
+ this._focused = true;
189
+ };
190
+
191
+ private _handleFocusOut = (event: FocusEvent): void => {
192
+ if (this._dragging) {
193
+ return;
194
+ }
195
+ const related = event.relatedTarget as Node | null;
196
+ if (!related || !this.renderRoot.contains(related)) {
197
+ this._focused = false;
198
+ }
199
+ };
200
+
109
201
  private _onSliderPointerDown = (event: PointerEvent) => {
110
202
  if (!this._hasAudio) {
111
203
  return;
@@ -113,30 +205,8 @@ export class FwVolumeControl extends LitElement {
113
205
 
114
206
  event.preventDefault();
115
207
  const target = event.currentTarget as HTMLElement;
116
- this._activePointerId = event.pointerId;
208
+ this._beginDragInteraction(target, event.pointerId);
117
209
  this._setVolumeFromClientX(event.clientX, target);
118
-
119
- const onMove = (moveEvent: PointerEvent) => {
120
- if (this._activePointerId !== moveEvent.pointerId) {
121
- return;
122
- }
123
- this._setVolumeFromClientX(moveEvent.clientX, target);
124
- };
125
-
126
- const onUp = (upEvent: PointerEvent) => {
127
- if (this._activePointerId !== upEvent.pointerId) {
128
- return;
129
- }
130
-
131
- this._activePointerId = null;
132
- window.removeEventListener("pointermove", onMove);
133
- window.removeEventListener("pointerup", onUp);
134
- window.removeEventListener("pointercancel", onUp);
135
- };
136
-
137
- window.addEventListener("pointermove", onMove);
138
- window.addEventListener("pointerup", onUp);
139
- window.addEventListener("pointercancel", onUp);
140
210
  };
141
211
 
142
212
  private _onWheel = (event: WheelEvent) => {
@@ -170,22 +240,10 @@ export class FwVolumeControl extends LitElement {
170
240
  })}
171
241
  role="group"
172
242
  aria-label="Volume controls"
173
- @mouseenter=${() => {
174
- this._hovered = true;
175
- }}
176
- @mouseleave=${() => {
177
- this._hovered = false;
178
- this._focused = false;
179
- }}
180
- @focusin=${() => {
181
- this._focused = true;
182
- }}
183
- @focusout=${(event: FocusEvent) => {
184
- const related = event.relatedTarget as Node | null;
185
- if (!related || !this.renderRoot.contains(related)) {
186
- this._focused = false;
187
- }
188
- }}
243
+ @mouseenter=${this._handleMouseEnter}
244
+ @mouseleave=${this._handleMouseLeave}
245
+ @focusin=${this._handleFocusIn}
246
+ @focusout=${this._handleFocusOut}
189
247
  @click=${(event: MouseEvent) => {
190
248
  if (this._hasAudio && event.target === event.currentTarget) {
191
249
  this.pc.toggleMute();