<commit_before>/**
 * @author Mat Groves http://matgroves.com/
 */

 /**
 *
 * @class Strip
 * @extends DisplayObjectContainer
 * @constructor
 * @param texture {Texture} The texture to use
 * @param width {Number} the width
 * @param height {Number} the height
 *
 */
PIXI.Strip = function(texture)
{
    PIXI.DisplayObjectContainer.call( this );

    /**
     * The texture of the strip
     *
     * @property texture
     * @type Texture
     */
    this.texture = texture;

    // set up the main bits..
    this.uvs = new PIXI.Float32Array([0, 1,
                                      1, 1,
                                      1, 0,
                                      0, 1]);

    this.verticies = new PIXI.Float32Array([0, 0,
                                            100, 0,
                                            100, 100,
                                            0, 100]);

    this.colors = new PIXI.Float32Array([1, 1, 1, 1]);

    this.indices = new PIXI.Uint16Array([0, 1, 2, 3]);

    /**
     * Whether the strip is dirty or not
     *
     * @property dirty
     * @type Boolean
     */
    this.dirty = true;

    /**
     * The blend mode to be applied to the sprite. Set to PIXI.blendModes.NORMAL to remove any blend mode.
     *
     * @property blendMode
     * @type Number
     * @default PIXI.blendModes.NORMAL;
     */
    this.blendMode = PIXI.blendModes.NORMAL;

    /**
     * if you need a padding, not yet implemented
     *
     * @property padding
     * @type Number
     */
    this.padding = 0;
     // NYI, TODO padding ?

};

// constructor
PIXI.Strip.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
PIXI.Strip.prototype.constructor = PIXI.Strip;

PIXI.Strip.prototype._renderWebGL = function(renderSession)
{
    // if the sprite is not visible or the alpha is 0 then no need to render this element
    if(!this.visible || this.alpha <= 0)return;
    // render triangle strip..

    renderSession.spriteBatch.stop();

    // init! init!
    if(!this._vertexBuffer)this._initWebGL(renderSession);

    renderSession.shaderManager.setShader(renderSession.shaderManager.stripShader);

    this._renderStrip(renderSession);

    ///renderSession.shaderManager.activateDefaultShader();

    renderSession.spriteBatch.start();

    //TODO check culling
};

PIXI.Strip.prototype._initWebGL = function(renderSession)
{
    // build the strip!
    var gl = renderSession.gl;

    this._vertexBuffer = gl.createBuffer();
    this._indexBuffer = gl.createBuffer();
    this._uvBuffer = gl.createBuffer();
    this._colorBuffer = gl.createBuffer();

    gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.verticies, gl.DYNAMIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
    gl.bufferData(gl.ARRAY_BUFFER,  this.uvs, gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this._colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.colors, gl.STATIC_DRAW);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);
};

PIXI.Strip.prototype._renderStrip = function(renderSession)
{
    var gl = renderSession.gl;
    var projection = renderSession.projection,
        offset = renderSession.offset,
        shader = renderSession.shaderManager.stripShader;

    // gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mat4Real);

    renderSession.blendModeManager.setBlendMode(this.blendMode);

    // set uniforms
    gl.uniformMatrix3fv(shader.translationMatrix, false, this.worldTransform.toArray(true));
    gl.uniform2f(shader.projectionVector, projection.x, -projection.y);
    gl.uniform2f(shader.offsetVector, -offset.x, -offset.y);
    gl.uniform1f(shader.alpha, this.worldAlpha);

    if(!this.dirty)
    {

        gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
        gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verticies);
        gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0);

        // update the uvs
        gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
        gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0);

        gl.activeTexture(gl.TEXTURE0);

        // check if a texture is dirty..
        if(this.texture.baseTexture._dirty[gl.id])
        {
            renderSession.renderer.updateTexture(this.texture.baseTexture);
        }
        else
        {
            // bind the current texture
            gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]);
        }

        // dont need to upload!
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);

    }
    else
    {

        this.dirty = false;
        gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.verticies, gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0);

        // update the uvs
        gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0);

        gl.activeTexture(gl.TEXTURE0);

        // check if a texture is dirty..
        if(this.texture.baseTexture._dirty[gl.id])
        {
            renderSession.renderer.updateTexture(this.texture.baseTexture);
        }
        else
        {
            gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]);
        }

        // dont need to upload!
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);

    }
    //console.log(gl.TRIANGLE_STRIP)
    //
    //
    gl.drawElements(gl.TRIANGLE_STRIP, this.indices.length, gl.UNSIGNED_SHORT, 0);

};

PIXI.Strip.prototype._renderCanvas = function(renderSession)
{
    var context = renderSession.context;

    var transform = this.worldTransform;

    if (renderSession.roundPixels)
    {
        context.setTransform(transform.a, transform.b, transform.c, transform.d, transform.tx | 0, transform.ty | 0);
    }
    else
    {
        context.setTransform(transform.a, transform.b, transform.c, transform.d, transform.tx, transform.ty);
    }

    var strip = this;
    // draw triangles!!
    var verticies = strip.verticies;
    var uvs = strip.uvs;

    var length = verticies.length/2;
    this.count++;

    for (var i = 0; i < length-2; i++)
    {
        // draw some triangles!
        var index = i*2;

        var x0 = verticies[index],   x1 = verticies[index+2], x2 = verticies[index+4];
        var y0 = verticies[index+1], y1 = verticies[index+3], y2 = verticies[index+5];

        if(this.padding > 0)
        {
            var centerX = (x0 + x1 + x2)/3;
            var centerY = (y0 + y1 + y2)/3;

            var normX = x0 - centerX;
            var normY = y0 - centerY;

            var dist = Math.sqrt( normX * normX + normY * normY );
            x0 = centerX + (normX / dist) * (dist + 3);
            y0 = centerY + (normY / dist) * (dist + 3);

            //

            normX = x1 - centerX;
            normY = y1 - centerY;

            dist = Math.sqrt( normX * normX + normY * normY );
            x1 = centerX + (normX / dist) * (dist + 3);
            y1 = centerY + (normY / dist) * (dist + 3);

            normX = x2 - centerX;
            normY = y2 - centerY;

            dist = Math.sqrt( normX * normX + normY * normY );
            x2 = centerX + (normX / dist) * (dist + 3);
            y2 = centerY + (normY / dist) * (dist + 3);
        }

        var u0 = uvs[index] * strip.texture.width,   u1 = uvs[index+2] * strip.texture.width, u2 = uvs[index+4]* strip.texture.width;
        var v0 = uvs[index+1]* strip.texture.height, v1 = uvs[index+3] * strip.texture.height, v2 = uvs[index+5]* strip.texture.height;

        context.save();
        context.beginPath();

        context.moveTo(x0, y0);
        context.lineTo(x1, y1);
        context.lineTo(x2, y2);

        context.closePath();

        context.clip();

        // Compute matrix transform
        var delta = u0*v1 + v0*u2 + u1*v2 - v1*u2 - v0*u1 - u0*v2;
        var deltaA = x0*v1 + v0*x2 + x1*v2 - v1*x2 - v0*x1 - x0*v2;
        var deltaB = u0*x1 + x0*u2 + u1*x2 - x1*u2 - x0*u1 - u0*x2;
        var deltaC = u0*v1*x2 + v0*x1*u2 + x0*u1*v2 - x0*v1*u2 - v0*u1*x2 - u0*x1*v2;
        var deltaD = y0*v1 + v0*y2 + y1*v2 - v1*y2 - v0*y1 - y0*v2;
        var deltaE = u0*y1 + y0*u2 + u1*y2 - y1*u2 - y0*u1 - u0*y2;
        var deltaF = u0*v1*y2 + v0*y1*u2 + y0*u1*v2 - y0*v1*u2 - v0*u1*y2 - u0*y1*v2;

        context.transform(deltaA / delta, deltaD / delta,
                            deltaB / delta, deltaE / delta,
                            deltaC / delta, deltaF / delta);

        context.drawImage(strip.texture.baseTexture.source, 0, 0);
        context.restore();
    }
};

/**
 * Renders a flat strip
 *
 * @method renderStripFlat
 * @param strip {Strip} The Strip to render
 * @private
 */
PIXI.Strip.prototype.renderStripFlat = function(strip)
{
    var context = this.context;
    var verticies = strip.verticies;

    var length = verticies.length/2;
    this.count++;

    context.beginPath();
    for (var i=1; i < length-2; i++)
    {
        // draw some triangles!
        var index = i*2;

        var x0 = verticies[index],   x1 = verticies[index+2], x2 = verticies[index+4];
        var y0 = verticies[index+1], y1 = verticies[index+3], y2 = verticies[index+5];

        context.moveTo(x0, y0);
        context.lineTo(x1, y1);
        context.lineTo(x2, y2);
    }

    context.fillStyle = "#FF0000";
    context.fill();
    context.closePath();
};

/*
PIXI.Strip.prototype.setTexture = function(texture)
{
    //TODO SET THE TEXTURES
    //TODO VISIBILITY

    // stop current texture
    this.texture = texture;
    this.width   = texture.frame.width;
    this.height  = texture.frame.height;
    this.updateFrame = true;
};
*/

/**
 * When the texture is updated, this event will fire to update the scale and frame
 *
 * @method onTextureUpdate
 * @param event
 * @private
 */

PIXI.Strip.prototype.onTextureUpdate = function()
{
    this.updateFrame = true;
};<commit_msg>-[STRIP] Added GL_TRIANGLES drawing mode besides of GL_TRIANGLE_STRIP. Should the name be changed from Strip to Mesh?<commit_after>/**
 * @author Mat Groves http://matgroves.com/
 */

 /**
 *
 * @class Strip
 * @extends DisplayObjectContainer
 * @constructor
 * @param texture {Texture} The texture to use
 * @param width {Number} the width
 * @param height {Number} the height
 *
 */
PIXI.Strip = function(texture)
{
    PIXI.DisplayObjectContainer.call( this );

    /**
     * The texture of the strip
     *
     * @property texture
     * @type Texture
     */
    this.texture = texture;

    // set up the main bits..
    this.uvs = new PIXI.Float32Array([0, 1,
                                      1, 1,
                                      1, 0,
                                      0, 1]);

    this.verticies = new PIXI.Float32Array([0, 0,
                                            100, 0,
                                            100, 100,
                                            0, 100]);

    this.colors = new PIXI.Float32Array([1, 1, 1, 1]);

    this.indices = new PIXI.Uint16Array([0, 1, 2, 3]);

    /**
     * Whether the strip is dirty or not
     *
     * @property dirty
     * @type Boolean
     */
    this.dirty = true;

    /**
     * The blend mode to be applied to the sprite. Set to PIXI.blendModes.NORMAL to remove any blend mode.
     *
     * @property blendMode
     * @type Number
     * @default PIXI.blendModes.NORMAL;
     */
    this.blendMode = PIXI.blendModes.NORMAL;

    /**
     * if you need a padding, not yet implemented
     *
     * @property padding
     * @type Number
     */
    this.padding = 0;
     // NYI, TODO padding ?

    this.drawMode = PIXI.Strip.DrawModes.TRIANGLE_STRIP;

};

// constructor
PIXI.Strip.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
PIXI.Strip.prototype.constructor = PIXI.Strip;

PIXI.Strip.prototype._renderWebGL = function(renderSession)
{
    // if the sprite is not visible or the alpha is 0 then no need to render this element
    if(!this.visible || this.alpha <= 0)return;
    // render triangle strip..

    renderSession.spriteBatch.stop();

    // init! init!
    if(!this._vertexBuffer)this._initWebGL(renderSession);

    renderSession.shaderManager.setShader(renderSession.shaderManager.stripShader);

    this._renderStrip(renderSession);

    ///renderSession.shaderManager.activateDefaultShader();

    renderSession.spriteBatch.start();

    //TODO check culling
};

