/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2019 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

var Class = require('../../utils/Class');
var GetAdvancedValue = require('../../utils/object/GetAdvancedValue');
var GetValue = require('../../utils/object/GetValue');
var MeasureText = require('./MeasureText');

//  Key: [ Object Key, Default Value ]

/**
 * A custom function that will be responsible for wrapping the text.
 * @callback TextStyleWordWrapCallback
 *
 * @param {string} text - The string to wrap.
 * @param {Phaser.GameObjects.Text} textObject - The Text instance.
 *
 * @return {(string|string[])} Should return the wrapped lines either as an array of lines or as a string with
 * newline characters in place to indicate where breaks should happen.
 */

var propertyMap = {
    fontFamily: [ 'fontFamily', 'Courier' ],
    fontSize: [ 'fontSize', '16px' ],
    fontStyle: [ 'fontStyle', '' ],
    backgroundColor: [ 'backgroundColor', null ],
    color: [ 'color', '#fff' ],
    stroke: [ 'stroke', '#fff' ],
    strokeThickness: [ 'strokeThickness', 0 ],
    shadowOffsetX: [ 'shadow.offsetX', 0 ],
    shadowOffsetY: [ 'shadow.offsetY', 0 ],
    shadowColor: [ 'shadow.color', '#000' ],
    shadowBlur: [ 'shadow.blur', 0 ],
    shadowStroke: [ 'shadow.stroke', false ],
    shadowFill: [ 'shadow.fill', false ],
    align: [ 'align', 'left' ],
    maxLines: [ 'maxLines', 0 ],
    fixedWidth: [ 'fixedWidth', 0 ],
    fixedHeight: [ 'fixedHeight', 0 ],
    resolution: [ 'resolution', 0 ],
    rtl: [ 'rtl', false ],
    testString: [ 'testString', '|MÃ‰qgy' ],
    baselineX: [ 'baselineX', 1.2 ],
    baselineY: [ 'baselineY', 1.4 ],
    wordWrapWidth: [ 'wordWrap.width', null ],
    wordWrapCallback: [ 'wordWrap.callback', null ],
    wordWrapCallbackScope: [ 'wordWrap.callbackScope', null ],
    wordWrapUseAdvanced: [ 'wordWrap.useAdvancedWrap', false ]
};

/**
 * @classdesc
 * A TextStyle class manages all of the style settings for a Text object.
 *
 * Text Game Objects create a TextStyle instance automatically, which is
 * accessed via the `Text.style` property. You do not normally need to
 * instantiate one yourself.
 *
 * @class TextStyle
 * @memberof Phaser.GameObjects
 * @constructor
 * @since 3.0.0
 *
 * @param {Phaser.GameObjects.Text} text - The Text object that this TextStyle is styling.
 * @param {object} style - The style settings to set.
 */
