UNPKG

21.2 kBSource Map (JSON)View Raw
1{"version":3,"file":"VideoResource.mjs","sources":["../../../src/textures/resources/VideoResource.ts"],"sourcesContent":["import { Ticker } from '@pixi/ticker';\nimport { BaseImageResource } from './BaseImageResource';\n\nimport type { Dict } from '@pixi/utils';\n\nexport interface IVideoResourceOptions\n{\n autoLoad?: boolean;\n autoPlay?: boolean;\n updateFPS?: number;\n crossorigin?: boolean | string;\n loop?: boolean;\n muted?: boolean;\n playsinline?: boolean;\n}\n\nexport interface IVideoResourceOptionsElement\n{\n src: string;\n mime: string;\n}\n\n/**\n * Resource type for {@link HTMLVideoElement}.\n * @memberof PIXI\n */\nexport class VideoResource extends BaseImageResource\n{\n /** Override the source to be the video element. */\n public source: HTMLVideoElement;\n\n /**\n * `true` to use Ticker.shared to auto update the base texture.\n * @default true\n */\n protected _autoUpdate: boolean;\n\n /**\n * `true` if the instance is currently connected to PIXI.Ticker.shared to auto update the base texture.\n * @default false\n */\n protected _isConnectedToTicker: boolean;\n protected _updateFPS: number;\n protected _msToNextUpdate: number;\n\n private _videoFrameRequestCallbackHandle: number | null;\n\n /**\n * When set to true will automatically play videos used by this texture once\n * they are loaded. If false, it will not modify the playing state.\n * @default true\n */\n protected autoPlay: boolean;\n\n /**\n * Promise when loading.\n * @default null\n */\n private _load: Promise<this>;\n\n /** Callback when completed with load. */\n private _resolve: (value?: this | PromiseLike<this>) => void;\n private _reject: (error: ErrorEvent) => void;\n\n /**\n * @param {HTMLVideoElement|object|string|Array<string|object>} source - Video element to use.\n * @param {object} [options] - Options to use\n * @param {boolean} [options.autoLoad=true] - Start loading the video immediately\n * @param {boolean} [options.autoPlay=true] - Start playing video immediately\n * @param {number} [options.updateFPS=0] - How many times a second to update the texture from the video.\n * If 0, `requestVideoFrameCallback` is used to update the texture.\n * If `requestVideoFrameCallback` is not available, the texture is updated every render.\n * @param {boolean} [options.crossorigin=true] - Load image using cross origin\n * @param {boolean} [options.loop=false] - Loops the video\n * @param {boolean} [options.muted=false] - Mutes the video audio, useful for autoplay\n * @param {boolean} [options.playsinline=true] - Prevents opening the video on mobile devices\n */\n constructor(\n source?: HTMLVideoElement | Array<string | IVideoResourceOptionsElement> | string, options?: IVideoResourceOptions\n )\n {\n options = options || {};\n\n if (!(source instanceof HTMLVideoElement))\n {\n const videoElement = document.createElement('video');\n\n // workaround for https://github.com/pixijs/pixijs/issues/5996\n if (options.autoLoad !== false)\n {\n videoElement.setAttribute('preload', 'auto');\n }\n\n if (options.playsinline !== false)\n {\n videoElement.setAttribute('webkit-playsinline', '');\n videoElement.setAttribute('playsinline', '');\n }\n\n if (options.muted === true)\n {\n // For some reason we need to set both muted flags for chrome to autoplay\n // https://stackoverflow.com/a/51189390\n\n videoElement.setAttribute('muted', '');\n videoElement.muted = true;\n }\n\n if (options.loop === true)\n {\n videoElement.setAttribute('loop', '');\n }\n\n if (options.autoPlay !== false)\n {\n videoElement.setAttribute('autoplay', '');\n }\n\n if (typeof source === 'string')\n {\n source = [source];\n }\n\n const firstSrc = (source[0] as IVideoResourceOptionsElement).src || source[0] as string;\n\n BaseImageResource.crossOrigin(videoElement, firstSrc, options.crossorigin);\n\n // array of objects or strings\n for (let i = 0; i < source.length; ++i)\n {\n const sourceElement = document.createElement('source');\n\n let { src, mime } = source[i] as IVideoResourceOptionsElement;\n\n src = src || source[i] as string;\n\n if (src.startsWith('data:'))\n {\n mime = src.slice(5, src.indexOf(';'));\n }\n else if (!src.startsWith('blob:'))\n {\n const baseSrc = src.split('?').shift().toLowerCase();\n const ext = baseSrc.slice(baseSrc.lastIndexOf('.') + 1);\n\n mime = mime || VideoResource.MIME_TYPES[ext] || `video/${ext}`;\n }\n\n sourceElement.src = src;\n\n if (mime)\n {\n sourceElement.type = mime;\n }\n\n videoElement.appendChild(sourceElement);\n }\n\n // Override the source\n source = videoElement;\n }\n\n super(source);\n\n this.noSubImage = true;\n\n this._autoUpdate = true;\n this._isConnectedToTicker = false;\n\n this._updateFPS = options.updateFPS || 0;\n this._msToNextUpdate = 0;\n this.autoPlay = options.autoPlay !== false;\n\n this._videoFrameRequestCallback = this._videoFrameRequestCallback.bind(this);\n this._videoFrameRequestCallbackHandle = null;\n\n this._load = null;\n this._resolve = null;\n this._reject = null;\n\n // Bind for listeners\n this._onCanPlay = this._onCanPlay.bind(this);\n this._onError = this._onError.bind(this);\n this._onPlayStart = this._onPlayStart.bind(this);\n this._onPlayStop = this._onPlayStop.bind(this);\n this._onSeeked = this._onSeeked.bind(this);\n\n if (options.autoLoad !== false)\n {\n this.load();\n }\n }\n\n /**\n * Trigger updating of the texture.\n * @param _deltaTime - time delta since last tick\n */\n update(_deltaTime = 0): void\n {\n if (!this.destroyed)\n {\n if (this._updateFPS)\n {\n // account for if video has had its playbackRate changed\n const elapsedMS = Ticker.shared.elapsedMS * (this.source as HTMLVideoElement).playbackRate;\n\n this._msToNextUpdate = Math.floor(this._msToNextUpdate - elapsedMS);\n }\n\n if (!this._updateFPS || this._msToNextUpdate <= 0)\n {\n super.update(/* deltaTime*/);\n this._msToNextUpdate = this._updateFPS ? Math.floor(1000 / this._updateFPS) : 0;\n }\n }\n }\n\n private _videoFrameRequestCallback(): void\n {\n this.update();\n\n if (!this.destroyed)\n {\n this._videoFrameRequestCallbackHandle = (this.source as any).requestVideoFrameCallback(\n this._videoFrameRequestCallback);\n }\n else\n {\n this._videoFrameRequestCallbackHandle = null;\n }\n }\n\n /**\n * Start preloading the video resource.\n * @returns {Promise<void>} Handle the validate event\n */\n load(): Promise<this>\n {\n if (this._load)\n {\n return this._load;\n }\n\n const source = this.source as HTMLVideoElement;\n\n if ((source.readyState === source.HAVE_ENOUGH_DATA || source.readyState === source.HAVE_FUTURE_DATA)\n && source.width && source.height)\n {\n (source as any).complete = true;\n }\n\n source.addEventListener('play', this._onPlayStart);\n source.addEventListener('pause', this._onPlayStop);\n source.addEventListener('seeked', this._onSeeked);\n\n if (!this._isSourceReady())\n {\n source.addEventListener('canplay', this._onCanPlay);\n source.addEventListener('canplaythrough', this._onCanPlay);\n source.addEventListener('error', this._onError, true);\n }\n else\n {\n this._onCanPlay();\n }\n\n this._load = new Promise((resolve, reject): void =>\n {\n if (this.valid)\n {\n resolve(this);\n }\n else\n {\n this._resolve = resolve;\n this._reject = reject;\n\n source.load();\n }\n });\n\n return this._load;\n }\n\n /**\n * Handle video error events.\n * @param event\n */\n private _onError(event: ErrorEvent): void\n {\n (this.source as HTMLVideoElement).removeEventListener('error', this._onError, true);\n this.onError.emit(event);\n\n if (this._reject)\n {\n this._reject(event);\n this._reject = null;\n this._resolve = null;\n }\n }\n\n /**\n * Returns true if the underlying source is playing.\n * @returns - True if playing.\n */\n private _isSourcePlaying(): boolean\n {\n const source = this.source as HTMLVideoElement;\n\n return (!source.paused && !source.ended);\n }\n\n /**\n * Returns true if the underlying source is ready for playing.\n * @returns - True if ready.\n */\n private _isSourceReady(): boolean\n {\n const source = this.source as HTMLVideoElement;\n\n return source.readyState > 2;\n }\n\n /** Runs the update loop when the video is ready to play. */\n private _onPlayStart(): void\n {\n // Just in case the video has not received its can play even yet..\n if (!this.valid)\n {\n this._onCanPlay();\n }\n\n this._configureAutoUpdate();\n }\n\n /** Fired when a pause event is triggered, stops the update loop. */\n private _onPlayStop(): void\n {\n this._configureAutoUpdate();\n }\n\n /** Fired when the video is completed seeking to the current playback position. */\n private _onSeeked(): void\n {\n if (this._autoUpdate && !this._isSourcePlaying())\n {\n this._msToNextUpdate = 0;\n this.update();\n this._msToNextUpdate = 0;\n }\n }\n\n /** Fired when the video is loaded and ready to play. */\n private _onCanPlay(): void\n {\n const source = this.source as HTMLVideoElement;\n\n source.removeEventListener('canplay', this._onCanPlay);\n source.removeEventListener('canplaythrough', this._onCanPlay);\n\n const valid = this.valid;\n\n this._msToNextUpdate = 0;\n this.update();\n this._msToNextUpdate = 0;\n\n // prevent multiple loaded dispatches..\n if (!valid && this._resolve)\n {\n this._resolve(this);\n this._resolve = null;\n this._reject = null;\n }\n\n if (this._isSourcePlaying())\n {\n this._onPlayStart();\n }\n else if (this.autoPlay)\n {\n source.play();\n }\n }\n\n /** Destroys this texture. */\n dispose(): void\n {\n this._configureAutoUpdate();\n\n const source = this.source as HTMLVideoElement;\n\n if (source)\n {\n source.removeEventListener('play', this._onPlayStart);\n source.removeEventListener('pause', this._onPlayStop);\n source.removeEventListener('seeked', this._onSeeked);\n source.removeEventListener('canplay', this._onCanPlay);\n source.removeEventListener('canplaythrough', this._onCanPlay);\n source.removeEventListener('error', this._onError, true);\n source.pause();\n source.src = '';\n source.load();\n }\n super.dispose();\n }\n\n /** Should the base texture automatically update itself, set to true by default. */\n get autoUpdate(): boolean\n {\n return this._autoUpdate;\n }\n\n set autoUpdate(value: boolean)\n {\n if (value !== this._autoUpdate)\n {\n this._autoUpdate = value;\n this._configureAutoUpdate();\n }\n }\n\n /**\n * How many times a second to update the texture from the video. If 0, `requestVideoFrameCallback` is used to\n * update the texture. If `requestVideoFrameCallback` is not available, the texture is updated every render.\n * A lower fps can help performance, as updating the texture at 60fps on a 30ps video may not be efficient.\n */\n get updateFPS(): number\n {\n return this._updateFPS;\n }\n\n set updateFPS(value: number)\n {\n if (value !== this._updateFPS)\n {\n this._updateFPS = value;\n this._configureAutoUpdate();\n }\n }\n\n private _configureAutoUpdate(): void\n {\n if (this._autoUpdate && this._isSourcePlaying())\n {\n if (!this._updateFPS && (this.source as any).requestVideoFrameCallback)\n {\n if (this._isConnectedToTicker)\n {\n Ticker.shared.remove(this.update, this);\n this._isConnectedToTicker = false;\n this._msToNextUpdate = 0;\n }\n\n if (this._videoFrameRequestCallbackHandle === null)\n {\n this._videoFrameRequestCallbackHandle = (this.source as any).requestVideoFrameCallback(\n this._videoFrameRequestCallback);\n }\n }\n else\n {\n if (this._videoFrameRequestCallbackHandle !== null)\n {\n (this.source as any).cancelVideoFrameCallback(this._videoFrameRequestCallbackHandle);\n this._videoFrameRequestCallbackHandle = null;\n }\n\n if (!this._isConnectedToTicker)\n {\n Ticker.shared.add(this.update, this);\n this._isConnectedToTicker = true;\n this._msToNextUpdate = 0;\n }\n }\n }\n else\n {\n if (this._videoFrameRequestCallbackHandle !== null)\n {\n (this.source as any).cancelVideoFrameCallback(this._videoFrameRequestCallbackHandle);\n this._videoFrameRequestCallbackHandle = null;\n }\n\n if (this._isConnectedToTicker)\n {\n Ticker.shared.remove(this.update, this);\n this._isConnectedToTicker = false;\n this._msToNextUpdate = 0;\n }\n }\n }\n\n /**\n * Used to auto-detect the type of resource.\n * @param {*} source - The source object\n * @param {string} extension - The extension of source, if set\n * @returns {boolean} `true` if video source\n */\n static test(source: unknown, extension?: string): source is HTMLVideoElement\n {\n return (globalThis.HTMLVideoElement && source instanceof HTMLVideoElement)\n || VideoResource.TYPES.includes(extension);\n }\n\n /**\n * List of common video file extensions supported by VideoResource.\n * @readonly\n */\n static TYPES: Array<string> = ['mp4', 'm4v', 'webm', 'ogg', 'ogv', 'h264', 'avi', 'mov'];\n\n /**\n * Map of video MIME types that can't be directly derived from file extensions.\n * @readonly\n */\n static MIME_TYPES: Dict<string> = {\n ogv: 'video/ogg',\n mov: 'video/quicktime',\n m4v: 'video/mp4',\n };\n}\n"],"names":["_VideoResource"],"mappings":"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"}
\No newline at end of file