Skip to content

Commit 656bff4

Browse files
authored
Allow removing TextTracks created by hls.js (video-dev#7515)
* allow removing text tracks * add unit tests for id3 track controller * fix textTracks may loss existing cues on Safari
1 parent f9e23c0 commit 656bff4

18 files changed

+499
-727
lines changed

api-extractor/report/hls.js.api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4015,7 +4015,6 @@ export interface MediaKeySessionContext {
40154015
export type MediaOverrides = {
40164016
duration?: number;
40174017
endOfStream?: boolean;
4018-
cueRemoval?: boolean;
40194018
};
40204019

40214020
// Warning: (ae-missing-release-tag) "MediaPlaylist" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -4057,6 +4056,8 @@ export interface MediaPlaylist {
40574056
// (undocumented)
40584057
textCodec?: string;
40594058
// (undocumented)
4059+
trackNode?: HTMLTrackElement;
4060+
// (undocumented)
40604061
type: MediaPlaylistType | 'main';
40614062
// (undocumented)
40624063
unknownCodecs?: string[];

demo/main.js

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ let stopOnStall = getDemoConfigPropOrDefault('stopOnStall', false);
4343
let bufferingIdx = -1;
4444
let selectedTestStream = null;
4545

46-
let video = document.querySelector('#video');
46+
const video = document.querySelector('#video');
4747
const startTime = Date.now();
4848

4949
let lastSeekingIdx;
@@ -222,6 +222,7 @@ $(document).ready(function () {
222222
$('#metricsButtonWindow').toggle(self.windowSliding);
223223
$('#metricsButtonFixed').toggle(!self.windowSliding);
224224

225+
addVideoEventListeners(video);
225226
loadSelectedStream();
226227

227228
let tabIndexesCSV = localStorage.getItem(STORAGE_KEYS.demo_tabs);
@@ -353,32 +354,7 @@ function loadSelectedStream() {
353354

354355
logStatus('Loading manifest and attaching video element...');
355356

356-
const expiredTracks = [].filter.call(
357-
video.textTracks,
358-
(track) => track.kind !== 'metadata'
359-
);
360-
if (expiredTracks.length) {
361-
const kinds = expiredTracks
362-
.map((track) => track.kind)
363-
.filter((kind, index, self) => self.indexOf(kind) === index);
364-
logStatus(
365-
`Replacing video element to remove ${kinds.join(' and ')} text tracks`
366-
);
367-
const videoWithExpiredTextTracks = video;
368-
video = videoWithExpiredTextTracks.cloneNode(false);
369-
video.removeAttribute('src');
370-
video.volume = videoWithExpiredTextTracks.volume;
371-
video.muted = videoWithExpiredTextTracks.muted;
372-
videoWithExpiredTextTracks.parentNode.insertBefore(
373-
video,
374-
videoWithExpiredTextTracks
375-
);
376-
videoWithExpiredTextTracks.parentNode.removeChild(
377-
videoWithExpiredTextTracks
378-
);
379-
}
380357
addChartEventListeners(hls);
381-
addVideoEventListeners(video);
382358

383359
hls.loadSource(url);
384360
hls.autoLevelCapping = levelCapping;

src/controller/buffer-controller.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,8 +544,6 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe
544544
}
545545
this.media = null;
546546
}
547-
548-
this.hls.trigger(Events.MEDIA_DETACHED, data);
549547
}
550548

551549
private onBufferReset() {

src/controller/id3-track-controller.ts

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ import {
88
import { MetadataSchema } from '../types/demuxer';
99
import { hexToArrayBuffer } from '../utils/hex';
1010
import { stringify } from '../utils/safe-json-stringify';
11-
import {
12-
clearCurrentCues,
13-
removeCuesInRange,
14-
sendAddTrackEvent,
15-
} from '../utils/texttrack-utils';
11+
import { createTrackNode, removeCuesInRange } from '../utils/texttrack-utils';
1612
import type { MediaFragment } from '../hls';
1713
import type Hls from '../hls';
1814
import type { DateRange } from '../loader/date-range';
@@ -77,7 +73,7 @@ const MAX_CUE_ENDTIME = (() => {
7773

7874
class ID3TrackController implements ComponentAPI {
7975
private hls: Hls | null;
80-
private id3Track: TextTrack | null = null;
76+
private id3Track: HTMLTrackElement | null = null;
8177
private media: HTMLMediaElement | null = null;
8278
private dateRangeCuesAppended: Record<
8379
string,
@@ -146,9 +142,6 @@ class ID3TrackController implements ComponentAPI {
146142
data: MediaAttachingData,
147143
): void {
148144
this.media = data.media;
149-
if (data.overrides?.cueRemoval === false) {
150-
this.removeCues = false;
151-
}
152145
}
153146

154147
private onMediaAttached() {
@@ -168,9 +161,7 @@ class ID3TrackController implements ComponentAPI {
168161
return;
169162
}
170163
if (this.id3Track) {
171-
if (this.removeCues) {
172-
clearCurrentCues(this.id3Track, this.onEventCueEnter);
173-
}
164+
this.id3Track.remove();
174165
this.id3Track = null;
175166
}
176167
this.dateRangeCuesAppended = {};
@@ -180,27 +171,8 @@ class ID3TrackController implements ComponentAPI {
180171
this.dateRangeCuesAppended = {};
181172
}
182173

183-
private createTrack(media: HTMLMediaElement): TextTrack {
184-
const track = this.getID3Track(media.textTracks) as TextTrack;
185-
track.mode = 'hidden';
186-
return track;
187-
}
188-
189-
private getID3Track(textTracks: TextTrackList): TextTrack | void {
190-
if (!this.media) {
191-
return;
192-
}
193-
for (let i = 0; i < textTracks.length; i++) {
194-
const textTrack: TextTrack = textTracks[i];
195-
if (textTrack.kind === 'metadata' && textTrack.label === 'id3') {
196-
// send 'addtrack' when reusing the textTrack for metadata,
197-
// same as what we do for captions
198-
sendAddTrackEvent(textTrack, this.media);
199-
200-
return textTrack;
201-
}
202-
}
203-
return this.media.addTextTrack('metadata', 'id3');
174+
private createTrack(media: HTMLMediaElement): HTMLTrackElement {
175+
return createTrackNode(media, 'metadata', 'id3', '', 'hidden');
204176
}
205177

206178
private onFragParsingMetadata(
@@ -264,15 +236,15 @@ class ID3TrackController implements ComponentAPI {
264236
type,
265237
);
266238
if (cue) {
267-
this.id3Track.addCue(cue);
239+
this.id3Track.track.addCue(cue);
268240
}
269241
}
270242
}
271243
}
272244
}
273245

274246
private updateId3CueEnds(startTime: number, type: MetadataSchema) {
275-
const cues = this.id3Track?.cues;
247+
const cues = this.id3Track?.track.cues;
276248
if (cues) {
277249
for (let i = cues.length; i--; ) {
278250
const cue = cues[i] as any;
@@ -315,7 +287,7 @@ class ID3TrackController implements ComponentAPI {
315287
enableID3MetadataCues) ||
316288
((cue as any).type === MetadataSchema.emsg && enableEmsgMetadataCues);
317289
}
318-
removeCuesInRange(id3Track, startOffset, endOffset, predicate);
290+
removeCuesInRange(id3Track.track, startOffset, endOffset, predicate);
319291
}
320292
}
321293

@@ -372,7 +344,7 @@ class ID3TrackController implements ComponentAPI {
372344
if (cue) {
373345
cue.id = assetPlayerId;
374346
this.id3Track ||= this.createTrack(this.media);
375-
this.id3Track.addCue(cue);
347+
this.id3Track.track.addCue(cue);
376348
cue.addEventListener('enter', this.onEventCueEnter);
377349
}
378350
}
@@ -387,7 +359,7 @@ class ID3TrackController implements ComponentAPI {
387359
let dateRangeCuesAppended = this.dateRangeCuesAppended;
388360
// Remove cues from track not found in details.dateRanges
389361
if (id3Track && removeOldCues) {
390-
if (id3Track.cues?.length) {
362+
if (id3Track.track.cues?.length) {
391363
const idsToRemove = Object.keys(dateRangeCuesAppended).filter(
392364
(id) => !ids.includes(id),
393365
);
@@ -401,7 +373,7 @@ class ID3TrackController implements ComponentAPI {
401373
if (cue) {
402374
cue.removeEventListener('enter', this.onEventCueEnter);
403375
try {
404-
id3Track.removeCue(cue);
376+
id3Track.track.removeCue(cue);
405377
} catch (e) {
406378
/* no-op */
407379
}
@@ -492,7 +464,7 @@ class ID3TrackController implements ComponentAPI {
492464
);
493465
if (cue) {
494466
cue.id = id;
495-
this.id3Track.addCue(cue);
467+
this.id3Track.track.addCue(cue);
496468
cues[key] = cue;
497469
if (__USE_INTERSTITIALS__ && interstitialsController) {
498470
if (key === 'X-ASSET-LIST' || key === 'X-ASSET-URL') {

src/controller/interstitials-controller.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -871,7 +871,6 @@ export default class InterstitialsController
871871
dataToAttach.overrides = {
872872
duration: schedule.duration,
873873
endOfStream: !isAssetPlayer || isAssetAtEndOfSchedule,
874-
cueRemoval: !isAssetPlayer,
875874
};
876875
}
877876
player.attachMedia(dataToAttach);

0 commit comments

Comments
 (0)