var TextStyle = new Class({

    initialize:

    function TextStyle (text, style)
    {
        /**
         * The Text object that this TextStyle is styling.
         *
         * @name Phaser.GameObjects.TextStyle#parent
         * @type {Phaser.GameObjects.Text}
         * @since 3.0.0
         */
        this.parent = text;

        /**
         * The font family.
         *
         * @name Phaser.GameObjects.TextStyle#fontFamily
         * @type {string}
         * @default 'Courier'
         * @since 3.0.0
         */
        this.fontFamily;

        /**
         * The font size.
         *
         * @name Phaser.GameObjects.TextStyle#fontSize
         * @type {string}
         * @default '16px'
         * @since 3.0.0
         */
        this.fontSize;

        /**
         * The font style.
         *
         * @name Phaser.GameObjects.TextStyle#fontStyle
         * @type {string}
         * @since 3.0.0
         */
        this.fontStyle;

        /**
         * The background color.
         *
         * @name Phaser.GameObjects.TextStyle#backgroundColor
         * @type {string}
         * @since 3.0.0
         */
        this.backgroundColor;

        /**
         * The text fill color.
         *
         * @name Phaser.GameObjects.TextStyle#color
         * @type {string}
         * @default '#fff'
         * @since 3.0.0
         */
        this.color;

        /**
         * The text stroke color.
         *
         * @name Phaser.GameObjects.TextStyle#stroke
         * @type {string}
         * @default '#fff'
         * @since 3.0.0
         */
        this.stroke;

        /**
         * The text stroke thickness.
         *
         * @name Phaser.GameObjects.TextStyle#strokeThickness
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.strokeThickness;

        /**
         * The horizontal shadow offset.
         *
         * @name Phaser.GameObjects.TextStyle#shadowOffsetX
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.shadowOffsetX;

        /**
         * The vertical shadow offset.
         *
         * @name Phaser.GameObjects.TextStyle#shadowOffsetY
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.shadowOffsetY;

        /**
         * The shadow color.
         *
         * @name Phaser.GameObjects.TextStyle#shadowColor
         * @type {string}
         * @default '#000'
         * @since 3.0.0
         */
        this.shadowColor;

        /**
         * The shadow blur radius.
         *
         * @name Phaser.GameObjects.TextStyle#shadowBlur
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.shadowBlur;

        /**
         * Whether shadow stroke is enabled or not.
         *
         * @name Phaser.GameObjects.TextStyle#shadowStroke
         * @type {boolean}
         * @default false
         * @since 3.0.0
         */
        this.shadowStroke;

        /**
         * Whether shadow fill is enabled or not.
         *
         * @name Phaser.GameObjects.TextStyle#shadowFill
         * @type {boolean}
         * @default false
         * @since 3.0.0
         */
        this.shadowFill;

        /**
         * The text alignment.
         *
         * @name Phaser.GameObjects.TextStyle#align
         * @type {string}
         * @default 'left'
         * @since 3.0.0
         */
        this.align;

        /**
         * The maximum number of lines to draw.
         *
         * @name Phaser.GameObjects.TextStyle#maxLines
         * @type {integer}
         * @default 0
         * @since 3.0.0
         */
        this.maxLines;

        /**
         * The fixed width of the text.
         *
         * `0` means no fixed with.
         *
         * @name Phaser.GameObjects.TextStyle#fixedWidth
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.fixedWidth;

        /**
         * The fixed height of the text.
         *
         * `0` means no fixed height.
         *
         * @name Phaser.GameObjects.TextStyle#fixedHeight
         * @type {number}
         * @default 0
         * @since 3.0.0
         */
        this.fixedHeight;

        /**
         * The resolution the text is rendered to its internal canvas at.
         * The default is 0, which means it will use the resolution set in the Game Config.
         *
         * @name Phaser.GameObjects.TextStyle#resolution
         * @type {number}
         * @default 0
         * @since 3.12.0
         */
        this.resolution;

        /**
         * Whether the text should render right to left.
         *
         * @name Phaser.GameObjects.TextStyle#rtl
         * @type {boolean}
         * @default false
         * @since 3.0.0
         */
        this.rtl;

        /**
         * The test string to use when measuring the font.
         *
         * @name Phaser.GameObjects.TextStyle#testString
         * @type {string}
         * @default '|MÉqgy'
         * @since 3.0.0
         */
        this.testString;

        /**
         * The amount of horizontal padding adding to the width of the text when calculating the font metrics.
         *
         * @name Phaser.GameObjects.TextStyle#baselineX
         * @type {number}
         * @default 1.2
         * @since 3.3.0
         */
        this.baselineX;

        /**
         * The amount of vertical padding adding to the width of the text when calculating the font metrics.
         *
         * @name Phaser.GameObjects.TextStyle#baselineY
         * @type {number}
         * @default 1.4
         * @since 3.3.0
         */
        this.baselineY;

        /**
         * The font style, size and family.
         *
         * @name Phaser.GameObjects.TextStyle#_font
         * @type {string}
         * @private
         * @since 3.0.0
         */
        this._font;

        //  Set to defaults + user style
        this.setStyle(style, false, true);

        var metrics = GetValue(style, 'metrics', false);

        //  Provide optional TextMetrics in the style object to avoid the canvas look-up / scanning
        //  Doing this is reset if you then change the font of this TextStyle after creation
        if (metrics)
        {
            this.metrics = {
                ascent: GetValue(metrics, 'ascent', 0),
                descent: GetValue(metrics, 'descent', 0),
                fontSize: GetValue(metrics, 'fontSize', 0)
            };
        }
        else
        {
            this.metrics = MeasureText(this);
        }
    },

    /**
     * Set the text style.
     *
     * @example
     * text.setStyle({
     *     fontSize: '64px',
     *     fontFamily: 'Arial',
     *     color: '#ffffff',
     *     align: 'center',
     *     backgroundColor: '#ff00ff'
     * });
     *
     * @method Phaser.GameObjects.TextStyle#setStyle
     * @since 3.0.0
     *
     * @param {object} style - The style settings to set.
     * @param {boolean} [updateText=true] - Whether to update the text immediately.
     * @param {boolean} [setDefaults=false] - Use the default values is not set, or the local values.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setStyle: function (style, updateText, setDefaults)
    {
        if (updateText === undefined) { updateText = true; }
        if (setDefaults === undefined) { setDefaults = false; }

        //  Avoid type mutation
        if (style && style.hasOwnProperty('fontSize') && typeof style.fontSize === 'number')
        {
            style.fontSize = style.fontSize.toString() + 'px';
        }

        for (var key in propertyMap)
        {
            var value = (setDefaults) ? propertyMap[key][1] : this[key];

            if (key === 'wordWrapCallback' || key === 'wordWrapCallbackScope')
            {
                // Callback & scope should be set without processing the values
                this[key] = GetValue(style, propertyMap[key][0], value);
            }
            else
            {
                this[key] = GetAdvancedValue(style, propertyMap[key][0], value);
            }
        }

        //  Allow for 'font' override
        var font = GetValue(style, 'font', null);

        if (font !== null)
        {
            this.setFont(font, false);
        }

        this._font = [ this.fontStyle, this.fontSize, this.fontFamily ].join(' ').trim();

        //  Allow for 'fill' to be used in place of 'color'
        var fill = GetValue(style, 'fill', null);

        if (fill !== null)
        {
            this.color = fill;
        }

        if (updateText)
        {
            return this.update(true);
        }
        else
        {
            return this.parent;
        }
    },

    /**
     * Synchronize the font settings to the given Canvas Rendering Context.
     *
     * @method Phaser.GameObjects.TextStyle#syncFont
     * @since 3.0.0
     *
     * @param {HTMLCanvasElement} canvas - The Canvas Element.
     * @param {CanvasRenderingContext2D} context - The Canvas Rendering Context.
     */
    syncFont: function (canvas, context)
    {
        context.font = this._font;
    },

    /**
     * Synchronize the text style settings to the given Canvas Rendering Context.
     *
     * @method Phaser.GameObjects.TextStyle#syncStyle
     * @since 3.0.0
     *
     * @param {HTMLCanvasElement} canvas - The Canvas Element.
     * @param {CanvasRenderingContext2D} context - The Canvas Rendering Context.
     */
    syncStyle: function (canvas, context)
    {
        context.textBaseline = 'alphabetic';

        context.fillStyle = this.color;
        context.strokeStyle = this.stroke;

        context.lineWidth = this.strokeThickness;
        context.lineCap = 'round';
        context.lineJoin = 'round';
    },

    /**
     * Synchronize the shadow settings to the given Canvas Rendering Context.
     *
     * @method Phaser.GameObjects.TextStyle#syncShadow
     * @since 3.0.0
     *
     * @param {CanvasRenderingContext2D} context - The Canvas Rendering Context.
     * @param {boolean} enabled - Whether shadows are enabled or not.
     */
    syncShadow: function (context, enabled)
    {
        if (enabled)
        {
            context.shadowOffsetX = this.shadowOffsetX;
            context.shadowOffsetY = this.shadowOffsetY;
            context.shadowColor = this.shadowColor;
            context.shadowBlur = this.shadowBlur;
        }
        else
        {
            context.shadowOffsetX = 0;
            context.shadowOffsetY = 0;
            context.shadowColor = 0;
            context.shadowBlur = 0;
        }
    },

    /**
     * Update the style settings for the parent Text object.
     *
     * @method Phaser.GameObjects.TextStyle#update
     * @since 3.0.0
     *
     * @param {boolean} recalculateMetrics - Whether to recalculate font and text metrics.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    update: function (recalculateMetrics)
    {
        if (recalculateMetrics)
        {
            this._font = [ this.fontStyle, this.fontSize, this.fontFamily ].join(' ').trim();

            this.metrics = MeasureText(this);
        }

        return this.parent.updateText();
    },

    /**
     * Set the font.
     *
     * If a string is given, the font family is set.
     *
     * If an object is given, the `fontFamily`, `fontSize` and `fontStyle`
     * properties of that object are set.
     *
     * @method Phaser.GameObjects.TextStyle#setFont
     * @since 3.0.0
     *
     * @param {(string|object)} font - The font family or font settings to set.
     * @param {boolean} [updateText=true] - Whether to update the text immediately.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setFont: function (font, updateText)
    {
        if (updateText === undefined) { updateText = true; }

        var fontFamily = font;
        var fontSize = '';
        var fontStyle = '';

        if (typeof font !== 'string')
        {
            fontFamily = GetValue(font, 'fontFamily', 'Courier');
            fontSize = GetValue(font, 'fontSize', '16px');
            fontStyle = GetValue(font, 'fontStyle', '');
        }
        else
        {
            var fontSplit = font.split(' ');

            var i = 0;

            fontStyle = (fontSplit.length > 2) ? fontSplit[i++] : '';
            fontSize = fontSplit[i++] || '16px';
            fontFamily = fontSplit[i++] || 'Courier';
        }

        if (fontFamily !== this.fontFamily || fontSize !== this.fontSize || fontStyle !== this.fontStyle)
        {
            this.fontFamily = fontFamily;
            this.fontSize = fontSize;
            this.fontStyle = fontStyle;

            if (updateText)
            {
                this.update(true);
            }
        }

        return this.parent;
    },

    /**
     * Set the font family.
     *
     * @method Phaser.GameObjects.TextStyle#setFontFamily
     * @since 3.0.0
     *
     * @param {string} family - The font family.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setFontFamily: function (family)
    {
        if (this.fontFamily !== family)
        {
            this.fontFamily = family;

            this.update(true);
        }

        return this.parent;
    },

    /**
     * Set the font style.
     *
     * @method Phaser.GameObjects.TextStyle#setFontStyle
     * @since 3.0.0
     *
     * @param {string} style - The font style.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setFontStyle: function (style)
    {
        if (this.fontStyle !== style)
        {
            this.fontStyle = style;

            this.update(true);
        }

        return this.parent;
    },

    /**
     * Set the font size.
     *
     * @method Phaser.GameObjects.TextStyle#setFontSize
     * @since 3.0.0
     *
     * @param {(number|string)} size - The font size.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setFontSize: function (size)
    {
        if (typeof size === 'number')
        {
            size = size.toString() + 'px';
        }

        if (this.fontSize !== size)
        {
            this.fontSize = size;

            this.update(true);
        }

        return this.parent;
    },

    /**
     * Set the test string to use when measuring the font.
     *
     * @method Phaser.GameObjects.TextStyle#setTestString
     * @since 3.0.0
     *
     * @param {string} string - The test string to use when measuring the font.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setTestString: function (string)
    {
        this.testString = string;

        return this.update(true);
    },

    /**
     * Set a fixed width and height for the text.
     *
     * Pass in `0` for either of these parameters to disable fixed width or height respectively.
     *
     * @method Phaser.GameObjects.TextStyle#setFixedSize
     * @since 3.0.0
     *
     * @param {number} width - The fixed width to set.
     * @param {number} height - The fixed height to set.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setFixedSize: function (width, height)
    {
        this.fixedWidth = width;
        this.fixedHeight = height;

        if (width)
        {
            this.parent.width = width;
        }

        if (height)
        {
            this.parent.height = height;
        }

        return this.update(false);
    },

    /**
     * Set the background color.
     *
     * @method Phaser.GameObjects.TextStyle#setBackgroundColor
     * @since 3.0.0
     *
     * @param {string} color - The background color.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setBackgroundColor: function (color)
    {
        this.backgroundColor = color;

        return this.update(false);
    },

    /**
     * Set the text fill color.
     *
     * @method Phaser.GameObjects.TextStyle#setFill
     * @since 3.0.0
     *
     * @param {string} color - The text fill color.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setFill: function (color)
    {
        this.color = color;

        return this.update(false);
    },

    /**
     * Set the text fill color.
     *
     * @method Phaser.GameObjects.TextStyle#setColor
     * @since 3.0.0
     *
     * @param {string} color - The text fill color.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setColor: function (color)
    {
        this.color = color;

        return this.update(false);
    },

    /**
     * Set the resolution used by the Text object.
     *
     * By default it will be set to match the resolution set in the Game Config,
     * but you can override it via this method. It allows for much clearer text on High DPI devices,
     * at the cost of memory because it uses larger internal Canvas textures for the Text.
     *
     * Please use with caution, as the more high res Text you have, the more memory it uses up.
     *
     * @method Phaser.GameObjects.TextStyle#setResolution
     * @since 3.12.0
     *
     * @param {number} value - The resolution for this Text object to use.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setResolution: function (value)
    {
        this.resolution = value;

        return this.update(false);
    },

    /**
     * Set the stroke settings.
     *
     * @method Phaser.GameObjects.TextStyle#setStroke
     * @since 3.0.0
     *
     * @param {string} color - The stroke color.
     * @param {number} thickness - The stroke thickness.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setStroke: function (color, thickness)
    {
        if (thickness === undefined) { thickness = this.strokeThickness; }

        if (color === undefined && this.strokeThickness !== 0)
        {
            //  Reset the stroke to zero (disabling it)
            this.strokeThickness = 0;

            this.update(true);
        }
        else if (this.stroke !== color || this.strokeThickness !== thickness)
        {
            this.stroke = color;
            this.strokeThickness = thickness;

            this.update(true);
        }

        return this.parent;
    },

    /**
     * Set the shadow settings.
     *
     * Calling this method always re-measures the parent Text object,
     * so only call it when you actually change the shadow settings.
     *
     * @method Phaser.GameObjects.TextStyle#setShadow
     * @since 3.0.0
     *
     * @param {number} [x=0] - The horizontal shadow offset.
     * @param {number} [y=0] - The vertical shadow offset.
     * @param {string} [color='#000'] - The shadow color.
     * @param {number} [blur=0] - The shadow blur radius.
     * @param {boolean} [shadowStroke=false] - Whether to stroke the shadow.
     * @param {boolean} [shadowFill=true] - Whether to fill the shadow.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setShadow: function (x, y, color, blur, shadowStroke, shadowFill)
    {
        if (x === undefined) { x = 0; }
        if (y === undefined) { y = 0; }
        if (color === undefined) { color = '#000'; }
        if (blur === undefined) { blur = 0; }
        if (shadowStroke === undefined) { shadowStroke = false; }
        if (shadowFill === undefined) { shadowFill = true; }

        this.shadowOffsetX = x;
        this.shadowOffsetY = y;
        this.shadowColor = color;
        this.shadowBlur = blur;
        this.shadowStroke = shadowStroke;
        this.shadowFill = shadowFill;

        return this.update(false);
    },

    /**
     * Set the shadow offset.
     *
     * @method Phaser.GameObjects.TextStyle#setShadowOffset
     * @since 3.0.0
     *
     * @param {number} [x=0] - The horizontal shadow offset.
     * @param {number} [y=0] - The vertical shadow offset.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setShadowOffset: function (x, y)
    {
        if (x === undefined) { x = 0; }
        if (y === undefined) { y = x; }

        this.shadowOffsetX = x;
        this.shadowOffsetY = y;

        return this.update(false);
    },

    /**
     * Set the shadow color.
     *
     * @method Phaser.GameObjects.TextStyle#setShadowColor
     * @since 3.0.0
     *
     * @param {string} [color='#000'] - The shadow color.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setShadowColor: function (color)
    {
        if (color === undefined) { color = '#000'; }

        this.shadowColor = color;

        return this.update(false);
    },

    /**
     * Set the shadow blur radius.
     *
     * @method Phaser.GameObjects.TextStyle#setShadowBlur
     * @since 3.0.0
     *
     * @param {number} [blur=0] - The shadow blur radius.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setShadowBlur: function (blur)
    {
        if (blur === undefined) { blur = 0; }

        this.shadowBlur = blur;

        return this.update(false);
    },

    /**
     * Enable or disable shadow stroke.
     *
     * @method Phaser.GameObjects.TextStyle#setShadowStroke
     * @since 3.0.0
     *
     * @param {boolean} enabled - Whether shadow stroke is enabled or not.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setShadowStroke: function (enabled)
    {
        this.shadowStroke = enabled;

        return this.update(false);
    },

    /**
     * Enable or disable shadow fill.
     *
     * @method Phaser.GameObjects.TextStyle#setShadowFill
     * @since 3.0.0
     *
     * @param {boolean} enabled - Whether shadow fill is enabled or not.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setShadowFill: function (enabled)
    {
        this.shadowFill = enabled;

        return this.update(false);
    },

    /**
     * Set the width (in pixels) to use for wrapping lines.
     *
     * Pass in null to remove wrapping by width.
     *
     * @method Phaser.GameObjects.TextStyle#setWordWrapWidth
     * @since 3.0.0
     *
     * @param {number} width - The maximum width of a line in pixels. Set to null to remove wrapping.
     * @param {boolean} [useAdvancedWrap=false] - Whether or not to use the advanced wrapping
     * algorithm. If true, spaces are collapsed and whitespace is trimmed from lines. If false,
     * spaces and whitespace are left as is.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setWordWrapWidth: function (width, useAdvancedWrap)
    {
        if (useAdvancedWrap === undefined) { useAdvancedWrap = false; }

        this.wordWrapWidth = width;
        this.wordWrapUseAdvanced = useAdvancedWrap;

        return this.update(false);
    },

    /**
     * Set a custom callback for wrapping lines.
     *
     * Pass in null to remove wrapping by callback.
     *
     * @method Phaser.GameObjects.TextStyle#setWordWrapCallback
     * @since 3.0.0
     *
     * @param {TextStyleWordWrapCallback} callback - A custom function that will be responsible for wrapping the
     * text. It will receive two arguments: text (the string to wrap), textObject (this Text
     * instance). It should return the wrapped lines either as an array of lines or as a string with
     * newline characters in place to indicate where breaks should happen.
     * @param {object} [scope=null] - The scope that will be applied when the callback is invoked.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setWordWrapCallback: function (callback, scope)
    {
        if (scope === undefined) { scope = null; }

        this.wordWrapCallback = callback;
        this.wordWrapCallbackScope = scope;

        return this.update(false);
    },

    /**
     * Set the text alignment.
     *
     * Expects values like `'left'`, `'right'`, `'center'` or `'justified'`.
     *
     * @method Phaser.GameObjects.TextStyle#setAlign
     * @since 3.0.0
     *
     * @param {string} align - The text alignment.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setAlign: function (align)
    {
        if (align === undefined) { align = 'left'; }

        this.align = align;

        return this.update(false);
    },

    /**
     * Set the maximum number of lines to draw.
     *
     * @method Phaser.GameObjects.TextStyle#setMaxLines
     * @since 3.0.0
     *
     * @param {integer} [max=0] - The maximum number of lines to draw.
     *
     * @return {Phaser.GameObjects.Text} The parent Text object.
     */
    setMaxLines: function (max)
    {
        if (max === undefined) { max = 0; }

        this.maxLines = max;

        return this.update(false);
    },

    /**
     * Get the current text metrics.
     *
     * @method Phaser.GameObjects.TextStyle#getTextMetrics
     * @since 3.0.0
     *
     * @return {BitmapTextMetrics} The text metrics.
     */
    getTextMetrics: function ()
    {
        var metrics = this.metrics;

        return {
            ascent: metrics.ascent,
            descent: metrics.descent,
            fontSize: metrics.fontSize
        };
    },

    /**
     * Build a JSON representation of this Text Style.
     *
     * @method Phaser.GameObjects.TextStyle#toJSON
     * @since 3.0.0
     *
     * @return {object} A JSON representation of this Text Style.
     */
    toJSON: function ()
    {
        var output = {};

        for (var key in propertyMap)
        {
            output[key] = this[key];
        }

        output.metrics = this.getTextMetrics();

        return output;
    },

    /**
     * Destroy this Text Style.
     *
     * @method Phaser.GameObjects.TextStyle#destroy
     * @since 3.0.0
     */
    destroy: function ()
    {
        this.parent = undefined;
    }

});

