Repository

js/Mobilizing/renderer/3D/three/texture/VideoTexture.js

import * as THREE from 'three';
import Component from '../../../../core/Component';

export default class VideoTexture extends Component {
    /**
    * VideoTexture Class give the possibility to use a movie file directly as a texture. Therefore, this class contains some movie media fonctions to manage time (play, pause, stop, etc). The texture must be mapped on an Mesh via a Material (setTexture()).
    *
    * @param {Object} params Parameters object, given by the constructor.
    * @param {Video} params.video the html5 video object to use for this VideoTexture
    * @param {Boolean} [params.loop = false] whether the video should loop after playback end
    * @param {Boolean} [params.autoPlay = false] whether the video should play at construction
    *
    * @example
    *    //TODO
    */
    constructor({
        video = undefined,
        loop = true,
        autoPlay = true,
        fps = 30,
    } = {}) {
        super(...arguments);

        this.video = video;
        this.loop = loop;
        this.autoPlay = autoPlay;
        this.fps = fps;

        if (this.video) {
            this.video.muted = true;
            this.video.setAttribute("playsinline", "");//to activate rendering on iOS
        }

        this.isPlaying = false;

        this._bufferedPourcent = { start: 0, end: 0 };

        this._texture = new THREE.Texture(this.video);
        this._texture.generateMipmaps = false;
        this._texture.minFilter = THREE.NearestFilter;
        this._texture.magFilter = THREE.NearestFilter;
    }

    /**
    * @return the Three.js native object used in this class
    */
    getNativeObject() {
        return this._texture;
    }

    setup() {
        super.setup();

        //to have some events to work with
        this.video.addEventListener("progress", this.onLoadProgess.bind(this));
        this.video.addEventListener("ended", this.onVideoEnded.bind(this));
    }

    on() {
        super.on();

        if (this.autoPlay) {
            this.play();
        }
    }

    /**
    * Defines a "load progress" behavior. User can access the result of the progress through the bufferedPourcent property (videoTexture.bufferedPourcent) which is an object constructed like this : this._bufferedPourcent = {start:<Number>, end: <Number>}. FIXME : should map to the orginal list produced by HTML5 TimeRange object
    * @private
    * @param {Event} e
    */
    onLoadProgess() {
        const timeRange = this.getBuffered();

        if (timeRange) {
            if (timeRange.length >= 1) {
                this._bufferedPourcent = {
                    "start": this.getBuffered().start(0) * 100 / this.getDuration(),
                    "end": this.getBuffered().end(0) * 100 / this.getDuration()
                };
            }
        }
    }

    /**
    * Ended event to manage loop playback
    * @private
    * @param {Event} e
    */
    onVideoEnded() {
        if (this.loop) {
            this.play();
        }
        else {
            this.stop();
        }
    }

    /**
    * Set Loop on or off for this video
    * @param {Boolean} val
    */
    setLoop(val) {
        this.loop = val;
    }

    /**
    * updates the video texture, should be done everytime an update is desired (i.e. every frame)
    */
    update() {
        if (this.video.readyState >= this.video.HAVE_FUTURE_DATA) {
            this._texture.needsUpdate = true;
        }
    }

    /**
    * Play the video
    */
    play() {
        const playPromise = this.video.play();

        if (playPromise !== undefined) {
            playPromise.then(() => {

            }).catch((error) => {

                console.error(error);
                alert(error);
            });
        }
        else {
            this.video.play();
        }

        this.isPlaying = true;
    }

    /**
    * Pause the video
    */
    pause() {
        this.video.pause();
        this.isPlaying = false;
    }

    /**
    * Stop the video
    */
    stop() {
        this.video.pause();
        this.video.currentTime = 0;
        this.isPlaying = false;
    }

    /**
    * Get playing state
    * @return {Boolean} playing state
    */
    getIsPlaying() {
        return this.isPlaying;
    }

    /**
    * Gets the current time of this video
    * @return {Number} the current time of this video in seconds
    */
    getCurrentTime() {
        return this.video.currentTime;
    }

    /**
    * Gets the current buffered part of this video
    * @return {Object} the current buffered part as a TimeRange Object {length:Number, start:Number, end:Number}
    */
    getBuffered() {
        return this.video.buffered;
    }

    /**
    * Set the current time of this video
    * @param {Number} t the current time (in second) to
    */
    setCurrentTime(time) {
        if (this.video.currentTime < this.video.duration) {
            this.video.currentTime = time;
        }
        else {
            this.video.currentTime = this.video.duration;
        }
    }

    /**
    * Get the duration of the video file in seconds
    * @return {Number} duration
    */
    getDuration() {
        return this.video.duration;
    }

    /**
    * Set the playback rate of the video. See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/playbackRate for more details
    * @param {Number} rate the playback rate
    */
    setPlaybackRate(rate) {
        this.video.playbackRate = rate;
    }

    /**
    * Get the current playback rate
    * @return {Number} the playback rate
    */
    getPlaybackRate() {
        return this.video.playbackRate;
    }

    /**
    * Get the current video width
    * @return {Number} the video width
    */
    getWidth() {
        return this.video.videoWidth;
    }

    /**
    * Get the current video height
    * @return {Number} the video height
    */
    getHeight() {
        return this.video.videoHeight;
    }
}