mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
104 lines
4.4 KiB
JavaScript
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
|