module.exports = TextStyle;


keep keep keep keep keep replace keep keep keep

/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2019 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

/**
 * The Pointer Move Input Event.
 *
 * This event is dispatched by the Input Plugin belonging to a Scene if a pointer is moved anywhere.
 *
 * Listen to this event from within a Scene using: `this.input.on('pointermove', listener)`.
 *
 * The event hierarchy is as follows:
 *
 * 1. [GAMEOBJECT_POINTER_MOVE]{@linkcode Phaser.Input.Events#event:GAMEOBJECT_POINTER_MOVE}
 * 2. [GAMEOBJECT_MOVE]{@linkcode Phaser.Input.Events#event:GAMEOBJECT_MOVE}
 * 3. [POINTER_MOVE]{@linkcode Phaser.Input.Events#event:POINTER_MOVE}
 *
 * With the top event being dispatched first and then flowing down the list. Note that higher-up event handlers can stop
 * the propagation of this event.
 *
 * @event Phaser.Input.Events#POINTER_MOVE
 * @since 3.0.0
 *
 * @param {Phaser.Input.Pointer} pointer - The Pointer responsible for triggering this event.
 * @param {Phaser.GameObjects.GameObject[]} currentlyOver - An array containing all interactive Game Objects that the pointer was over when the event was created.
 */
module.exports = 'pointermove';


<%_ if (hasESLint) { _%>
module.exports = {
rules: {
'no-unused-expressions': 'off'
}
}
<%_ } _%>

jest.setTimeout(35000)

const generateWithPlugin = require('@vue/cli-test-utils/generateWithPlugin')
const create = require('@vue/cli-test-utils/createTestProject')

test('base', async () => {
  const { pkg } = await generateWithPlugin({
    id: 'eslint',
    apply: require('../generator'),
    options: {}
  })

  expect(pkg.scripts.lint).toBeTruthy()
  expect(pkg.eslintConfig.extends).toEqual([
    'plugin:vue/essential', 'eslint:recommended'
  ])
})

test('airbnb', async () => {
  const { pkg } = await generateWithPlugin({
    id: 'eslint',
    apply: require('../generator'),
    options: {
      config: 'airbnb'
    }
  })

  expect(pkg.scripts.lint).toBeTruthy()
  expect(pkg.eslintConfig.extends).toEqual([
    'plugin:vue/essential',
    '@vue/airbnb'
  ])
  expect(pkg.devDependencies).toHaveProperty('@vue/eslint-config-airbnb')
})

test('standard', async () => {
  const { pkg } = await generateWithPlugin({
    id: 'eslint',
    apply: require('../generator'),
    options: {
      config: 'standard'
    }
  })

  expect(pkg.scripts.lint).toBeTruthy()
  expect(pkg.eslintConfig.extends).toEqual([
    'plugin:vue/essential',
    '@vue/standard'
  ])
  expect(pkg.devDependencies).toHaveProperty('@vue/eslint-config-standard')
})

test('prettier', async () => {
  const { pkg } = await generateWithPlugin({
    id: 'eslint',
    apply: require('../generator'),
    options: {
      config: 'prettier'
    }
  })

  expect(pkg.scripts.lint).toBeTruthy()
  expect(pkg.eslintConfig.extends).toEqual([
    'plugin:vue/essential',
    'eslint:recommended',
    '@vue/prettier'
  ])
  expect(pkg.devDependencies).toHaveProperty('@vue/eslint-config-prettier')
})

test('babel', async () => {
  const { pkg } = await generateWithPlugin([
    {
      id: 'eslint',
      apply: require('../generator'),
      options: {}
    },
    {
      id: 'babel',
      apply: require('@vue/cli-plugin-babel/generator'),
      options: {}
    }
  ])

  expect(pkg.scripts.lint).toBeTruthy()
  expect(pkg.devDependencies).toHaveProperty('babel-eslint')
  expect(pkg.eslintConfig.parserOptions).toEqual({
    parser: 'babel-eslint'
  })
})

test('typescript', async () => {
  const { pkg } = await generateWithPlugin([
    {
      id: 'eslint',
      apply: require('../generator'),
      options: {
        config: 'prettier'
      }
    },
    {
      id: 'typescript',
      apply: require('@vue/cli-plugin-typescript/generator'),
      options: {}
    }
  ])

  expect(pkg.scripts.lint).toBeTruthy()
  expect(pkg.eslintConfig.extends).toEqual([
    'plugin:vue/essential',
    'eslint:recommended',
    '@vue/typescript/recommended',
    '@vue/prettier',
    '@vue/prettier/@typescript-eslint'
  ])
  expect(pkg.devDependencies).toHaveProperty('@vue/eslint-config-prettier')
  expect(pkg.devDependencies).toHaveProperty('@vue/eslint-config-typescript')
})

