Note that this little experiment will only run on Chrome browsers.

Interactive Video

This experiment saves a video frame sequence to an array and lets the user play them. The destination frame is defined by the user while a easing algorithm is applied to not immediately jump to the frame but rather play selected frames on the way there starting at the current one.

The 'recording' function will render the video to a texture and then grab its pixels, storing them in an array.


recordFrame = function( video )
{
    _gl.clear( _gl.COLOR_BUFFER_BIT );
    _gl.clearColor( 1, 1, 1, 0 );
    _gl.activeTexture( _gl.TEXTURE0 );

    _gl.bindTexture( _gl.TEXTURE_2D, _glInputTexture );
    _gl.texImage2D( _gl.TEXTURE_2D, 0, _gl.RGBA, _gl.RGBA, _gl.UNSIGNED_BYTE, video );

    // check if texture is readable
    if( _gl.checkFramebufferStatus( _gl.FRAMEBUFFER ) == _gl.FRAMEBUFFER_COMPLETE )
    {
        var pixels = new Uint8Array( new ArrayBuffer( _videoWidth * _videoHeight * 4 ) );

        _gl.bindFramebuffer( _gl.FRAMEBUFFER, _glFrameBuffer );
        _gl.readPixels( 0, 0, _videoWidth, _videoHeight, _gl.RGBA, _gl.UNSIGNED_BYTE, pixels );
        _gl.bindFramebuffer( _gl.FRAMEBUFFER, null );

        // quick hack to exclude black frames
        if( pixels[ _videoWidth * ( _videoHeight / 2 ) * 4 ] > 0 )
        {
            _frames.push( pixels );
        }
    }
    else
    {
        console.log( "Can't read from texture." );
    }

    var index = _frames.length - 1;

    _gl.bindTexture( _gl.TEXTURE_2D, _glOutputTexture );
    _gl.texImage2D( _gl.TEXTURE_2D, 0, _gl.RGBA, _videoWidth, _videoHeight, 0, _gl.RGBA, _gl.UNSIGNED_BYTE, _frames[ index ] );
    _gl.bindBuffer( _gl.ARRAY_BUFFER, _glBuffer );
    _gl.bufferData( _gl.ARRAY_BUFFER, createRectangle( 0, 0, _canvas.width, _canvas.height ), _gl.STATIC_DRAW );
    _gl.drawArrays( _gl.TRIANGLES, 0, 6 );
    _gl.bindBuffer( _gl.ARRAY_BUFFER, null );
    _gl.bindTexture( _gl.TEXTURE_2D, null );

    return index / _frames.length;
};

                    

And the render frame function. It will take a value from 0 to 1 and map it to the frames array. Selecting the texture to be applied.


renderFrame = function( value )
{
    _gl.clear( _gl.COLOR_BUFFER_BIT );
    _gl.clearColor( 1, 1, 1, 0 );
    _gl.activeTexture( _gl.TEXTURE0 );

    _newValue = value;
    _oldValue += ( _newValue - _oldValue ) * _speed;

    if( Math.abs( _oldValue - _newValue ) < 0.001 )
    {
        _oldValue = _newValue;
    }

    var index = clamp( Math.floor( _frames.length * _oldValue ), 0, _frames.length - 1 );

    _gl.bindTexture( _gl.TEXTURE_2D, _glOutputTexture );
    _gl.texImage2D( _gl.TEXTURE_2D, 0, _gl.RGBA, _videoWidth, _videoHeight, 0, _gl.RGBA, _gl.UNSIGNED_BYTE, _frames[ index ] );
    _gl.bindBuffer( _gl.ARRAY_BUFFER, _glBuffer );
    _gl.bufferData( _gl.ARRAY_BUFFER, createRectangle( 0, 0, _canvas.width, _canvas.height ), _gl.STATIC_DRAW );
    _gl.drawArrays( _gl.TRIANGLES, 0, 6 );
    _gl.bindBuffer( _gl.ARRAY_BUFFER, null );
    _gl.bindTexture( _gl.TEXTURE_2D, null );

    return index / _frames.length;
};