@pixi/core
Version:
Core PixiJS
164 lines (163 loc) • 9.68 kB
JavaScript
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