hls-video-element
Version:
Custom element (web component) for playing video using the HTTP Live Streaming (HLS) format. Uses HLS.js.
256 lines (255 loc) • 10.1 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var hls_video_element_exports = {};
__export(hls_video_element_exports, {
Hls: () => import_hls.default,
HlsVideoElement: () => HlsVideoElement,
HlsVideoMixin: () => HlsVideoMixin,
default: () => hls_video_element_default
});
module.exports = __toCommonJS(hls_video_element_exports);
var import_custom_media_element = require("custom-media-element");
var import_media_tracks = require("media-tracks");
var import_hls = __toESM(require("hls.js/dist/hls.mjs"), 1);
const HlsVideoMixin = (superclass) => {
return class HlsVideo extends superclass {
static shadowRootOptions = { ...superclass.shadowRootOptions };
static getTemplateHTML = (attrs, props = {}) => {
const { src, ...rest } = attrs;
return `
<script type="application/json" id="config">
${JSON.stringify(props.config || {})}
</script>
${superclass.getTemplateHTML(rest)}
`;
};
#airplaySourceEl = null;
#config = null;
constructor() {
super();
this.#upgradeProperty("config");
}
get config() {
return this.#config;
}
set config(value) {
this.#config = value;
}
attributeChangedCallback(attrName, oldValue, newValue) {
if (attrName !== "src") {
super.attributeChangedCallback(attrName, oldValue, newValue);
}
if (attrName === "src" && oldValue != newValue) {
this.load();
}
}
#destroy() {
var _a, _b;
(_a = this.#airplaySourceEl) == null ? void 0 : _a.remove();
(_b = this.nativeEl) == null ? void 0 : _b.removeEventListener(
"webkitcurrentplaybacktargetiswirelesschanged",
this.#toggleHlsLoad
);
if (this.api) {
this.api.detachMedia();
this.api.destroy();
this.api = null;
}
}
async load() {
var _a, _b;
const isFirstLoad = !this.api;
this.#destroy();
if (!this.src) {
return;
}
if (isFirstLoad && !this.#config) {
this.#config = JSON.parse(((_a = this.shadowRoot.getElementById("config")) == null ? void 0 : _a.textContent) || "{}");
}
if (import_hls.default.isSupported()) {
this.api = new import_hls.default({
// Mimic the media element with an Infinity duration for live streams.
liveDurationInfinity: true,
// Disable auto quality level/fragment loading.
autoStartLoad: false,
// Custom configuration for hls.js.
...this.config
});
await Promise.resolve();
this.api.loadSource(this.src);
this.api.attachMedia(this.nativeEl);
switch (this.nativeEl.preload) {
case "none": {
const loadSourceOnPlay = () => this.api.startLoad();
this.nativeEl.addEventListener("play", loadSourceOnPlay, {
once: true
});
this.api.on(import_hls.default.Events.DESTROYING, () => {
this.nativeEl.removeEventListener("play", loadSourceOnPlay);
});
break;
}
case "metadata": {
const originalLength = this.api.config.maxBufferLength;
const originalSize = this.api.config.maxBufferSize;
this.api.config.maxBufferLength = 1;
this.api.config.maxBufferSize = 1;
const increaseBufferOnPlay = () => {
this.api.config.maxBufferLength = originalLength;
this.api.config.maxBufferSize = originalSize;
};
this.nativeEl.addEventListener("play", increaseBufferOnPlay, {
once: true
});
this.api.on(import_hls.default.Events.DESTROYING, () => {
this.nativeEl.removeEventListener("play", increaseBufferOnPlay);
});
this.api.startLoad();
break;
}
default:
this.api.startLoad();
}
if (this.nativeEl.webkitCurrentPlaybackTargetIsWireless) {
this.api.stopLoad();
}
this.nativeEl.addEventListener(
"webkitcurrentplaybacktargetiswirelesschanged",
this.#toggleHlsLoad
);
this.#airplaySourceEl = document.createElement("source");
this.#airplaySourceEl.setAttribute("type", "application/x-mpegURL");
this.#airplaySourceEl.setAttribute("src", this.src);
this.nativeEl.disableRemotePlayback = false;
this.nativeEl.append(this.#airplaySourceEl);
const levelIdMap = /* @__PURE__ */ new WeakMap();
this.api.on(import_hls.default.Events.MANIFEST_PARSED, (event, data) => {
if (this.nativeEl.autoplay && this.nativeEl.paused) {
this.nativeEl.play().catch((err) => {
console.warn("Autoplay failed:", err);
});
}
removeAllMediaTracks();
let videoTrack = this.videoTracks.getTrackById("main");
if (!videoTrack) {
videoTrack = this.addVideoTrack("main");
videoTrack.id = "main";
videoTrack.selected = true;
}
for (const [id, level] of data.levels.entries()) {
const videoRendition = videoTrack.addRendition(
level.url[0],
level.width,
level.height,
level.videoCodec,
level.bitrate
);
levelIdMap.set(level, `${id}`);
videoRendition.id = `${id}`;
}
for (let [id, a] of data.audioTracks.entries()) {
const kind = a.default ? "main" : "alternative";
const audioTrack = this.addAudioTrack(kind, a.name, a.lang);
audioTrack.id = `${id}`;
if (a.default) {
audioTrack.enabled = true;
}
}
});
this.audioTracks.addEventListener("change", () => {
var _a2;
const audioTrackId = +((_a2 = [...this.audioTracks].find((t) => t.enabled)) == null ? void 0 : _a2.id);
const availableIds = this.api.audioTracks.map((t) => t.id);
if (audioTrackId != this.api.audioTrack && availableIds.includes(audioTrackId)) {
this.api.audioTrack = audioTrackId;
}
});
this.api.on(import_hls.default.Events.LEVELS_UPDATED, (event, data) => {
const videoTrack = this.videoTracks[this.videoTracks.selectedIndex ?? 0];
if (!videoTrack) return;
const levelIds = data.levels.map((l) => levelIdMap.get(l));
for (const rendition of this.videoRenditions) {
if (rendition.id && !levelIds.includes(rendition.id)) {
videoTrack.removeRendition(rendition);
}
}
});
const switchRendition = (event) => {
const level = event.target.selectedIndex;
if (level != this.api.nextLevel) {
this.api.nextLevel = level;
}
};
(_b = this.videoRenditions) == null ? void 0 : _b.addEventListener("change", switchRendition);
const removeAllMediaTracks = () => {
for (const videoTrack of this.videoTracks) {
this.removeVideoTrack(videoTrack);
}
for (const audioTrack of this.audioTracks) {
this.removeAudioTrack(audioTrack);
}
};
this.api.once(import_hls.default.Events.DESTROYING, removeAllMediaTracks);
return;
}
await Promise.resolve();
if (this.nativeEl.canPlayType("application/vnd.apple.mpegurl")) {
this.nativeEl.src = this.src;
}
}
#toggleHlsLoad = () => {
var _a, _b, _c;
if ((_a = this.nativeEl) == null ? void 0 : _a.webkitCurrentPlaybackTargetIsWireless) {
(_b = this.api) == null ? void 0 : _b.stopLoad();
} else {
(_c = this.api) == null ? void 0 : _c.startLoad();
}
};
// This is a pattern to update property values that are set before
// the custom element is upgraded.
// https://web.dev/custom-elements-best-practices/#make-properties-lazy
#upgradeProperty(prop) {
if (Object.prototype.hasOwnProperty.call(this, prop)) {
const value = this[prop];
delete this[prop];
this[prop] = value;
}
}
};
};
const HlsVideoElement = HlsVideoMixin((0, import_media_tracks.MediaTracksMixin)(import_custom_media_element.CustomVideoElement));
if (globalThis.customElements && !globalThis.customElements.get("hls-video")) {
globalThis.customElements.define("hls-video", HlsVideoElement);
}
var hls_video_element_default = HlsVideoElement;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Hls,
HlsVideoElement,
HlsVideoMixin
});