@nomercy-entertainment/nomercy-video-player 2.2.2 → 2.2.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.2.3] — 2026-09-25
6
+
7
+ ### Added
8
+
9
+ - `disableClickToPause` in `setup()` stops a click from pausing the video.
10
+
11
+ ### Fixed
12
+
13
+ - Streams added with `registerStream()` are now used for playback.
14
+ - Playlist thumbnails load from the configured image URL.
15
+ - Changing auth at runtime now applies to media requests.
16
+ - Georgia subtitles fall back to a serif font, and subtitle colors match the palette.
17
+ - One press of `?` opens the shortcuts overlay.
18
+ - Cast artwork shows the right image, and the media-key option works.
19
+ - The help key on a TV remote works.
20
+
5
21
  ## [2.2.2] — 2026-09-04
6
22
 
7
23
  ### Fixed
@@ -1,5 +1,11 @@
1
- import type { AudioTrack, AuthHeaderProvider, BackendLoaderState, BackendState, HdrOnSdrFallback, MinimalBackendEventPayload, QualityLevel, SubtitleCueChange, SubtitleTrack } from '@nomercy-entertainment/nomercy-player-core';
1
+ import type { AudioTrack, AuthHeaderProvider, BackendLoaderState, BackendState, HdrOnSdrFallback, IStreamSource, MinimalBackendEventPayload, QualityLevel, SubtitleCueChange, SubtitleTrack } from '@nomercy-entertainment/nomercy-player-core';
2
2
  import type { HtmlPreloadMode } from '../../types.js';
3
+ /**
4
+ * Asks the player's stream registry whether a consumer registered a factory for
5
+ * this URL. Returns a source to play it with, or `undefined` to let the backend
6
+ * handle the URL the way it always has.
7
+ */
8
+ export type StreamResolver = (url: string, contentType?: string) => IStreamSource | undefined;
3
9
  /**
4
10
  * Per-event payload map. Each backend event has a fixed payload shape so
5
11
  * `IVideoBackend.on(event, fn)` narrows the listener's parameter type
@@ -136,6 +142,14 @@ export interface IVideoBackend {
136
142
  * stack omit it.
137
143
  */
138
144
  setAuthHeaderProvider?(provider: AuthHeaderProvider): void;
145
+ /**
146
+ * Wire the lookup that asks the player's stream registry whether a consumer
147
+ * has registered a factory for a URL. The backend calls it before its own
148
+ * HLS and progressive handling, and falls through to that handling when the
149
+ * lookup returns nothing, so a player with no registered factory behaves
150
+ * exactly as it did before. Optional — backends with no registry omit it.
151
+ */
152
+ setStreamResolver?(resolver: StreamResolver): void;
139
153
  /**
140
154
  * Consumer policy for an all-HDR item on a display that can't show HDR and
141
155
  * a backend that can't tone-map it. Optional — backends that never face the
@@ -1,15 +1,16 @@
1
1
  import type { AudioTrack, HdrOnSdrFallback, QualityLevel, SubtitleTrack } from '@nomercy-entertainment/nomercy-player-core';
2
2
  import type { HtmlPreloadMode } from '../../types.js';
3
- import type { BackendEventPayload, BackendState, IVideoBackend } from './IVideoBackend.js';
3
+ import type { BackendEventPayload, BackendState, IVideoBackend, StreamResolver } from './IVideoBackend.js';
4
4
  import { MediaElementBackend } from '@nomercy-entertainment/nomercy-player-core';
5
5
  /**
6
6
  * Default video backend. Wraps an `<HTMLVideoElement>` for transport.
7
7
  *
8
- * HLS support: native pass-through when `canPlayType` reports support
9
- * (Safari / iOS), otherwise dynamically imports `hls.js` (peer dep — not
10
- * bundled; IIFE build externalises it as `window.Hls`, ESM build resolves
11
- * it from the consumer's node_modules) and attaches it. MSE / WebCodecs
12
- * backends ship later.
8
+ * HLS support: native pass-through when `canPlayType` reports support,
9
+ * otherwise dynamically imports `hls.js` and attaches it. `hls.js` is a
10
+ * runtime dependency of nomercy-player-core, not a peer dependency, so an
11
+ * npm consumer installs it without asking. The IIFE build externalizes it
12
+ * as `window.Hls`; the ESM build resolves it from node_modules. MSE /
13
+ * WebCodecs backends ship later.
13
14
  */
