@nomercy-entertainment/nomercy-video-player 2.1.1 → 2.1.2
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 +8 -0
- package/README.md +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +55 -12
- package/dist/nomercy-video-player.iife.js +1 -1
- package/dist/player/track-language-memory.d.ts +26 -0
- package/dist/player/track-language-memory.js +67 -0
- package/dist/plugins/octopus/font-names.d.ts +13 -0
- package/dist/plugins/octopus/font-names.js +82 -0
- package/dist/plugins/octopus/index.d.ts +6 -2
- package/dist/plugins/octopus/index.js +16 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2.1.2] — 2026-08-29
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- ASS/SSA subtitles rendered in the wrong face. The libass font map was keyed by the attachment's filename, so a release whose attachments are not named after their family resolved nothing and every line fell back to an arbitrary attached font. Keys now come from the font's own name table.
|
|
10
|
+
- The chosen audio and subtitle language is remembered and re-applied on every item, instead of the player trusting a track index and the playlist's `DEFAULT` flag — both of which differ per file, so a picked language was lost on the next episode.
|
|
11
|
+
- A selected track index left over from the previous item no longer ticks in the menu while the stream plays a different track.
|
|
12
|
+
|
|
5
13
|
## [2.1.0] — 2026-08-20
|
|
6
14
|
|
|
7
15
|
### Added
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://www.npmjs.com/package/@nomercy-entertainment/nomercy-video-player)
|
|
2
2
|
[](./LICENSE)
|
|
3
3
|
[](https://bundlephobia.com/package/@nomercy-entertainment/nomercy-video-player)
|
|
4
4
|
|
package/dist/index.d.ts
CHANGED
|
@@ -172,6 +172,12 @@ export declare class NMVideoPlayer<T extends VideoPlaylistItem = VideoPlaylistIt
|
|
|
172
172
|
plugins: () => ReadonlyArray<Plugin>;
|
|
173
173
|
enabledPlugins: () => ReadonlyArray<Plugin>;
|
|
174
174
|
constructor(id?: string | number);
|
|
175
|
+
private _languageMemoryInstance;
|
|
176
|
+
/**
|
|
177
|
+
* Carries the viewer's chosen audio / subtitle language across items. Built
|
|
178
|
+
* on first use — `options` is not assigned yet when fields initialise.
|
|
179
|
+
*/
|
|
180
|
+
private get languageMemory();
|
|
175
181
|
private _wantedPoster;
|
|
176
182
|
private _defaultQualityApplied;
|
|
177
183
|
/**
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { readItemImage } from './player/itemImage.js';
|
|
|
11
11
|
import { normalizeVideoPlaylistItem } from './player/normalize-item.js';
|
|
12
12
|
import { VideoPreloadStrategy } from './player/preload.js';
|
|
13
13
|
import { pickStartItem } from './player/start-selection.js';
|
|
14
|
+
import { SUBTITLES_OFF, TrackLanguageMemory } from './player/track-language-memory.js';
|
|
14
15
|
import { titleTokenTranslations } from './plugins/desktop-ui/i18n/token-bundle.js';
|
|
15
16
|
import { containedRect, FullscreenState, PipState, SubtitleState, TheaterState } from './types.js';
|
|
16
17
|
export { VttChapterSource } from './adapters/chapter-source/vtt-chapters.js';
|
|
@@ -111,6 +112,17 @@ export class NMVideoPlayer extends EventEmitter {
|
|
|
111
112
|
this._applyDefaultTracks();
|
|
112
113
|
this._emitVideoRect();
|
|
113
114
|
});
|
|
115
|
+
// Record the chosen LANGUAGE, not the index — an index means a different
|
|
116
|
+
// language on the next file. Fires for the viewer's own pick and for the
|
|
117
|
+
// selection _applyDefaultTracks restores, which write the same value.
|
|
118
|
+
this.on('audioTrack', ({ id }) => {
|
|
119
|
+
if (id === null)
|
|
120
|
+
return;
|
|
121
|
+
this.languageMemory.rememberAudio(this.audioTracks()[id]?.language);
|
|
122
|
+
});
|
|
123
|
+
this.on('subtitle', ({ track }) => {
|
|
124
|
+
this.languageMemory.rememberSubtitle(track === null ? SUBTITLES_OFF : this.subtitles()[track]?.language);
|
|
125
|
+
});
|
|
114
126
|
// Re-emit videoRect on signals that change the displayed content rect.
|
|
115
127
|
this.on('duration', () => { this._emitVideoRect(); });
|
|
116
128
|
this.on('fullscreen', () => { this._emitVideoRect(); });
|
|
@@ -129,6 +141,15 @@ export class NMVideoPlayer extends EventEmitter {
|
|
|
129
141
|
catch { /* backend may not be ready yet — ignore */ }
|
|
130
142
|
});
|
|
131
143
|
}
|
|
144
|
+
_languageMemoryInstance;
|
|
145
|
+
/**
|
|
146
|
+
* Carries the viewer's chosen audio / subtitle language across items. Built
|
|
147
|
+
* on first use — `options` is not assigned yet when fields initialise.
|
|
148
|
+
*/
|
|
149
|
+
get languageMemory() {
|
|
150
|
+
this._languageMemoryInstance ??= new TrackLanguageMemory(this.options?.storage);
|
|
151
|
+
return this._languageMemoryInstance;
|
|
152
|
+
}
|
|
132
153
|
_wantedPoster = null;
|
|
133
154
|
_defaultQualityApplied = false;
|
|
134
155
|
/**
|
|
@@ -194,8 +215,12 @@ export class NMVideoPlayer extends EventEmitter {
|
|
|
194
215
|
* `'en-US'`). No match → leave selection at off; no warning emitted.
|
|
195
216
|
*/
|
|
196
217
|
_applyDefaultTracks() {
|
|
197
|
-
const
|
|
198
|
-
|
|
218
|
+
const rememberedSubtitle = this.languageMemory.subtitleLanguage();
|
|
219
|
+
const subtitleLang = rememberedSubtitle ?? this.options?.defaultSubtitleLanguage;
|
|
220
|
+
if (subtitleLang === SUBTITLES_OFF) {
|
|
221
|
+
this.subtitle(-1);
|
|
222
|
+
}
|
|
223
|
+
else if (subtitleLang) {
|
|
199
224
|
let tracks = [];
|
|
200
225
|
try {
|
|
201
226
|
tracks = this.subtitles();
|
|
@@ -206,17 +231,35 @@ export class NMVideoPlayer extends EventEmitter {
|
|
|
206
231
|
this.subtitle(matchIdx);
|
|
207
232
|
}
|
|
208
233
|
}
|
|
209
|
-
const audioLang = this.options?.defaultAudioLanguage;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
234
|
+
const audioLang = this.languageMemory.audioLanguage() ?? this.options?.defaultAudioLanguage;
|
|
235
|
+
const selectedIdx = this.audioTrack()?.index ?? null;
|
|
236
|
+
// Nothing to honour and no selection that could have gone stale — leave
|
|
237
|
+
// the backend alone rather than reading its track list for no reason.
|
|
238
|
+
if (!audioLang && selectedIdx === null)
|
|
239
|
+
return;
|
|
240
|
+
let audioTracks = [];
|
|
241
|
+
try {
|
|
242
|
+
audioTracks = this.audioTracks();
|
|
243
|
+
}
|
|
244
|
+
catch { /* not yet available */ }
|
|
245
|
+
const matchIdx = audioLang
|
|
246
|
+
? _matchLanguage(audioTracks.map(audioTrack => audioTrack.language), audioLang)
|
|
247
|
+
: -1;
|
|
248
|
+
// With no language to honour, fall back to whichever rendition the stream
|
|
249
|
+
// marks default. The selected index survives an item change, so a stale
|
|
250
|
+
// one left alone ticks the previous item's track in the menu while the
|
|
251
|
+
// stream plays its own default.
|
|
252
|
+
const targetIdx = matchIdx >= 0
|
|
253
|
+
? matchIdx
|
|
254
|
+
: audioTracks.findIndex(audioTrack => audioTrack.default === true);
|
|
255
|
+
if (matchIdx >= 0) {
|
|
256
|
+
if (selectedIdx !== matchIdx)
|
|
218
257
|
this.audioTrack(matchIdx);
|
|
219
|
-
|
|
258
|
+
}
|
|
259
|
+
// Nothing to honour: only correct a selection that actually disagrees.
|
|
260
|
+
// Re-asserting the track already playing costs a switch for no change.
|
|
261
|
+
else if (targetIdx >= 0 && selectedIdx !== null && selectedIdx !== targetIdx) {
|
|
262
|
+
this.audioTrack(targetIdx);
|
|
220
263
|
}
|
|
221
264
|
}
|
|
222
265
|
/** Test-only: clear the registry. Not part of the public API. */
|