Skip to content

Commit 1f0058a

Browse files
committed
refactor: streamline chapter handling by introducing cleanup registries for per-chapter and persistent resources, and update URL signing logic in prepareChaptersVttSrc
1 parent 1f8806f commit 1f0058a

2 files changed

Lines changed: 65 additions & 22 deletions

File tree

packages/video-player/javascript/modules/chapters/chapters.ts

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type Player from 'video.js/dist/types/player';
22
import type { SourceOptions, IKPlayerOptions } from '../../interfaces';
33
import { ChapterMarker, parseChaptersFromVTT } from './chapterMarkerProgressBar';
4-
import { prepareChaptersVttSrc } from '../../utils';
4+
import { prepareChaptersVttSrc, CleanupRegistry } from '../../utils';
55

66
interface ChapterTrackMetadata {
77
langCode: string;
@@ -12,6 +12,12 @@ interface ChapterTrackMetadata {
1212
// Map to store chapter tracks by language
1313
const chapterTracksCache = new Map<string, ChapterTrackMetadata>();
1414

15+
// Cleanup registry for per-chapter resources (tracks, labels, progress bar)
16+
let perChapterCleanup: CleanupRegistry | null = null;
17+
18+
// Cleanup registry for persistent resources (subtitle sync listener)
19+
let persistentCleanup: CleanupRegistry | null = null;
20+
1521
/**
1622
* Clean up existing chapter text tracks from the player.
1723
*/
@@ -48,17 +54,39 @@ function cleanupChapterLabelDisplay(player: Player): void {
4854

4955
/**
5056
* Clean up all chapter-related components from the player.
51-
* Removes chapter text tracks, label display, and progress bar control.
57+
* Removes chapter text tracks, label display, progress bar control, and per-chapter event listeners.
58+
* Does NOT clean up persistent listeners (like subtitle sync).
5259
*/
5360
function cleanupChapters(player: Player): void {
5461
cleanupChapterTextTracks(player);
5562
cleanupChapterLabelDisplay(player);
63+
64+
// Dispose per-chapter cleanups (cuechange listeners, etc.)
65+
if (perChapterCleanup) {
66+
perChapterCleanup.dispose();
67+
perChapterCleanup = null;
68+
}
69+
5670
const existing = player.getChild('ChapterMarkersProgressBarControl');
5771
if (existing) {
5872
existing.dispose();
5973
}
6074
}
6175

76+
/**
77+
* Clean up ALL chapter resources including persistent listeners.
78+
* Should be called when completely removing chapters (e.g., new source).
79+
*/
80+
function cleanupAllChapters(player: Player): void {
81+
cleanupChapters(player);
82+
83+
// Dispose persistent cleanups (subtitle sync listener)
84+
if (persistentCleanup) {
85+
persistentCleanup.dispose();
86+
persistentCleanup = null;
87+
}
88+
}
89+
6290

6391
/**
6492
* Load chapters for a specific language
@@ -160,12 +188,19 @@ function applyChaptersToPlayer(player: Player, chapterList: ChapterMarker[]): vo
160188
function setupSubtitleChapterSync(player: Player): void {
161189
const textTracks = player.textTracks();
162190

163-
textTracks.addEventListener('change', () => {
191+
// Initialize persistent cleanup registry if needed
192+
if (!persistentCleanup) {
193+
persistentCleanup = new CleanupRegistry();
194+
}
195+
196+
const handler = () => {
164197
let foundActiveTrack = false;
165198

166199
// Check for active subtitle track
167-
for (let i = 0; i < textTracks.length; i++) {
168-
const track = textTracks[i];
200+
// TextTrackList is array-like, iterate using index access
201+
const textTracksList = textTracks as unknown as TextTrack[];
202+
for (let i = 0; i < textTracksList.length; i++) {
203+
const track = textTracksList[i];
169204

170205
// Find the active subtitle track
171206
if ((track.kind === 'subtitles' || track.kind === 'captions') && track.mode === 'showing') {
@@ -184,7 +219,10 @@ function setupSubtitleChapterSync(player: Player): void {
184219
if (!foundActiveTrack && chapterTracksCache.has('base')) {
185220
switchChaptersLanguage(player, 'base');
186221
}
187-
});
222+
};
223+
224+
// Register the event listener with persistent cleanup registry
225+
persistentCleanup.registerEventListener(textTracks as unknown as EventTarget, 'change', handler);
188226
}
189227

190228
/**
@@ -211,7 +249,7 @@ function setupChapterLabelDisplay(player: Player, chaptersTrack: TextTrack): voi
211249
spacer.classList.add('vjs-control-bar-chapter-wrapper');
212250
spacer.appendChild(controlBarChapterHolder);
213251

214-
chaptersTrack.addEventListener('cuechange', () => {
252+
const cueChangeHandler = () => {
215253
// Safari needs Array.from() for activeCues
216254
const activeCues = Array.from(chaptersTrack.activeCues);
217255
if (activeCues.length > 0) {
@@ -220,7 +258,13 @@ function setupChapterLabelDisplay(player: Player, chaptersTrack: TextTrack): voi
220258
} else {
221259
controlBarChapterHolder.innerText = '';
222260
}
223-
});
261+
};
262+
263+
// Register the cuechange listener with per-chapter cleanup registry
264+
if (!perChapterCleanup) {
265+
perChapterCleanup = new CleanupRegistry();
266+
}
267+
perChapterCleanup.registerEventListener(chaptersTrack, 'cuechange', cueChangeHandler);
224268
}
225269

226270
/**
@@ -236,7 +280,8 @@ export async function initChapterMarkers(
236280
ikGlobalSettings: IKPlayerOptions
237281
): Promise<void> {
238282

239-
cleanupChapters(player);
283+
// Clean up ALL chapters including persistent listeners for new source
284+
cleanupAllChapters(player);
240285

241286
// Clear cache for new source
242287
chapterTracksCache.clear();

packages/video-player/javascript/utils.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ export async function prepareChaptersVttSrc(
268268
opts: IKPlayerOptions
269269
): Promise<{ baseUrl: string; translatedUrls: Map<string, string> }> {
270270
let videoSrcUrl = input.src;
271-
let chaptersVttSrc: string;
271+
let baseUrl: string;
272272

273273
const url = new URL(videoSrcUrl);
274274

@@ -282,21 +282,12 @@ export async function prepareChaptersVttSrc(
282282

283283
filterTrQueryParam(url, ALLOWED_TRANSFORM_PARAMS_CHAPTERS);
284284

285-
chaptersVttSrc = ikBuild({
285+
baseUrl = ikBuild({
286286
src: url.toString(),
287287
urlEndpoint: '',
288288
transformation: [],
289289
});
290290

291-
if (opts.signerFn) {
292-
try {
293-
chaptersVttSrc = await opts.signerFn(chaptersVttSrc);
294-
} catch (err) {
295-
throw new Error(`Signing failed: ${err}`);
296-
}
297-
}
298-
299-
const baseUrl = chaptersVttSrc;
300291
const translatedUrls = new Map<string, string>();
301292

302293
// Check if user has translation options in textTracks
@@ -321,8 +312,6 @@ export async function prepareChaptersVttSrc(
321312
// Sign the URL if signerFn is provided
322313
if (opts.signerFn) {
323314
try {
324-
// remove ik-s and ik-t query params before signing
325-
finalUrl = finalUrl.replace(/ik-s=[^&]*&?/g, '').replace(/ik-t=[^&]*&?/g, '');
326315
finalUrl = await opts.signerFn(finalUrl);
327316
} catch (err) {
328317
console.error(`Failed to sign translated chapter URL for ${langCode}:`, err);
@@ -336,6 +325,15 @@ export async function prepareChaptersVttSrc(
336325
}
337326
}
338327

328+
// sign the base URL if signerFn is provided
329+
if (opts.signerFn) {
330+
try {
331+
baseUrl = await opts.signerFn(baseUrl);
332+
} catch (err) {
333+
throw new Error(`Signing failed: ${err}`);
334+
}
335+
}
336+
339337
return { baseUrl, translatedUrls };
340338
}
341339

0 commit comments

Comments
 (0)