PIXI.Strip.prototype._initWebGL = function(renderSession)
{
    // build the strip!
    var gl = renderSession.gl;

    this._vertexBuffer = gl.createBuffer();
    this._indexBuffer = gl.createBuffer();
    this._uvBuffer = gl.createBuffer();
    this._colorBuffer = gl.createBuffer();

    gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.verticies, gl.DYNAMIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
    gl.bufferData(gl.ARRAY_BUFFER,  this.uvs, gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this._colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.colors, gl.STATIC_DRAW);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);
};

PIXI.Strip.prototype._renderStrip = function(renderSession)
{
    var gl = renderSession.gl;
    var projection = renderSession.projection,
        offset = renderSession.offset,
        shader = renderSession.shaderManager.stripShader;

    var drawMode = this.drawMode === PIXI.Strip.DrawModes.TRIANGLE_STRIP ? gl.TRIANGLE_STRIP : gl.TRIANGLES;

    // gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mat4Real);

    renderSession.blendModeManager.setBlendMode(this.blendMode);

    // set uniforms
    gl.uniformMatrix3fv(shader.translationMatrix, false, this.worldTransform.toArray(true));
    gl.uniform2f(shader.projectionVector, projection.x, -projection.y);
    gl.uniform2f(shader.offsetVector, -offset.x, -offset.y);
    gl.uniform1f(shader.alpha, this.worldAlpha);

    if(!this.dirty)
    {

        gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
        gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verticies);
        gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0);

        // update the uvs
        gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
        gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0);

        gl.activeTexture(gl.TEXTURE0);

        // check if a texture is dirty..
        if(this.texture.baseTexture._dirty[gl.id])
        {
            renderSession.renderer.updateTexture(this.texture.baseTexture);
        }
        else
        {
            // bind the current texture
            gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]);
        }

        // dont need to upload!
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);

    }
    else
    {

        this.dirty = false;
        gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.verticies, gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0);

        // update the uvs
        gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.STATIC_DRAW);
        gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0);

        gl.activeTexture(gl.TEXTURE0);

        // check if a texture is dirty..
        if(this.texture.baseTexture._dirty[gl.id])
        {
            renderSession.renderer.updateTexture(this.texture.baseTexture);
        }
        else
        {
            gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]);
        }

        // dont need to upload!
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);

    }
    //console.log(gl.TRIANGLE_STRIP)
    //
    //
    gl.drawElements(drawMode, this.indices.length, gl.UNSIGNED_SHORT, 0);

};

PIXI.Strip.prototype._renderCanvas = function(renderSession)
{
    var context = renderSession.context;

    var transform = this.worldTransform;

    if (renderSession.roundPixels)
    {
        context.setTransform(transform.a, transform.b, transform.c, transform.d, transform.tx | 0, transform.ty | 0);
    }
    else
    {
        context.setTransform(transform.a, transform.b, transform.c, transform.d, transform.tx, transform.ty);
    }

    if (this.drawMode === PIXI.Strip.DrawModes.TRIANGLE_STRIP)
    {
        this._renderCanvasTriangleStrip(context);
    }
    else
    {
        this._renderCanvasTriangles(context);
    }
};

PIXI.Strip.prototype._renderCanvasTriangleStrip = function(context)
{
    var strip = this;
    // draw triangles!!
    var verticies = strip.verticies;
    var uvs = strip.uvs;

    var length = verticies.length / 2;
    this.count++;

    for (var i = 0; i < length - 2; i++) {
        // draw some triangles!
        var index = i * 2;

        var x0 = verticies[index], x1 = verticies[index + 2], x2 = verticies[index + 4];
        var y0 = verticies[index + 1], y1 = verticies[index + 3], y2 = verticies[index + 5];

        if (this.padding > 0) {
            var centerX = (x0 + x1 + x2) / 3;
            var centerY = (y0 + y1 + y2) / 3;

            var normX = x0 - centerX;
            var normY = y0 - centerY;

            var dist = Math.sqrt(normX * normX + normY * normY);
            x0 = centerX + (normX / dist) * (dist + 3);
            y0 = centerY + (normY / dist) * (dist + 3);

            //

            normX = x1 - centerX;
            normY = y1 - centerY;

            dist = Math.sqrt(normX * normX + normY * normY);
            x1 = centerX + (normX / dist) * (dist + 3);
            y1 = centerY + (normY / dist) * (dist + 3);

            normX = x2 - centerX;
            normY = y2 - centerY;

            dist = Math.sqrt(normX * normX + normY * normY);
            x2 = centerX + (normX / dist) * (dist + 3);
            y2 = centerY + (normY / dist) * (dist + 3);
        }

        var u0 = uvs[index] * strip.texture.width, u1 = uvs[index + 2] * strip.texture.width, u2 = uvs[index + 4] * strip.texture.width;
        var v0 = uvs[index + 1] * strip.texture.height, v1 = uvs[index + 3] * strip.texture.height, v2 = uvs[index + 5] * strip.texture.height;

        context.save();
        context.beginPath();

        context.moveTo(x0, y0);
        context.lineTo(x1, y1);
        context.lineTo(x2, y2);

        context.closePath();

        context.clip();

        // Compute matrix transform
        var delta = u0 * v1 + v0 * u2 + u1 * v2 - v1 * u2 - v0 * u1 - u0 * v2;
        var deltaA = x0 * v1 + v0 * x2 + x1 * v2 - v1 * x2 - v0 * x1 - x0 * v2;
        var deltaB = u0 * x1 + x0 * u2 + u1 * x2 - x1 * u2 - x0 * u1 - u0 * x2;
        var deltaC = u0 * v1 * x2 + v0 * x1 * u2 + x0 * u1 * v2 - x0 * v1 * u2 - v0 * u1 * x2 - u0 * x1 * v2;
        var deltaD = y0 * v1 + v0 * y2 + y1 * v2 - v1 * y2 - v0 * y1 - y0 * v2;
        var deltaE = u0 * y1 + y0 * u2 + u1 * y2 - y1 * u2 - y0 * u1 - u0 * y2;
        var deltaF = u0 * v1 * y2 + v0 * y1 * u2 + y0 * u1 * v2 - y0 * v1 * u2 - v0 * u1 * y2 - u0 * y1 * v2;

        context.transform(deltaA / delta, deltaD / delta,
            deltaB / delta, deltaE / delta,
            deltaC / delta, deltaF / delta);

        context.drawImage(strip.texture.baseTexture.source, 0, 0);
        context.restore();
    }
};

PIXI.Strip.prototype._renderCanvasTriangles = function(context)
{
    var strip = this;
    // draw triangles!!
    var verticies = strip.verticies;
    var uvs = strip.uvs;
    var indices = strip.indices;

    var length = indices.length;
    this.count++;

    for (var i = 0; i < length; i += 3) {
        // draw some triangles!
        var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2;

        var x0 = verticies[index0], x1 = verticies[index1], x2 = verticies[index2];
        var y0 = verticies[index0 + 1], y1 = verticies[index1 + 1], y2 = verticies[index2 + 1];

        if (this.padding > 0) {
            var padding = this.padding;
            var centerX = (x0 + x1 + x2) / 3;
            var centerY = (y0 + y1 + y2) / 3;

            var normX = x0 - centerX;
            var normY = y0 - centerY;

            var dist = Math.sqrt(normX * normX + normY * normY);
            x0 = centerX + (normX / dist) * (dist + padding);
            y0 = centerY + (normY / dist) * (dist + padding);

            //

            normX = x1 - centerX;
            normY = y1 - centerY;

            dist = Math.sqrt(normX * normX + normY * normY);
            x1 = centerX + (normX / dist) * (dist + padding);
            y1 = centerY + (normY / dist) * (dist + padding);

            normX = x2 - centerX;
            normY = y2 - centerY;

            dist = Math.sqrt(normX * normX + normY * normY);
            x2 = centerX + (normX / dist) * (dist + padding);
            y2 = centerY + (normY / dist) * (dist + padding);
        }

        var u0 = uvs[index0] * strip.texture.width, u1 = uvs[index1] * strip.texture.width, u2 = uvs[index2] * strip.texture.width;
        var v0 = uvs[index0 + 1] * strip.texture.height, v1 = uvs[index1 + 1] * strip.texture.height, v2 = uvs[index2 + 1] * strip.texture.height;

        context.save();
        context.beginPath();

        context.moveTo(x0, y0);
        context.lineTo(x1, y1);
        context.lineTo(x2, y2);

        context.closePath();

        context.clip();

        // Compute matrix transform
        var delta = u0 * v1 + v0 * u2 + u1 * v2 - v1 * u2 - v0 * u1 - u0 * v2;
        var deltaA = x0 * v1 + v0 * x2 + x1 * v2 - v1 * x2 - v0 * x1 - x0 * v2;
        var deltaB = u0 * x1 + x0 * u2 + u1 * x2 - x1 * u2 - x0 * u1 - u0 * x2;
        var deltaC = u0 * v1 * x2 + v0 * x1 * u2 + x0 * u1 * v2 - x0 * v1 * u2 - v0 * u1 * x2 - u0 * x1 * v2;
        var deltaD = y0 * v1 + v0 * y2 + y1 * v2 - v1 * y2 - v0 * y1 - y0 * v2;
        var deltaE = u0 * y1 + y0 * u2 + u1 * y2 - y1 * u2 - y0 * u1 - u0 * y2;
        var deltaF = u0 * v1 * y2 + v0 * y1 * u2 + y0 * u1 * v2 - y0 * v1 * u2 - v0 * u1 * y2 - u0 * y1 * v2;

        context.transform(deltaA / delta, deltaD / delta,
            deltaB / delta, deltaE / delta,
            deltaC / delta, deltaF / delta);

        context.drawImage(strip.texture.baseTexture.source, 0, 0);
        context.restore();
    }
};

/**
 * Renders a flat strip
 *
 * @method renderStripFlat
 * @param strip {Strip} The Strip to render
 * @private
 */
PIXI.Strip.prototype.renderStripFlat = function(strip)
{
    var context = this.context;
    var verticies = strip.verticies;

    var length = verticies.length/2;
    this.count++;

    context.beginPath();
    for (var i=1; i < length-2; i++)
    {
        // draw some triangles!
        var index = i*2;

        var x0 = verticies[index],   x1 = verticies[index+2], x2 = verticies[index+4];
        var y0 = verticies[index+1], y1 = verticies[index+3], y2 = verticies[index+5];

        context.moveTo(x0, y0);
        context.lineTo(x1, y1);
        context.lineTo(x2, y2);
    }

    context.fillStyle = '#FF0000';
    context.fill();
    context.closePath();
};

/*
PIXI.Strip.prototype.setTexture = function(texture)
{
    //TODO SET THE TEXTURES
    //TODO VISIBILITY

    // stop current texture
    this.texture = texture;
    this.width   = texture.frame.width;
    this.height  = texture.frame.height;
    this.updateFrame = true;
};
*/

/**
 * When the texture is updated, this event will fire to update the scale and frame
 *
 * @method onTextureUpdate
 * @param event
 * @private
 */

PIXI.Strip.prototype.onTextureUpdate = function()
{
    this.updateFrame = true;
};

PIXI.Strip.DrawModes = {
    TRIANGLE_STRIP: 0,
    TRIANGLES: 1
};
<|endoftext|>


<commit_before>// Copyright 2014 Globo.com Player authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

var Playback = require('../../base/playback')
var JST = require('../../base/jst')
var assign = require('lodash.assign')
var template = require('../../base/template')

