UNPKG

@pixi/core

Version:
164 lines (163 loc) 9.68 kB
import { Ticker } from "@pixi/ticker"; import { BaseImageResource } from "./BaseImageResource.mjs"; const _VideoResource = class _VideoResource2 extends BaseImageResource { /** * @param {HTMLVideoElement|object|string|Array<string|object>} source - Video element to use. * @param {object} [options] - Options to use * @param {boolean} [options.autoLoad=true] - Start loading the video immediately * @param {boolean} [options.autoPlay=true] - Start playing video immediately * @param {number} [options.updateFPS=0] - How many times a second to update the texture from the video. * If 0, `requestVideoFrameCallback` is used to update the texture. * If `requestVideoFrameCallback` is not available, the texture is updated every render. * @param {boolean} [options.crossorigin=true] - Load image using cross origin * @param {boolean} [options.loop=false] - Loops the video * @param {boolean} [options.muted=false] - Mutes the video audio, useful for autoplay * @param {boolean} [options.playsinline=true] - Prevents opening the video on mobile devices */ constructor(source, options) { if (options = options || {}, !(source instanceof HTMLVideoElement)) { const videoElement = document.createElement("video"); options.autoLoad !== !1 && videoElement.setAttribute("preload", "auto"), options.playsinline !== !1 && (videoElement.setAttribute("webkit-playsinline", ""), videoElement.setAttribute("playsinline", "")), options.muted === !0 && (videoElement.setAttribute("muted", ""), videoElement.muted = !0), options.loop === !0 && videoElement.setAttribute("loop", ""), options.autoPlay !== !1 && videoElement.setAttribute("autoplay", ""), typeof source == "string" && (source = [source]); const firstSrc = source[0].src || source[0]; BaseImageResource.crossOrigin(videoElement, firstSrc, options.crossorigin); for (let i = 0; i < source.length; ++i) { const sourceElement = document.createElement("source"); let { src, mime } = source[i]; if (src = src || source[i], src.startsWith("data:")) mime = src.slice(5, src.indexOf(";")); else if (!src.startsWith("blob:")) { const baseSrc = src.split("?").shift().toLowerCase(), ext = baseSrc.slice(baseSrc.lastIndexOf(".") + 1); mime = mime || _VideoResource2.MIME_TYPES[ext] || `video/${ext}`; } sourceElement.src = src, mime && (sourceElement.type = mime), videoElement.appendChild(sourceElement); } source = videoElement; } super(source), this.noSubImage = !0, this._autoUpdate = !0, this._isConnectedToTicker = !1, this._updateFPS = options.updateFPS || 0, this._msToNextUpdate = 0, this.autoPlay = options.autoPlay !== !1, this._videoFrameRequestCallback = this._videoFrameRequestCallback.bind(this), this._videoFrameRequestCallbackHandle = null, this._load = null, this._resolve = null, this._reject = null, this._onCanPlay = this._onCanPlay.bind(this), this._onError = this._onError.bind(this), this._onPlayStart = this._onPlayStart.bind(this), this._onPlayStop = this._onPlayStop.bind(this), this._onSeeked = this._onSeeked.bind(this), options.autoLoad !== !1 && this.load(); } /** * Trigger updating of the texture. * @param _deltaTime - time delta since last tick */ update(_deltaTime = 0) { if (!this.destroyed) { if (this._updateFPS) { const elapsedMS = Ticker.shared.elapsedMS * this.source.playbackRate; this._msToNextUpdate = Math.floor(this._msToNextUpdate - elapsedMS); } (!this._updateFPS || this._msToNextUpdate <= 0) && (super.update( /* deltaTime*/ ), this._msToNextUpdate = this._updateFPS ? Math.floor(1e3 / this._updateFPS) : 0); } } _videoFrameRequestCallback() { this.update(), this.destroyed ? this._videoFrameRequestCallbackHandle = null : this._videoFrameRequestCallbackHandle = this.source.requestVideoFrameCallback( this._videoFrameRequestCallback ); } /** * Start preloading the video resource. * @returns {Promise<void>} Handle the validate event */ load() { if (this._load) return this._load; const source = this.source; return (source.readyState === source.HAVE_ENOUGH_DATA || source.readyState === source.HAVE_FUTURE_DATA) && source.width && source.height && (source.complete = !0), source.addEventListener("play", this._onPlayStart), source.addEventListener("pause", this._onPlayStop), source.addEventListener("seeked", this._onSeeked), this._isSourceReady() ? this._onCanPlay() : (source.addEventListener("canplay", this._onCanPlay), source.addEventListener("canplaythrough", this._onCanPlay), source.addEventListener("error", this._onError, !0)), this._load = new Promise((resolve, reject) => { this.valid ? resolve(this) : (this._resolve = resolve, this._reject = reject, source.load()); }), this._load; } /** * Handle video error events. * @param event */ _onError(event) { this.source.removeEventListener("error", this._onError, !0), this.onError.emit(event), this._reject && (this._reject(event), this._reject = null, this._resolve = null); } /** * Returns true if the underlying source is playing. * @returns - True if playing. */ _isSourcePlaying() { const source = this.source; return !source.paused && !source.ended; } /** * Returns true if the underlying source is ready for playing. * @returns - True if ready. */ _isSourceReady() { return this.source.readyState > 2; } /** Runs the update loop when the video is ready to play. */ _onPlayStart() { this.valid || this._onCanPlay(), this._configureAutoUpdate(); } /** Fired when a pause event is triggered, stops the update loop. */ _onPlayStop() { this._configureAutoUpdate(); } /** Fired when the video is completed seeking to the current playback position. */ _onSeeked() { this._autoUpdate && !this._isSourcePlaying() && (this._msToNextUpdate = 0, this.update(), this._msToNextUpdate = 0); } /** Fired when the video is loaded and ready to play. */ _onCanPlay() { const source = this.source; source.removeEventListener("canplay", this._onCanPlay), source.removeEventListener("canplaythrough", this._onCanPlay); const valid = this.valid; this._msToNextUpdate = 0, this.update(), this._msToNextUpdate = 0, !valid && this._resolve && (this._resolve(this), this._resolve = null, this._reject = null), this._isSourcePlaying() ? this._onPlayStart() : this.autoPlay && source.play(); } /** Destroys this texture. */ dispose() { this._configureAutoUpdate(); const source = this.source; source && (source.removeEventListener("play", this._onPlayStart), source.removeEventListener("pause", this._onPlayStop), source.removeEventListener("seeked", this._onSeeked), source.removeEventListener("canplay", this._onCanPlay), source.removeEventListener("canplaythrough", this._onCanPlay), source.removeEventListener("error", this._onError, !0), source.pause(), source.src = "", source.load()), super.dispose(); } /** Should the base texture automatically update itself, set to true by default. */ get autoUpdate() { return this._autoUpdate; } set autoUpdate(value) { value !== this._autoUpdate && (this._autoUpdate = value, this._configureAutoUpdate()); } /** * How many times a second to update the texture from the video. If 0, `requestVideoFrameCallback` is used to * update the texture. If `requestVideoFrameCallback` is not available, the texture is updated every render. * A lower fps can help performance, as updating the texture at 60fps on a 30ps video may not be efficient. */ get updateFPS() { return this._updateFPS; } set updateFPS(value) { value !== this._updateFPS && (this._updateFPS = value, this._configureAutoUpdate()); } _configureAutoUpdate() { this._autoUpdate && this._isSourcePlaying() ? !this._updateFPS && this.source.requestVideoFrameCallback ? (this._isConnectedToTicker && (Ticker.shared.remove(this.update, this), this._isConnectedToTicker = !1, this._msToNextUpdate = 0), this._videoFrameRequestCallbackHandle === null && (this._videoFrameRequestCallbackHandle = this.source.requestVideoFrameCallback( this._videoFrameRequestCallback ))) : (this._videoFrameRequestCallbackHandle !== null && (this.source.cancelVideoFrameCallback(this._videoFrameRequestCallbackHandle), this._videoFrameRequestCallbackHandle = null), this._isConnectedToTicker || (Ticker.shared.add(this.update, this), this._isConnectedToTicker = !0, this._msToNextUpdate = 0)) : (this._videoFrameRequestCallbackHandle !== null && (this.source.cancelVideoFrameCallback(this._videoFrameRequestCallbackHandle), this._videoFrameRequestCallbackHandle = null), this._isConnectedToTicker && (Ticker.shared.remove(this.update, this), this._isConnectedToTicker = !1, this._msToNextUpdate = 0)); } /** * Used to auto-detect the type of resource. * @param {*} source - The source object * @param {string} extension - The extension of source, if set * @returns {boolean} `true` if video source */ static test(source, extension) { return globalThis.HTMLVideoElement && source instanceof HTMLVideoElement || _VideoResource2.TYPES.includes(extension); } }; _VideoResource.TYPES = ["mp4", "m4v", "webm", "ogg", "ogv", "h264", "avi", "mov"], /** * Map of video MIME types that can't be directly derived from file extensions. * @readonly */ _VideoResource.MIME_TYPES = { ogv: "video/ogg", mov: "video/quicktime", m4v: "video/mp4" }; let VideoResource = _VideoResource; export { VideoResource }; //# sourceMappingURL=VideoResource.mjs.map