@nomercy-entertainment/nomercy-video-player 2.2.2 → 2.2.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.
- package/CHANGELOG.md +25 -0
- package/dist/adapters/video-backend/IVideoBackend.d.ts +15 -1
- package/dist/adapters/video-backend/html5.d.ts +15 -6
- package/dist/adapters/video-backend/html5.js +34 -6
- package/dist/index.d.ts +9 -2
- package/dist/index.js +29 -2
- package/dist/nomercy-video-player.iife.js +1 -1
- package/dist/player/preload.d.ts +5 -1
- package/dist/player/preload.js +5 -1
- package/dist/player/track-language-memory.d.ts +6 -0
- package/dist/player/track-language-memory.js +20 -3
- package/dist/plugins/cast-sender/index.js +7 -2
- package/dist/plugins/desktop-ui/data/buttons.js +12 -0
- package/dist/plugins/desktop-ui/helpers/menuControl.js +6 -1
- package/dist/plugins/desktop-ui/mixins/domMethods.js +10 -1
- package/dist/plugins/key-handler/index.d.ts +6 -0
- package/dist/plugins/key-handler/index.js +7 -1
- package/dist/plugins/octopus/index.d.ts +12 -0
- package/dist/plugins/octopus/index.js +30 -1
- package/dist/plugins/octopus/style-scale.d.ts +8 -0
- package/dist/plugins/octopus/style-scale.js +46 -0
- package/dist/plugins/touch-zones/index.js +1 -1
- package/dist/plugins/tv-key-handler/index.js +5 -1
- package/dist/types.d.ts +10 -2
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.2.4] — 2026-10-08
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Core 2.2.4: the saved volume no longer turns to 0 after a mute.
|
|
10
|
+
- A language-only caption pick now lands on the full track before a sign-language or forced one.
|
|
11
|
+
- Only the viewer's own caption pick is remembered.
|
|
12
|
+
- The viewer's caption size now applies to ASS tracks and redraws when it changes.
|
|
13
|
+
|
|
14
|
+
## [2.2.3] — 2026-09-25
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `disableClickToPause` in `setup()` stops a click from pausing the video.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Streams added with `registerStream()` are now used for playback.
|
|
23
|
+
- Playlist thumbnails load from the configured image URL.
|
|
24
|
+
- Changing auth at runtime now applies to media requests.
|
|
25
|
+
- Georgia subtitles fall back to a serif font, and subtitle colors match the palette.
|
|
26
|
+
- One press of `?` opens the shortcuts overlay.
|
|
27
|
+
- Cast artwork shows the right image, and the media-key option works.
|
|
28
|
+
- The help key on a TV remote works.
|
|
29
|
+
|
|
5
30
|
## [2.2.2] — 2026-09-04
|
|
6
31
|
|
|
7
32
|
### 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
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
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
|
|
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,19 @@ 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
|
|
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;
|
|
208
|
+
/** True while the player picks a caption itself; such a pick is never remembered. */
|
|
209
|
+
private _selectingDefaults;
|
|
210
|
+
private _selectDefaultSubtitle;
|
|
211
|
+
private _selectDefaultAudio;
|
|
206
212
|
/** Test-only: clear the registry. Not part of the public API. */
|
|
207
213
|
static _resetRegistry(): void;
|
|
208
214
|
registerStream: (factory: IStreamFactory, prepend?: boolean) => this;
|
|
215
|
+
resolveCustomStream: (url: string, contentType?: string) => IStreamSource | undefined;
|
|
209
216
|
unregisterStream: (id: string) => this;
|
|
210
217
|
streams: () => ReadonlyArray<string>;
|
|
211
218
|
getStreamFactory: (id: string) => IStreamFactory | undefined;
|
package/dist/index.js
CHANGED
|
@@ -115,12 +115,16 @@ export class NMVideoPlayer extends EventEmitter {
|
|
|
115
115
|
// Record the chosen LANGUAGE, not the index — an index means a different
|
|
116
116
|
// language on the next file. Fires for the viewer's own pick and for the
|
|
117
117
|
// selection _applyDefaultTracks restores, which write the same value.
|
|
118
|
+
// The caption slot below is different: only the viewer's pick is kept.
|
|
118
119
|
this.on('audioTrack', ({ id }) => {
|
|
119
120
|
if (id === null)
|
|
120
121
|
return;
|
|
121
122
|
this.languageMemory.rememberAudio(this.audioTracks()[id]?.language);
|
|
122
123
|
});
|
|
123
124
|
this.on('subtitle', ({ track }) => {
|
|
125
|
+
// A pick _applyDefaultTracks makes is the player's, not the viewer's.
|
|
126
|
+
if (this._selectingDefaults)
|
|
127
|
+
return;
|
|
124
128
|
this.languageMemory.rememberSubtitle(track === null ? SUBTITLES_OFF : this.subtitles()[track]);
|
|
125
129
|
});
|
|
126
130
|
// Re-emit videoRect on signals that change the displayed content rect.
|
|
@@ -212,9 +216,23 @@ export class NMVideoPlayer extends EventEmitter {
|
|
|
212
216
|
* `mediaReady` fires so the backend's track lists are populated.
|
|
213
217
|
*
|
|
214
218
|
* Language matching: exact tag first, then prefix (e.g. `'en'` matches
|
|
215
|
-
* `'en-US'`). No match
|
|
219
|
+
* `'en-US'`). No match leaves an UNSELECTED slot alone, but where a track is
|
|
220
|
+
* already selected the slot is handed to the stream's own default rendition
|
|
221
|
+
* instead. No warning either way.
|
|
216
222
|
*/
|
|
217
223
|
_applyDefaultTracks() {
|
|
224
|
+
this._selectingDefaults = true;
|
|
225
|
+
try {
|
|
226
|
+
this._selectDefaultSubtitle();
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
this._selectingDefaults = false;
|
|
230
|
+
}
|
|
231
|
+
this._selectDefaultAudio();
|
|
232
|
+
}
|
|
233
|
+
/** True while the player picks a caption itself; such a pick is never remembered. */
|
|
234
|
+
_selectingDefaults = false;
|
|
235
|
+
_selectDefaultSubtitle() {
|
|
218
236
|
const remembered = this.languageMemory.subtitleChoice();
|
|
219
237
|
const wanted = remembered
|
|
220
238
|
?? (this.options?.defaultSubtitleLanguage
|
|
@@ -234,6 +252,8 @@ export class NMVideoPlayer extends EventEmitter {
|
|
|
234
252
|
this.subtitle(matchIdx);
|
|
235
253
|
}
|
|
236
254
|
}
|
|
255
|
+
}
|
|
256
|
+
_selectDefaultAudio() {
|
|
237
257
|
const audioLang = this.languageMemory.audioLanguage() ?? this.options?.defaultAudioLanguage;
|
|
238
258
|
const selectedIdx = this.audioTrack()?.index ?? null;
|
|
239
259
|
// Nothing to honour and no selection that could have gone stale — leave
|
|
@@ -308,7 +328,14 @@ export class NMVideoPlayer extends EventEmitter {
|
|
|
308
328
|
// lazily so getter-style tokens (Vue refs, stores) stay live.
|
|
309
329
|
// Read per request rather than captured, so a consumer whose token or
|
|
310
330
|
// rule changes mid-session is not answered from a stale closure.
|
|
311
|
-
|
|
331
|
+
// Live config, not `options.auth`: setup seeds it and `player.auth()`
|
|
332
|
+
// updates it, so a rule set after setup reaches media requests too.
|
|
333
|
+
instance.setAuthHeaderProvider?.((url) => this.auth()?.mediaAuthorization?.(url));
|
|
334
|
+
// Without this the stream registry is seeded and never asked, so
|
|
335
|
+
// `registerStream` records a factory that no load path consults. Only
|
|
336
|
+
// consumer-registered factories are offered: HLS and progressive files
|
|
337
|
+
// stay on the backend's own path exactly as before.
|
|
338
|
+
instance.setStreamResolver?.((url, contentType) => this.resolveCustomStream?.(url, contentType));
|
|
312
339
|
// Consumer's HDR-on-SDR policy, read once here rather than normalized
|
|
313
340
|
// into `options` — `hdrDecision`'s own default ('play') applies at the
|
|
314
341
|
// point of use when this was never pushed at all.
|