var Mediator = require('../../components/mediator')
var Browser = require('../../components/browser')
var Events = require('../../base/events')
var Styler = require('../../base/styler')
var $ = require('clappr-zepto')

var HLSEvents = require('./flashls_events')

var objectIE = '<object type="application/x-shockwave-flash" id="<%= cid %>" class="hls-playback" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" data-hls="" width="100%" height="100%"><param name="movie" value="<%= baseUrl %>/assets/HLSPlayer.swf"> <param name="quality" value="autohigh"> <param name="swliveconnect" value="true"> <param name="allowScriptAccess" value="always"> <param name="bgcolor" value="#001122"> <param name="allowFullScreen" value="false"> <param name="wmode" value="transparent"> <param name="tabindex" value="1"> <param name=FlashVars value="playbackId=<%= playbackId %>" /> </object>'

class HLS extends Playback {
  get name() { return 'hls' }
  get tagName() { return 'object' }
  get template() { return JST.hls }
  get attributes() {
    return {
      'class': 'hls-playback',
      'data-hls': '',
      'type': 'application/x-shockwave-flash',
      'width': '100%',
      'height': '100%'
    }
  }

  constructor(options) {
    super(options)
    this.src = options.src
    this.baseUrl = options.baseUrl;
    this.flushLiveURLCache = (options.flushLiveURLCache === undefined) ? true : options.flushLiveURLCache
    this.capLevelToStage = (options.capLevelToStage === undefined) ? false : options.capLevelToStage
    this.useHardwareVideoDecoder = (options.useHardwareVideoDecoder === undefined) ? false : options.useHardwareVideoDecoder
    this.maxBufferLength = (options.maxBufferLength === undefined) ? 120 : options.maxBufferLength
    this.hlsMinimumDvrSize = (options.hlsMinimumDvrSize == undefined) ? 60 : options.hlsMinimumDvrSize
    this.hlsLogEnabled = (options.hlsLogEnabled == undefined) ? true : options.hlsLogEnabled
    this.highDefinition = false
    this.autoPlay = options.autoPlay
    this.defaultSettings = {
      left: ["playstop"],
      default: ['seekbar'],
      right: ["fullscreen", "volume", "hd-indicator"],
      seekEnabled: false
    }
    this.settings = assign({}, this.defaultSettings)
    this.playbackType = 'live'
    this.addListeners()
  }

  addListeners() {
    Mediator.on(this.cid + ':flashready', () => this.bootstrap())
    Mediator.on(this.cid + ':timeupdate', (timeMetrics) => this.updateTime(timeMetrics))
    Mediator.on(this.cid + ':playbackstate', (state) => this.setPlaybackState(state))
    Mediator.on(this.cid + ':levelchanged', (level) => this.updateHighDefinition(level))
    Mediator.on(this.cid + ':playbackerror', () => this.flashPlaybackError())
    Mediator.once(this.cid + ':manifestloaded',(duration, loadmetrics) => this.manifestLoaded(duration, loadmetrics))
  }

  stopListening() {
    super.stopListening()
    Mediator.off(this.cid + ':flashready')
    Mediator.off(this.cid + ':timeupdate')
    Mediator.off(this.cid + ':playbackstate')
    Mediator.off(this.cid + ':levelchanged')
    Mediator.off(this.cid + ':playbackerror')
    Mediator.off(this.cid + ':manifestloaded')
  }

  bootstrap() {
    this.el.width = "100%"
    this.el.height = "100%"
    this.isReady = true
    this.srcLoaded = false
    this.currentState = "IDLE"
    this.setFlashSettings()
    this.updatePlaybackType()
    this.autoPlay && this.play()
    this.trigger(Events.PLAYBACK_READY, this.name)
  }

  setFlashSettings() {
    this.el.playerSetflushLiveURLCache(this.flushLiveURLCache)
    this.el.playerCapLeveltoStage(this.capLevelToStage)
    this.el.playerSetmaxBufferLength(this.maxBufferLength)
    this.el.playerSetUseHardwareVideoDecoder(this.useHardwareVideoDecoder)
    this.el.playerSetLogInfo(this.hlsLogEnabled)
  }

  updateHighDefinition(level) {
    var currentLevel = this.getLevels()[level]
    this.highDefinition = (currentLevel.height >= 720 || (currentLevel.bitrate / 1000) >= 2000);
    this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE)
    this.trigger(Events.PLAYBACK_BITRATE, {'bitrate': this.getCurrentBitrate()})
  }

  updateTime(timeMetrics) {
    if (this.currentState === 'IDLE') return

    var duration = this.normalizeDuration(timeMetrics.duration)
    var position = Math.min(Math.max(timeMetrics.position, 0), duration)
    var previousDVRStatus = this.dvrEnabled
    var livePlayback = (this.playbackType === 'live')
    this.dvrEnabled = (livePlayback && duration > this.hlsMinimumDvrSize)

    if (duration === 100 || livePlayback === undefined) {
      return;
    }

    if (this.dvrEnabled !== previousDVRStatus) {
      this.updateSettings()
      this.trigger(Events.PLAYBACK_SETTINGSUPDATE, this.name)
    }

    if (livePlayback && (!this.dvrEnabled || !this.dvrInUse)) {
      position = duration
    }

    this.trigger(Events.PLAYBACK_TIMEUPDATE, position, duration, this.name)
  }

  play() {
    if(this.currentState === 'PAUSED') {
      this.el.playerResume()
    } else if (!this.srcLoaded && this.currentState !== "PLAYING") {
      this.firstPlay()
    } else {
      this.el.playerPlay()
    }
  }

  getPlaybackType() {
    return this.playbackType? this.playbackType: null
  }

  getCurrentLevelIndex() {
    return this.el.getCurrentLevel()
  }

  getCurrentLevel() {
    return this.getLevels()[this.getCurrentLevelIndex()]
  }

  getCurrentBitrate() {
    return this.getCurrentLevel().bitrate
  }

  setCurrentLevel(level) {
    this.el.playerSetCurrentLevel(level)
  }

  isHighDefinitionInUse() {
    return this.highDefinition
  }

  getLevels() {
    if (!this.levels || this.levels.length === 0) {
      this.levels = this.el.getLevels()
    }
    return this.levels
  }

  setPlaybackState(state) {
    if (["PLAYING_BUFFERING", "PAUSED_BUFFERING"].indexOf(state) >= 0)  {
      this.trigger(Events.PLAYBACK_BUFFERING, this.name)
      this.updateCurrentState(state)
    } else if (["PLAYING", "PAUSED"].indexOf(state) >= 0) {
      if (["PLAYING_BUFFERING", "PAUSED_BUFFERING", "IDLE"].indexOf(this.currentState) >= 0) {
        this.trigger(Events.PLAYBACK_BUFFERFULL, this.name)
      }
      this.updateCurrentState(state)
    } else if (state === "IDLE") {
      this.updateCurrentState(state)
      this.trigger(Events.PLAYBACK_TIMEUPDATE, 0, this.el.getDuration(), this.name)
      this.trigger(Events.PLAYBACK_ENDED, this.name)
    }
  }

  updateCurrentState(state) {
    this.currentState = state
    this.updatePlaybackType()
    if (state === "PLAYING") {
      this.trigger(Events.PLAYBACK_PLAY, this.name)
    } else if (state === "PAUSED") {
      this.trigger(Events.PLAYBACK_PAUSE, this.name)
    }
  }

  updatePlaybackType() {
    this.playbackType = this.el.getType()
    if (this.playbackType) {
      this.playbackType = this.playbackType.toLowerCase()
      if (this.playbackType === 'vod') {
        this.startReportingProgress()
      } else {
        this.stopReportingProgress()
      }
    }
    this.trigger(Events.PLAYBACK_PLAYBACKSTATE)
  }

  startReportingProgress() {
    if (!this.reportingProgress) {
      this.reportingProgress = true
      Mediator.on(this.cid + ':fragmentloaded',() => this.onFragmentLoaded())
    }
  }

  stopReportingProgress() {
    Mediator.off(this.cid + ':fragmentloaded', this.onFragmentLoaded, this)
  }

  onFragmentLoaded() {
    var buffered = this.el.getPosition() + this.el.getbufferLength()
    this.trigger(Events.PLAYBACK_PROGRESS, this.el.getPosition(), buffered, this.el.getDuration(), this.name)
  }

  firstPlay() {
    this.setFlashSettings() //ensure flushLiveURLCache will work (#327)
    this.el.playerLoad(this.src)
    Mediator.once(this.cid + ':manifestloaded',() => this.el.playerPlay())
    this.srcLoaded = true
  }

  volume(value) {
    if (this.isReady) {
      this.el.playerVolume(value)
    } else {
      this.listenToOnce(this, Events.PLAYBACK_BUFFERFULL, () => this.volume(value))
    }
  }

  pause() {
    if (this.playbackType !== 'live' || this.dvrEnabled) {
      this.el.playerPause()
      if (this.playbackType === 'live' && this.dvrEnabled) {
        this.updateDvr(true)
      }
    }
  }

  stop() {
    this.el.playerStop()
    this.trigger(Events.PLAYBACK_TIMEUPDATE, 0, this.name)
  }

  isPlaying() {
    if (this.currentState) {
      return !!(this.currentState.match(/playing/i))
    }
    return false
  }

  getDuration() {
    return this.normalizeDuration(this.el.getDuration())
  }

  normalizeDuration(duration) {
    if (this.playbackType === 'live') {
      // estimate 10 seconds of buffer time for live streams for seek positions
      duration = duration - 10
    }
    return duration
  }

  seek(time) {
    var duration = this.el.getDuration()
    if (time > 0) {
      time = duration * time / 100
    }

    if (this.playbackType === 'live') {
      // seek operations to a time within 5 seconds from live stream will position playhead back to live
      var dvrInUse = (time >= 0 && duration - time > 5)
      if (!dvrInUse) {
        time = -1
      }
      this.updateDvr(dvrInUse)
    }
    this.el.playerSeek(time)
    this.trigger(Events.PLAYBACK_TIMEUPDATE, time, duration, this.name)
    this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE)
  }

  updateDvr(dvrInUse) {
    var previousDvrInUse = !!this.dvrInUse
    this.dvrInUse = dvrInUse
    if (this.dvrInUse !== previousDvrInUse) {
      this.updateSettings()
      this.trigger(Events.PLAYBACK_DVR, this.dvrInUse)
      this.trigger(Events.PLAYBACK_STATS_ADD, {'dvr': this.dvrInUse})
    }
  }

  flashPlaybackError() {
    this.trigger(Events.PLAYBACK_STOP)
  }

  manifestLoaded(duration, loadmetrics) {
    this.trigger(Events.PLAYBACK_LOADEDMETADATA, duration, loadmetrics)
  }

  timeUpdate(time, duration) {
    this.trigger(Events.PLAYBACK_TIMEUPDATE, time, duration, this.name)
  }

  destroy() {
    this.stopListening()
    this.$el.remove()
  }

  setupFirefox() {
    var $el = this.$('embed')
    $el.attr('data-hls', '')
    this.setElement($el)
  }

  setupIE() {
    this.setElement($(template(objectIE)({cid: this.cid, baseUrl: this.baseUrl, playbackId: this.uniqueId})))
  }

  updateSettings() {
    this.settings = assign({}, this.defaultSettings)
    if (this.playbackType === "vod" || this.dvrInUse) {
      this.settings.left = ["playpause", "position", "duration"]
      this.settings.seekEnabled = true
    } else if (this.dvrEnabled) {
      this.settings.left = ["playpause"]
      this.settings.seekEnabled = true
    } else {
      this.settings.seekEnabled = false
    }
  }

  setElement(element) {
    this.$el = element
    this.el = element[0]
  }

  createCallbacks() {
    if (!window.Clappr.flashlsCallbacks) {
      window.Clappr.flashlsCallbacks = {}
    }
    this.flashlsEvents = new HLSEvents(this.cid)
    window.Clappr.flashlsCallbacks[this.cid] = (eventName, args) => {
      this.flashlsEvents[eventName].apply(this.flashlsEvents, args)
    }
  }

  render() {
    var style = Styler.getStyleFor(this.name)
    if(Browser.isLegacyIE) {
      this.setupIE()
    } else {
      var callbackName = this.createCallbacks()
      this.$el.html(this.template({cid: this.cid, baseUrl: this.baseUrl, playbackId: this.uniqueId, callbackName: `window.Clappr.flashlsCallbacks.${this.cid}`}))
      if(Browser.isFirefox) {
        this.setupFirefox()
      } else if (Browser.isIE) {
        this.$('embed').remove()
      }
    }
    this.el.id = this.cid
    this.$el.append(style)
    return this
  }
}

