Files
v10/dist/dev/dom/hls-js/text-tracks.js
T

104 lines
4.4 KiB
JavaScript

import { isCaptionOrSubtitleTrack, listen } from "@videojs/utils/dom";
import Hls from "hls.js";
//#region src/dom/hls-js/text-tracks.ts
/**
* Bridges hls.js non-native text tracks to native `<track>` elements so the
* rest of the player can treat them like any other text track.
*
* When `renderTextTracksNatively: false`, hls.js fires
* `NON_NATIVE_TEXT_TRACKS_FOUND` with track metadata and `CUES_PARSED` with
* VTTCues. This mixin creates `<track>` elements on the media target and
* forwards cues into them. It also syncs user track-mode changes back to
* hls.js via `engine.subtitleTrack`.
*/
function HlsJsMediaTextTracksMixin(BaseClass) {
class HlsJsMediaTextTracks extends BaseClass {
#disconnect = null;
constructor(...args) {
super(...args);
this.engine?.on(Hls.Events.MANIFEST_LOADING, () => this.#init());
this.engine?.on(Hls.Events.MEDIA_ATTACHED, () => this.#init());
this.engine?.on(Hls.Events.MEDIA_DETACHED, () => this.#destroy());
this.engine?.on(Hls.Events.DESTROYING, () => this.#destroy());
}
#destroy() {
this.#disconnect?.abort();
this.#disconnect = null;
}
#init() {
this.#disconnect?.abort();
this.#disconnect = new AbortController();
const { signal } = this.#disconnect;
const { engine } = this;
if (!engine || !this.target) return;
const media = this.target;
const onTracksFound = (_event, data) => {
this.#clearTracks();
for (const trackObj of data.tracks) {
const baseTrackObj = trackObj.subtitleTrack ?? trackObj.closedCaptions;
const idx = engine.subtitleTracks.findIndex(({ lang, name, type }) => {
return lang === baseTrackObj?.lang && name === trackObj.label && type.toLowerCase() === trackObj.kind;
});
const id = trackObj._id ?? trackObj.default ? "default" : `${trackObj.kind}${idx}`;
addTextTrack(media, trackObj.kind, trackObj.label, baseTrackObj?.lang, id, trackObj.default);
}
};
const onCuesParsed = (_event, { track, cues }) => {
const textTrack = media.textTracks.getTrackById(track);
if (!textTrack) return;
const disabled = textTrack.mode === "disabled";
if (disabled) textTrack.mode = "hidden";
cues.forEach((cue) => {
if (textTrack.cues?.getCueById(cue.id)) return;
textTrack.addCue(cue);
});
if (disabled) textTrack.mode = "disabled";
};
const onTextTrackChange = () => {
if (!engine.subtitleTracks.length) return;
const showingTrack = Array.from(media.textTracks).find((textTrack) => {
return textTrack.id && textTrack.mode === "showing" && isCaptionOrSubtitleTrack(textTrack);
});
if (!showingTrack) return;
const currentHlsTrack = engine.subtitleTracks[engine.subtitleTrack];
const hlsTrackId = !currentHlsTrack ? void 0 : currentHlsTrack.default ? "default" : `${engine.subtitleTracks[engine.subtitleTrack]?.type.toLowerCase()}${engine.subtitleTrack}`;
if (engine.subtitleTrack < 0 || showingTrack?.id !== hlsTrackId) engine.subtitleTrack = engine.subtitleTracks.findIndex(({ lang, name, type, default: defaultTrack }) => {
return showingTrack.id === "default" && defaultTrack || lang === showingTrack.language && name === showingTrack.label && type.toLowerCase() === showingTrack.kind;
});
if (showingTrack?.id === hlsTrackId) {
if (showingTrack.cues) Array.from(showingTrack.cues).forEach((cue) => {
showingTrack.addCue(cue);
});
}
};
engine.on(Hls.Events.NON_NATIVE_TEXT_TRACKS_FOUND, onTracksFound);
engine.on(Hls.Events.CUES_PARSED, onCuesParsed);
listen(media.textTracks, "change", onTextTrackChange, { signal });
signal.addEventListener("abort", () => {
engine.off(Hls.Events.NON_NATIVE_TEXT_TRACKS_FOUND, onTracksFound);
engine.off(Hls.Events.CUES_PARSED, onCuesParsed);
this.#clearTracks();
}, { once: true });
}
#clearTracks() {
(this.target?.querySelectorAll?.("track[data-removeondestroy]") ?? []).forEach((trackEl) => trackEl.remove());
}
}
return HlsJsMediaTextTracks;
}
function addTextTrack(mediaEl, kind, label, lang, id, defaultTrack) {
const trackEl = document.createElement("track");
trackEl.kind = kind;
trackEl.label = label;
if (lang) trackEl.srclang = lang;
if (id) trackEl.id = id;
if (defaultTrack) trackEl.default = true;
trackEl.track.mode = isCaptionOrSubtitleTrack({ kind }) ? "disabled" : "hidden";
trackEl.setAttribute("data-removeondestroy", "");
mediaEl.append(trackEl);
return trackEl.track;
}
//#endregion
export { HlsJsMediaTextTracksMixin };
//# sourceMappingURL=text-tracks.js.map