@nomercy-entertainment/nomercy-video-player 2.0.3 → 2.0.4

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.
@@ -1,4 +1,4 @@
1
- import type { AudioTrack, BackendLoaderState, BackendState, HdrOnSdrFallback, MinimalBackendEventPayload, QualityLevel, SubtitleCueChange, SubtitleTrack } from '@nomercy-entertainment/nomercy-player-core';
1
+ import type { AudioTrack, AuthHeaderProvider, BackendLoaderState, BackendState, HdrOnSdrFallback, MinimalBackendEventPayload, QualityLevel, SubtitleCueChange, SubtitleTrack } from '@nomercy-entertainment/nomercy-player-core';
2
2
  import type { HtmlPreloadMode } from '../../types.js';
3
3
  /**
4
4
  * Per-event payload map. Each backend event has a fixed payload shape so
@@ -135,7 +135,7 @@ export interface IVideoBackend {
135
135
  * manifests/segments). Optional — backends without their own network
136
136
  * stack omit it.
137
137
  */
138
- setAuthHeaderProvider?(provider: () => string | undefined | Promise<string | undefined>): void;
138
+ setAuthHeaderProvider?(provider: AuthHeaderProvider): void;
139
139
  /**
140
140
  * Consumer policy for an all-HDR item on a display that can't show HDR and
141
141
  * a backend that can't tone-map it. Optional — backends that never face the
@@ -219,7 +219,7 @@ export class Html5VideoBackend extends MediaElementBackend {
219
219
  // what produces the ~8 s stall on auto-advance.
220
220
  resetMediaElement(this.element);
221
221
  performance.mark('nm:backend:load:start');
222
- const headerValue = await this._authHeaderProvider?.();
222
+ const headerValue = this._authHeaderProvider?.(url);
223
223
  if (hlsUrl && !nativeHls) {
224
224
  const { default: Hls } = await import('hls.js');
225
225
  if (!Hls?.isSupported?.()) {
@@ -239,7 +239,7 @@ export class Html5VideoBackend extends MediaElementBackend {
239
239
  enableCEA708Captions: true,
240
240
  startPosition: typeof opts?.startTime === 'number' && opts.startTime > 0 ? opts.startTime : -1,
241
241
  startFragPrefetch: true,
242
- xhrSetup: createAuthorizationXhrSetup(headerValue),
242
+ xhrSetup: createAuthorizationXhrSetup(this._authHeaderProvider),
243
243
  });
244
244
  this.hls = hlsInstance;
245
245
  this.hlsInstance = hlsInstance;
package/dist/index.d.ts CHANGED
@@ -107,6 +107,8 @@ export declare class NMVideoPlayer<T extends VideoPlaylistItem = VideoPlaylistIt
107
107
  private _checkItemEndingSoon;
108
108
  /** @internal */
109
109
  private _timeStateAt;
110
+ /** @internal */
111
+ private _mediaIsStale;
110
112
  playbackRate: {
111
113
  (): number;
112
114
  (rate: number): Promise<void>;
package/dist/index.js CHANGED
@@ -260,13 +260,9 @@ export class NMVideoPlayer extends EventEmitter {
260
260
  // Authenticated media servers need the Authorization header on every
261
261
  // hls.js manifest/segment request. The provider reads the auth config
262
262
  // lazily so getter-style tokens (Vue refs, stores) stay live.
263
- instance.setAuthHeaderProvider?.(async () => {
264
- const bearer = this.options?.auth?.bearerToken;
265
- if (!bearer)
266
- return undefined;
267
- const token = typeof bearer === 'function' ? await bearer() : bearer;
268
- return token ? `Bearer ${token}` : undefined;
269
- });
263
+ // Read per request rather than captured, so a consumer whose token or
264
+ // rule changes mid-session is not answered from a stale closure.
265
+ instance.setAuthHeaderProvider?.((url) => this.options?.auth?.mediaAuthorization?.(url));
270
266
  // Consumer's HDR-on-SDR policy, read once here rather than normalized
271
267
  // into `options` — `hdrDecision`'s own default ('play') applies at the
272
268
  // point of use when this was never pushed at all.
@@ -360,6 +356,15 @@ export class NMVideoPlayer extends EventEmitter {
360
356
  this.emit('subtitleCue', data);
361
357
  });
362
358
  instance.on('timeupdate', () => {
359
+ // Between a `next()` and the incoming item's mount, this element is
360
+ // still the OUTGOING item's — the cursor already moved. Publishing
361
+ // its position here hands every listener the previous item's
362
+ // position stamped with the new item's identity. Optional call:
363
+ // the dependency range admits a core that predates the reader, and
364
+ // an older core must degrade to the old behaviour, not throw on
365
+ // every tick.
366
+ if (this._mediaIsStale?.())
367
+ return;
363
368
  const currentTime = instance.currentTime();
364
369
  const duration = instance.duration();
365
370
  // The payload is core's full TimeState snapshot, built from the
@@ -702,9 +707,23 @@ composeMixins(NMVideoPlayer.prototype, ...playerCoreMethods);
702
707
  {
703
708
  const composedDispose = NMVideoPlayer.prototype.dispose;
704
709
  NMVideoPlayer.prototype.dispose = async function () {
710
+ // The container id is released before the first await, not after the
711
+ // teardown resolves. Consumers re-create on the same container in the
712
+ // same tick — a responsive view handing `#player1` from its desktop
713
+ // variant to its mobile one does exactly that — and the registry still
714
+ // held this player, so they were given back the very instance they had
715
+ // just asked to dispose. `setup()` then threw `already-setup`, and the
716
+ // throw left that player attached and playing with nobody holding a
717
+ // reference to stop it.
718
+ const claimedId = this.playerId;
719
+ _instances.delete(claimedId);
705
720
  await composedDispose.call(this);
706
- if (this.phase() !== 'disposed')
707
- return; // beforeDispose was prevented — backend + registry stay intact
721
+ if (this.phase() !== 'disposed') {
722
+ // beforeDispose was prevented — the player is still alive, so it
723
+ // takes its id back and the backend stays intact.
724
+ _instances.set(claimedId, this);
725
+ return;
726
+ }
708
727
  this._disposeBackend();
709
728
  };
710
729
  }