HLS.canPlay = function(resource, mimeType) {
  var resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
  return Browser.hasFlash &&
        ((resourceParts.length > 1 && resourceParts[1] == "m3u8") ||
          mimeType === 'application/x-mpegURL' || mimeType === 'application/vnd.apple.mpegurl')
}

module.exports = HLS
<commit_msg>hls: expose playback error event<commit_after>// Copyright 2014 Globo.com Player authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

var Playback = require('../../base/playback')
var JST = require('../../base/jst')
var assign = require('lodash.assign')
var template = require('../../base/template')

var Mediator = require('../../components/mediator')
var Browser = require('../../components/browser')
var Events = require('../../base/events')
var Styler = require('../../base/styler')
var $ = require('clappr-zepto')

var HLSEvents = require('./flashls_events')

var objectIE = '<object type="application/x-shockwave-flash" id="<%= cid %>" class="hls-playback" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" data-hls="" width="100%" height="100%"><param name="movie" value="<%= baseUrl %>/assets/HLSPlayer.swf"> <param name="quality" value="autohigh"> <param name="swliveconnect" value="true"> <param name="allowScriptAccess" value="always"> <param name="bgcolor" value="#001122"> <param name="allowFullScreen" value="false"> <param name="wmode" value="transparent"> <param name="tabindex" value="1"> <param name=FlashVars value="playbackId=<%= playbackId %>" /> </object>'

class HLS extends Playback {
  get name() { return 'hls' }
  get tagName() { return 'object' }
  get template() { return JST.hls }
  get attributes() {
    return {
      'class': 'hls-playback',
      'data-hls': '',
      'type': 'application/x-shockwave-flash',
      'width': '100%',
      'height': '100%'
    }
  }

  constructor(options) {
    super(options)
    this.src = options.src
    this.baseUrl = options.baseUrl;
    this.flushLiveURLCache = (options.flushLiveURLCache === undefined) ? true : options.flushLiveURLCache
    this.capLevelToStage = (options.capLevelToStage === undefined) ? false : options.capLevelToStage
    this.useHardwareVideoDecoder = (options.useHardwareVideoDecoder === undefined) ? false : options.useHardwareVideoDecoder
    this.maxBufferLength = (options.maxBufferLength === undefined) ? 120 : options.maxBufferLength
    this.hlsMinimumDvrSize = (options.hlsMinimumDvrSize == undefined) ? 60 : options.hlsMinimumDvrSize
    this.hlsLogEnabled = (options.hlsLogEnabled == undefined) ? true : options.hlsLogEnabled
    this.highDefinition = false
    this.autoPlay = options.autoPlay
    this.defaultSettings = {
      left: ["playstop"],
      default: ['seekbar'],
      right: ["fullscreen", "volume", "hd-indicator"],
      seekEnabled: false
    }
    this.settings = assign({}, this.defaultSettings)
    this.playbackType = 'live'
    this.addListeners()
  }

  addListeners() {
    Mediator.on(this.cid + ':flashready', () => this.bootstrap())
    Mediator.on(this.cid + ':timeupdate', (timeMetrics) => this.updateTime(timeMetrics))
    Mediator.on(this.cid + ':playbackstate', (state) => this.setPlaybackState(state))
    Mediator.on(this.cid + ':levelchanged', (level) => this.updateHighDefinition(level))
    Mediator.on(this.cid + ':error', (code, url, message) => this.flashPlaybackError(code, url, message))
    Mediator.once(this.cid + ':manifestloaded',(duration, loadmetrics) => this.manifestLoaded(duration, loadmetrics))
  }

  stopListening() {
    super.stopListening()
    Mediator.off(this.cid + ':flashready')
    Mediator.off(this.cid + ':timeupdate')
    Mediator.off(this.cid + ':playbackstate')
    Mediator.off(this.cid + ':levelchanged')
    Mediator.off(this.cid + ':playbackerror')
    Mediator.off(this.cid + ':manifestloaded')
  }

  bootstrap() {
    this.el.width = "100%"
    this.el.height = "100%"
    this.isReady = true
    this.srcLoaded = false
    this.currentState = "IDLE"
    this.setFlashSettings()
    this.updatePlaybackType()
    this.autoPlay && this.play()
    this.trigger(Events.PLAYBACK_READY, this.name)
  }

  setFlashSettings() {
    this.el.playerSetflushLiveURLCache(this.flushLiveURLCache)
    this.el.playerCapLeveltoStage(this.capLevelToStage)
    this.el.playerSetmaxBufferLength(this.maxBufferLength)
    this.el.playerSetUseHardwareVideoDecoder(this.useHardwareVideoDecoder)
    this.el.playerSetLogInfo(this.hlsLogEnabled)
  }

  updateHighDefinition(level) {
    var currentLevel = this.getLevels()[level]
    this.highDefinition = (currentLevel.height >= 720 || (currentLevel.bitrate / 1000) >= 2000);
    this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE)
    this.trigger(Events.PLAYBACK_BITRATE, {'bitrate': this.getCurrentBitrate()})
  }

  updateTime(timeMetrics) {
    if (this.currentState === 'IDLE') return

    var duration = this.normalizeDuration(timeMetrics.duration)
    var position = Math.min(Math.max(timeMetrics.position, 0), duration)
    var previousDVRStatus = this.dvrEnabled
    var livePlayback = (this.playbackType === 'live')
    this.dvrEnabled = (livePlayback && duration > this.hlsMinimumDvrSize)

    if (duration === 100 || livePlayback === undefined) {
      return;
    }

    if (this.dvrEnabled !== previousDVRStatus) {
      this.updateSettings()
      this.trigger(Events.PLAYBACK_SETTINGSUPDATE, this.name)
    }

    if (livePlayback && (!this.dvrEnabled || !this.dvrInUse)) {
      position = duration
    }

    this.trigger(Events.PLAYBACK_TIMEUPDATE, position, duration, this.name)
  }

  play() {
    if(this.currentState === 'PAUSED') {
      this.el.playerResume()
    } else if (!this.srcLoaded && this.currentState !== "PLAYING") {
      this.firstPlay()
    } else {
      this.el.playerPlay()
    }
  }

  getPlaybackType() {
    return this.playbackType? this.playbackType: null
  }

  getCurrentLevelIndex() {
    return this.el.getCurrentLevel()
  }

  getCurrentLevel() {
    return this.getLevels()[this.getCurrentLevelIndex()]
  }

  getCurrentBitrate() {
    return this.getCurrentLevel().bitrate
  }

  setCurrentLevel(level) {
    this.el.playerSetCurrentLevel(level)
  }

  isHighDefinitionInUse() {
    return this.highDefinition
  }

  getLevels() {
    if (!this.levels || this.levels.length === 0) {
      this.levels = this.el.getLevels()
    }
    return this.levels
  }

  setPlaybackState(state) {
    if (["PLAYING_BUFFERING", "PAUSED_BUFFERING"].indexOf(state) >= 0)  {
      this.trigger(Events.PLAYBACK_BUFFERING, this.name)
      this.updateCurrentState(state)
    } else if (["PLAYING", "PAUSED"].indexOf(state) >= 0) {
      if (["PLAYING_BUFFERING", "PAUSED_BUFFERING", "IDLE"].indexOf(this.currentState) >= 0) {
        this.trigger(Events.PLAYBACK_BUFFERFULL, this.name)
      }
      this.updateCurrentState(state)
    } else if (state === "IDLE") {
      this.updateCurrentState(state)
      this.trigger(Events.PLAYBACK_TIMEUPDATE, 0, this.el.getDuration(), this.name)
      this.trigger(Events.PLAYBACK_ENDED, this.name)
    }
  }

  updateCurrentState(state) {
    this.currentState = state
    this.updatePlaybackType()
    if (state === "PLAYING") {
      this.trigger(Events.PLAYBACK_PLAY, this.name)
    } else if (state === "PAUSED") {
      this.trigger(Events.PLAYBACK_PAUSE, this.name)
    }
  }

  updatePlaybackType() {
    this.playbackType = this.el.getType()
    if (this.playbackType) {
      this.playbackType = this.playbackType.toLowerCase()
      if (this.playbackType === 'vod') {
        this.startReportingProgress()
      } else {
        this.stopReportingProgress()
      }
    }
    this.trigger(Events.PLAYBACK_PLAYBACKSTATE)
  }

  startReportingProgress() {
    if (!this.reportingProgress) {
      this.reportingProgress = true
      Mediator.on(this.cid + ':fragmentloaded',() => this.onFragmentLoaded())
    }
  }

  stopReportingProgress() {
    Mediator.off(this.cid + ':fragmentloaded', this.onFragmentLoaded, this)
  }

  onFragmentLoaded() {
    var buffered = this.el.getPosition() + this.el.getbufferLength()
    this.trigger(Events.PLAYBACK_PROGRESS, this.el.getPosition(), buffered, this.el.getDuration(), this.name)
  }

  firstPlay() {
    this.setFlashSettings() //ensure flushLiveURLCache will work (#327)
    this.el.playerLoad(this.src)
    Mediator.once(this.cid + ':manifestloaded',() => this.el.playerPlay())
    this.srcLoaded = true
  }

  volume(value) {
    if (this.isReady) {
      this.el.playerVolume(value)
    } else {
      this.listenToOnce(this, Events.PLAYBACK_BUFFERFULL, () => this.volume(value))
    }
  }

  pause() {
    if (this.playbackType !== 'live' || this.dvrEnabled) {
      this.el.playerPause()
      if (this.playbackType === 'live' && this.dvrEnabled) {
        this.updateDvr(true)
      }
    }
  }

  stop() {
    this.el.playerStop()
    this.trigger(Events.PLAYBACK_TIMEUPDATE, 0, this.name)
  }

  isPlaying() {
    if (this.currentState) {
      return !!(this.currentState.match(/playing/i))
    }
    return false
  }

  getDuration() {
    return this.normalizeDuration(this.el.getDuration())
  }

  normalizeDuration(duration) {
    if (this.playbackType === 'live') {
      // estimate 10 seconds of buffer time for live streams for seek positions
      duration = duration - 10
    }
    return duration
  }

  seek(time) {
    var duration = this.el.getDuration()
    if (time > 0) {
      time = duration * time / 100
    }

    if (this.playbackType === 'live') {
      // seek operations to a time within 5 seconds from live stream will position playhead back to live
      var dvrInUse = (time >= 0 && duration - time > 5)
      if (!dvrInUse) {
        time = -1
      }
      this.updateDvr(dvrInUse)
    }
    this.el.playerSeek(time)
    this.trigger(Events.PLAYBACK_TIMEUPDATE, time, duration, this.name)
    this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE)
  }

  updateDvr(dvrInUse) {
    var previousDvrInUse = !!this.dvrInUse
    this.dvrInUse = dvrInUse
    if (this.dvrInUse !== previousDvrInUse) {
      this.updateSettings()
      this.trigger(Events.PLAYBACK_DVR, this.dvrInUse)
      this.trigger(Events.PLAYBACK_STATS_ADD, {'dvr': this.dvrInUse})
    }
  }

  flashPlaybackError(code, url, message) {
    this.trigger(Events.PLAYBACK_ERROR, {code: code, url: url, message: message})
    this.trigger(Events.PLAYBACK_STOP)
  }

  manifestLoaded(duration, loadmetrics) {
    this.trigger(Events.PLAYBACK_LOADEDMETADATA, duration, loadmetrics)
  }

  timeUpdate(time, duration) {
    this.trigger(Events.PLAYBACK_TIMEUPDATE, time, duration, this.name)
  }

  destroy() {
    this.stopListening()
    this.$el.remove()
  }

  setupFirefox() {
    var $el = this.$('embed')
    $el.attr('data-hls', '')
    this.setElement($el)
  }

  setupIE() {
    this.setElement($(template(objectIE)({cid: this.cid, baseUrl: this.baseUrl, playbackId: this.uniqueId})))
  }

  updateSettings() {
    this.settings = assign({}, this.defaultSettings)
    if (this.playbackType === "vod" || this.dvrInUse) {
      this.settings.left = ["playpause", "position", "duration"]
      this.settings.seekEnabled = true
    } else if (this.dvrEnabled) {
      this.settings.left = ["playpause"]
      this.settings.seekEnabled = true
    } else {
      this.settings.seekEnabled = false
    }
  }

  setElement(element) {
    this.$el = element
    this.el = element[0]
  }

  createCallbacks() {
    if (!window.Clappr.flashlsCallbacks) {
      window.Clappr.flashlsCallbacks = {}
    }
    this.flashlsEvents = new HLSEvents(this.cid)
    window.Clappr.flashlsCallbacks[this.cid] = (eventName, args) => {
      this.flashlsEvents[eventName].apply(this.flashlsEvents, args)
    }
  }

  render() {
    var style = Styler.getStyleFor(this.name)
    if(Browser.isLegacyIE) {
      this.setupIE()
    } else {
      var callbackName = this.createCallbacks()
      this.$el.html(this.template({cid: this.cid, baseUrl: this.baseUrl, playbackId: this.uniqueId, callbackName: `window.Clappr.flashlsCallbacks.${this.cid}`}))
      if(Browser.isFirefox) {
        this.setupFirefox()
      } else if (Browser.isIE) {
        this.$('embed').remove()
      }
    }
    this.el.id = this.cid
    this.$el.append(style)
    return this
  }
}

