@livepeer-frameworks/player-wc 0.2.7 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livepeer-frameworks/player-wc",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
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/esm/index.js",
@@ -24,19 +24,9 @@
24
24
  "default": "./dist/fw-player.iife.js"
25
25
  }
26
26
  },
27
- "scripts": {
28
- "build": "rm -rf dist && pnpm run build:css && pnpm run build:types && NODE_ENV=production rollup -c",
29
- "build:watch": "NODE_ENV=development rollup -c --watch",
30
- "build:css": "node scripts/generate-shared-styles.js",
31
- "build:types": "tsc --noEmit false --emitDeclarationOnly --outDir dist/types",
32
- "type-check": "tsc --noEmit",
33
- "test": "vitest run",
34
- "test:watch": "vitest",
35
- "test:coverage": "vitest run --coverage"
36
- },
37
27
  "dependencies": {
38
- "@livepeer-frameworks/player-core": "workspace:*",
39
- "lit": "^3.3.2"
28
+ "lit": "^3.3.2",
29
+ "@livepeer-frameworks/player-core": "0.2.3"
40
30
  },
41
31
  "devDependencies": {
42
32
  "@rollup/plugin-commonjs": "^29.0.0",
@@ -58,5 +48,15 @@
58
48
  "custom-elements"
59
49
  ],
60
50
  "author": "Livepeer FrameWorks",
61
- "license": "Unlicense"
62
- }
51
+ "license": "Unlicense",
52
+ "scripts": {
53
+ "build": "rm -rf dist && pnpm run build:css && pnpm run build:types && NODE_ENV=production rollup -c",
54
+ "build:watch": "NODE_ENV=development rollup -c --watch",
55
+ "build:css": "node scripts/generate-shared-styles.js",
56
+ "build:types": "tsc --noEmit false --emitDeclarationOnly --outDir dist/types",
57
+ "type-check": "tsc --noEmit",
58
+ "test": "vitest run",
59
+ "test:watch": "vitest",
60
+ "test:coverage": "vitest run --coverage"
61
+ }
62
+ }
@@ -31,7 +31,7 @@ export class FwSkipButton extends LitElement {
31
31
  }
32
32
 
33
33
  private handleClick() {
34
- const delta = this.direction === "back" ? -this.seconds : this.seconds;
34
+ const delta = (this.direction === "back" ? -this.seconds : this.seconds) * 1000;
35
35
  this._player?.pc?.seekBy(delta);
36
36
  }
37
37
 
