avbridge 2.5.0 → 2.7.0

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/player.cjs CHANGED
@@ -2390,6 +2390,35 @@ async function loadBridge() {
2390
2390
  }
2391
2391
  }
2392
2392
 
2393
+ // src/util/time-ranges.ts
2394
+ function makeTimeRanges(ranges) {
2395
+ const frozen = ranges.slice();
2396
+ const impl = {
2397
+ get length() {
2398
+ return frozen.length;
2399
+ },
2400
+ start(index) {
2401
+ if (index < 0 || index >= frozen.length) {
2402
+ throw new DOMException(
2403
+ `TimeRanges.start: index ${index} out of range (length=${frozen.length})`,
2404
+ "IndexSizeError"
2405
+ );
2406
+ }
2407
+ return frozen[index][0];
2408
+ },
2409
+ end(index) {
2410
+ if (index < 0 || index >= frozen.length) {
2411
+ throw new DOMException(
2412
+ `TimeRanges.end: index ${index} out of range (length=${frozen.length})`,
2413
+ "IndexSizeError"
2414
+ );
2415
+ }
2416
+ return frozen[index][1];
2417
+ }
2418
+ };
2419
+ return impl;
2420
+ }
2421
+
2393
2422
  // src/strategies/hybrid/index.ts
2394
2423
  var READY_AUDIO_BUFFER_SECONDS = 0.3;
2395
2424
  var READY_TIMEOUT_SECONDS = 10;
@@ -2447,6 +2476,18 @@ async function createHybridSession(ctx, target, transport) {
2447
2476
  get: () => ctx.duration ?? NaN
2448
2477
  });
2449
2478
  }
2479
+ Object.defineProperty(target, "readyState", {
2480
+ configurable: true,
2481
+ get: () => {
2482
+ if (!renderer.hasFrames()) return 0;
2483
+ if (!audio.isPlaying() && audio.bufferAhead() <= 0 && !audio.isNoAudio()) return 1;
2484
+ return 2;
2485
+ }
2486
+ });
2487
+ Object.defineProperty(target, "seekable", {
2488
+ configurable: true,
2489
+ get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
2490
+ });
2450
2491
  async function waitForBuffer() {
2451
2492
  const start = performance.now();
2452
2493
  while (true) {
@@ -2528,6 +2569,8 @@ async function createHybridSession(ctx, target, transport) {
2528
2569
  delete target.paused;
2529
2570
  delete target.volume;
2530
2571
  delete target.muted;
2572
+ delete target.readyState;
2573
+ delete target.seekable;
2531
2574
  } catch {
2532
2575
  }
2533
2576
  },
@@ -3048,6 +3091,18 @@ async function createFallbackSession(ctx, target, transport) {
3048
3091
  get: () => ctx.duration ?? NaN
3049
3092
  });
3050
3093
  }
3094
+ Object.defineProperty(target, "readyState", {
3095
+ configurable: true,
3096
+ get: () => {
3097
+ if (!renderer.hasFrames()) return 0;
3098
+ if (!audio.isPlaying() && audio.bufferAhead() <= 0 && !audio.isNoAudio()) return 1;
3099
+ return 2;
3100
+ }
3101
+ });
3102
+ Object.defineProperty(target, "seekable", {
3103
+ configurable: true,
3104
+ get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
3105
+ });
3051
3106
  async function waitForBuffer() {
3052
3107
  const start = performance.now();
3053
3108
  let firstFrameAtMs = 0;
@@ -3150,6 +3205,8 @@ async function createFallbackSession(ctx, target, transport) {
3150
3205
  delete target.paused;
3151
3206
  delete target.volume;
3152
3207
  delete target.muted;
3208
+ delete target.readyState;
3209
+ delete target.seekable;
3153
3210
  } catch {
3154
3211
  }
3155
3212
  },
@@ -3773,7 +3830,13 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
3773
3830
  _strategy = null;
3774
3831
  _strategyClass = null;
3775
3832
  _audioTracks = [];
3833
+ /** Subtitle tracks reported by the active UnifiedPlayer (options.subtitles
3834
+ * + embedded container tracks + programmatic addSubtitle calls). */
3776
3835
  _subtitleTracks = [];