HLS.canPlay = function(resource, mimeType) {
  var resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
  return Browser.hasFlash &&
        ((resourceParts.length > 1 && resourceParts[1] == "m3u8") ||
          mimeType === 'application/x-mpegURL' || mimeType === 'application/vnd.apple.mpegurl')
}

module.exports = HLS
<|endoftext|>


<commit_before>
define( function() {

	var defaults = {

		/** TODO: */
	}

	, rowsToRanges = function( rows, lastCell ) {

		var ranges = [];

		for ( var i = 0; i < rows.length; ++i ) {
			ranges.push( new Slick.Range( rows[ i ], 0, rows[ i ], lastCell ) );
		}

		return ranges;
	}

	, rangesToRows = function( ranges ) {

		var rows = [];

		for ( var i = 0, length = ranges.length; i < length; ++i ) {
			var range = ranges[ i ];

			for ( var n = range.fromRow; n <= range.toRow; rows.push( n++ ) );
		}
		return rows;
	}

	, Rowselected = function( $G ) {

		var handler = new Slick.EventHandler()

		, inHandler = false

		, wrapHandler = function( handler ) {

			return function() {

				if ( !inHandler ) {
					inHandler = !(void handler.apply( this, arguments ));
					inHandler = !inHandler;
				}
			};
		}

		/** Store the selected items */
		, _ranges = [];

		$.extend( this, {

			init: function() {

				handler
					.subscribe( $G.onClick, wrapHandler( handleClick ) )
					.subscribe( $G.onKeyDown, wrapHandler( handleKeyDown ) );
			},

			destroy: function() {
				handler.unsubscribeAll();
			},

			onSelectedRangesChanged: new Slick.Event(),

			setSelectedRanges: function( ranges ) {
				_ranges = ranges;
				this.onSelectedRangesChanged.notify( _ranges );
			},

			getSelectedRanges: function() {
				return _ranges;
			},

			setSelectedRows: function( rows ) {
				this.setSelectedRanges( rowsToRanges( rows ) );
			},

			getSelectedRows: function() {
				return rangesToRows( this.getSelectedRanges );
			}
		} );

		function handleClick( e ) {

			var cell = $G.getCellFromEvent( e )

			, selecteds, index;

			/** Cell is null */
			if ( !cell ||
				/** Cann't multi select */
				!$G.getOptions().multiSelect ||
				/** Without function key */
				(!e.ctrlKey && !e.shiftKey && !e.metaKey) ||
				/** Cann't focus */
				!$G.canCellBeActive( cell.row, cell.cell ) ) {

				return false;
			}

			e.stopImmediatePropagation();

			selecteds = rangesToRows( _ranges );

			index = $.inArray( cell.row, selecteds );

			switch ( true ) {

				case index === -1 && !e.shiftKey:
					/** Selected */
					selecteds.push( cell.row );
					$G.setActiveCell( cell.row, cell.cell );
					break;

				case index > -1 && !e.shiftKey:
					/** Deselected */
					selecteds = $.grep( selecteds, function( idx ) {

						return idx !== cell.row;
					} );
					break;

				case !!(e.shiftKey && selecteds.length):

					var last = selecteds.pop();

					selecteds = [];

					for ( var i = Math.min( cell.row, last ), length = Math.max( cell.row, last ); i < length;
							selecteds.push( i++ ) );
					$G.setActiveCell( cell.row, cell.cell );
					break;

				default:
					return;
			}

			this.setSelectedRows( selecteds );
		}

		function handleKeyDown( e ) {

			e.stopImmediatePropagation();

			var cell = $G.getActiveCell(), active;

			if ( cell && e.shiftKey && (e.which === 38 || e.which === 40) ) {
				var selecteds = this.getSelectedRows()

				, from = Math.min.apply( Math, selecteds )
				, to = Math.max.apply( Math, selecteds );

				Infinity === from && (from = to = cell.row);

				if ( e.which === 40 ) {
					active = cell.row < to || from == to ? ++to : ++from;
				} else {
					active = cell.row < to ? --to : --from;
				}

				if ( active >= 0 && active < $G.getDataLength() ) {

					$G.scrollRowIntoView( active );

					for ( selecteds = []; from <= to; selecteds.push( from++ ) );

					this.setSelectedRows( selecteds );
				}
			}
		}
	};

	return function( $G ) {

		return new Rowselected( $G );
	};
} );
<commit_msg>Update RowsModel.js<commit_after>
define( function() {

	var defaults = {

		/** TODO: */
	}

	, rowsToRanges = function( rows, lastCell ) {

		var ranges = [];

		for ( var i = 0; i < rows.length; ++i ) {
			ranges.push( new Slick.Range( rows[ i ], 0, rows[ i ], lastCell ) );
		}

		return ranges;
	}

	, rangesToRows = function( ranges ) {

		var rows = [];

		for ( var i = 0, length = ranges.length; i < length; ++i ) {
			var range = ranges[ i ];

			for ( var n = range.fromRow; n <= range.toRow; rows.push( n++ ) );
		}
		return rows;
	}

	, Rowselected = function( $G ) {

		var handler = new Slick.EventHandler()

		, inHandler = false

		, wrapHandler = function( handler ) {

			return function() {

				if ( !inHandler ) {
					inHandler = !(void handler.apply( this, arguments ));
					inHandler = !inHandler;
				}
			};
		}

		/** Store the selected items */
		, _ranges = [];

		$.extend( this, {

			init: function() {

				handler
					.subscribe( $G.onClick, wrapHandler( handleClick ) )
					.subscribe( $G.onKeyDown, wrapHandler( handleKeyDown ) );
			},

			destroy: function() {
				handler.unsubscribeAll();
			},

			onSelectedRangesChanged: new Slick.Event(),

			setSelectedRanges: function( ranges ) {
				_ranges = ranges;
				this.onSelectedRangesChanged.notify( _ranges );
			},

			getSelectedRanges: function() {
				return _ranges;
			},

			setSelectedRows: function( rows ) {
				this.setSelectedRanges( rowsToRanges( rows ) );
			},

			getSelectedRows: function() {
				return rangesToRows( this.getSelectedRanges );
			}
		} );

		function handleClick( e ) {

			var cell = $G.getCellFromEvent( e )

			, selecteds, index;

			/** Cell is null */
			if ( !cell ||
				/** Cann't multi select */
				!$G.getOptions().multiSelect ||
				/** Without function key */
				(!e.ctrlKey && !e.shiftKey && !e.metaKey) ||
				/** Cann't focus */
				!$G.canCellBeActive( cell.row, cell.cell ) ) {

				return false;
			}

			e.stopImmediatePropagation();

			selecteds = rangesToRows( _ranges );

			index = $.inArray( cell.row, selecteds );

			switch ( true ) {

				case index === -1 && !e.shiftKey:
					/** Selected */
					selecteds.push( cell.row );
					$G.setActiveCell( cell.row, cell.cell );
					break;

				case index > -1 && !e.shiftKey:
					/** Deselected */
					selecteds = $.grep( selecteds, function( idx ) {

						return idx !== cell.row;
					} );
					break;

				case !!(e.shiftKey && selecteds.length):

					var last = selecteds.pop();

					selecteds = [];

					for ( var i = Math.min( cell.row, last ), length = Math.max( cell.row, last ); i < length;
							selecteds.push( i++ ) );
					$G.setActiveCell( cell.row, cell.cell );
					break;

				default:
					return;
			}

			this.setSelectedRows( selecteds );
		}

		function handleKeyDown( e ) {

			var cell = $G.getActiveCell(), active;

			if ( cell && e.shiftKey && (e.which === 38 || e.which === 40) ) {
				var selecteds = this.getSelectedRows()

				, from = Math.min.apply( Math, selecteds )
				, to = Math.max.apply( Math, selecteds );

				Infinity === from && (from = to = cell.row);

				if ( e.which === 40 ) {
					active = cell.row < to || from == to ? ++to : ++from;
				} else {
					active = cell.row < to ? --to : --from;
				}

				if ( active >= 0 && active < $G.getDataLength() ) {

					$G.scrollRowIntoView( active );

					for ( selecteds = []; from <= to; selecteds.push( from++ ) );

					this.setSelectedRows( selecteds );
				}
			}
		}
	};

	return function( $G ) {

		return new Rowselected( $G );
	};
} );
<|endoftext|>


<commit_before>const { h, Component } = require('preact')

class UrlUI extends Component {
  constructor (props) {
    super(props)
    this.handleKeyPress = this.handleKeyPress.bind(this)
    this.handleClick = this.handleClick.bind(this)
  }

  componentDidMount () {
    this.input.value = ''
    // My guess about why browser scrolls to top on focus:
    // Component is mounted right away, but the tab panel might be animating
    // still, so input element is positioned outside viewport. This fixes it.
    setTimeout(() => {
      if (!this.connectButton) return
      this.input.focus({ preventScroll: true })
    }, 150)
  }