test('lint on save', async () => {
  const { pkg } = await generateWithPlugin({
    id: 'eslint',
    apply: require('../generator'),
    options: {
      lintOn: 'save'
    }
  })
  // lintOnSave defaults to true so no need for the vue config
  expect(pkg.vue).toBeFalsy()
})

test('lint on commit', async () => {
  const { pkg } = await generateWithPlugin({
    id: 'eslint',
    apply: require('../generator'),
    options: {
      lintOn: 'commit'
    }
  })
  expect(pkg.gitHooks['pre-commit']).toBe('lint-staged')
  expect(pkg.devDependencies).toHaveProperty('lint-staged')
  expect(pkg['lint-staged']).toEqual({
    '*.{js,jsx,vue}': ['vue-cli-service lint', 'git add']
  })
  expect(pkg.vue).toEqual({
    lintOnSave: false
  })
})

test('should lint ts files when typescript plugin co-exists', async () => {
  const { read } = await create('eslint-lint-ts-files', {
    plugins: {
      '@vue/cli-plugin-eslint': {
        lintOn: 'commit'
      },
      '@vue/cli-plugin-typescript': {}
    }
  }, null, true)
  const pkg = JSON.parse(await read('package.json'))
  expect(pkg).toMatchObject({
    'lint-staged': {
      '*.{js,jsx,vue,ts,tsx}': ['vue-cli-service lint', 'git add']
    }
  })
})

test('generate .editorconfig for new projects', async () => {
  const { files } = await generateWithPlugin({
    id: 'eslint',
    apply: require('../generator'),
    options: {
      config: 'airbnb'
    }
  })
  expect(files['.editorconfig']).toBeTruthy()
})

test('append to existing .editorconfig', async () => {
  const { dir, read, write } = await create('eslint-editorconfig', {
    plugins: {
      '@vue/cli-plugin-eslint': {}
    }
  }, null, true)
  await write('.editorconfig', 'root = true\n')

  const invoke = require('@vue/cli/lib/invoke')
  await invoke(`eslint`, { config: 'airbnb' }, dir)

  const editorconfig = await read('.editorconfig')
  expect(editorconfig).toMatch('root = true')
  expect(editorconfig).toMatch('[*.{js,jsx,ts,tsx,vue}]')
})

test('airbnb config + typescript + unit-mocha', async () => {
  await create('eslint-airbnb-typescript', {
    plugins: {
      '@vue/cli-plugin-eslint': {
        config: 'airbnb',
        lintOn: 'commit'
      },
      '@vue/cli-plugin-typescript': {
        classComponent: true
      },
      '@vue/cli-plugin-unit-mocha': {}
    }
  })
}, 30000)

test('should be able to parse dynamic import syntax', async () => {
  await create('eslint-dynamic-import', {
    plugins: {
      '@vue/cli-plugin-eslint': {},
      '@vue/cli-plugin-router': {}
    }
  })
}, 30000)


mutation darkModeSet($enabled: Boolean!) {
  darkModeSet (enabled: $enabled) @client
}


const { createSchema, validateSync } = require('@vue/cli-shared-utils')

const schema = createSchema(joi => ({
  id: joi.string().required(),
  path: joi.string(),
  url: joi.string()
}))

exports.validateClientAddon = (options) => {
  validateSync(options, schema)
}