3836
+ /** Subtitle tracks derived from light-DOM `<track>` children. Maintained
3837
+ * by _syncTextTracks on every mutation. Merged into the public
3838
+ * `subtitleTracks` getter so the player's settings menu sees them. */
3839
+ _htmlTrackInfo = [];
3777
3840
  /**
3778
3841
  * External subtitle list forwarded to `createPlayer()` on the next
3779
3842
  * bootstrap. Setting this after bootstrap queues it for the next
@@ -3893,12 +3956,28 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
3893
3956
  _syncTextTracks() {
3894
3957
  const existing = this._videoEl.querySelectorAll("track");
3895
3958
  for (const t of Array.from(existing)) t.remove();
3959
+ this._htmlTrackInfo = [];
3960
+ let htmlIdx = 0;
3896
3961
  for (const child of Array.from(this.children)) {
3897
3962
  if (child.tagName === "TRACK") {
3898
- const clone = child.cloneNode(true);
3963
+ const track = child;
3964
+ const clone = track.cloneNode(true);
3899
3965
  this._videoEl.appendChild(clone);
3966
+ const src = track.getAttribute("src") ?? void 0;
3967
+ const format = src?.toLowerCase().endsWith(".srt") ? "srt" : "vtt";
3968
+ this._htmlTrackInfo.push({
3969
+ id: 1e4 + htmlIdx,
3970
+ format,
3971
+ language: track.srclang || track.getAttribute("label") || void 0,
3972
+ sidecarUrl: src
3973
+ });
3974
+ htmlIdx++;
3900
3975
  }
3901
3976
  }
3977
+ this._dispatch("trackschange", {
3978
+ audioTracks: this._audioTracks,
3979
+ subtitleTracks: this.subtitleTracks
3980
+ });
3902
3981
  }
3903
3982
  /** Internal src setter — separate from the property setter so the
3904
3983
  * attributeChangedCallback can use it without re-entering reflection. */
@@ -4226,7 +4305,7 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
4226
4305
  return this._audioTracks;
4227
4306
  }
4228
4307
  get subtitleTracks() {
4229
- return this._subtitleTracks;
4308
+ return this._htmlTrackInfo.length === 0 ? this._subtitleTracks : [...this._subtitleTracks, ...this._htmlTrackInfo];
4230
4309
  }
4231
4310
  /**
4232
4311
  * External subtitle files to attach when the source loads. Takes effect
@@ -4315,6 +4394,12 @@ var AvbridgeVideoElement = class extends HTMLElementCtor {
4315
4394
  getDiagnostics() {
4316
4395
  return this._player?.getDiagnostics() ?? null;
4317
4396
  }
4397
+ addEventListener(type, listener, options) {
4398
+ super.addEventListener(type, listener, options);
4399
+ }
4400
+ removeEventListener(type, listener, options) {
4401
+ super.removeEventListener(type, listener, options);
4402
+ }
4318
4403
  // ── Event helpers ──────────────────────────────────────────────────────
4319
4404
  _dispatch(name, detail) {
4320
4405
  this.dispatchEvent(new CustomEvent(name, { detail, bubbles: false }));
@@ -4363,6 +4448,18 @@ var PLAYER_STYLES = (
4363
4448
  height: 100%;
4364
4449
  }
4365
4450
 
4451
+ /* Drag-and-drop file target highlight. */
4452
+ .avp.avp-dragover::after {
4453
+ content: "";
4454
+ position: absolute;
4455
+ inset: 8px;
4456
+ border: 2px dashed rgba(255, 255, 255, 0.75);
4457
+ border-radius: 4px;
4458
+ background: rgba(0, 0, 0, 0.25);
4459
+ pointer-events: none;
4460
+ z-index: 10;
4461
+ }
4462
+
4366
4463
  /* \u2500\u2500 Center overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4367
4464
 
4368
4465
  .avp-overlay {
@@ -5054,6 +5151,31 @@ var AvbridgePlayerElement = class extends HTMLElement {
5054
5151
  on(container, "pointerdown", (e) => this._onPointerDown(e));
5055
5152
  on(container, "pointerup", (e) => this._onPointerUp(e));
5056
5153
  on(container, "pointercancel", () => this._cancelHold());
5154
+ on(container, "dragenter", (e) => {
5155
+ e.preventDefault();
5156
+ const dt = e.dataTransfer;
5157
+ if (!dt || !Array.from(dt.types).includes("Files")) return;
5158
+ container.classList.add("avp-dragover");
5159
+ });
5160
+ on(container, "dragover", (e) => {
5161
+ e.preventDefault();
5162
+ const dt = e.dataTransfer;
5163
+ if (dt) dt.dropEffect = "copy";
5164
+ });
5165
+ on(container, "dragleave", (e) => {
5166
+ if (e.target === container) {
5167
+ container.classList.remove("avp-dragover");
5168
+ }
5169
+ });
5170
+ on(container, "drop", (e) => {
5171
+ e.preventDefault();
5172
+ container.classList.remove("avp-dragover");
5173
+ const file = e.dataTransfer?.files?.[0];
5174
+ if (!file) return;
5175
+ this._video.source = file;
5176
+ void this._video.play().catch(() => {
5177
+ });
5178
+ });
5057
5179
  on(this, "keydown", (e) => this._onKeydown(e));
5058
5180
  if (!this.hasAttribute("tabindex")) {
5059
5181
  this.setAttribute("tabindex", "0");
@@ -5619,6 +5741,12 @@ var AvbridgePlayerElement = class extends HTMLElement {
5619
5741
  canPlayType(mime) {
5620
5742
  return this._video.canPlayType(mime);
5621
5743
  }
5744
+ addEventListener(type, listener, options) {
5745
+ super.addEventListener(type, listener, options);
5746
+ }
5747
+ removeEventListener(type, listener, options) {
5748
+ super.removeEventListener(type, listener, options);
5749
+ }
5622
5750
  };
5623
5751
 
5624
5752
  // src/player-element.ts