  handleKeyPress (ev) {
    if (ev.keyCode === 13) {
      this.props.addFile(this.input.value)
    }
  }

  handleClick () {
    this.props.addFile(this.input.value)
  }

  render () {
    return

      <input
        class="uppy-c-textInput uppy-Url-input"
        type="text"
        placeholder={this.props.i18n('enterUrlToImport')}
        onkeyup={this.handleKeyPress}
        ref={(input) => { this.input = input }} />
      <button
        class="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Url-importButton"
        type="button"
        aria-label={this.props.i18n('import')}
        onclick={this.handleClick}>
        {this.props.i18n('import')}
      </button>

  }
}

module.exports = UrlUI
<commit_msg>Url: fix input focus<commit_after>const { h, Component } = require('preact')

class UrlUI extends Component {
  constructor (props) {
    super(props)
    this.handleKeyPress = this.handleKeyPress.bind(this)
    this.handleClick = this.handleClick.bind(this)
  }

  componentDidMount () {
    this.input.value = ''
    // My guess about why browser scrolls to top on focus:
    // Component is mounted right away, but the tab panel might be animating
    // still, so input element is positioned outside viewport. This fixes it.
    setTimeout(() => {
      if (!this.input) return
      this.input.focus({ preventScroll: true })
    }, 150)
  }

  handleKeyPress (ev) {
    if (ev.keyCode === 13) {
      this.props.addFile(this.input.value)
    }
  }

  handleClick () {
    this.props.addFile(this.input.value)
  }

  render () {
    return

      <input
        class="uppy-c-textInput uppy-Url-input"
        type="text"
        placeholder={this.props.i18n('enterUrlToImport')}
        onkeyup={this.handleKeyPress}
        ref={(input) => { this.input = input }} />
      <button
        class="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Url-importButton"
        type="button"
        aria-label={this.props.i18n('import')}
        onclick={this.handleClick}>
        {this.props.i18n('import')}
      </button>

  }
}

module.exports = UrlUI
<|endoftext|>


<commit_before>const Plugin = require('../core/Plugin')
const cuid = require('cuid')
const Translator = require('../core/Translator')
const UppySocket = require('../core/UppySocket')
const {
  emitSocketProgress,
  getSocketHost,
  settle,
  limitPromises
} = require('../core/Utils')

function buildResponseError (xhr, error) {
  // No error message
  if (!error) error = new Error('Upload error')
  // Got an error message string
  if (typeof error === 'string') error = new Error(error)
  // Got something else
  if (!(error instanceof Error)) {
    error = Object.assign(new Error('Upload error'), { data: error })
  }

  error.request = xhr
  return error
}

module.exports = class XHRUpload extends Plugin {
  constructor (uppy, opts) {
    super(uppy, opts)
    this.type = 'uploader'
    this.id = 'XHRUpload'
    this.title = 'XHRUpload'

    const defaultLocale = {
      strings: {
        timedOut: 'Upload stalled for %{seconds} seconds, aborting.'
      }
    }

    // Default options
    const defaultOptions = {
      formData: true,
      fieldName: 'files[]',
      method: 'post',
      metaFields: null,
      responseUrlFieldName: 'url',
      bundle: false,
      headers: {},
      locale: defaultLocale,
      timeout: 30 * 1000,
      limit: 0,
      /**
       * @typedef respObj
       * @property {string} responseText
       * @property {number} status
       * @property {string} statusText
       * @property {Object.<string, string>} headers
       *
       * @param {string} responseText the response body string
       * @param {XMLHttpRequest | respObj} response the response object (XHR or similar)
       */
      getResponseData (responseText, response) {
        let parsedResponse = {}
        try {
          parsedResponse = JSON.parse(responseText)
        } catch (err) {
          console.log(err)
        }

        return parsedResponse
      },
      /**
       *
       * @param {string} responseText the response body string
       * @param {XMLHttpRequest | respObj} response the response object (XHR or similar)
       */
      getResponseError (responseText, response) {
        return new Error('Upload error')
      }
    }

    // Merge default options with the ones set by user
    this.opts = Object.assign({}, defaultOptions, opts)
    this.locale = Object.assign({}, defaultLocale, this.opts.locale)
    this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)

    // i18n
    this.translator = new Translator({ locale: this.locale })
    this.i18n = this.translator.translate.bind(this.translator)

    this.handleUpload = this.handleUpload.bind(this)

    // Simultaneous upload limiting is shared across all uploads with this plugin.
    if (typeof this.opts.limit === 'number' && this.opts.limit !== 0) {
      this.limitUploads = limitPromises(this.opts.limit)
    } else {
      this.limitUploads = (fn) => fn
    }

    if (this.opts.bundle && !this.opts.formData) {
      throw new Error('`opts.formData` must be true when `opts.bundle` is enabled.')
    }
  }

  getOptions (file) {
    const opts = Object.assign({},
      this.opts,
      this.uppy.state.xhrUpload || {},
      file.xhrUpload || {}
    )
    opts.headers = {}
    Object.assign(opts.headers, this.opts.headers)
    if (this.uppy.state.xhrUpload) {
      Object.assign(opts.headers, this.uppy.state.xhrUpload.headers)
    }
    if (file.xhrUpload) {
      Object.assign(opts.headers, file.xhrUpload.headers)
    }

    return opts
  }

  // Helper to abort upload requests if there has not been any progress for `timeout` ms.
  // Create an instance using `timer = createProgressTimeout(10000, onTimeout)`
  // Call `timer.progress()` to signal that there has been progress of any kind.
  // Call `timer.done()` when the upload has completed.
  createProgressTimeout (timeout, timeoutHandler) {
    const uppy = this.uppy
    const self = this
    function onTimedOut () {
      uppy.log(`[XHRUpload] timed out`)
      const error = new Error(self.i18n('timedOut', { seconds: Math.ceil(timeout / 1000) }))
      timeoutHandler(error)
    }

    let aliveTimer = null
    function progress () {
      if (timeout > 0) {
        done()
        aliveTimer = setTimeout(onTimedOut, timeout)
      }
    }

    function done () {
      if (aliveTimer) {
        clearTimeout(aliveTimer)
        aliveTimer = null
      }
    }

    return {
      progress,
      done
    }
  }

  createFormDataUpload (file, opts) {
    const formPost = new FormData()

    const metaFields = Array.isArray(opts.metaFields)
      ? opts.metaFields
      // Send along all fields by default.
      : Object.keys(file.meta)
    metaFields.forEach((item) => {
      formPost.append(item, file.meta[item])
    })

    formPost.append(opts.fieldName, file.data)

    return formPost
  }

  createBareUpload (file, opts) {
    return file.data
  }

  upload (file, current, total) {
    const opts = this.getOptions(file)

    this.uppy.log(`uploading ${current} of ${total}`)
    return new Promise((resolve, reject) => {
      const data = opts.formData
        ? this.createFormDataUpload(file, opts)
        : this.createBareUpload(file, opts)

      const timer = this.createProgressTimeout(opts.timeout, (error) => {
        xhr.abort()
        this.uppy.emit('upload-error', file, error)
        reject(error)
      })

      const xhr = new XMLHttpRequest()
      const id = cuid()

      xhr.upload.addEventListener('loadstart', (ev) => {
        this.uppy.log(`[XHRUpload] ${id} started`)
        // Begin checking for timeouts when loading starts.
        timer.progress()
      })

      xhr.upload.addEventListener('progress', (ev) => {
        this.uppy.log(`[XHRUpload] ${id} progress: ${ev.loaded} / ${ev.total}`)
        timer.progress()

        if (ev.lengthComputable) {
          this.uppy.emit('upload-progress', file, {
            uploader: this,
            bytesUploaded: ev.loaded,
            bytesTotal: ev.total
          })
        }
      })

      xhr.addEventListener('load', (ev) => {
        this.uppy.log(`[XHRUpload] ${id} finished`)
        timer.done()

        if (ev.target.status >= 200 && ev.target.status < 300) {
          const body = opts.getResponseData(xhr.responseText, xhr)
          const uploadURL = body[opts.responseUrlFieldName]

          const response = {
            status: ev.target.status,
            body,
            uploadURL
          }

          this.uppy.setFileState(file.id, { response })

          this.uppy.emit('upload-success', file, body, uploadURL)

          if (uploadURL) {
            this.uppy.log(`Download ${file.name} from ${file.uploadURL}`)
          }

          return resolve(file)
        } else {
          const body = opts.getResponseData(xhr.responseText, xhr)
          const error = buildResponseError(xhr, opts.getResponseError(xhr.responseText, xhr))

          const response = {
            status: ev.target.status,
            body
          }

          this.uppy.setFileState(file.id, { response })

          this.uppy.emit('upload-error', file, error)
          return reject(error)
        }
      })

      xhr.addEventListener('error', (ev) => {
        this.uppy.log(`[XHRUpload] ${id} errored`)
        timer.done()

        const error = buildResponseError(xhr, opts.getResponseError(xhr.responseText, xhr))
        this.uppy.emit('upload-error', file, error)
        return reject(error)
      })

      xhr.open(opts.method.toUpperCase(), opts.endpoint, true)

      Object.keys(opts.headers).forEach((header) => {
        xhr.setRequestHeader(header, opts.headers[header])
      })

      xhr.send(data)

      this.uppy.on('file-removed', (removedFile) => {
        if (removedFile.id === file.id) {
          timer.done()
          xhr.abort()
        }
      })

      this.uppy.on('upload-cancel', (fileID) => {
        if (fileID === file.id) {
          timer.done()
          xhr.abort()
        }
      })

      this.uppy.on('cancel-all', () => {
        timer.done()
        xhr.abort()
      })
    })
  }

  uploadRemote (file, current, total) {
    const opts = this.getOptions(file)
    return new Promise((resolve, reject) => {
      const fields = {}
      const metaFields = Array.isArray(opts.metaFields)
        ? opts.metaFields
        // Send along all fields by default.
        : Object.keys(file.meta)

      metaFields.forEach((name) => {
        fields[name] = file.meta[name]
      })

      fetch(file.remote.url, {
        method: 'post',
        credentials: 'include',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(Object.assign({}, file.remote.body, {
          endpoint: opts.endpoint,
          size: file.data.size,
          fieldname: opts.fieldName,
          metadata: fields,
          headers: opts.headers
        }))
      })
      .then((res) => {
        if (res.status < 200 && res.status > 300) {
          return reject(res.statusText)
        }

        res.json().then((data) => {
          const token = data.token
          const host = getSocketHost(file.remote.host)
          const socket = new UppySocket({ target: `${host}/api/${token}` })

          socket.on('progress', (progressData) => emitSocketProgress(this, progressData, file))

          socket.on('success', (data) => {
            const resp = opts.getResponseData(data.response.responseText, data.response)
            const uploadURL = resp[opts.responseUrlFieldName]
            this.uppy.emit('upload-success', file, resp, uploadURL)
            socket.close()
            return resolve()
          })

          socket.on('error', (errData) => {
            const resp = errData.response
            const error = resp
              ? opts.getResponseError(resp.responseText, resp)
              : Object.assign(new Error(errData.error.message), { cause: errData.error })
            this.uppy.emit('upload-error', file, error)
            reject(error)
          })
        })
      })
    })
  }

  uploadBundle (files) {
    return new Promise((resolve, reject) => {
      const endpoint = this.opts.endpoint
      const method = this.opts.method

      const formData = new FormData()
      files.forEach((file, i) => {
        const opts = this.getOptions(file)
        formData.append(opts.fieldName, file.data)
      })

      const xhr = new XMLHttpRequest()

      const timer = this.createProgressTimeout(this.opts.timeout, (error) => {
        xhr.abort()
        emitError(error)
        reject(error)
      })

      const emitError = (error) => {
        files.forEach((file) => {
          this.uppy.emit('upload-error', file, error)
        })
      }

      xhr.upload.addEventListener('loadstart', (ev) => {
        this.uppy.log('[XHRUpload] started uploading bundle')
        timer.progress()
      })

      xhr.upload.addEventListener('progress', (ev) => {
        timer.progress()

        if (!ev.lengthComputable) return

        files.forEach((file) => {
          this.uppy.emit('upload-progress', file, {
            uploader: this,
            bytesUploaded: ev.loaded,
            bytesTotal: ev.total
          })
        })
      })

      xhr.addEventListener('load', (ev) => {
        timer.done()

        if (ev.target.status >= 200 && ev.target.status < 300) {
          const resp = this.opts.getResponseData(xhr.responseText, xhr)
          files.forEach((file) => {
            this.uppy.emit('upload-success', file, resp)
          })
          return resolve()
        }

        const error = this.opts.getResponseError(xhr.responseText, xhr) || new Error('Upload error')
        error.request = xhr
        emitError(error)
        return reject(error)
      })

      xhr.addEventListener('error', (ev) => {
        timer.done()

        const error = this.opts.getResponseError(xhr.responseText, xhr) || new Error('Upload error')
        emitError(error)
        return reject(error)
      })

      this.uppy.on('cancel-all', () => {
        timer.done()
        xhr.abort()
      })

      xhr.open(method.toUpperCase(), endpoint, true)

      Object.keys(this.opts.headers).forEach((header) => {
        xhr.setRequestHeader(header, this.opts.headers[header])
      })

      xhr.send(formData)

      files.forEach((file) => {
        this.uppy.emit('upload-started', file)
      })
    })
  }

  uploadFiles (files) {
    const actions = files.map((file, i) => {
      const current = parseInt(i, 10) + 1
      const total = files.length

      if (file.error) {
        return () => Promise.reject(new Error(file.error))
      } else if (file.isRemote) {
        // We emit upload-started here, so that it's also emitted for files
        // that have to wait due to the `limit` option.
        this.uppy.emit('upload-started', file)
        return this.uploadRemote.bind(this, file, current, total)
      } else {
        this.uppy.emit('upload-started', file)
        return this.upload.bind(this, file, current, total)
      }
    })

    const promises = actions.map((action) => {
      const limitedAction = this.limitUploads(action)
      return limitedAction()
    })

    return settle(promises)
  }

  handleUpload (fileIDs) {
    if (fileIDs.length === 0) {
      this.uppy.log('[XHRUpload] No files to upload!')
      return Promise.resolve()
    }

    this.uppy.log('[XHRUpload] Uploading...')
    const files = fileIDs.map((fileID) => this.uppy.getFile(fileID))

    if (this.opts.bundle) {
      return this.uploadBundle(files)
    }

    return this.uploadFiles(files).then(() => null)
  }

  install () {
    this.uppy.addUploader(this.handleUpload)
  }

  uninstall () {
    this.uppy.removeUploader(this.handleUpload)
  }
}
<commit_msg>fix bytesUploaded and bytesTotal for progress<commit_after>const Plugin = require('../core/Plugin')
const cuid = require('cuid')
const Translator = require('../core/Translator')
const UppySocket = require('../core/UppySocket')
const {
  emitSocketProgress,
  getSocketHost,
  settle,
  limitPromises
} = require('../core/Utils')