test('should be able to parse dynamic import syntax', async () => {

test('typescript + e2e-nightwatch', async () => {

const { run } = await create('eslint-typescript-nightwatch', {

<%_ if (hasESLint) { _%>
module.exports = {
  rules: {
    'no-unused-expressions': 'off'
  }
}
<%_ } _%>


/**
 * This file is copied during the test inside the project folder and used to inpsect the results
 */
const fs = require('fs')

module.exports = {
  afterEach (browser, cb) {
    fs.writeFile('test_settings.json', JSON.stringify(browser.options), cb)
  },

  reporter (results, cb) {
    fs.writeFile('test_results.json', JSON.stringify(results), cb)
  }
}


jest.setTimeout(15000)

const path = require('path')
const portfinder = require('portfinder')
const createServer = require('@vue/cli-test-utils/createServer')
const { defaultPreset } = require('@vue/cli/lib/options')
const create = require('@vue/cli-test-utils/createTestProject')
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')

let server, browser, page
test('build as wc in async mode', async () => {
  const project = await create('build-wc-async', defaultPreset)

  const { stdout } = await project.run(`vue-cli-service build --target wc-async **/*.vue`)
  expect(stdout).toMatch('Build complete.')

  expect(project.has('dist/demo.html')).toBe(true)
  expect(project.has('dist/build-wc-async.js')).toBe(true)
  expect(project.has('dist/build-wc-async.min.js')).toBe(true)

  // code-split chunks
  expect(project.has('dist/build-wc-async.1.js')).toBe(true)
  expect(project.has('dist/build-wc-async.1.min.js')).toBe(true)
  expect(project.has('dist/build-wc-async.2.js')).toBe(true)
  expect(project.has('dist/build-wc-async.2.min.js')).toBe(true)

  const port = await portfinder.getPortPromise()
  server = createServer({ root: path.join(project.dir, 'dist') })

  await new Promise((resolve, reject) => {
    server.listen(port, err => {
      if (err) return reject(err)
      resolve()
    })
  })

  const launched = await launchPuppeteer(`http://localhost:${port}/demo.html`)
  browser = launched.browser
  page = launched.page

  const styleCount = await page.evaluate(() => {
    return document.querySelector('build-wc-async-app').shadowRoot.querySelectorAll('style').length
  })
  expect(styleCount).toBe(2) // should contain styles from both app and child

  const h1Text = await page.evaluate(() => {
    return document.querySelector('build-wc-async-app').shadowRoot.querySelector('h1').textContent
  })
  expect(h1Text).toMatch('Welcome to Your Vue.js App')

  const childStyleCount = await page.evaluate(() => {
    return document.querySelector('build-wc-async-hello-world').shadowRoot.querySelectorAll('style').length
  })
  expect(childStyleCount).toBe(1)

  const h3Text = await page.evaluate(() => {
    return document.querySelector('build-wc-async-hello-world').shadowRoot.querySelector('h3').textContent
  })
  expect(h3Text).toMatch('Installed CLI Plugins')
})

afterAll(async () => {
  if (browser) {
    await browser.close()
  }
  if (server) {
    server.close()
  }
})


<template>
  <Widget
    :widget="widget"
    :shell-style="{
      left: `${this.dashboard.left + 8}px`,
      top: `${this.dashboard.top + 8}px`,
      width: `${this.dashboard.width}px`,
      height: `${this.dashboard.height}px`,
      transformOrigin: `${this.shellOrigin.x}px ${this.shellOrigin.y}px`
    }"
    class="widget-details-view"
    details
    @close="close()"
  />
</template>

<script>
export default {
  inject: [
    'dashboard'
  ],

  props: {
    widget: {
      type: Object,
      required: true
    },

    shellOrigin: {
      type: Object,
      required: true
    }
  },

  methods: {
    close () {
      this.$emit('close')
    }
  }
}
</script>

<style lang="stylus" scoped>
.widget-details-view
  /deep/ .shell
    position fixed
    z-index 50
</style>


const { run } = await create('eslint-dynamic-import', {


module.exports = {
  presets: [
    '@vue/app'
  ]
}


const Lowdb = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
const path = require('path')
const { rcFolder } = require('./rcFolder')

const db = new Lowdb(new FileSync(path.resolve(rcFolder, 'db.json')))

// Seed an empty DB
db.defaults({
  projects: [],
  foldersFavorite: [],
  tasks: [],
  config: {}
}).write()

module.exports = {
  db
}


#import "./suggestionFragment.gql"

subscription suggestionAdded {
  suggestionAdded {
    ...suggestion
  }
}


// Enable mocking in vue.config.js with `"pluginOptions": { "graphqlMock": true }`
// Customize mocking: https://www.apollographql.com/docs/graphql-tools/mocking.html#Customizing-mocks
module.exports = {
  // Mock resolvers here
}


<template>

      <VueLoadingIndicator/>

    <template v-else>

          {{ $t('org.vue.widgets.vulnerability.messages.attention', { n: details.vulnerabilities.length }) }}

          <div
            v-for="(severity, key) of severities"
            :key="key"
            :class="`severity-${severity.class}`"
            class="summary-item"
          >
            <VueIcon
              :icon="severity.icon"
              :class="severity.class"
            />
            {{ details.summary[key] }}
            {{ $t(`org.vue.widgets.vulnerability.severity.${key}`) }}

      <transition name="vue-ui-fade">
        <DynamicScroller
          v-if="showList"
          ref="scroller"
          class="items"
          :items="details.vulnerabilities"
          :min-item-size="75"
          v-slot="{ item, active }"
        >
          <DynamicScrollerItem
            :item="item"
            :active="active"
            :size-dependencies="[
              showMoreParentsMap[item.id]
            ]"
          >
            <VulnerabilityItem
              :item="item"
              :show-more-parents="showMoreParentsMap[item.id]"
              @toggle-more-parents="$set(showMoreParentsMap, item.id, !showMoreParentsMap[item.id])"
            />
          </DynamicScrollerItem>
        </DynamicScroller>
      </transition>
    </template>

</template>


There are five classes associated with fluid mode, `vjs-fluid`, `vjs-16-9`, `vjs-4-3`, `vjs-9-16` and `vjs-1-1`.

# Layout

Video.js generally lays out the player to the dimensions that are set as attributes or via CSS, like other DOM elements. However, we provide a few ways to make the player be more fluid.

## Fluid Mode

Video.js has a fluid mode that keeps the player sized to a particular aspect ratio.

By default, fluid mode will use the intrinsic size of the video once loaded but you can change it with classes or with the `aspectRatio` option.

Enabling fluid mode will disable fill mode. If both are enabled, fluid mode takes precedence.

You can enable fluid in a few ways:

* Add `vjs-fluid`, `vjs-16-9`, or `vjs-4-3` as a class to the player element.
* pass `fluid` option to the player.
* call `player.fluid(true)`.
* pass `aspectRatio` option to the player.
* call `player.aspectRatio('16:9')`.

### Classes

There are three classes associated with fluid mode, `vjs-fluid`, `vjs-16-9`, and `vjs-4-3`.

`vjs-fluid` turns on the general fluid mode which will wait for the video to load to calculate the aspect ratio of the video.

Alternatively, because 16:9 and 4:3 aspect ratios are so common, we provided them as classes by default for you to use if you know that your videos are 16:9 or 4:3.

### Enabling Fluid Mode

You can pass in the `fluid` option to the player or call `player.fluid(true)`. This will enable the generic fluid mode.

```js
var player = videojs('vid1', {
  fluid: true
});
```

```js
var player = videojs('vid2');

player.fluid(true);
```

### Setting Aspect Ratio

You can specify an aspect ratio for us to use if you don't want to use the intrinsic values from the video element or if you have a specific ratio in mind. It works as either a method call or an option to the player.

This option is in the form of two integers separated by a colon like so `16:9` or `4:3`.

```js
// make a vertical video
var player = videojs('vid1', {
  aspectRatio: '9:16'
});
```

```js
var player = videojs('vid2');

// make a square video
player.aspectRatio('1:1');
```

### Disabling Fluid Mode

You can disable fluid mode by remove the associated classes or by calling passing in `false` to the method.

```js
player.fluid(false);
```

## Fill Mode

Fill mode will make the player fit and fill out its container. This is often useful if you have a responsive website and already have a container for Video.js that resizes properly to your design. It can be set either via a class or an option.

If fill is enabled, it'll turn off fluid mode. If the player is configured with both fluid and fill options, fluid mode takes precedence.

### Class

There's just one class for this one: `vjs-fill`. When available, Video.js will enter fill mode.

### Enabling Fill Mode

You can pass in the `fill` option to the player or call `player.fill(true)`. This will enable fill mode.

```js
var player = videojs('vid1', {
  fill: true
});
```

```js
var player = videojs('vid2');

player.fill(true);
```

### Disabling Fill Mode

You can disable fill mode by removing the associated class or by passing `false` in to the method.

```js
player.fill(false);
```

## Responsive Mode

Responsive mode will make the player's UI customize itself based on the size of the player. This is useful if you have embeds of varying sizes - or if you want a fluid/fill player to adjust its UI based on its size.

Responsive mode is independent of fluid mode or fill mode - it only deals with the arrangements of the UI within the player, not with the size of the player. However, it is often useful to use responsive mode in conjunction with either fluid mode or fill mode!

### Class

A player in responsive mode will add and remove classes based on its size breakpoints. The default breakpoints, classes, and sizes are outlined below:

| Name     | Class                | Min. Width | Max. Width |
| -------- | -------------------- | ---------- | ---------- |
| `tiny`   | `vjs-layout-tiny`    | 0          | 210        |
| `xsmall` | `vjs-layout-x-small` | 211        | 320        |
| `small`  | `vjs-layout-small`   | 321        | 425        |
| `medium` | `vjs-layout-medium`  | 426        | 768        |
| `large`  | `vjs-layout-large`   | 769        | 1440       |
| `xlarge` | `vjs-layout-x-large` | 1441       | 2560       |
| `huge`   | `vjs-layout-huge`    | 2561       | Infinity   |

### Enabling Responsive Mode

You can enable responsive mode by passing the `responsive` option or by calling `player.responsive(true)`.

```js
var player = videojs('vid1', {
  responsive: true
});
```

```js
var player = videojs('vid2');

player.responsive(true);
```

### Disabling Responsive Mode

You can disable responsive mode by passing `false` to the method.

```js
player.responsive(false);
```

### Customizing Breakpoints

The default breakpoints can be customized by passing the `breakpoints` option or by calling `player.breakpoints({...})`.

```js
var player = videojs('vid1', {
  breakpoints: {
    medium: 500
  }
});
```

```js
var player = videojs('vid2');

player.breakpoints({
  medium: 500
});
```

The breakpoints object should have keys matching the **Name** from the table above and values matching the **Max. Width** from the table above. The **Min. Width** is calculated by adding one to the previous breakpoint's **Max. Width**.

Anytime breakpoints are customized, previous customizations are discarded.

### Restoring Default Breakpoints

The default breakpoints can be restored by calling `player.breakpoints(true)`.

```js
var player = videojs('vid1');

player.breakpoints(true);
```

This is only useful if breakpoints had previously been customized.


# Video.js and Angular integration

Here's a basic Angular player implementation.

It just instantiates the Video.js player on `OnInit` and destroys it on `OnDestroy`.

```ts
// vjs-player.component.ts
import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import videojs from 'video.js';

@Component({
  selector: 'app-vjs-player',
  template: `
    <video #target class="video-js" controls muted playsinline preload="none"></video>
  `,
  styleUrls: [
    './vjs-player.component.css'
  ],
  encapsulation: ViewEncapsulation.None,
})
export class VjsPlayerComponent implements OnInit, OnDestroy {
  @ViewChild('target', {static: true}) target: ElementRef;
  // see options: https://github.com/videojs/video.js/blob/main/docs/guides/options.md
  @Input() options: {
      fluid: boolean,
      aspectRatio: string,
      autoplay: boolean,
      sources: {
          src: string,
          type: string,
      }[],
  };
  player: videojs.Player;

  constructor(
    private elementRef: ElementRef,
  ) { }

  ngOnInit() {
    // instantiate Video.js
    this.player = videojs(this.target.nativeElement, this.options, function onPlayerReady() {
      console.log('onPlayerReady', this);
    });
  }

  ngOnDestroy() {
    // destroy player
    if (this.player) {
      this.player.dispose();
    }
  }
}
```

Don't forget to include the Video.js CSS, located at `video.js/dist/video-js.css`.

```css
/* vjs-player.component.css */
@import '~video.js/dist/video-js.css';
```

You can then use it like this.

```html
<app-vjs-player [options]="{ autoplay: true, controls: true, sources: [{ src: '/path/to/video.mp4', type: 'video/mp4' }]}"></app-vjs-player>
```


/**
 * @file video-track-list.js
 */
import TrackList from './track-list';

/**
 * Un-select all other {@link VideoTrack}s that are selected.
 *
 * @param {VideoTrackList} list
 *        list to work on
 *
 * @param {VideoTrack} track
 *        The track to skip
 *
 * @private
 */
const disableOthers = function(list, track) {
  for (let i = 0; i < list.length; i++) {
    if (!Object.keys(list[i]).length || track.id === list[i].id) {
      continue;
    }
    // another video track is enabled, disable it
    list[i].selected = false;
  }
};

/**
 * The current list of {@link VideoTrack} for a video.
 *
 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
 * @extends TrackList
 */
class VideoTrackList extends TrackList {

  /**
   * Create an instance of this class.
   *
   * @param {VideoTrack[]} [tracks=[]]
   *        A list of `VideoTrack` to instantiate the list with.
   */
  constructor(tracks = []) {
    // make sure only 1 track is enabled
    // sorted from last index to first index
    for (let i = tracks.length - 1; i >= 0; i--) {
      if (tracks[i].selected) {
        disableOthers(tracks, tracks[i]);
        break;
      }
    }

    super(tracks);
    this.changing_ = false;

    /**
     * @member {number} VideoTrackList#selectedIndex
     *         The current index of the selected {@link VideoTrack`}.
     */
    Object.defineProperty(this, 'selectedIndex', {
      get() {
        for (let i = 0; i < this.length; i++) {
          if (this[i].selected) {
            return i;
          }
        }
        return -1;
      },
      set() {}
    });
  }

  /**
   * Add a {@link VideoTrack} to the `VideoTrackList`.
   *
   * @param {VideoTrack} track
   *        The VideoTrack to add to the list
   *
   * @fires TrackList#addtrack
   */
  addTrack(track) {
    if (track.selected) {
      disableOthers(this, track);
    }

    super.addTrack(track);
    // native tracks don't have this
    if (!track.addEventListener) {
      return;
    }

    track.selectedChange_ = () => {
      if (this.changing_) {
        return;
      }
      this.changing_ = true;
      disableOthers(this, track);
      this.changing_ = false;
      this.trigger('change');
    };

    /**
     * @listens VideoTrack#selectedchange
     * @fires TrackList#change
     */
    track.addEventListener('selectedchange', track.selectedChange_);
  }

  removeTrack(rtrack) {
    super.removeTrack(rtrack);

    if (rtrack.removeEventListener && rtrack.selectedChange_) {
      rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
      rtrack.selectedChange_ = null;
    }
  }
}

export default VideoTrackList;


/* eslint-env qunit */
import TestHelpers from './test-helpers.js';
import sinon from 'sinon';
import window from 'global/window';
import * as middleware from '../../src/js/tech/middleware.js';
import videojs from '../../src/js/video.js';

const middleWareTerminations = ['terminates', 'does not-terminate'];
const playReturnValues = ['non-promise', 'promise'];

const mainModule = function(playReturnValue, middlewareTermination, subhooks) {
  subhooks.beforeEach(function(assert) {
    this.clock = sinon.useFakeTimers();
    this.techPlayCalls = 0;
    this.playsTerminated = 0;
    this.playTests = [];
    this.terminate = false;

    if (middlewareTermination === 'terminates') {
      this.terminate = true;
    }
    this.techPlay = () => {
      this.techPlayCalls++;

      if (playReturnValue === 'promise') {
        return window.Promise.resolve('foo');
      }
      return 'foo';
    };

    this.finish = function() {
      const done = assert.async(this.playTests.length);

      const singleFinish = (playValue, assertName) => {
        assert.equal(playValue, 'foo', `play call from - ${assertName} - is correct`);
        done();
      };

      this.playTests.forEach(function(test) {
        const playRetval = test.playRetval;
        const testName = test.assertName;

        if (typeof playRetval === 'string') {
          singleFinish(playRetval, testName);
        } else {
          playRetval.then((v) => {
            singleFinish(v, testName);
          });
        }
      });
    };

    this.checkState = (assertName, options = {}) => {
      const expectedState = videojs.mergeOptions({
        playCalls: 0,
        techLoaded: false,
        techReady: false,
        playerReady: false,
        changingSrc: false,
        playsTerminated: 0
      }, options);

      if (typeof options.techLoaded === 'undefined' && typeof options.techReady !== 'undefined') {
        expectedState.techLoaded = options.techReady;
      }

      const currentState = {
        playCalls: this.techPlayCalls,
        techLoaded: Boolean(this.player.tech_),
        techReady: Boolean((this.player.tech_ || {}).isReady_),
        playerReady: Boolean(this.player.isReady_),
        changingSrc: Boolean(this.player.changingSrc_),
        playsTerminated: Number(this.playsTerminated)
      };

      assert.deepEqual(currentState, expectedState, assertName);
    };

    this.playTerminatedQueue = () => this.playsTerminated++;

    this.playTest = (assertName, options = {}) => {
      if (this.player.playTerminatedQueue_ !== this.playTerminatedQueue) {
        this.player.runPlayTerminatedQueue_ = this.playTerminatedQueue;
      }
      if (this.player && this.player.tech_ && this.player.tech_.play !== this.techPlay) {
        this.player.tech_.play = this.techPlay;
      }
      this.playTests.push({assertName, playRetval: this.player.play()});
      this.checkState(assertName, options);
    };

    this.middleware = () => {
      return {
        // pass along source
        setSource(srcObj, next) {
          next(null, srcObj);
        },
        callPlay: () => {
          if (this.terminate) {
            return middleware.TERMINATOR;
          }
        }
      };
    };

    middleware.use('*', this.middleware);
  });

  subhooks.afterEach(function() {
    // remove added middleware
    const middlewareList = middleware.getMiddleware('*');

    for (let i = 0; i < middlewareList.length; i++) {
      if (middlewareList[i] === this.middleware) {
        middlewareList.splice(i, 1);
      }
    }
    if (this.player) {
      this.player.dispose();
    }
    this.clock.restore();
  });

  QUnit.test('Player#play() resolves correctly with dom sources and async tech ready', function(assert) {
    // turn of mediaLoader to prevent setting a tech right away
    // similar to settings sources in the DOM
    // turn off autoReady to prevent syncronous ready from the tech
    this.player = TestHelpers.makePlayer({mediaLoader: false, techFaker: {autoReady: false}});

    this.playTest('before anything is ready');

    this.player.src({
      src: 'http://example.com/video.mp4',
      type: 'video/mp4'
    });

    this.playTest('only changingSrc', {
      changingSrc: true
    });

    this.clock.tick(1);

    this.playTest('still changingSrc, tech loaded', {
      techLoaded: true,
      changingSrc: true
    });

    this.player.tech_.triggerReady();
    this.playTest('still changingSrc, tech loaded and ready', {
      techReady: true,
      changingSrc: true
    });
    this.clock.tick(1);

    this.playTest('done changingSrc, tech/player ready', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.clock.tick(1);

    this.checkState('state stays the same', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.playTest('future calls hit tech#play directly, unless terminated', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 2,
      playsTerminated: this.terminate ? 2 : 0
    });

    if (this.terminate) {
      this.terminate = false;

      this.playTest('play works if not terminated', {
        playerReady: true,
        techReady: true,
        playCalls: 1,
        playsTerminated: 2
      });

      this.playTest('future calls hit tech#play directly', {
        playerReady: true,
        techReady: true,
        playCalls: 2,
        playsTerminated: 2
      });
    }

    this.finish(assert);
  });

  QUnit.test('Player#play() resolves correctly with dom sources', function(assert) {
    this.player = TestHelpers.makePlayer({mediaLoader: false});

    this.playTest('before anything is ready');

    this.player.src({
      src: 'http://example.com/video.mp4',
      type: 'video/mp4'
    });

    this.playTest('only changingSrc', {
      changingSrc: true
    });

    this.clock.tick(1);

    this.playTest('still changingSrc, tech/player ready', {
      techLoaded: true,
      changingSrc: true,
      playerReady: true,
      techReady: true
    });

    this.clock.tick(1);

    this.playTest('done changingSrc, tech#play is called', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.clock.tick(1);

    this.checkState('state stays the same', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.playTest('future calls hit tech#play directly', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 2,
      playsTerminated: this.terminate ? 2 : 0
    });

    if (this.terminate) {
      this.terminate = false;

      this.playTest('play works if not terminated', {
        playerReady: true,
        techReady: true,
        playCalls: 1,
        playsTerminated: 2
      });

      this.playTest('future calls hit tech#play directly', {
        playerReady: true,
        techReady: true,
        playCalls: 2,
        playsTerminated: 2
      });
    }

    this.finish(assert);
  });

  QUnit.test('Player#play() resolves correctly with async tech ready', function(assert) {
    this.player = TestHelpers.makePlayer({techFaker: {autoReady: false}});

    this.playTest('before anything is ready', {
      techLoaded: true
    });

    this.player.src({
      src: 'http://example.com/video.mp4',
      type: 'video/mp4'
    });

    this.playTest('tech loaded changingSrc', {
      techLoaded: true,
      changingSrc: true
    });

    this.clock.tick(1);

    this.playTest('still changingSrc, tech loaded', {
      techLoaded: true,
      changingSrc: true
    });

    this.clock.tick(1);
    this.playTest('still changingSrc, tech loaded again', {
      techLoaded: true,
      changingSrc: true
    });

    this.player.tech_.triggerReady();
    this.playTest('still changingSrc, tech loaded and ready', {
      techReady: true,
      changingSrc: true
    });
    this.clock.tick(1);

    this.playTest('still changingSrc tech/player ready', {
      changingSrc: true,
      playerReady: true,
      techReady: true
    });

    // player ready calls fire now
    // which sets changingSrc_ to false
    this.clock.tick(1);

    this.checkState('play was called on ready', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.playTest('future calls hit tech#play directly', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 2,
      playsTerminated: this.terminate ? 2 : 0
    });

    if (this.terminate) {
      this.terminate = false;

      this.playTest('play works if not terminated', {
        playerReady: true,
        techReady: true,
        playCalls: 1,
        playsTerminated: 2
      });

      this.playTest('future calls hit tech#play directly', {
        playerReady: true,
        techReady: true,
        playCalls: 2,
        playsTerminated: 2
      });
    }

    this.finish(assert);
  });

  QUnit.test('Player#play() resolves correctly', function(assert) {
    this.player = TestHelpers.makePlayer();

    this.playTest('player/tech start out ready', {
      techReady: true,
      playerReady: true
    });

    this.player.src({
      src: 'http://example.com/video.mp4',
      type: 'video/mp4'
    });

    this.playTest('now changingSrc', {
      techReady: true,
      playerReady: true,
      changingSrc: true
    });

    this.clock.tick(1);

    this.playTest('done changingSrc, play called if not terminated', {
      techReady: true,
      playerReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.clock.tick(2);

    this.checkState('state stays the same', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 1,
      playsTerminated: this.terminate ? 1 : 0
    });

    this.playTest('future calls hit tech#play directly', {
      playerReady: true,
      techReady: true,
      playCalls: this.terminate ? 0 : 2,
      playsTerminated: this.terminate ? 2 : 0
    });

    if (this.terminate) {
      this.terminate = false;

      this.playTest('play works if not terminated', {
        playerReady: true,
        techReady: true,
        playCalls: 1,
        playsTerminated: 2
      });

      this.playTest('future calls hit tech#play directly', {
        playerReady: true,
        techReady: true,
        playCalls: 2,
        playsTerminated: 2
      });
    }

    this.clock.tick(1000);

    this.finish(assert);
  });

  // without enableSourceset this test will fail.
  QUnit.test('Player#play() resolves correctly on tech el src', function(assert) {
    this.player = TestHelpers.makePlayer({techOrder: ['html5'], enableSourceset: true});

    this.playTest('player/tech start out ready', {
      techReady: true,
      playerReady: true
    });

    this.player.tech_.el_.src = 'http://vjs.zencdn.net/v/oceans.mp4';

    this.player.on('loadstart', () => {
      this.checkState('play should have been called', {
        techReady: true,
        playerReady: true,
        playCalls: 1
      });

      this.finish(assert);
    });
  });
};

QUnit.module('Player#play()', (hooks) => {
  playReturnValues.forEach((playReturnValue) => {
    middleWareTerminations.forEach((middlewareTermination) => {
      QUnit.module(`tech#play() => ${playReturnValue}, middleware ${middlewareTermination}`, (subhooks) => {
        mainModule(playReturnValue, middlewareTermination, subhooks);
      });
    });
  });
});


Alternatively, because 16:9 and 4:3 aspect ratios are so common, we provided them as classes by default for you to use if you know that your videos are 16:9 or 4:3.

You can pass in the `fluid` option to the player or call `player.fluid(true)`. This will enable the generic fluid mode.

keep keep keep replace keep keep keep keep keep

Alternatively, because 16:9, 4:3, 9:16 and 1:1  aspect ratios are so common, we provided them as classes by default for you to use if you know that your videos are 16:9 or , 4:3, 9:16 or 1:1.

# Video.js Options Reference

> **Note:** This document is only a reference for available options. To learn about passing options to Video.js, see [the setup guide](/docs/guides/setup.md#options).

## Table of Contents

* [Standard <video> Element Options](#standard-video-element-options)
  * [autoplay](#autoplay)
    * [More info on autoplay support and changes:](#more-info-on-autoplay-support-and-changes)
  * [controls](#controls)
  * [height](#height)
  * [loop](#loop)
  * [muted](#muted)
  * [poster](#poster)
  * [preload](#preload)
    * ['auto'](#auto)
    * ['metadata'](#metadata)
    * ['none'](#none)
  * [src](#src)
  * [width](#width)
* [Video.js-specific Options](#videojs-specific-options)
  * [aspectRatio](#aspectratio)
  * [autoSetup](#autosetup)
  * [breakpoints](#breakpoints)
  * [children](#children)
  * [fluid](#fluid)
  * [inactivityTimeout](#inactivitytimeout)
  * [language](#language)
  * [languages](#languages)
  * [liveui](#liveui)
  * [liveTracker.trackingThreshold](#livetrackertrackingthreshold)
  * [liveTracker.liveTolerance](#livetrackerlivetolerance)
  * [nativeControlsForTouch](#nativecontrolsfortouch)
  * [notSupportedMessage](#notsupportedmessage)
  * [noUITitleAttributes](#nouititleattributes)
  * [fullscreen](#fullscreen)
    * [options](#options)
  * [playbackRates](#playbackrates)
  * [plugins](#plugins)
  * [responsive](#responsive)
  * [sources](#sources)
  * [suppressNotSupportedError](#suppressnotsupportederror)
  * [techCanOverridePoster](#techcanoverrideposter)
  * [techOrder](#techorder)
  * [userActions](#useractions)
  * [userActions.doubleClick](#useractionsdoubleclick)
  * [userActions.hotkeys](#useractionshotkeys)
  * [userActions.hotkeys.fullscreenKey](#useractionshotkeysfullscreenkey)
  * [userActions.hotkeys.muteKey](#useractionshotkeysmutekey)
  * [userActions.hotkeys.playPauseKey](#useractionshotkeysplaypausekey)
  * [vtt.js](#vttjs)
* [Component Options](#component-options)
  * [children](#children-1)
  * [${componentName}](#componentname)
* [Tech Options](#tech-options)
  * [${techName}](#techname)
  * [html5](#html5)
    * [nativeControlsForTouch](#nativecontrolsfortouch-1)
    * [nativeAudioTracks](#nativeaudiotracks)
    * [nativeTextTracks](#nativetexttracks)
    * [nativeVideoTracks](#nativevideotracks)
    * [preloadTextTracks](#preloadtexttracks)

## Standard `<video>` Element Options

Each of these options is also available as a [standard `<video>` element attribute][video-attrs]; so, they can be defined in all three manners [outlined in the setup guide](/docs/guides/setup.md#options). Typically, defaults are not listed as this is left to browser vendors.

### `autoplay`

> Type: `boolean|string`
> NOTE: At this point, the autoplay attribute and option are NOT a guarantee that your video will autoplay.
> NOTE2: If there is an attribute on the media element the option will be ignored.
> NOTE3: You cannot pass a string value in the attribute, you must pass it in the videojs options

Instead of using the `autoplay` attribute you should pass an `autoplay` option to the `videojs` function. The following values
are valid:

* a boolean value of `false`: the same as having no attribute on the video element, won't `autoplay`
* a boolean value of `true`: the same as having attribute on the video element, will use browsers `autoplay`
* a string value of `'muted'`: will mute the video element and then manually call `play()` on `loadstart`. This is likely to work.
* a string value of `'play'`: will call `play()` on `loadstart`, similar to browsers `autoplay`
* a string value of `'any'`: will call `play()` on `loadstart` and if the promise is rejected it will mute the video element then call `play()`.

To pass the option

```js
var player = videojs('my-video', {
  autoplay: 'muted'
});

// or

player.autoplay('muted');
```

#### More info on autoplay support and changes:

* See our blog post: [Autoplay Best Practices with Video.js](https://videojs.com/blog/autoplay-best-practices-with-video-js/)

### `controls`

> Type: `boolean`

Determines whether or not the player has controls that the user can interact with. Without controls the only way to start the video playing is with the `autoplay` attribute or through the Player API.

### `height`

> Type: `string|number`

Sets the display height of the video player in pixels.

### `loop`

> Type: `boolean`

Causes the video to start over as soon as it ends.

### `muted`

> Type: `boolean`

Will silence any audio by default.

### `poster`

> Type: `string`

A URL to an image that displays before the video begins playing. This is often a frame of the video or a custom title screen. As soon as the user hits "play" the image will go away.

### `preload`

> Type: `string`

Suggests to the browser whether or not the video data should begin downloading as soon as the `<video>` element is loaded. Supported values are:

#### `'auto'`

Start loading the video immediately (if the browser supports it). Some mobile devices will not preload the video in order to protect their users' bandwidth/data usage. This is why the value is called 'auto' and not something more conclusive like `'true'`.

_This tends to be the most common and recommended value as it allows the browser to choose the best behavior._

#### `'metadata'`

Load only the meta data of the video, which includes information like the duration and dimensions of the video. Sometimes, the meta data will be loaded by downloading a few frames of video.

#### `'none'`

Don't preload any data. The browser will wait until the user hits "play" to begin downloading.

### `src`

> Type: `string`

The source URL to a video source to embed.

### `width`

> Type: `string|number`

Sets the display width of the video player in pixels.

## Video.js-specific Options

Each option is `undefined` by default unless otherwise specified.

### `aspectRatio`

> Type: `string`

Puts the player in [fluid](#fluid) mode and the value is used when calculating the dynamic size of the player. The value should represent a ratio - two numbers separated by a colon (e.g. `"16:9"` or `"4:3"`).

### `autoSetup`

> Type: `boolean`

Prevents the player from running the autoSetup for media elements with `data-setup` attribute.

> **Note**: this must be set globally with `videojs.options.autoSetup = false` in the same tick as videojs source is loaded to take effect.

### `breakpoints`

> Type: `Object`

When used with the [`responsive` option](#responsive), sets breakpoints that will configure how class names are toggled on the player to adjust the UI based on the player's dimensions.

By default, the breakpoints are:

| Class Name           | Width Range |
| -------------------- | ----------- |
| `vjs-layout-tiny`    | 0-210       |
| `vjs-layout-x-small` | 211-320     |
| `vjs-layout-small`   | 321-425     |
| `vjs-layout-medium`  | 426-768     |
| `vjs-layout-large`   | 769-1440    |
| `vjs-layout-x-large` | 1441-2560   |
| `vjs-layout-huge`    | 2561+       |

While the class names cannot be changed, the width ranges can be configured via an object like this:

```js
breakpoints: {
  tiny: 300,
  xsmall: 400,
  small: 500,
  medium: 600,
  large: 700,
  xlarge: 800,
  huge: 900
}
```

* The _keys_ of the `breakpoints` object are derived from the associated class names by removing the `vjs-layout-` prefix and any `-` characters.
* The _values_ of the `breakpoints` object define the max width for a range.
* Not all keys need to be defined. You can easily override a single breakpoint by passing an object with one key/value pair! Customized breakpoints will be merged with default breakpoints when the player is created.

When the player's size changes, the merged breakpoints will be inspected in the size order until a matching breakpoint is found.

That breakpoint's associated class name will be added as a class to the player. The previous breakpoint's class will be removed.

See the file `sandbox/responsive.html.example` for an example of a responsive player using the default breakpoints.

### `children`

> Type: `Array|Object`

This option is inherited from the [`Component` base class](#component-options).

### `fluid`

> Type: `boolean`

When `true`, the Video.js player will have a fluid size. In other words, it will scale to fit its container.

Also, if the `<video>` element has the `"vjs-fluid"`, this option is automatically set to `true`.

### `inactivityTimeout`

> Type: `number`

Video.js indicates that the user is interacting with the player by way of the `"vjs-user-active"` and `"vjs-user-inactive"` classes and the `"useractive"` event.

The `inactivityTimeout` determines how many milliseconds of inactivity is required before declaring the user inactive. A value of `0` indicates that there is no `inactivityTimeout` and the user will never be considered inactive.

### `language`

> Type: `string`, Default: browser default or `'en'`

A [language code][lang-codes] matching one of the available languages in the player. This sets the initial language for a player, but it can always be changed.

Learn more about [languages in Video.js][languages].

### `languages`

> Type: `Object`

Customize which languages are available in a player. The keys of this object will be [language codes][lang-codes] and the values will be objects with English keys and translated values.

Learn more about [languages in Video.js][languages]

> **Note**: Generally, this option is not needed and it would be better to pass your custom languages to `videojs.addLanguage()`, so they are available in all players!

### `liveui`

> Type: `boolean`
> Default: `false`

Allows the player to use the new live ui that includes:

* A progress bar for seeking within the live window
* A button that can be clicked to seek to the live edge with a circle indicating if you are at the live edge or not.

Without this option the progress bar will be hidden and in its place will be text that indicates `LIVE` playback. There will be no progress control
and you will not be able click the text to seek to the live edge. `liveui` will default to `true` in a future version!

### `liveTracker.trackingThreshold`

> Type: `number`
> Default: `30`

An option for the liveTracker component of the player that controls when the liveui should be shown. By default if a stream has less than 30s on the seekBar then we do not show the new liveui even with the liveui option set.

### `liveTracker.liveTolerance`

> Type: `number`
> Default: `15`

An option for the liveTracker component of the player that controls how far from the seekable end should be considered live playback. By default anything further than 15s from the live seekable edge is considered behind live and everything else is considered live. Any user interaction to seek backwards will ignore this value as a user would expect.

### `nativeControlsForTouch`

> Type: `boolean`

Explicitly set a default value for [the associated tech option](#nativecontrolsfortouch).

### `notSupportedMessage`

> Type: `string`

Allows overriding the default message that is displayed when Video.js cannot play back a media source.

### `noUITitleAttributes`

> Type: `boolean`
> Default: `false`

Control whether UI elements have a `title` attribute. A `title` attribute is shown on mouse hover, which can be helpful for usability, but has drawbacks for accessibility. Setting `noUITitleAttributes` to `true` prevents the `title` attribute from being added to UI elements, allowing for more accessible tooltips to be added to controls by a plugin or external framework.

### `fullscreen`

> Type: `Object`
> Default: `{options: {navigationUI: 'hide'}`

`fullscreen.options` can be set to pass in specific fullscreen options. At some point, it will be augmented with `element` and `handler` for more functionality.

#### `options`

> Type: `Object`
> Default: `{navigationUI: 'hide'}`

See [The Fullscreen API Spec](https://fullscreen.spec.whatwg.org/#dictdef-fullscreenoptions) for more details.

### `playbackRates`

> Type: `Array`

An array of numbers strictly greater than 0, where 1 means regular speed
(100%), 0.5 means half-speed (50%), 2 means double-speed (200%), etc.
If specified, Video.js displays a control (of class `vjs-playback-rate`)
allowing the user to choose playback speed from among the array of choices.
The choices are presented in the specified order from bottom to top.

For example:

```js
videojs('my-player', {
  playbackRates: [0.5, 1, 1.5, 2]
});
```

### `plugins`

> Type: `Object`

This supports having plugins be initialized automatically with custom options when the player is initialized - rather than requiring you to initialize them manually.

```js
videojs('my-player', {
  plugins: {
    foo: {bar: true},
    boo: {baz: false}
  }
});
```

The above is roughly equivalent to:

```js
var player = videojs('my-player');

player.foo({bar: true});
player.boo({baz: false});
```

Although, since the `plugins` option is an object, the order of initialization is not guaranteed!

See [the plugins guide][plugins] for more information on Video.js plugins.

### `responsive`

> Type: `boolean`, Default: `false`

Setting this option to `true` will cause the player to customize itself based on responsive breakpoints (see: [`breakpoints` option](#breakpoints)).

When this option is `false` (the default), responsive breakpoints will be ignored.

### `sources`

> Type: `Array`

An array of objects that mirror the native `<video>` element's capability to have a series of child `<source>` elements. This should be an array of objects with the `src` and `type` properties. For example:

```js
videojs('my-player', {
  sources: [{
    src: '//path/to/video.mp4',
    type: 'video/mp4'
  }, {
    src: '//path/to/video.webm',
    type: 'video/webm'
  }]
});
```

Using `<source>` elements will have the same effect:

```html
<video ...>
  <source src="//path/to/video.mp4" type="video/mp4">
  <source src="//path/to/video.webm" type="video/webm">
</video>
```

### `suppressNotSupportedError`

> Type: `boolean`

If set to true, then the no compatible source error will not be triggered immediately and instead will occur on the first user interaction. This is useful for Google's "mobile friendly" test tool, which can't play video but where you might not want to see an error displayed.

### `techCanOverridePoster`

> Type: `boolean`

Gives the possibility to techs to override the player's poster
and integrate into the player's poster life-cycle.
This can be useful when multiple techs are used and each has to set their own poster
 any time a new source is played.

### `techOrder`

> Type: `Array`, Default: `['html5']`

Defines the order in which Video.js techs are preferred. By default, this means that the `Html5` tech is preferred. Other registered techs will be added after this tech in the order in which they are registered.

### `userActions`

> Type: `Object`

### `userActions.doubleClick`

> Type: `boolean|function`

Controls how double-clicking on the player/tech operates. If set to `false`, double-clicking is disabled. If undefined or set to `true`, double-clicking is enabled and toggles fullscreen mode. To override the default double-click handling, set `userActions.doubleClick` to a function which accepts a `dblclick` event:

```js
function myDoubleClickHandler(event) = {
  // `this` is the player in this context

  this.pause();
};

videojs('my-player', {
  userActions: {
    doubleClick: myDoubleClickHandler
  }
});
```

### `userActions.hotkeys`

> Type: `boolean|function|object`

Controls how player-wide hotkeys operate. If set to `false`, or `undefined`, hotkeys are disabled. If set to `true` or an object (to allow definitions of `fullscreenKey` etc. below), hotkeys are enabled as described below. To override the default hotkey handling, set `userActions.hotkeys` to a function which accepts a `keydown` event:

```js
var player = videojs('my-player', {
  userActions: {
    hotkeys: function(event) {
      // `this` is the player in this context

      // `x` key = pause
      if (event.which === 88) {
        this.pause();
      }
      // `y` key = play
      if (event.which === 89) {
        this.play();
      }
    }
  }
});
```

Default hotkey handling is:

|   Key   | Action            | Enabled by                                                        |
| :-----: | ----------------- | ----------------------------------------------------------------- |
|   `f`   | toggle fullscreen | only enabled if a Fullscreen button is present in the Control Bar |
|   `m`   | toggle mute       | always enabled, even if no Control Bar is present                 |
|   `k`   | toggle play/pause | always enabled, even if no Control Bar is present                 |
| `Space` | toggle play/pause | always enabled, even if no Control Bar is present                 |

Hotkeys require player focus first. Note that the `Space` key activates controls such as buttons and menus if that control has keyboard focus. The other hotkeys work regardless of which control in the player has focus.

### `userActions.hotkeys.fullscreenKey`

> Type: `function`

Override the fullscreen key definition. If this is set, the function receives the `keydown` event; if the function returns `true`, then the fullscreen toggle action is performed.

```js
var player = videojs('my-player', {
  userActions: {
    hotkeys: {
      muteKey: function(event) {
        // disable mute key
      },
      fullscreenKey: function(event) {
        // override fullscreen to trigger when pressing the v key
        return (event.which === 86);
      }
    }
  }
});
```

### `userActions.hotkeys.muteKey`

> Type: `function`

Override the mute key definition. If this is set, the function receives the `keydown` event; if the function returns `true`, then the mute toggle action is performed.

### `userActions.hotkeys.playPauseKey`

> Type: `function`

Override the play/pause key definition. If this is set, the function receives the `keydown` event; if the function returns `true`, then the play/pause toggle action is performed.

### `vtt.js`

> Type: `string`

Allows overriding the default URL to vtt.js, which may be loaded asynchronously to polyfill support for `WebVTT`.

This option will be used in the "novtt" build of Video.js (i.e. `video.novtt.js`). Otherwise, vtt.js is bundled with Video.js.

## Component Options

The Video.js player is a component. Like all components, you can define what children it includes, what order they appear in, and what options are passed to them.

This is meant to be a quick reference; so, for more detailed information on components in Video.js, check out the [components guide](/docs/guides/components.md).

### `children`

> Type: `Array|Object`

If an `Array` - which is the default - this is used to determine which children (by component name) and in which order they are created on a player (or other component):

```js
// The following code creates a player with ONLY bigPlayButton and
// controlBar child components.
videojs('my-player', {
  children: [
    'bigPlayButton',
    'controlBar'
  ]
});
```

The `children` options can also be passed as an `Object`. In this case, it is used to provide `options` for any/all children, including disabling them with `false`:

```js
// This player's ONLY child will be the controlBar. Clearly, this is not the
// ideal method for disabling a grandchild!
videojs('my-player', {
  children: {
    controlBar: {
      fullscreenToggle: false
    }
  }
});
```

### `${componentName}`

> Type: `Object`

Components can be given custom options via the _lower-camel-case variant of the component name_ (e.g. `controlBar` for `ControlBar`). These can be nested in a representation of grandchild relationships. For example, to disable the fullscreen control:

```js
videojs('my-player', {
  controlBar: {
    fullscreenToggle: false
  }
});
```

## Tech Options

### `${techName}`

> Type: `Object`

Video.js playback technologies (i.e. "techs") can be given custom options as part of the options passed to the `videojs` function. They should be passed under the _lower-case variant of the tech name_ (e.g. `"html5"`).

### `html5`

#### `nativeControlsForTouch`

> Type: `boolean`

Only supported by the `Html5` tech, this option can be set to `true` to force native controls for touch devices.

#### `nativeAudioTracks`

> Type: `boolean`

Can be set to `false` to disable native audio track support. Most commonly used with [videojs-contrib-hls][videojs-contrib-hls].

#### `nativeTextTracks`

> Type: `boolean`

Can be set to `false` to force emulation of text tracks instead of native support. The `nativeCaptions` option also exists, but is simply an alias to `nativeTextTracks`.

#### `nativeVideoTracks`

> Type: `boolean`

Can be set to `false` to disable native video track support. Most commonly used with [videojs-contrib-hls][videojs-contrib-hls].

#### `preloadTextTracks`

> Type: `boolean`

Can be set to `false` to delay loading of non-active text tracks until use. This can cause a short delay when switching captions during which there may be missing captions.

The default behavior is to preload all text tracks.

[plugins]: /docs/guides/plugins.md

[languages]: /docs/guides/languages.md

[ios-10-updates]: https://webkit.org/blog/6784/new-video-policies-for-ios/

[lang-codes]: https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry

[video-attrs]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#Attributes

[videojs-contrib-hls]: https://github.com/videojs/videojs-contrib-hls