UNPKG

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
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 });