Home Identifier Source

src/sources/mediasource.js

//Matthew Shotton, R&D User Experince,© BBC 2015

class MediaSource {
    /**
    * Parent class of all MediaSources 
    *
    * A MediaSource is the manifestation of a mediaSourceReference from a playlist object. It typically contains the 
    * original DOM element to be composited along with a number of functions to load, play, pause, seek and render that 
    * element to the webgl context.
    * 
    * A MediaSource exists for a period slightly before a MediaSource is to play in order to give it time to preload and
    * is destroyed as soon as the MediaSource has finished playing.
    *
    * @param {Object} properties - An object with the following attributes: id, duration, start, and src or element. 
    * Where src is the URL of something that can be used to create a DOM element that can be rendered to canvas, or 
    * element is a DOM element that can be rendered to a canvas.
    * 
    * @param {WebGLContext} gl - a webGl context to render too.
    */
    constructor(properties, gl){
        this.gl = gl;
        this.id = properties.id;
        this.duration = properties.duration;
        this.start = properties.start;
        this.playing = false;
        this.ready = false;
        this.element = undefined;
        this.src = undefined;
        this.texture = undefined;
        this.width = undefined;
        this.height = undefined;
        this.mediaSourceListeners = [];

        this.disposeOfElementOnDestroy = false;

        //If the mediaSource is created from a src string then it must be resonsible for cleaning itself up.
        if (properties.src !== undefined){
            this.disposeOfElementOnDestroy = true;
            this.src = properties.src;
        }else {
            //If the MediaSource is created from an element then it should not clean the element up on destruction as it may be used elsewhere.
            this.disposeOfElementOnDestroy = false;
            this.element = properties.element;
        }


        /*let positionLocation = gl.getAttribLocation(program, "a_position");
        let texCoordLocation = gl.getAttribLocation(program, "a_texCoord");*/
        
        //Hard Code these for now, but this is baaaaaad
        let positionLocation = 0;
        let texCoordLocation = 1;
        
        gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
        gl.enable ( gl.BLEND) ;
        // Create a texture.
        this.texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, this.texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
        // Set the parameters so we can render any size image.
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        

        let buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.enableVertexAttribArray(positionLocation);
        gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

        gl.bufferData(
            gl.ARRAY_BUFFER,
            /*new Float32Array([
                1.0, 1.0,
                 -1.0, 1.0,
                1.0,  -1.0,
                1.0,  -1.0,
                -1.0, 1.0,
                -1.0, -1.0]),*/
            new Float32Array([
                1.0, 1.0,
                0.0, 1.0,
                1.0, 0.0,
                1.0, 0.0,
                0.0, 1.0,
                0.0, 0.0]),
            gl.STATIC_DRAW);
        gl.enableVertexAttribArray(texCoordLocation);
        gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

    }
    
    /**
    * Set the MediaSource playing.
    */
    play(){
        //console.log("Playing", this.id);
        if (this.playing === false){
            for (let i = 0; i < this.mediaSourceListeners.length; i++) {
                if(typeof this.mediaSourceListeners[i].play === 'function')this.mediaSourceListeners[i].play(this);
            }    
        }
        this.playing = true;
    }
    /**
    * Pause the MediaSource if it is playing.
    */
    pause(){
        console.debug("Pausing", this.id);
        this.playing = false;
        for (let i = 0; i < this.mediaSourceListeners.length; i++) {
            if(typeof this.mediaSourceListeners[i].pause === 'function')this.mediaSourceListeners[i].pause(this);
        }
    }
    /**
    * Seek the MediaSource 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(seekTime){
        //this.currentTime = seekTime;
        for (let i = 0; i < this.mediaSourceListeners.length; i++) {
            if(typeof this.mediaSourceListeners[i].seek === 'function')this.mediaSourceListeners[i].seek(this, seekTime);
        }
    }
    /**
    * Check if the MediaSource is ready to start playing.
    */
    isReady(){
        let listenerReady = true;
        for (let i = 0; i < this.mediaSourceListeners.length; i++) {
            if(typeof this.mediaSourceListeners[i].isReady === 'function'){
                if (this.mediaSourceListeners[i].isReady(this) === false){
                    listenerReady = false;
                }
            }
        }
        if (listenerReady === true && this.ready === true) return true;
        return false;
    }
    /**
    * Set the MediaSource loading, when it's ready isReady() will return true.
    */
    load(){
        console.debug("Loading", this.id);
        for (let i = 0; i < this.mediaSourceListeners.length; i++) {
            if(typeof this.mediaSourceListeners[i].load === 'function')this.mediaSourceListeners[i].load(this);
        }
        if (this.element !== undefined) {
            return true;
        }
        return false;
    }
    /**
    * Clean up the MediaSource for detruction.
    */
    destroy(){
        console.debug("Destroying", this.id);
        for (let i = 0; i < this.mediaSourceListeners.length; i++) {
            if (typeof this.mediaSourceListeners[i].destroy === 'function') this.mediaSourceListeners[i].destroy(this);
        }
        if (this.disposeOfElementOnDestroy){
            delete this.element;  
        }
    }
    /**
    * Render the MediaSource to the WebGL context passed into the constructor.
    */
    render(program, renderParameters, textures){
        //renders the media source to the WebGL context using the pased program
        let overriddenElement;
        for (let i = 0; i < this.mediaSourceListeners.length; i++) {
            if (typeof this.mediaSourceListeners[i].render === 'function'){
                let result =  this.mediaSourceListeners[i].render(this, renderParameters);
                if (result !== undefined) overriddenElement = result;
            }
        }

        this.gl.useProgram(program);
        let renderParametersKeys = Object.keys(renderParameters);
        let textureOffset = 1;
        for (let index in renderParametersKeys){
            let key = renderParametersKeys[index];
            let parameterLoctation = this.gl.getUniformLocation(program, key);
            if (parameterLoctation !== -1){
                if (typeof renderParameters[key] === "number"){
                    this.gl.uniform1f(parameterLoctation, renderParameters[key]);
                }
                else if( Object.prototype.toString.call(renderParameters[key]) === '[object Array]'){
                    let array = renderParameters[key];
                    if(array.length === 1){
                        this.gl.uniform1fv(parameterLoctation, array);
                    } else if(array.length === 2){
                        this.gl.uniform2fv(parameterLoctation, array);
                    } else if(array.length === 3){
                        this.gl.uniform3fv(parameterLoctation, array);
                    } else if(array.length === 4){
                        this.gl.uniform4fv(parameterLoctation, array);
                    } else{
                        console.debug("Shader parameter", key, "is too long and array:", array);
                    }
                }
                else{
                    //Is a texture
                    this.gl.activeTexture(this.gl.TEXTURE0 + textureOffset);
                    this.gl.uniform1i(parameterLoctation, textureOffset);
                    this.gl.bindTexture(this.gl.TEXTURE_2D, textures[textureOffset-1]);
                }
            }
        }
        
        this.gl.activeTexture(this.gl.TEXTURE0);
        let textureLocation = this.gl.getUniformLocation(program, "u_image");
        this.gl.uniform1i(textureLocation, 0);
        this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
        if (overriddenElement !== undefined){
            this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, overriddenElement);
        } else {
            this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element);
        }
        this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
    }
    onready(mediaSource){
    }
}

export default MediaSource;