@@ -717,7 +717,9 @@ export class FwDevModePanel extends LitElement {
717
717
  : html`
718
718
  ${allCombinations.map((combo, index) => {
719
719
  const isCodecIncompatible = combo.codecIncompatible === true;
720
- if (!combo.compatible && !isCodecIncompatible && !this._showDisabledPlayers) {
720
+ const isPartial = ((combo as any).missingTracks?.length ?? 0) > 0;
721
+ const isWarn = isCodecIncompatible || isPartial;
722
+ if (!combo.compatible && !isWarn && !this._showDisabledPlayers) {
721
723
  return nothing;
722
724
  }
723
725
 
@@ -726,9 +728,9 @@ export class FwDevModePanel extends LitElement {
726
728
  SOURCE_TYPE_LABELS[combo.sourceType] || combo.sourceType.split("/").pop();
727
729
 
728
730
  const scoreClass =
729
- !combo.compatible && !isCodecIncompatible
731
+ !combo.compatible && !isWarn
730
732
  ? "fw-dev-combo-score--disabled"
731
- : isCodecIncompatible
733
+ : isWarn
732
734
  ? "fw-dev-combo-score--low"
733
735
  : combo.score >= 2
734
736
  ? "fw-dev-combo-score--high"
@@ -738,16 +740,16 @@ export class FwDevModePanel extends LitElement {
738
740
 
739
741
  const rankClass = isActive
740
742
  ? "fw-dev-combo-rank--active"
741
- : !combo.compatible && !isCodecIncompatible
743
+ : !combo.compatible && !isWarn
742
744
  ? "fw-dev-combo-rank--disabled"
743
- : isCodecIncompatible
745
+ : isWarn
744
746
  ? "fw-dev-combo-rank--warn"
745
747
  : "";
746
748
 
747
749
  const typeClass =
748
- !combo.compatible && !isCodecIncompatible
750
+ !combo.compatible && !isWarn
749
751
  ? "fw-dev-combo-type--disabled"
750
- : isCodecIncompatible
752
+ : isWarn
751
753
  ? "fw-dev-combo-type--warn"
752
754
  : "";
753
755
 
@@ -766,8 +768,8 @@ export class FwDevModePanel extends LitElement {
766
768
  class=${classMap({
767
769
  "fw-dev-combo-btn": true,
768
770
  "fw-dev-combo-btn--active": isActive,
769
- "fw-dev-combo-btn--disabled": !combo.compatible && !isCodecIncompatible,
770
- "fw-dev-combo-btn--codec-warn": isCodecIncompatible,
771
+ "fw-dev-combo-btn--disabled": !combo.compatible && !isWarn,
772
+ "fw-dev-combo-btn--codec-warn": isWarn,
771
773
  })}
772
774
  @click=${() => this._handleSelectCombo(index)}
773
775
  >
@@ -776,9 +778,9 @@ export class FwDevModePanel extends LitElement {
776
778
  "fw-dev-combo-rank": true,
777
779
  [rankClass]: rankClass.length > 0,
778
780
  })}
779
- >${combo.compatible
781
+ >${combo.compatible && !isPartial
780
782
  ? index + 1
781
- : isCodecIncompatible
783
+ : isWarn
782
784
  ? "\u26A0"
783
785
  : "\u2014"}</span
784
786
  >
@@ -822,6 +824,14 @@ export class FwDevModePanel extends LitElement {
822
824
  : nothing}
823
825
  </div>
824
826
 
827
+ ${combo.note
828
+ ? html`<div class="fw-dev-tooltip-note">${combo.note}</div>`
829
+ : nothing}
830
+ ${isPartial
831
+ ? html`<div class="fw-dev-tooltip-note">
832
+ No compatible ${(combo as any).missingTracks.join(", ")} codec
833
+ </div>`
834
+ : nothing}
825
835
  ${combo.compatible && combo.scoreBreakdown
826
836
  ? html`
827
837
  <div class="fw-dev-tooltip-score">
@@ -169,13 +169,16 @@ export class FwPlayerControls extends LitElement {
169
169
  };
170
170
 
171
171
  private _deriveBufferWindowMs(
172
- tracks?: Record<string, { firstms?: number; lastms?: number }>
172
+ tracks?: Record<string, { type?: string; firstms?: number; lastms?: number }>
173
173
  ): number | undefined {
174
174
  if (!tracks) {
175
175
  return undefined;
176
176
  }
177
177
 
178
- const trackValues = Object.values(tracks);
178
+ // Filter out meta tracks and tracks with lastms <= 0 (same as PlayerController)
179
+ const trackValues = Object.values(tracks).filter(
180
+ (t) => t.type !== "meta" && (t.lastms === undefined || t.lastms > 0)
181
+ );
179
182
  if (trackValues.length === 0) {
180
183
  return undefined;
181
184
  }
@@ -191,8 +194,8 @@ export class FwPlayerControls extends LitElement {
191
194
  return undefined;
192
195
  }
193
196
 
194
- const firstms = Math.max(...firstmsValues);
195
- const lastms = Math.min(...lastmsValues);
197
+ const firstms = Math.min(...firstmsValues);
198
+ const lastms = Math.max(...lastmsValues);
196
199
  const window = lastms - firstms;
197
200
 
198
201
  if (!Number.isFinite(window) || window <= 0) {
@@ -213,7 +216,7 @@ export class FwPlayerControls extends LitElement {
213
216
  mistStreamInfo?.meta?.buffer_window ??
214
217
  this._deriveBufferWindowMs(
215
218
  mistStreamInfo?.meta?.tracks as
216
- | Record<string, { firstms?: number; lastms?: number }>
219
+ | Record<string, { type?: string; firstms?: number; lastms?: number }>
217
220
  | undefined
218
221
  );
219
222
 
@@ -386,7 +389,7 @@ export class FwPlayerControls extends LitElement {
386
389
  class="fw-btn-flush hidden sm:flex"
387
390
  ?disabled=${disabled}
388
391
  aria-label=${this.pc.t("skipBackward")}
389
- @click=${() => this.pc.seekBy(-10)}
392
+ @click=${() => this.pc.seekBy(-10000)}
390
393
  >
391
394
  ${skipBackIcon(16)}
392
395
  </button>
@@ -395,7 +398,7 @@ export class FwPlayerControls extends LitElement {
395
398
  class="fw-btn-flush hidden sm:flex"
396
399
  ?disabled=${disabled}
397
400
  aria-label=${this.pc.t("skipForward")}
398
- @click=${() => this.pc.seekBy(10)}
401
+ @click=${() => this.pc.seekBy(10000)}
399
402
  >
400
403
  ${skipForwardIcon(16)}
401
404
  </button>
@@ -33,7 +33,7 @@ export class FwPlayer extends LitElement {
33
33
  @property({ attribute: "mist-url" }) mistUrl?: string;
34
34
  @property({ attribute: "auth-token" }) authToken?: string;
35
35
  @property({ type: Boolean }) autoplay = true;
36
- @property({ type: Boolean }) muted = true;
36
+ @property({ type: Boolean }) muted = false;
37
37
  // React/Svelte use `stockControls` for native controls. Keep `controls` as a
38
38
  // compatibility no-op so WC parity does not hide custom controls/seekbar.
39
39
  @property({ type: Boolean }) controls = false;
@@ -765,7 +765,7 @@ export class FwPlayer extends LitElement {
765
765
  >
766
766
  ${this.pc.t("retry")}
767
767
  </button>
768
- ${this.pc.canAttemptFallback()
768
+ ${this.devMode && this.pc.canAttemptFallback()
769
769
  ? html`
770
770
  <button
771
771
  type="button"
@@ -780,17 +780,21 @@ export class FwPlayer extends LitElement {
780
780
  </button>
781
781
  `
782
782
  : nothing}
783
- <button
784
- type="button"
785
- class="fw-error-btn fw-error-btn--secondary"
786
- aria-label=${this.pc.t("reloadPlayer")}
787
- @click=${() => {
788
- this.pc.clearError();
789
- this.pc.reload();
790
- }}
791
- >
792
- ${this.pc.t("reloadPlayer")}
793
- </button>
783
+ ${this.devMode
784
+ ? html`
785
+ <button
786
+ type="button"
787
+ class="fw-error-btn fw-error-btn--secondary"
788
+ aria-label=${this.pc.t("reloadPlayer")}
789
+ @click=${() => {
790
+ this.pc.clearError();
791
+ this.pc.reload();
792
+ }}
793
+ >
794
+ ${this.pc.t("reloadPlayer")}
795
+ </button>
796
+ `
797
+ : nothing}
794
798
  </div>
795
799
  </div>
796
800
  </div>
@@ -108,8 +108,9 @@ export class FwSeekBar extends LitElement {
108
108
 
109
109
  const segments: BufferedSegment[] = [];
110
110
  for (let i = 0; i < buffered.length; i += 1) {
111
- const start = buffered.start(i);
112
- const end = buffered.end(i);
111
+ // buffered TimeRanges are in seconds (browser API), convert to ms
112
+ const start = buffered.start(i) * 1000;
113
+ const end = buffered.end(i) * 1000;
113
114
  const relativeStart = start - rangeStart;
114
115
  const relativeEnd = end - rangeStart;
115
116
 
@@ -122,12 +123,12 @@ export class FwSeekBar extends LitElement {
122
123
  return segments;
123
124
  }
124
125
 
125
- private _formatTime(seconds: number): string {
126
- if (!Number.isFinite(seconds) || seconds < 0) {
126
+ private _formatTime(ms: number): string {
127
+ if (!Number.isFinite(ms) || ms < 0) {
127
128
  return "0:00";
128
129
  }
129
130
 
130
- const total = Math.floor(seconds);
131
+ const total = Math.floor(ms / 1000);
131
132
  const hours = Math.floor(total / 3600);
132
133
  const minutes = Math.floor((total % 3600) / 60);
133
134
  const secs = total % 60;
@@ -139,13 +140,13 @@ export class FwSeekBar extends LitElement {
139
140
  return `${minutes}:${String(secs).padStart(2, "0")}`;
140
141
  }
141
142
 
142
- private _formatLiveTime(seconds: number, edge: number): string {
143
- const behindSeconds = edge - seconds;
144
- if (behindSeconds < 1) {
143
+ private _formatLiveTime(ms: number, edgeMs: number): string {
144
+ const behindMs = edgeMs - ms;
145
+ if (behindMs < 1000) {
145
146
  return "LIVE";
146
147
  }
147
148
 
148
- const total = Math.floor(behindSeconds);
149
+ const total = Math.floor(behindMs / 1000);
149
150
  const hours = Math.floor(total / 3600);
150
151
  const minutes = Math.floor((total % 3600) / 60);
151
152
  const secs = total % 60;
@@ -218,7 +219,7 @@ export class FwSeekBar extends LitElement {
218
219
  return;
219
220
  }
220
221
 
221
- const step = event.shiftKey ? 10 : 5;
222
+ const step = event.shiftKey ? 10000 : 5000;
222
223
  const rawRangeEnd = this.isLive ? this._effectiveLiveEdge : this.duration;
223
224
  const rangeEnd = Number.isFinite(rawRangeEnd) ? rawRangeEnd : this.currentTime + step;
224
225
  const rangeStart = this.isLive ? this.seekableStart : 0;
@@ -380,6 +381,13 @@ export class FwSeekBar extends LitElement {
380
381
  `
381
382
  )}
382
383
  <div class="fw-seek-progress" style=${styleMap({ width: `${progressPercent}%` })}></div>
384
+ ${this._hovering && !this._dragging
385
+ ? html`<div
386
+ class="fw-seek-hover-line"
387
+ style=${styleMap({ left: `${this._hoverPosition}%` })}
388
+ ></div>`
389
+ : nothing}
390
+ ${this.isLive ? html`<div class="fw-seek-live-edge"></div>` : nothing}
383
391
  </div>
384
392
 
385
393
  <div
@@ -179,7 +179,7 @@ export class FwSubtitleRenderer extends LitElement {
179
179
  return;
180
180
  }
181
181
 
182
- const currentTimeMs = this.currentTime * 1000;
182
+ const currentTimeMs = this.currentTime;
183
183
  const activeCue = this._getAllCues().find(
184
184
  (cue) => currentTimeMs >= cue.startTime && currentTimeMs < cue.endTime
185
185
  );
@@ -195,7 +195,7 @@ export class FwSubtitleRenderer extends LitElement {
195
195
  return;
196
196
  }
197
197
 
198
- const currentTimeMs = this.currentTime * 1000;
198
+ const currentTimeMs = this.currentTime;
199
199
  const filtered = this._liveCues.filter((cue) => {
200
200
  const endTime = cue.endTime === Infinity ? cue.startTime + 10000 : cue.endTime;
201
201
  return endTime >= currentTimeMs - 30000;
@@ -73,7 +73,7 @@ const initialState: PlayerControllerHostState = {
73
73
  isPlaying: false,
74
74
  isPaused: true,
75
75
  isBuffering: false,
76
- isMuted: true,
76
+ isMuted: false,
77
77
  volume: 1,
78
78
  error: null,
79
79
  errorDetails: null,
@@ -209,7 +209,10 @@ export class PlayerControllerHost implements ReactiveController {
209
209
 
210
210
  u.push(
211
211
  controller.on("stateChange", ({ state }) => {
212
- this.update({ state });
212
+ this.update({
213
+ state,
214
+ shouldShowIdleScreen: controller.shouldShowIdleScreen(),
215
+ });
213
216
  this.dispatchEvent("fw-state-change", { state });
214
217
  })
215
218
  );
@@ -249,6 +252,7 @@ export class PlayerControllerHost implements ReactiveController {
249
252
  this.update({
250
253
  error,
251
254
  isPassiveError: controller.isPassiveError(),
255
+ shouldShowIdleScreen: controller.shouldShowIdleScreen(),
252
256
  });
253
257
  this.dispatchEvent("fw-error", { error });
254
258
  })
@@ -803,15 +803,16 @@ export const sharedStyles = css`
803
803
  position: absolute;
804
804
  left: 0;
805
805
  right: 0;
806
- height: 4px;
806
+ height: 3px;
807
807
  border-radius: 9999px;
808
808
  background: hsl(var(--fw-text-faint) / 0.4);
809
809
  transition: height 0.15s ease;
810
810
  }
811
811
 
812
812
  .fw-seek-wrapper:hover .fw-seek-track,
813
+ .fw-seek-root:hover .fw-seek-track,
813
814
  .fw-seek-track--active {
814
- height: 6px;
815
+ height: 8px;
815
816
  }
816
817
 
817
818
  .fw-seek-buffered {
@@ -819,7 +820,9 @@ export const sharedStyles = css`
819
820
  height: 100%;
820
821
  border-radius: 9999px;
821
822
  background: hsl(var(--fw-text) / 0.3);
822
- transition: all 0.2s ease;
823
+ transition:
824
+ left 0.2s ease,
825
+ width 0.2s ease;
823
826
  }
824
827
 
825
828
  .fw-seek-progress {
@@ -827,7 +830,29 @@ export const sharedStyles = css`
827
830
  height: 100%;
828
831
  border-radius: 9999px;
829
832
  background: hsl(var(--fw-accent));
830
- transition: width 0.1s linear;
833
+ will-change: width;
834
+ }
835
+
836
+ .fw-seek-hover-line {
837
+ position: absolute;
838
+ top: 0;
839
+ bottom: 0;
840
+ width: 2px;
841
+ background: hsl(var(--fw-text) / 0.5);
842
+ transform: translateX(-50%);
843
+ pointer-events: none;
844
+ border-radius: 1px;
845
+ }
846
+
847
+ .fw-seek-live-edge {
848
+ position: absolute;
849
+ top: -2px;
850
+ bottom: -2px;
851
+ right: 0;
852
+ width: 3px;
853
+ background: hsl(var(--fw-accent));
854
+ border-radius: 1px;
855
+ opacity: 0.7;
831
856
  }
832
857
 
833
858
  .fw-seek-thumb {
@@ -847,6 +872,7 @@ export const sharedStyles = css`
847
872
  }
848
873
 
849
874
  .fw-seek-wrapper:hover .fw-seek-thumb,
875
+ .fw-seek-root:hover .fw-seek-thumb,
850
876
  .fw-seek-thumb--active {
851
877
  opacity: 1;
852
878
  transform: translate(-50%, -50%) scale(1);
@@ -1863,6 +1889,16 @@ export const sharedStyles = css`
1863
1889
  margin-top: 0.25rem;
1864
1890
  }
1865
1891
 
1892
+ .fw-dev-tooltip-note {
1893
+ color: hsl(var(--fw-text-muted));
1894
+ font-style: italic;
1895
+ font-size: 0.75rem;
1896
+ margin: 0.25rem 0;
1897
+ line-height: 1.3;
1898
+ white-space: normal;
1899
+ overflow-wrap: break-word;
1900
+ }
1901
+
1866
1902
  .fw-dev-tooltip-value {
1867
1903
  color: hsl(var(--fw-text));
1868
1904
  }