Home Identifier Source

src/sources/videosource.js

//Matthew Shotton, R&D User Experince,© BBC 2015
import MediaSource from "./mediasource";


function eventOneTime(element, type, callback){
    let handleEvent = function(e){
        e.target.removeEventListener(e.type, handleEvent);
        return callback(e);
    };

    element.addEventListener(type, handleEvent, false);         
}



class VideoSource extends MediaSource{
    /**
    * Video playback source. Inherits from MediaSource 
    *
    * A VideoSource is the manifestation of a mediaSourceReference from a playlist object which has type "video". 
    * 
    * A VideoSource exists for a period slightly before a VideoSource is to play in order to give it time to preload and
    * is destroyed as soon as the VideoSource has finished playing. You can define an offset into the original video to 
    * start playing by passing in a sourceStart value in the properties.
    *
    * @param {Object} properties - An object with the following attributes: id, duration, start, sourceStart, and src or element. 
    * Where src is the URL of a video, or element is a DOM Video element.
    * 
    * @param {WebGLContext} gl - a webGl context to render too.
    */
    constructor(properties, gl){
        super(properties, gl);
        this.sourceStart = 0;
        this._volume = 1.0;
        if (properties.sourceStart !== undefined){
            this.sourceStart = properties.sourceStart;
        }
        if (properties.volume !== undefined){
            this._volume = properties.volume;
        }
    }
    /**
    * Set the VideoSource playing.
    */
    play(){
        super.play();
        let _this = this;

        let playVideo = function(){
            if (_this.element.readyState > 3){
                _this.ready = true;
                _this.element.play();
            } else {
                console.debug("Can't play video due to readyState");
                _this.ready = false;
                eventOneTime(_this.element, "canplay", playVideo);
            }
        };

        playVideo();
    }
    /**
    * Seek the VideoSource to an appropriate point for the passed time.
    * @param {number} seekTime - The time to seek too, this is the overall time for the whole playlist.
    */
    seek(time){
        super.seek();
        let _this = this;

        let seekVideo = function(){
            if (_this.element.readyState > 3){
                _this.ready = true;
                if ((time - _this.start) < 0 || time >(_this.start+_this.duration)){
                    _this.element.currentTime = _this.sourceStart;
                } else {
                    _this.element.currentTime = (time - _this.start) + _this.sourceStart;
                }
            } else {
                //If the element isn't ready to seek create a one-time event which seeks the element once it is ready.
                console.debug("Can't seek video due to readyState");
                _this.ready = false;
                eventOneTime(_this.element, "canplay", seekVideo);
            }
        };

        seekVideo();  
    }
    /**
    * Pause the VideoSource if it is playing.
    */
    pause(){
        super.pause();
        this.element.pause();
    }
    /**
    * Set the VideoSource loading, when it's ready isReady() will return true.
    */
    load(){
        //check if we're using an already instatiated element, if so don't do anything.

        if (super.load()){
            //this.element.currentTime = this.sourceStart;
            this.seek(0);
            this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element);
            this.ready = true;
            this.width = this.element.videoWidth;
            this.height = this.element.videoHeight;
            this.onready(this);
            return;
        }
        //otherwise begin the loading process for this mediaSource
        this.element = document.createElement('video');            
        //construct a fragement URL to cut the required segment from the source video
        this.element.src = this.src;
        this.element.volume = this._volume;
        this.element.preload = "auto";
        this.element.load();
        let _this = this;
        this.element.addEventListener('loadeddata', function() {
            _this.element.currentTime = _this.sourceStart;
            _this.seek(0);
            _this.gl.texImage2D(_this.gl.TEXTURE_2D, 0, _this.gl.RGBA, _this.gl.RGBA, _this.gl.UNSIGNED_BYTE, _this.element);
            _this.ready = true;
            _this.width = _this.element.videoWidth;
            _this.height = _this.element.videoHeight;
            _this.onready(_this);
        }, false);
        /*this.element.addEventListener('seeked', function(){
            console.log("SEEKED");
            _this.ready = true;
            _this.onready(_this);
        })*/


    }
    /**
    * Render the VideoSource to the WebGL context passed into the constructor.
    */
    render(program, renderParameters, textures){
        this.element.playbackRate = renderParameters["playback_rate"];
        super.render(program, renderParameters, textures);
    }
    /**
    * Clean up the VideoSource for detruction.
    */
    destroy(){
        this.element.pause();
        if (this.disposeOfElementOnDestroy){
            this.element.src = "";
            this.element.removeAttribute("src");    
        }
        super.destroy();
    }
}

export default VideoSource;