14
15
  export declare class Html5VideoBackend extends MediaElementBackend<HTMLVideoElement, BackendEventPayload> implements IVideoBackend {
15
16
  readonly kind: "html5";
@@ -137,6 +138,14 @@ export declare class Html5VideoBackend extends MediaElementBackend<HTMLVideoElem
137
138
  unload(): void;
138
139
  dispose(): void;
139
140
  private _teardownAudioGraph;
141
+ private _streamResolver?;
142
+ private _customSource?;
143
+ /**
144
+ * Wire the lookup that asks the player's stream registry for a consumer's
145
+ * factory. Without it the backend keeps its own HLS and progressive handling
146
+ * and a registered factory is never consulted.
147
+ */
148
+ setStreamResolver(resolver: StreamResolver): void;
140
149
  buffered(): number;
141
150
  videoWidth(): number;
142
151
  videoHeight(): number;
@@ -34,11 +34,12 @@ function humanCodec(raw) {
34
34
  /**
35
35
  * Default video backend. Wraps an `<HTMLVideoElement>` for transport.
36
36
  *
37
- * HLS support: native pass-through when `canPlayType` reports support
38
- * (Safari / iOS), otherwise dynamically imports `hls.js` (peer dep — not
39
- * bundled; IIFE build externalises it as `window.Hls`, ESM build resolves
40
- * it from the consumer's node_modules) and attaches it. MSE / WebCodecs
41
- * backends ship later.
37
+ * HLS support: native pass-through when `canPlayType` reports support,
38
+ * otherwise dynamically imports `hls.js` and attaches it. `hls.js` is a
39
+ * runtime dependency of nomercy-player-core, not a peer dependency, so an
40
+ * npm consumer installs it without asking. The IIFE build externalizes it
41
+ * as `window.Hls`; the ESM build resolves it from node_modules. MSE /
42
+ * WebCodecs backends ship later.
42
43
  */
43
44
  export class Html5VideoBackend extends MediaElementBackend {
44
45
  kind = 'html5';
@@ -238,6 +239,21 @@ export class Html5VideoBackend extends MediaElementBackend {
238
239
  resetMediaElement(this.element);
239
240
  performance.mark('nm:backend:load:start');
240
241
  const headerValue = this._authHeaderProvider?.(url);
242
+ // A consumer's factory is asked first, because the branches below cover
243
+ // only HLS and whatever the element itself decodes. Nothing registered
244
+ // means nothing returned, and the built-in path runs untouched.
245
+ if (this._customSource) {
246
+ this._customSource.destroy();
247
+ this._customSource = undefined;
248
+ }
249
+ const customSource = this._streamResolver?.(url);
250
+ if (customSource) {
251
+ this._customSource = customSource;
252
+ await customSource.attach(this.element);
253
+ this._state = 'ready';
254
+ performance.mark('nm:backend:load:end');
255
+ return;
256
+ }
241
257
  if (hlsUrl && !nativeHls) {
242
258
  const { default: Hls } = await import('hls.js');
243
259
  if (!Hls?.isSupported?.()) {
@@ -349,6 +365,16 @@ export class Html5VideoBackend extends MediaElementBackend {
349
365
  // playbackRate / volume / mute / unmute / captureStream / setSinkId /
350
366
  // getSinkId / mediaKeys / setMediaKeys / mediaElement / pauseLoader /
351
367
  // resumeLoader / loaderState / setAuthHeaderProvider
368
+ _streamResolver;
369
+ _customSource;
370
+ /**
371
+ * Wire the lookup that asks the player's stream registry for a consumer's
372
+ * factory. Without it the backend keeps its own HLS and progressive handling
373
+ * and a registered factory is never consulted.
374
+ */
375
+ setStreamResolver(resolver) {
376
+ this._streamResolver = resolver;
377
+ }
352
378
  // ── Time / position (video-specific: buffered uses currentTime walk) ──
353
379
  buffered() {
354
380
  const ranges = this.element.buffered;
@@ -381,7 +407,9 @@ export class Html5VideoBackend extends MediaElementBackend {
381
407
  default: hlsAudioTrack.default === true,
382
408
  }));
383
409
  }
384
- // Native: HTMLMediaElement.audioTracks (Safari/Chrome with multi-audio).
410
+ // Native: HTMLMediaElement.audioTracks. Measured absent in Chrome 152,
411
+ // Edge 152 and bundled Chromium, so this branch is Safari in practice
412
+ // and yields [] elsewhere; the HLS path above is what fills the list.
385
413
  const nativeTracks = this.element.audioTracks;
386
414
  if (nativeTracks && nativeTracks.length > 0) {
387
415
  const out = [];
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ActionOptions, AriaLiveLevel, AudioTrack, AuthConfig, BasePlaylistItem, BufferState, CanPlayResult, CastState, CastTarget, Chapter, CurrentAudioTrackSelection, CurrentQualitySelection, CurrentSubtitleSelection, DeviceCapabilities, ICueParser, IPlatform, IPlayer, IPreloadStrategy, IStreamFactory, ITransitionStrategy, IUrlResolver, TimeState as KitTimeState, LoadOptions, NetworkState, PlaybackMetrics, PlayerExperimental, PlayerPhase, Plugin, PluginCtorWithId, QualityLevel, ResolvedUrl, SetupState, SidecarSubtitleInput, SubtitleStyle, SubtitleTrack, Translations, UrlCategory, VisibilityState } from '@nomercy-entertainment/nomercy-player-core';
1
+ import type { ActionOptions, AriaLiveLevel, AudioTrack, AuthConfig, BasePlaylistItem, BufferState, CanPlayResult, CastState, CastTarget, Chapter, CurrentAudioTrackSelection, CurrentQualitySelection, CurrentSubtitleSelection, DeviceCapabilities, ICueParser, IPlatform, IPlayer, IPreloadStrategy, IStreamFactory, IStreamSource, ITransitionStrategy, IUrlResolver, TimeState as KitTimeState, LoadOptions, NetworkState, PlaybackMetrics, PlayerExperimental, PlayerPhase, Plugin, PluginCtorWithId, QualityLevel, ResolvedUrl, SetupState, SidecarSubtitleInput, SubtitleStyle, SubtitleTrack, Translations, UrlCategory, VisibilityState } from '@nomercy-entertainment/nomercy-player-core';
2
2
  import type { IVideoBackend, VideoBackendKind } from './adapters/video-backend/IVideoBackend.js';
3
3
  import type { AudioTrackState, IVideoPlayer, PlayState, QualityState, RepeatState, SegmentOptions, ShuffleState, Stretching, VideoEventMap, VideoPlayerConfig, VideoPlaylistItem, VideoRect, VolumeState } from './types.js';
4
4
  import { EventEmitter } from '@nomercy-entertainment/nomercy-player-core';
@@ -200,12 +200,15 @@ export declare class NMVideoPlayer<T extends VideoPlaylistItem = VideoPlaylistIt
200
200
  * `mediaReady` fires so the backend's track lists are populated.
201
201
  *
202
202
  * Language matching: exact tag first, then prefix (e.g. `'en'` matches
203
- * `'en-US'`). No match → leave selection at off; no warning emitted.
203
+ * `'en-US'`). No match leaves an UNSELECTED slot alone, but where a track is
204
+ * already selected the slot is handed to the stream's own default rendition
205
+ * instead. No warning either way.
204
206
  */
205
207
  private _applyDefaultTracks;
206
208
  /** Test-only: clear the registry. Not part of the public API. */
207
209
  static _resetRegistry(): void;
208
210
  registerStream: (factory: IStreamFactory, prepend?: boolean) => this;
211
+ resolveCustomStream: (url: string, contentType?: string) => IStreamSource | undefined;
209
212
  unregisterStream: (id: string) => this;
210
213
  streams: () => ReadonlyArray<string>;
211
214
  getStreamFactory: (id: string) => IStreamFactory | undefined;
package/dist/index.js CHANGED
@@ -212,7 +212,9 @@ export class NMVideoPlayer extends EventEmitter {
212
212
  * `mediaReady` fires so the backend's track lists are populated.
213
213
  *
214
214
  * Language matching: exact tag first, then prefix (e.g. `'en'` matches
215
- * `'en-US'`). No match → leave selection at off; no warning emitted.
215
+ * `'en-US'`). No match leaves an UNSELECTED slot alone, but where a track is
216
+ * already selected the slot is handed to the stream's own default rendition
217
+ * instead. No warning either way.
216
218
  */
217
219
  _applyDefaultTracks() {
218
220
  const remembered = this.languageMemory.subtitleChoice();
@@ -308,7 +310,14 @@ export class NMVideoPlayer extends EventEmitter {
308
310
  // lazily so getter-style tokens (Vue refs, stores) stay live.
309
311
  // Read per request rather than captured, so a consumer whose token or
310
312
  // rule changes mid-session is not answered from a stale closure.
311
- instance.setAuthHeaderProvider?.((url) => this.options?.auth?.mediaAuthorization?.(url));
313
+ // Live config, not `options.auth`: setup seeds it and `player.auth()`
314
+ // updates it, so a rule set after setup reaches media requests too.
315
+ instance.setAuthHeaderProvider?.((url) => this.auth()?.mediaAuthorization?.(url));
316
+ // Without this the stream registry is seeded and never asked, so
317
+ // `registerStream` records a factory that no load path consults. Only
318
+ // consumer-registered factories are offered: HLS and progressive files
319
+ // stay on the backend's own path exactly as before.
320
+ instance.setStreamResolver?.((url, contentType) => this.resolveCustomStream?.(url, contentType));
312
321
  // Consumer's HDR-on-SDR policy, read once here rather than normalized
313
322
  // into `options` — `hdrDecision`'s own default ('play') applies at the
314
323
  // point of use when this was never pushed at all.