function buildResponseError (xhr, error) {
  // No error message
  if (!error) error = new Error('Upload error')
  // Got an error message string
  if (typeof error === 'string') error = new Error(error)
  // Got something else
  if (!(error instanceof Error)) {
    error = Object.assign(new Error('Upload error'), { data: error })
  }

  error.request = xhr
  return error
}

module.exports = class XHRUpload extends Plugin {
  constructor (uppy, opts) {
    super(uppy, opts)
    this.type = 'uploader'
    this.id = 'XHRUpload'
    this.title = 'XHRUpload'

    const defaultLocale = {
      strings: {
        timedOut: 'Upload stalled for %{seconds} seconds, aborting.'
      }
    }

    // Default options
    const defaultOptions = {
      formData: true,
      fieldName: 'files[]',
      method: 'post',
      metaFields: null,
      responseUrlFieldName: 'url',
      bundle: false,
      headers: {},
      locale: defaultLocale,
      timeout: 30 * 1000,
      limit: 0,
      /**
       * @typedef respObj
       * @property {string} responseText
       * @property {number} status
       * @property {string} statusText
       * @property {Object.<string, string>} headers
       *
       * @param {string} responseText the response body string
       * @param {XMLHttpRequest | respObj} response the response object (XHR or similar)
       */
      getResponseData (responseText, response) {
        let parsedResponse = {}
        try {
          parsedResponse = JSON.parse(responseText)
        } catch (err) {
          console.log(err)
        }

        return parsedResponse
      },
      /**
       *
       * @param {string} responseText the response body string
       * @param {XMLHttpRequest | respObj} response the response object (XHR or similar)
       */
      getResponseError (responseText, response) {
        return new Error('Upload error')
      }
    }

    // Merge default options with the ones set by user
    this.opts = Object.assign({}, defaultOptions, opts)
    this.locale = Object.assign({}, defaultLocale, this.opts.locale)
    this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)

    // i18n
    this.translator = new Translator({ locale: this.locale })
    this.i18n = this.translator.translate.bind(this.translator)

    this.handleUpload = this.handleUpload.bind(this)

    // Simultaneous upload limiting is shared across all uploads with this plugin.
    if (typeof this.opts.limit === 'number' && this.opts.limit !== 0) {
      this.limitUploads = limitPromises(this.opts.limit)
    } else {
      this.limitUploads = (fn) => fn
    }

    if (this.opts.bundle && !this.opts.formData) {
      throw new Error('`opts.formData` must be true when `opts.bundle` is enabled.')
    }
  }

  getOptions (file) {
    const opts = Object.assign({},
      this.opts,
      this.uppy.state.xhrUpload || {},
      file.xhrUpload || {}
    )
    opts.headers = {}
    Object.assign(opts.headers, this.opts.headers)
    if (this.uppy.state.xhrUpload) {
      Object.assign(opts.headers, this.uppy.state.xhrUpload.headers)
    }
    if (file.xhrUpload) {
      Object.assign(opts.headers, file.xhrUpload.headers)
    }

    return opts
  }

  // Helper to abort upload requests if there has not been any progress for `timeout` ms.
  // Create an instance using `timer = createProgressTimeout(10000, onTimeout)`
  // Call `timer.progress()` to signal that there has been progress of any kind.
  // Call `timer.done()` when the upload has completed.
  createProgressTimeout (timeout, timeoutHandler) {
    const uppy = this.uppy
    const self = this
    function onTimedOut () {
      uppy.log(`[XHRUpload] timed out`)
      const error = new Error(self.i18n('timedOut', { seconds: Math.ceil(timeout / 1000) }))
      timeoutHandler(error)
    }

    let aliveTimer = null
    function progress () {
      if (timeout > 0) {
        done()
        aliveTimer = setTimeout(onTimedOut, timeout)
      }
    }

    function done () {
      if (aliveTimer) {
        clearTimeout(aliveTimer)
        aliveTimer = null
      }
    }

    return {
      progress,
      done
    }
  }

  createFormDataUpload (file, opts) {
    const formPost = new FormData()

    const metaFields = Array.isArray(opts.metaFields)
      ? opts.metaFields
      // Send along all fields by default.
      : Object.keys(file.meta)
    metaFields.forEach((item) => {
      formPost.append(item, file.meta[item])
    })

    formPost.append(opts.fieldName, file.data)

    return formPost
  }

  createBareUpload (file, opts) {
    return file.data
  }

  upload (file, current, total) {
    const opts = this.getOptions(file)

    this.uppy.log(`uploading ${current} of ${total}`)
    return new Promise((resolve, reject) => {
      const data = opts.formData
        ? this.createFormDataUpload(file, opts)
        : this.createBareUpload(file, opts)

      const timer = this.createProgressTimeout(opts.timeout, (error) => {
        xhr.abort()
        this.uppy.emit('upload-error', file, error)
        reject(error)
      })

      const xhr = new XMLHttpRequest()
      const id = cuid()

      xhr.upload.addEventListener('loadstart', (ev) => {
        this.uppy.log(`[XHRUpload] ${id} started`)
        // Begin checking for timeouts when loading starts.
        timer.progress()
      })

      xhr.upload.addEventListener('progress', (ev) => {
        this.uppy.log(`[XHRUpload] ${id} progress: ${ev.loaded} / ${ev.total}`)
        timer.progress()

        if (ev.lengthComputable) {
          this.uppy.emit('upload-progress', file, {
            uploader: this,
            bytesUploaded: ev.loaded,
            bytesTotal: ev.total
          })
        }
      })

      xhr.addEventListener('load', (ev) => {
        this.uppy.log(`[XHRUpload] ${id} finished`)
        timer.done()

        if (ev.target.status >= 200 && ev.target.status < 300) {
          const body = opts.getResponseData(xhr.responseText, xhr)
          const uploadURL = body[opts.responseUrlFieldName]

          const response = {
            status: ev.target.status,
            body,
            uploadURL
          }

          this.uppy.setFileState(file.id, { response })

          this.uppy.emit('upload-success', file, body, uploadURL)

          if (uploadURL) {
            this.uppy.log(`Download ${file.name} from ${file.uploadURL}`)
          }

          return resolve(file)
        } else {
          const body = opts.getResponseData(xhr.responseText, xhr)
          const error = buildResponseError(xhr, opts.getResponseError(xhr.responseText, xhr))

          const response = {
            status: ev.target.status,
            body
          }

          this.uppy.setFileState(file.id, { response })

          this.uppy.emit('upload-error', file, error)
          return reject(error)
        }
      })

      xhr.addEventListener('error', (ev) => {
        this.uppy.log(`[XHRUpload] ${id} errored`)
        timer.done()

        const error = buildResponseError(xhr, opts.getResponseError(xhr.responseText, xhr))
        this.uppy.emit('upload-error', file, error)
        return reject(error)
      })

      xhr.open(opts.method.toUpperCase(), opts.endpoint, true)

      Object.keys(opts.headers).forEach((header) => {
        xhr.setRequestHeader(header, opts.headers[header])
      })

      xhr.send(data)

      this.uppy.on('file-removed', (removedFile) => {
        if (removedFile.id === file.id) {
          timer.done()
          xhr.abort()
        }
      })

      this.uppy.on('upload-cancel', (fileID) => {
        if (fileID === file.id) {
          timer.done()
          xhr.abort()
        }
      })

      this.uppy.on('cancel-all', () => {
        timer.done()
        xhr.abort()
      })
    })
  }

  uploadRemote (file, current, total) {
    const opts = this.getOptions(file)
    return new Promise((resolve, reject) => {
      const fields = {}
      const metaFields = Array.isArray(opts.metaFields)
        ? opts.metaFields
        // Send along all fields by default.
        : Object.keys(file.meta)

      metaFields.forEach((name) => {
        fields[name] = file.meta[name]
      })

      fetch(file.remote.url, {
        method: 'post',
        credentials: 'include',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(Object.assign({}, file.remote.body, {
          endpoint: opts.endpoint,
          size: file.data.size,
          fieldname: opts.fieldName,
          metadata: fields,
          headers: opts.headers
        }))
      })
      .then((res) => {
        if (res.status < 200 && res.status > 300) {
          return reject(res.statusText)
        }

        res.json().then((data) => {
          const token = data.token
          const host = getSocketHost(file.remote.host)
          const socket = new UppySocket({ target: `${host}/api/${token}` })

          socket.on('progress', (progressData) => emitSocketProgress(this, progressData, file))

          socket.on('success', (data) => {
            const resp = opts.getResponseData(data.response.responseText, data.response)
            const uploadURL = resp[opts.responseUrlFieldName]
            this.uppy.emit('upload-success', file, resp, uploadURL)
            socket.close()
            return resolve()
          })

          socket.on('error', (errData) => {
            const resp = errData.response
            const error = resp
              ? opts.getResponseError(resp.responseText, resp)
              : Object.assign(new Error(errData.error.message), { cause: errData.error })
            this.uppy.emit('upload-error', file, error)
            reject(error)
          })
        })
      })
    })
  }

  uploadBundle (files) {
    return new Promise((resolve, reject) => {
      const endpoint = this.opts.endpoint
      const method = this.opts.method

      const formData = new FormData()
      files.forEach((file, i) => {
        const opts = this.getOptions(file)
        formData.append(opts.fieldName, file.data)
      })

      const xhr = new XMLHttpRequest()

      const timer = this.createProgressTimeout(this.opts.timeout, (error) => {
        xhr.abort()
        emitError(error)
        reject(error)
      })

      const emitError = (error) => {
        files.forEach((file) => {
          this.uppy.emit('upload-error', file, error)
        })
      }

      xhr.upload.addEventListener('loadstart', (ev) => {
        this.uppy.log('[XHRUpload] started uploading bundle')
        timer.progress()
      })

      xhr.upload.addEventListener('progress', (ev) => {
        timer.progress()

        if (!ev.lengthComputable) return

        files.forEach((file) => {
          this.uppy.emit('upload-progress', file, {
            uploader: this,
            bytesUploaded: ev.loaded / ev.total * file.size,
            bytesTotal: file.size
          })
        })
      })

      xhr.addEventListener('load', (ev) => {
        timer.done()

        if (ev.target.status >= 200 && ev.target.status < 300) {
          const resp = this.opts.getResponseData(xhr.responseText, xhr)
          files.forEach((file) => {
            this.uppy.emit('upload-success', file, resp)
          })
          return resolve()
        }

        const error = this.opts.getResponseError(xhr.responseText, xhr) || new Error('Upload error')
        error.request = xhr
        emitError(error)
        return reject(error)
      })

      xhr.addEventListener('error', (ev) => {
        timer.done()

        const error = this.opts.getResponseError(xhr.responseText, xhr) || new Error('Upload error')
        emitError(error)
        return reject(error)
      })

      this.uppy.on('cancel-all', () => {
        timer.done()
        xhr.abort()
      })

      xhr.open(method.toUpperCase(), endpoint, true)

      Object.keys(this.opts.headers).forEach((header) => {
        xhr.setRequestHeader(header, this.opts.headers[header])
      })

      xhr.send(formData)

      files.forEach((file) => {
        this.uppy.emit('upload-started', file)
      })
    })
  }

  uploadFiles (files) {
    const actions = files.map((file, i) => {
      const current = parseInt(i, 10) + 1
      const total = files.length

      if (file.error) {
        return () => Promise.reject(new Error(file.error))
      } else if (file.isRemote) {
        // We emit upload-started here, so that it's also emitted for files
        // that have to wait due to the `limit` option.
        this.uppy.emit('upload-started', file)
        return this.uploadRemote.bind(this, file, current, total)
      } else {
        this.uppy.emit('upload-started', file)
        return this.upload.bind(this, file, current, total)
      }
    })

    const promises = actions.map((action) => {
      const limitedAction = this.limitUploads(action)
      return limitedAction()
    })

    return settle(promises)
  }

  handleUpload (fileIDs) {
    if (fileIDs.length === 0) {
      this.uppy.log('[XHRUpload] No files to upload!')
      return Promise.resolve()
    }

    this.uppy.log('[XHRUpload] Uploading...')
    const files = fileIDs.map((fileID) => this.uppy.getFile(fileID))

    if (this.opts.bundle) {
      return this.uploadBundle(files)
    }

    return this.uploadFiles(files).then(() => null)
  }

  install () {
    this.uppy.addUploader(this.handleUpload)
  }

  uninstall () {
    this.uppy.removeUploader(this.handleUpload)
  }
}
<|endoftext|>


