import TextureResource from './TextureResource'; import * as ticker from '../../ticker'; export default class VideoResource extends TextureResource { constructor(source) { super(source); this._autoUpdate = true; this._isAutoUpdating = false; /** * When set to true will automatically play videos used by this texture once * they are loaded. If false, it will not modify the playing state. * * @member {boolean} * @default true */ this.autoPlay = true; this.update = this.update.bind(this); this._onCanPlay = this._onCanPlay.bind(this); if ((source.readyState === source.HAVE_ENOUGH_DATA || source.readyState === source.HAVE_FUTURE_DATA) && source.width && source.height) { source.complete = true; } source.addEventListener('play', this._onPlayStart.bind(this)); source.addEventListener('pause', this._onPlayStop.bind(this)); if (!this._isSourceReady()) { source.addEventListener('canplay', this._onCanPlay); source.addEventListener('canplaythrough', this._onCanPlay); } else { this._onCanPlay(); } this.load = new Promise((resolve) => { this.resolve = resolve; if (this.loaded) { this.resolve(this); } }); } update() { // TODO - slow down and base on the videos framerate this.resourceUpdated.emit(); } /** * Returns true if the underlying source is playing. * * @private * @return {boolean} True if playing. */ _isSourcePlaying() { const source = this.source; return (source.currentTime > 0 && source.paused === false && source.ended === false && source.readyState > 2); } /** * Returns true if the underlying source is ready for playing. * * @private * @return {boolean} True if ready. */ _isSourceReady() { return this.source.readyState === 3 || this.source.readyState === 4; } /** * Runs the update loop when the video is ready to play * * @private */ _onPlayStart() { // Just in case the video has not received its can play even yet.. if (!this.loaded) { this._onCanPlay(); } if (!this._isAutoUpdating && this.autoUpdate) { ticker.shared.add(this.update, this); this._isAutoUpdating = true; } } /** * Fired when a pause event is triggered, stops the update loop * * @private */ _onPlayStop() { if (this._isAutoUpdating) { ticker.shared.remove(this.update, this); this._isAutoUpdating = false; } } /** * Fired when the video is loaded and ready to play * * @private */ _onCanPlay() { if (this.source) { this.source.removeEventListener('canplay', this._onCanPlay); this.source.removeEventListener('canplaythrough', this._onCanPlay); this.width = this.source.videoWidth; this.height = this.source.videoHeight; // prevent multiple loaded dispatches.. if (!this.loaded) { this.loaded = true; if (this.resolve) { this.resolve(this); } } if (this._isSourcePlaying()) { this._onPlayStart(); } else if (this.autoPlay) { this.source.play(); } } } /** * Destroys this texture * */ destroy() { if (this._isAutoUpdating) { ticker.shared.remove(this.update, this); } /* if (this.source && this.source._pixiId) { delete BaseTextureCache[this.source._pixiId]; delete this.source._pixiId; } */ // super.destroy(); } /** * Should the base texture automatically update itself, set to true by default * * @member {boolean} */ get autoUpdate() { return this._autoUpdate; } set autoUpdate(value) // eslint-disable-line require-jsdoc { if (value !== this._autoUpdate) { this._autoUpdate = value; if (!this._autoUpdate && this._isAutoUpdating) { ticker.shared.remove(this.update, this); this._isAutoUpdating = false; } else if (this._autoUpdate && !this._isAutoUpdating) { ticker.shared.add(this.update, this); this._isAutoUpdating = true; } } } /** * Helper function that creates a new BaseTexture based on the given video element. * This BaseTexture can then be used to create a texture * * @static * @param {string|object|string[]|object[]} videoSrc - The URL(s) for the video. * @param {string} [videoSrc.src] - One of the source urls for the video * @param {string} [videoSrc.mime] - The mimetype of the video (e.g. 'video/mp4'). If not specified * the url's extension will be used as the second part of the mime type. * @param {number} scaleMode - See {@link PIXI.SCALE_MODES} for possible values * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture */ static fromUrl(videoSrc, scaleMode) { const video = document.createElement('video'); video.setAttribute('webkit-playsinline', ''); video.setAttribute('playsinline', ''); // array of objects or strings if (Array.isArray(videoSrc)) { for (let i = 0; i < videoSrc.length; ++i) { video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); } } // single object or string else { video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); } video.load(); return new VideoResource(video, scaleMode); } } function createSource(path, type) { if (!type) { type = `video/${path.substr(path.lastIndexOf('.') + 1)}`; } const source = document.createElement('source'); source.src = path; source.type = type; return source; }