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