UNPKG

9.76 kBJavaScriptView Raw
1"use strict";
2var ticker = require("@pixi/ticker"), BaseImageResource = require("./BaseImageResource.js");
3const _VideoResource = class _VideoResource2 extends BaseImageResource.BaseImageResource {
4 /**
5 * @param {HTMLVideoElement|object|string|Array<string|object>} source - Video element to use.
6 * @param {object} [options] - Options to use
7 * @param {boolean} [options.autoLoad=true] - Start loading the video immediately
8 * @param {boolean} [options.autoPlay=true] - Start playing video immediately
9 * @param {number} [options.updateFPS=0] - How many times a second to update the texture from the video.
10 * If 0, `requestVideoFrameCallback` is used to update the texture.
11 * If `requestVideoFrameCallback` is not available, the texture is updated every render.
12 * @param {boolean} [options.crossorigin=true] - Load image using cross origin
13 * @param {boolean} [options.loop=false] - Loops the video
14 * @param {boolean} [options.muted=false] - Mutes the video audio, useful for autoplay
15 * @param {boolean} [options.playsinline=true] - Prevents opening the video on mobile devices
16 */
17 constructor(source, options) {
18 if (options = options || {}, !(source instanceof HTMLVideoElement)) {
19 const videoElement = document.createElement("video");
20 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]);
21 const firstSrc = source[0].src || source[0];
22 BaseImageResource.BaseImageResource.crossOrigin(videoElement, firstSrc, options.crossorigin);
23 for (let i = 0; i < source.length; ++i) {
24 const sourceElement = document.createElement("source");
25 let { src, mime } = source[i];
26 if (src = src || source[i], src.startsWith("data:"))
27 mime = src.slice(5, src.indexOf(";"));
28 else if (!src.startsWith("blob:")) {
29 const baseSrc = src.split("?").shift().toLowerCase(), ext = baseSrc.slice(baseSrc.lastIndexOf(".") + 1);
30 mime = mime || _VideoResource2.MIME_TYPES[ext] || `video/${ext}`;
31 }
32 sourceElement.src = src, mime && (sourceElement.type = mime), videoElement.appendChild(sourceElement);
33 }
34 source = videoElement;
35 }
36 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();
37 }
38 /**
39 * Trigger updating of the texture.
40 * @param _deltaTime - time delta since last tick
41 */
42 update(_deltaTime = 0) {
43 if (!this.destroyed) {
44 if (this._updateFPS) {
45 const elapsedMS = ticker.Ticker.shared.elapsedMS * this.source.playbackRate;
46 this._msToNextUpdate = Math.floor(this._msToNextUpdate - elapsedMS);
47 }
48 (!this._updateFPS || this._msToNextUpdate <= 0) && (super.update(
49 /* deltaTime*/
50 ), this._msToNextUpdate = this._updateFPS ? Math.floor(1e3 / this._updateFPS) : 0);
51 }
52 }
53 _videoFrameRequestCallback() {
54 this.update(), this.destroyed ? this._videoFrameRequestCallbackHandle = null : this._videoFrameRequestCallbackHandle = this.source.requestVideoFrameCallback(
55 this._videoFrameRequestCallback
56 );
57 }
58 /**
59 * Start preloading the video resource.
60 * @returns {Promise<void>} Handle the validate event
61 */
62 load() {
63 if (this._load)
64 return this._load;
65 const source = this.source;
66 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) => {
67 this.valid ? resolve(this) : (this._resolve = resolve, this._reject = reject, source.load());
68 }), this._load;
69 }
70 /**
71 * Handle video error events.
72 * @param event
73 */
74 _onError(event) {
75 this.source.removeEventListener("error", this._onError, !0), this.onError.emit(event), this._reject && (this._reject(event), this._reject = null, this._resolve = null);
76 }
77 /**
78 * Returns true if the underlying source is playing.
79 * @returns - True if playing.
80 */
81 _isSourcePlaying() {
82 const source = this.source;
83 return !source.paused && !source.ended;
84 }
85 /**
86 * Returns true if the underlying source is ready for playing.
87 * @returns - True if ready.
88 */
89 _isSourceReady() {
90 return this.source.readyState > 2;
91 }
92 /** Runs the update loop when the video is ready to play. */
93 _onPlayStart() {
94 this.valid || this._onCanPlay(), this._configureAutoUpdate();
95 }
96 /** Fired when a pause event is triggered, stops the update loop. */
97 _onPlayStop() {
98 this._configureAutoUpdate();
99 }
100 /** Fired when the video is completed seeking to the current playback position. */
101 _onSeeked() {
102 this._autoUpdate && !this._isSourcePlaying() && (this._msToNextUpdate = 0, this.update(), this._msToNextUpdate = 0);
103 }
104 /** Fired when the video is loaded and ready to play. */
105 _onCanPlay() {
106 const source = this.source;
107 source.removeEventListener("canplay", this._onCanPlay), source.removeEventListener("canplaythrough", this._onCanPlay);
108 const valid = this.valid;
109 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();
110 }
111 /** Destroys this texture. */
112 dispose() {
113 this._configureAutoUpdate();
114 const source = this.source;
115 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();
116 }
117 /** Should the base texture automatically update itself, set to true by default. */
118 get autoUpdate() {
119 return this._autoUpdate;
120 }
121 set autoUpdate(value) {
122 value !== this._autoUpdate && (this._autoUpdate = value, this._configureAutoUpdate());
123 }
124 /**
125 * How many times a second to update the texture from the video. If 0, `requestVideoFrameCallback` is used to
126 * update the texture. If `requestVideoFrameCallback` is not available, the texture is updated every render.
127 * A lower fps can help performance, as updating the texture at 60fps on a 30ps video may not be efficient.
128 */
129 get updateFPS() {
130 return this._updateFPS;
131 }
132 set updateFPS(value) {
133 value !== this._updateFPS && (this._updateFPS = value, this._configureAutoUpdate());
134 }
135 _configureAutoUpdate() {
136 this._autoUpdate && this._isSourcePlaying() ? !this._updateFPS && this.source.requestVideoFrameCallback ? (this._isConnectedToTicker && (ticker.Ticker.shared.remove(this.update, this), this._isConnectedToTicker = !1, this._msToNextUpdate = 0), this._videoFrameRequestCallbackHandle === null && (this._videoFrameRequestCallbackHandle = this.source.requestVideoFrameCallback(
137 this._videoFrameRequestCallback
138 ))) : (this._videoFrameRequestCallbackHandle !== null && (this.source.cancelVideoFrameCallback(this._videoFrameRequestCallbackHandle), this._videoFrameRequestCallbackHandle = null), this._isConnectedToTicker || (ticker.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.Ticker.shared.remove(this.update, this), this._isConnectedToTicker = !1, this._msToNextUpdate = 0));
139 }
140 /**
141 * Used to auto-detect the type of resource.
142 * @param {*} source - The source object
143 * @param {string} extension - The extension of source, if set
144 * @returns {boolean} `true` if video source
145 */
146 static test(source, extension) {
147 return globalThis.HTMLVideoElement && source instanceof HTMLVideoElement || _VideoResource2.TYPES.includes(extension);
148 }
149};
150_VideoResource.TYPES = ["mp4", "m4v", "webm", "ogg", "ogv", "h264", "avi", "mov"], /**
151* Map of video MIME types that can't be directly derived from file extensions.
152* @readonly
153*/
154_VideoResource.MIME_TYPES = {
155 ogv: "video/ogg",
156 mov: "video/quicktime",
157 m4v: "video/mp4"
158};
159let VideoResource = _VideoResource;
160exports.VideoResource = VideoResource;
161//# sourceMappingURL=VideoResource.js.map