<commit_before>'use strict'

const fs = require('fs')

const config = require('../internals/config')

function validateMessage(message) {
    if (!message.text) return false;
    // Is in a public channel? ignore
    if (message.channel.indexOf('C') === 0) return false;
    // It's a message from a bot? ignore
    if (message.bot_id) return false;

    return true;
}

function slackRules(services, message) {
    fs.readFile('./assets/rules.md', 'utf8', function (err, data) {
        if (err) {
            return console.log(err);
        }
        services.slack.sendMessage(message.channel, data)
    });
}

function cantUnderstand(services, message) {
    services.slack.sendMessage(message.channel, 'Lo siento, no entiendo ese comando. Te dejo una lista de las cosas que puedes pedirme:');
    help(services, message);
}

function help(services, message) {
    services.slack.sendMessage(message.channel, '`reglas slack` -> Las reglas de este slack');
}

const autoreplyPlugin = {
    id: 'jamses.autoreply',
    name: 'Autoreply',
    run(services) {
        services.slack.listenMessages((message) => {
            if (!validateMessage(message)) {
                return;
            }
            var parsedMessage = message.text.toLowerCase();
            switch(parsedMessage) {
                case "reglas slack":
                    slackRules(services, message);
                    break;
                default:
                    cantUnderstand(services, message);
                    break;
            }
        })
    }
}

module.exports = autoreplyPlugin
<commit_msg>Add timeout for help<commit_after>'use strict'

const fs = require('fs')

const config = require('../internals/config')

function validateMessage(message) {
    if (!message.text) return false;
    // Is in a public channel? ignore
    if (message.channel.indexOf('C') === 0) return false;
    // It's a message from a bot? ignore
    if (message.bot_id) return false;

    return true;
}

function slackRules(services, message) {
    fs.readFile('./assets/rules.md', 'utf8', function (err, data) {
        if (err) {
            return console.log(err);
        }
        services.slack.sendMessage(message.channel, data)
    });
}

function cantUnderstand(services, message) {
    services.slack.sendMessage(message.channel, 'Lo siento, no entiendo ese comando. Te dejo una lista de las cosas que puedes pedirme:');
    setTimeout(function() {
        help(services, message);
    }, 2000);
}

function help(services, message) {
    services.slack.sendMessage(message.channel, '`reglas slack` -> Las reglas de este slack');
}

const autoreplyPlugin = {
    id: 'jamses.autoreply',
    name: 'Autoreply',
    run(services) {
        services.slack.listenMessages((message) => {
            if (!validateMessage(message)) {
                return;
            }
            var parsedMessage = message.text.toLowerCase();
            switch(parsedMessage) {
                case "reglas slack":
                    slackRules(services, message);
                    break;
                default:
                    cantUnderstand(services, message);
                    break;
            }
        })
    }
}

module.exports = autoreplyPlugin
<|endoftext|>


<commit_before>const { next } = require('hooter/effects')
const {
  InputError, findDefaultCommand, findCommandByFullName, getCommandFromEvent,
} = require('../../common')
const composeHelp = require('./help')
const parseArgs = require('./parseArgs')
const modifySchema = require('./modifySchema')

function print(string, level = 'log') {
  /* eslint-disable no-console */
  console[level]()
  console[level](string)
  console[level]()
}

function handleError(err, commandConfig, commandName) {
  /* eslint-disable no-console */
  // TODO: show help on error

  let text = err

  if (err instanceof InputError) {
    text = err.message

    if (commandConfig) {
      text += '\n\n'
      text += composeHelp(commandConfig, commandName)
    }
  }

  print(text, 'error')
}

function handleResult(result) {
  if (typeof result === 'string') {
    print(result)
  }
}

module.exports = function cliPlugin(lifecycle) {
  lifecycle.hook('schema', function* (schema) {
    schema = modifySchema(schema)
    return yield next(schema)
  })

  lifecycle.hook('start', function* (config, ...args) {
    Promise.resolve()
      .then(() => {
        let args = process.argv.slice(2)
        return parseArgs(args, config)
      })
      .then((request) => {
        return lifecycle.toot('execute', request)
      })
      .then(handleResult)
      .catch((err) => {
        lifecycle.tootWith('error', (_, err, event) => {
          let commandConfig = findDefaultCommand(config, true)
          let commandName

          if (err.command) {
            let command = err.command
            let { fullName, inputName } = command
            commandConfig = findCommandByFullName(config, fullName, true)
            commandName = inputName
          } else if (event) {
            let command = getCommandFromEvent(event)
            commandConfig = command.config
            commandName = command.inputName
          }

          handleError(err, commandConfig, commandName)
        }, err)
      })

    return yield next(config, ...args)
  })

  lifecycle.hook('process', function* (_, command) {
    // This repeats the similar block in the help plugin
    // TODO: DRY it

    let { inputName, options, config } = command

    let isHelpAsked = options && options.some((option) => {
      return option.config && option.config.isHelpOption
    })

    if (!isHelpAsked) {
      return yield next(_, command)
    }

    if (config) {
      if (typeof config.help === 'string') {
        return config.help
      } else {
        return composeHelp(config, inputName)
      }
    }

    return `Help is unavailable for "${inputName}"`
  })
}
<commit_msg>Remove TODO comments in the CLI plugin<commit_after>const { next } = require('hooter/effects')
const {
  InputError, findDefaultCommand, findCommandByFullName, getCommandFromEvent,
} = require('../../common')
const composeHelp = require('./help')
const parseArgs = require('./parseArgs')
const modifySchema = require('./modifySchema')

function print(string, level = 'log') {
  /* eslint-disable no-console */
  console[level]()
  console[level](string)
  console[level]()
}

function handleError(err, commandConfig, commandName) {
  /* eslint-disable no-console */

  let text = err

  if (err instanceof InputError) {
    text = err.message

    if (commandConfig) {
      text += '\n\n'
      text += composeHelp(commandConfig, commandName)
    }
  }

  print(text, 'error')
}

function handleResult(result) {
  if (typeof result === 'string') {
    print(result)
  }
}

module.exports = function cliPlugin(lifecycle) {
  lifecycle.hook('schema', function* (schema) {
    schema = modifySchema(schema)
    return yield next(schema)
  })

  lifecycle.hook('start', function* (config, ...args) {
    Promise.resolve()
      .then(() => {
        let args = process.argv.slice(2)
        return parseArgs(args, config)
      })
      .then((request) => {
        return lifecycle.toot('execute', request)
      })
      .then(handleResult)
      .catch((err) => {
        lifecycle.tootWith('error', (_, err, event) => {
          let commandConfig = findDefaultCommand(config, true)
          let commandName

          if (err.command) {
            let command = err.command
            let { fullName, inputName } = command
            commandConfig = findCommandByFullName(config, fullName, true)
            commandName = inputName
          } else if (event) {
            let command = getCommandFromEvent(event)
            commandConfig = command.config
            commandName = command.inputName
          }

          handleError(err, commandConfig, commandName)
        }, err)
      })

    return yield next(config, ...args)
  })

  lifecycle.hook('process', function* (_, command) {
    let { inputName, options, config } = command

    let isHelpAsked = options && options.some((option) => {
      return option.config && option.config.isHelpOption
    })

    if (!isHelpAsked) {
      return yield next(_, command)
    }

    if (config) {
      if (typeof config.help === 'string') {
        return config.help
      } else {
        return composeHelp(config, inputName)
      }
    }

    return `Help is unavailable for "${inputName}"`
  })
}
<|endoftext|>