Source: AvInsights/avinsights.js

/**
 * @class
 * @classdesc Plugin to measure media events.
 * @name AvInsights
 * @memberof ATInternet.Tracker.Plugins
 * @type {function}
 * @param tag {Object} Instance of the Tag used
 * @public
 */
ATInternet.Tracker.Plugins.AvInsights = function (tag) {

    'use strict';

    var _config = null;

    /**
     * Object used to manage utility methods.
     * @name Utility
     * @memberof ATInternet.Tracker.Plugins.AvInsights#
     * @inner
     * @constructor
     * @property {Object} debugError Utility object, see details here {@link ATInternet.Tracker.Tag#Utility.debugError}
     * @property {function} processHeartbeatValue Utility helper, see details here {@link ATInternet.Tracker.Tag#Utility.processHeartbeatValue}
     * @property {function} value2Number Utility helper, see details here {@link ATInternet.Tracker.Tag#Utility.value2Number}
     * @private
     */
    var Utility = function () {

        var _thisUtil = this;

        /**
         * Debug object error.
         * @alias Utility.debugError
         * @memberof! ATInternet.Tracker.Plugins.AvInsights#
         * @Object
         * @private
         */
        _thisUtil.debugError = {
            trigger: 'AvInsights:Media:setContentValues:Error',
            level: 'ERROR',
            messageObject: 'Not an object'
        };

        /**
         * Manage heartbeat values.
         * @alias Utility.processHeartbeatValue
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @param val {number|string} Heartbeat value to process
         * @param min {number} Minimum value to set
         * @return {number}
         * @private
         */
        _thisUtil.processHeartbeatValue = function (val, min) {
            var _val = parseInt(val, 10);
            if (!!_val) {
                return Math.max(_val, min);
            }
            return 0;
        };

        /**
         * Process value to number.
         * @alias Utility.value2Number
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @param val {*} Cursor position | Speed factor
         * @return {number}
         * @private
         */
        _thisUtil.value2Number = function (val) {
            var validNumber = 0;
            if (!isNaN(Number(val))) {
                validNumber = Number(val);
            }
            return Math.max(validNumber, 0);
        };

    };

    /**
     * Object used to manage audio and video events
     * @name Media
     * @memberof ATInternet.Tracker.Plugins.AvInsights#
     * @inner
     * @constructor
     * @param heartbeatValue {Object|string|number|undefined}
     * @param bufferHeartbeatValue {Object|string|number|undefined}
     * @param sessionId {string|number|undefined}

     * @property {function} set Media helper, see details here {@link ATInternet.Tracker.Tag#Media.set}
     * @property {function} get Media helper, see details here {@link ATInternet.Tracker.Tag#Media.get}
     * @property {function} del Media helper, see details here {@link ATInternet.Tracker.Tag#Media.del}
     * @property {function} setProps Media helper, see details here {@link ATInternet.Tracker.Tag#Media.setProps}
     * @property {function} getProps Media helper, see details here {@link ATInternet.Tracker.Tag#Media.getProps}
     * @property {function} delProps Media helper, see details here {@link ATInternet.Tracker.Tag#Media.delProps}
     * @property {function} setPlaybackSpeed Media helper, see details here {@link ATInternet.Tracker.Tag#Media.setPlaybackSpeed}
     * @property {function} getSessionID Media helper, see details here {@link ATInternet.Tracker.Tag#Media.getSessionID}

     * @property {function} setHeartbeat Media helper, see details here {@link ATInternet.Tracker.Tag#Media.setHeartbeat}
     * @property {function} getHeartbeat Media helper, see details here {@link ATInternet.Tracker.Tag#Media.getHeartbeat}
     *
     * @property {function} track Media helper, see details here {@link ATInternet.Tracker.Tag#Media.track}
     * @property {function} heartbeat Media helper, see details here {@link ATInternet.Tracker.Tag#Media.heartbeat}
     * @property {function} bufferHeartbeat Media helper, see details here {@link ATInternet.Tracker.Tag#Media.bufferHeartbeat}
     * @property {function} rebufferHeartbeat Media helper, see details here {@link ATInternet.Tracker.Tag#Media.rebufferHeartbeat}
     * @property {function} play Media helper, see details here {@link ATInternet.Tracker.Tag#Media.play}
     * @property {function} bufferStart Media helper, see details here {@link ATInternet.Tracker.Tag#Media.bufferStart}
     * @property {function} playbackStart Media helper, see details here {@link ATInternet.Tracker.Tag#Media.playbackStart}
     * @property {function} playbackResumed Media helper, see details here {@link ATInternet.Tracker.Tag#Media.playbackResumed}
     * @property {function} playbackPaused Media helper, see details here {@link ATInternet.Tracker.Tag#Media.playbackPaused}
     * @property {function} playbackStopped Media helper, see details here {@link ATInternet.Tracker.Tag#Media.playbackStopped}
     * @property {function} seek Media helper, see details here {@link ATInternet.Tracker.Tag#Media.seek}
     * @property {function} seekBackward Media helper, see details here {@link ATInternet.Tracker.Tag#Media.seekBackward}
     * @property {function} seekForward Media helper, see details here {@link ATInternet.Tracker.Tag#Media.seekForward}
     * @property {function} seekStart Media helper, see details here {@link ATInternet.Tracker.Tag#Media.seekStart}
     * @property {function} adClick Media helper, see details here {@link ATInternet.Tracker.Tag#Media.adClick}
     * @property {function} adSkip Media helper, see details here {@link ATInternet.Tracker.Tag#Media.adSkip}
     * @property {function} error Media helper, see details here {@link ATInternet.Tracker.Tag#Media.error}
     * @property {function} display Media helper, see details here {@link ATInternet.Tracker.Tag#Media.display}
     * @property {function} close Media helper, see details here {@link ATInternet.Tracker.Tag#Media.close}
     * @property {function} volume Media helper, see details here {@link ATInternet.Tracker.Tag#Media.volume}
     * @property {function} subtitleOn Media helper, see details here {@link ATInternet.Tracker.Tag#Media.subtitleOn}
     * @property {function} subtitleOff Media helper, see details here {@link ATInternet.Tracker.Tag#Media.subtitleOff}
     * @property {function} fullscreenOn Media helper, see details here {@link ATInternet.Tracker.Tag#Media.fullscreenOn}
     * @property {function} fullscreenOff Media helper, see details here {@link ATInternet.Tracker.Tag#Media.fullscreenOff}
     * @property {function} quality Media helper, see details here {@link ATInternet.Tracker.Tag#Media.quality}
     * @property {function} speed Media helper, see details here {@link ATInternet.Tracker.Tag#Media.speed}
     * @property {function} share Media helper, see details here {@link ATInternet.Tracker.Tag#Media.share}
     * @public
     */
    var Media = function (heartbeatValue, bufferHeartbeatValue, sessionId) {

        var _thisMedia = this;
        var _utility = new Utility();
        var _context = null;
        var _timers = null;
        var _properties = null;

        // Utility methods

        /**
         * Initialize media context.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @private
         */
        var _initContext = function () {
            _context = {
                previousCursorPosition: 0, // Position of the cursor of the previous event
                currentCursorPosition: 0, // Position of the cursor of the current event
                eventDuration: 0, // Event duration
                playbackSpeed: 1, // Playback speed
                previousEvent: '',
                isPlaybackActivated: false,
                isPlaying: false,
                sessionId: '',
                delayConfiguration: [], // Delay structure
                delayConfigurationBackup: [], // Delay structure backup
                delayBufferingConfiguration: [], // Delay buffering structure
                delayBufferingConfigurationBackup: [] // Delay buffering structure backup
            };
        };

        /**
         * Initialize session ID.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @private
         */
        var _initSessionID = function () {
            _context.sessionId = sessionId || ATInternet.Utils.uuid().v4();
        };

        /**
         * Reset session.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @private
         */
        var _resetSession = function () {
            _context.previousCursorPosition = 0;
            _context.currentCursorPosition = 0;
            _context.eventDuration = 0;
            _context.previousEvent = '';
            _context.sessionId = ATInternet.Utils.uuid().v4();
        };

        /**
         * Restore heartbeat values.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param buffering {boolean}
         * @function
         * @private
         */
        var _restoreDelayConfiguration = function (buffering) {
            if (buffering) {
                _context.delayBufferingConfiguration = ATInternet.Utils.cloneSimpleObject(_context.delayBufferingConfigurationBackup);
            } else {
                _context.delayConfiguration = ATInternet.Utils.cloneSimpleObject(_context.delayConfigurationBackup);
            }
        };

        /**
         * Backup heartbeat values.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param buffering {boolean}
         * @function
         * @private
         */
        var _saveDelayConfiguration = function (buffering) {
            if (buffering) {
                _context.delayBufferingConfigurationBackup = ATInternet.Utils.cloneSimpleObject(_context.delayBufferingConfiguration);
            } else {
                _context.delayConfigurationBackup = ATInternet.Utils.cloneSimpleObject(_context.delayConfiguration);
            }
        };

        /**
         * Reset hearbeat values.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param buffering {boolean}
         * @function
         * @private
         */
        var _resetDelayConfiguration = function (buffering) {
            if (buffering) {
                _context.delayBufferingConfiguration = [];
                _context.delayBufferingConfigurationBackup = [];
            } else {
                _context.delayConfiguration = [];
                _context.delayConfigurationBackup = [];
            }
        };

        /**
         * Sort heartbeat values.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param buffering {boolean}
         * @function
         * @return {number}
         * @private
         */
        var _sortDelayConfiguration = function (buffering) {
            var delayConfig = buffering ? _context.delayBufferingConfiguration : _context.delayConfiguration;
            delayConfig.sort(function (a, b) {
                if (a.delay < b.delay) {
                    return -1;
                }
                if (a.delay > b.delay) {
                    return 1;
                }
                return 0;
            });
        };

        /**
         * Set the number of sendings remaining for the current time slot.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param buffering {boolean}
         * @function
         * @private
         */
        var _updateDelayConfiguration = function (buffering) {
            var nextDelay;
            var delayConfig = buffering ? _context.delayBufferingConfiguration : _context.delayConfiguration;
            if (typeof delayConfig[1] !== 'undefined') {
                nextDelay = delayConfig[1].delay;
            }
            if (typeof nextDelay === 'undefined') {
                delayConfig[0].number = 1;
            } else if (delayConfig[0].number > 0) {
                delayConfig[0].number--;
            } else if (typeof nextDelay === 'number') {
                delayConfig[0].number = Math.floor((nextDelay - delayConfig[0].delay) * 60 / delayConfig[0].refresh) - 1;
            }
        };

        /**
         * Set heartbeat values.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param buffering {boolean}
         * @param heartbeat {number|Object}
         * @function
         * @private
         * @example
         * _initHeartbeat(true, 5);
         * _initHeartbeat(false, {0: 5, 1: 15, 5: 30, 10: 60});
         */
        var _initHeartbeat = function (buffering, heartbeat) {

            if (heartbeat) {

                _resetDelayConfiguration(buffering);

                // heartbeat configuration is now forced
                var _heartbeatObject = heartbeat;

                for (var key in _heartbeatObject) {
                    if (_heartbeatObject.hasOwnProperty(key)) {
                        if (buffering) {
                            _context.delayBufferingConfiguration.push({
                                delay: _utility.processHeartbeatValue(key, 0),
                                number: 0,
                                timeout: -1,
                                refresh: _utility.processHeartbeatValue(_heartbeatObject[key], _config.minBufferingHeartbeat)
                            });
                        } else {
                            _context.delayConfiguration.push({
                                delay: _utility.processHeartbeatValue(key, 0),
                                number: 0,
                                timeout: -1,
                                refresh: _utility.processHeartbeatValue(_heartbeatObject[key], _config.minHeartbeat)
                            });
                        }
                    }
                }
                _sortDelayConfiguration(buffering);
                _saveDelayConfiguration(buffering);
            }
        };

        /**
         * Init heartbeats.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @private
         */
        var _initHeartbeats = function () {
            var _heartbeatValueForced = heartbeatValue ? {
                0: 5,
                1: 10,
                5: 20,
                15: 30,
                30: 60
            } : null;
            var _bufferHeartbeatValueForced = bufferHeartbeatValue ? {
                0: 1,
                1: 10,
                5: 20,
                15: 30,
                30: 60
            } : null;
            _initHeartbeat(false, _heartbeatValueForced);
            _initHeartbeat(true, _bufferHeartbeatValueForced);
        };

        var _initProperties = function () {
            _properties = {};
        };

        /**
         * Add optional properties.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param object2Send {Object}
         * @function
         * @private
         */
        var _addOptionalProperties = function (object2Send) {
            object2Send.av_previous_position = {};
            object2Send.av_previous_position[ATInternet.Utils.ATVALUE] = _context.previousCursorPosition;
            object2Send.av_previous_position[ATInternet.Utils.ATPREFIX] = '';
            object2Send.av_position = {};
            object2Send.av_position[ATInternet.Utils.ATVALUE] = _context.currentCursorPosition;
            object2Send.av_position[ATInternet.Utils.ATPREFIX] = '';
            object2Send.av_duration = {};
            object2Send.av_duration[ATInternet.Utils.ATVALUE] = _context.eventDuration;
            object2Send.av_duration[ATInternet.Utils.ATPREFIX] = '';
            object2Send.av_previous_event = {};
            object2Send.av_previous_event[ATInternet.Utils.ATVALUE] = _context.previousEvent;
            object2Send.av_previous_event[ATInternet.Utils.ATPREFIX] = '';
        };

        /**
         * Get final formatted object from properties.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param object2Send {Object}
         * @function
         * @return {Object}
         * @private
         */
        var _getFinalObject = function (object2Send) {
            var content = {};
            for (var key in object2Send) {
                if (object2Send.hasOwnProperty(key)) {
                    ATInternet.Utils.flatten2Object(content, key, object2Send[key]);
                }
            }
            return ATInternet.Utils.getFormattedObject(content);
        };

        /**
         * Manage properties and send event.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param action {string}
         * @param withOptions {boolean}
         * @param callback {function}
         * @param extraProps {Object}
         * @function
         * @private
         */
        var _sendEvent = function (action, withOptions, callback, extraProps) {

            // Process media properties
            var object2Send = ATInternet.Utils.cloneSimpleObject(_properties);

            // Adding session ID
            object2Send.av_session_id = {};
            object2Send.av_session_id[ATInternet.Utils.ATVALUE] = _context.sessionId;
            object2Send.av_session_id[ATInternet.Utils.ATPREFIX] = '';

            // Adding optional properties
            if (withOptions) {
                _addOptionalProperties(object2Send);
                // Update this contextual value only for events with options
                _context.previousEvent = action;
            }

            // Adding extra properties
            if (ATInternet.Utils.isObject(extraProps)) {
                ATInternet.Utils.object2Flatten(extraProps, null, object2Send, null, true);
            }

            // Getting final object
            var finalObject = _getFinalObject(object2Send);

            // Sending hit
            tag.event.send(action, finalObject, {
                'origin': 'avinsights',
                'callback': callback
            });

        };

        /**
         * Object used to manage timers.
         * @name Timers
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @inner
         * @type {function}
         * @property {function} getEventDuration Tag helper, see details here {@link ATInternet.Tracker.Tag#Timers.getEventDuration}
         * @property {function} initBaseTime Tag helper, see details here {@link ATInternet.Tracker.Tag#Timers.initBaseTime}
         * @property {function} resetProperties Tag helper, see details here {@link ATInternet.Tracker.Tag#Timers.resetProperties}
         * @property {function} initHeartbeatTimer Tag helper, see details here {@link ATInternet.Tracker.Tag#Timers.initHeartbeatTimer}
         * @property {function} stopHeartbeatTimer Tag helper, see details here {@link ATInternet.Tracker.Tag#Timers.stopHeartbeatTimer}
         * @private
         */
        var Timers = function () {

            var _thisTimers = this;
            var _baseTime = 0; // Base time UTC
            var _totalEventDuration = 0; // Cumulative duration of events

            /**
             * Get event duration (time elapsed between the two last events in milliseconds).
             * @alias Timers.getEventDuration
             * @memberof ATInternet.Tracker.Plugins.AvInsights#
             * @function
             * @return {number}
             * @private
             */
            _thisTimers.getEventDuration = function () {
                var eventDuration = new Date().getTime() - _baseTime - _totalEventDuration;
                _totalEventDuration += eventDuration;
                return eventDuration;
            };

            /**
             * Initialize base time.
             * @alias Timers.initBaseTime
             * @memberof ATInternet.Tracker.Plugins.AvInsights#
             * @function
             * @private
             */
            _thisTimers.initBaseTime = function () {
                if (_baseTime === 0) {
                    _baseTime = new Date().getTime();
                }
            };

            /**
             * Reset Timer properties.
             * @alias Timers.resetProperties
             * @memberof ATInternet.Tracker.Plugins.AvInsights#
             * @function
             * @private
             */
            _thisTimers.resetProperties = function () {
                _baseTime = 0;
                _totalEventDuration = 0;
            };

            /**
             * Initialize heartbeat timer.
             * @alias Timers.initHeartbeatTimer
             * @memberof ATInternet.Tracker.Plugins.AvInsights#
             * @param callback {function}
             * @param buffering {boolean}
             * @function
             * @private
             */
            _thisTimers.initHeartbeatTimer = function (callback, buffering) {
                var delayConfig = buffering ? _context.delayBufferingConfiguration : _context.delayConfiguration;
                if (delayConfig.length > 0) {
                    _updateDelayConfiguration(buffering);
                    window.clearTimeout(delayConfig[0].timeout);
                    delayConfig[0].timeout = window.setTimeout(function () {
                        if (delayConfig[0].number === 0) {
                            delayConfig.splice(0, 1);
                        }
                        callback && callback();
                    }, delayConfig[0].refresh * 1e3);
                }
            };

            /**
             * Stop heartbeat timer.
             * @alias Timers.stopHeartbeatTimer
             * @memberof ATInternet.Tracker.Plugins.AvInsights#
             * @param buffering {boolean}
             * @function
             * @private
             */
            _thisTimers.stopHeartbeatTimer = function (buffering) {
                var delayConfig = buffering ? _context.delayBufferingConfiguration : _context.delayConfiguration;
                for (var i = 0; i < delayConfig.length; i++) {
                    window.clearTimeout(delayConfig[i].timeout);
                    delayConfig[i].timeout = -1;
                }
            };

            // For unit tests on private elements !!!
            /* @if test */
            _thisTimers._baseTime = _baseTime;
            _thisTimers._totalEventDuration = _totalEventDuration;
            /* @endif */

        };

        /**
         * Init timer.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @private
         */
        var _initTimers = function () {
            _timers = new Timers();
            ATInternet.Utils.addEvtListener(window, 'unload', function () {
                _timers.stopHeartbeatTimer(false);
                _timers.stopHeartbeatTimer(true);
            });
        };

        /* -------- Media content -------- */

        // Media properties

        /**
         * Set media property.
         * @alias Media.set
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param propKey {string} Media property key
         * @param propValue {string|number} Media property value
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.set('av_content_id', 'fg456');
         */
        _thisMedia.set = function (propKey, propValue) {
            var splittedObject = ATInternet.Utils.splitProtocolAndKey(propKey, true);
            _properties[splittedObject.key] = _properties[splittedObject.key] || {};
            _properties[splittedObject.key][ATInternet.Utils.ATVALUE] = propValue;
            _properties[splittedObject.key][ATInternet.Utils.ATPREFIX] = splittedObject.prefix;
        };

        /**
         * Get media property.
         * @alias Media.get
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param propKey {string} Media property key
         * @function
         * @return {Object|string|null}
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * var contentID = myMedia.get('av_content_id'); // 'fg456'
         */
        _thisMedia.get = function (propKey) {
            var property = null;
            var splittedObject = ATInternet.Utils.splitProtocolAndKey(propKey, true);
            if (typeof _properties[splittedObject.key] !== 'undefined') {
                property = _properties[splittedObject.key][ATInternet.Utils.ATVALUE];
            }
            return property;
        };

        /**
         * Delete media property.
         * @alias Media.del
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param propKey {string} Media property key
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.del('content_id');
         */
        _thisMedia.del = function (propKey) {
            var splittedObject = ATInternet.Utils.splitProtocolAndKey(propKey, true);
            if (typeof _properties[splittedObject.key] !== 'undefined') {
                delete _properties[splittedObject.key];
            }
        };

        /**
         * Set media properties.
         * @alias Media.setProps
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param properties {Object}
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.setProps({'av_content_id': 'fg456', 'av_content': 'My Content'});
         */
        _thisMedia.setProps = function (properties) {
            if (ATInternet.Utils.isObject(properties)) {
                ATInternet.Utils.object2Flatten(properties, null, _properties, null, true);
            } else if (properties) {
                /* @if debug */
                tag.debug(_utility.debugError.trigger, _utility.debugError.level, _utility.debugError.messageObject, {
                    data: properties
                });
                /* @endif */
            }
        };

        /**
         * Get all media properties.
         * @alias Media.getProps
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @return {Object}
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * var props = myMedia.getProps(); // {'av_content_id': 'fg456', 'av_content': 'My Content'}
         */
        _thisMedia.getProps = function () {
            var properties = null;
            for (var key in _properties) {
                if (_properties.hasOwnProperty(key)) {
                    properties = properties || {};
                    properties[key] = _properties[key][ATInternet.Utils.ATVALUE];
                }
            }
            return properties;
        };

        /**
         * Delete media properties.
         * @alias Media.delProps
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.delProps();
         */
        _thisMedia.delProps = function () {
            _properties = {};
        };

        /* -------- Media context -------- */

        // Context methods

        /**
         * Generate heartbeat event.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param autoPosition {boolean} With or without automatic position calculation
         * @param autoTimer {boolean} With or without timer management
         * @param [cursorPosition] {number} Cursor position (milliseconds)
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @private
         */
        var _heartbeat = function (autoPosition, autoTimer, cursorPosition, callback, extraProps) {

            _timers.initBaseTime();

            _context.eventDuration = _timers.getEventDuration(); // duration since the last event (usually heartbeat)
            _context.previousCursorPosition = _context.currentCursorPosition; // event-1 Cursor Position  (usually HB)
            _context.currentCursorPosition = autoPosition ? (_context.previousCursorPosition + Math.floor(_context.playbackSpeed * _context.eventDuration)) : cursorPosition; // event Cursor Position

            autoTimer && _timers.initHeartbeatTimer(function () {
                _heartbeat(true, true);
            }, false);

            _sendEvent('av.heartbeat', true, callback, extraProps);

        };

        /**
         * Generate heartbeat event during buffering.
         * Buffering Heartbeat event between play attempt and playback start.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param autoTimer {boolean} With or without timer management
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @private
         */
        var _bufferHeartbeat = function (autoTimer, callback, extraProps) {

            _timers.initBaseTime();

            _context.eventDuration = _timers.getEventDuration(); // duration since the last event

            autoTimer && _timers.initHeartbeatTimer(function () {
                _bufferHeartbeat(true);
            }, true);

            _sendEvent('av.buffer.heartbeat', true, callback, extraProps);

        };

        /**
         * Generate heartbeat event during re-buffering.
         * Buffering Heartbeat event after playback start.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param autoTimer {boolean} With or without timer management
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @private
         */
        var _rebufferHeartbeat = function (autoTimer, callback, extraProps) {

            _timers.initBaseTime();

            _context.eventDuration = _timers.getEventDuration(); // duration since Rebuffer start or since the last rebuffer heartbeat
            _context.previousCursorPosition = _context.currentCursorPosition; // event-1 Cursor Position (rebuffer start or rebuffer HB)

            autoTimer && _timers.initHeartbeatTimer(function () {
                _rebufferHeartbeat(true);
            }, true);

            _sendEvent('av.rebuffer.heartbeat', true, callback, extraProps);

        };

        /**
         * Set playback speed.
         * @alias Media.setPlaybackSpeed
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param playbackSpeed {number}
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.setPlaybackSpeed(2);
         */
        _thisMedia.setPlaybackSpeed = function (playbackSpeed) {

            var pbSpeed = _utility.value2Number(playbackSpeed) || _context.playbackSpeed;
            if (pbSpeed !== _context.playbackSpeed) {
                _timers.stopHeartbeatTimer(false);
                if (_context.isPlaying) {
                    _heartbeat(true, false);
                    _timers.initHeartbeatTimer(function () {
                        _heartbeat(true, true);
                    }, false);
                }
                _context.playbackSpeed = pbSpeed;
            }

        };

        /**
         * Get session ID.
         * @alias Media.getSessionID
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @return {string}
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * var sessionID = myMedia.getSessionID(); // 'custom_session_id' or 'xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxx'
         */
        _thisMedia.getSessionID = function () {
            return _context.sessionId;
        };

        // Helpers

        // Event methods

        /**
         * Track standard or custom actions.
         * @alias Media.track
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param action {string} Action to measure
         * @param [options] {Object} Options depending on current action
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.track('av.play', {av_position: 0}, function() {console.log('Action av.play generated');}, null);
         * myMedia.track('custom', null, null, {'customProp': 'customValue'});
         */
        _thisMedia.track = function (action, options, callback, extraProps) {
            var opt = options || {};
            switch (action) {
                case 'av.heartbeat':
                    _thisMedia.heartbeat(opt.av_position, callback, extraProps);
                    break;
                case 'av.buffer.heartbeat':
                    _thisMedia.bufferHeartbeat(callback, extraProps);
                    break;
                case 'av.rebuffer.heartbeat':
                    _thisMedia.rebufferHeartbeat(callback, extraProps);
                    break;
                case 'av.play':
                    _thisMedia.play(opt.av_position, callback, extraProps);
                    break;
                case 'av.buffer.start':
                    _thisMedia.bufferStart(opt.av_position, callback, extraProps);
                    break;
                case 'av.start':
                    _thisMedia.playbackStart(opt.av_position, callback, extraProps);
                    break;
                case 'av.resume':
                    _thisMedia.playbackResumed(opt.av_position, callback, extraProps);
                    break;
                case 'av.pause':
                    _thisMedia.playbackPaused(opt.av_position, callback, extraProps);
                    break;
                case 'av.stop':
                    _thisMedia.playbackStopped(opt.av_position, callback, extraProps);
                    break;
                case 'av.backward':
                    _thisMedia.seekBackward(opt.av_previous_position, opt.av_position, callback, extraProps);
                    break;
                case 'av.forward':
                    _thisMedia.seekForward(opt.av_previous_position, opt.av_position, callback, extraProps);
                    break;
                case 'av.seek.start':
                    _thisMedia.seekStart(opt.av_previous_position, callback, extraProps);
                    break;
                case 'av.error':
                    _thisMedia.error(opt.av_player_error, callback, extraProps);
                    break;
                default:
                    _sendEvent(action, false, callback, extraProps);
            }
        };

        /**
         * Generate heartbeat event.
         * @alias Media.heartbeat
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [cursorPosition] {number} Cursor position (milliseconds)
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.heartbeat(function() {console.log('Action av.heartbeat generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.heartbeat = function (cursorPosition, callback, extraProps) {

            var autoPosition = true;
            var position;
            if ((typeof cursorPosition !== 'undefined') && (cursorPosition !== null) && (cursorPosition >= 0)) {
                autoPosition = false;
                position = _utility.value2Number(cursorPosition);
            }

            _heartbeat(autoPosition, false, position, callback, extraProps);

        };

        /**
         * Generate heartbeat event during buffering.
         * Buffering Heartbeat event between play attempt and playback start.
         * @alias Media.bufferHeartbeat
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.bufferHeartbeat(function() {console.log('Action av.buffer.heartbeat generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.bufferHeartbeat = function (callback, extraProps) {

            _bufferHeartbeat(false, callback, extraProps);

        };

        /**
         * Generate heartbeat event during re-buffering.
         * Buffering Heartbeat event after playback start.
         * @alias Media.rebufferHeartbeat
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.rebufferHeartbeat(function() {console.log('Action av.rebuffer.heartbeat generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.rebufferHeartbeat = function (callback, extraProps) {

            _rebufferHeartbeat(false, callback, extraProps);

        };

        /**
         * Generate play event (play attempt).
         * @alias Media.play
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param cursorPosition {number} Cursor position (milliseconds)
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.play(0, function() {console.log('Action av.play generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.play = function (cursorPosition, callback, extraProps) {

            _timers.initBaseTime();

            var processedPosition = _utility.value2Number(cursorPosition);

            _context.eventDuration = 0; // always 0
            _context.previousCursorPosition = processedPosition; // event Cursor Position
            _context.currentCursorPosition = processedPosition; // event Cursor Position
            _context.isPlaying = false;
            _context.isPlaybackActivated = false;

            _timers.stopHeartbeatTimer(false);
            _timers.stopHeartbeatTimer(true);

            _sendEvent('av.play', true, callback, extraProps);

        };

        /**
         * Player buffering start to initiate the launch of the media.
         * @alias Media.bufferStart
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param cursorPosition {number} Cursor position (milliseconds)
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.bufferStart(0, function() {console.log('Action av.buffer.start or av.rebuffer.start generated depending on the playback launch');}, {'customProp': 'customValue'});
         */
        _thisMedia.bufferStart = function (cursorPosition, callback, extraProps) {

            _timers.initBaseTime();

            var processedPosition = _utility.value2Number(cursorPosition);

            _context.eventDuration = _timers.getEventDuration(); // duration since play
            _context.previousCursorPosition = _context.currentCursorPosition; // event -1 Cursor Position (usually HB)
            _context.currentCursorPosition = processedPosition; // event Cursor Position

            _timers.stopHeartbeatTimer(false);
            _timers.stopHeartbeatTimer(true);

            if (_context.isPlaybackActivated) {
                _timers.initHeartbeatTimer(function () {
                    _rebufferHeartbeat(true);
                }, true);
                _sendEvent('av.rebuffer.start', true, callback, extraProps);
            } else {
                _timers.initHeartbeatTimer(function () {
                    _bufferHeartbeat(true);
                }, true);
                _sendEvent('av.buffer.start', true, callback, extraProps);
            }

        };

        /**
         * Media playback start (first frame of the media).
         * @alias Media.playbackStart
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param cursorPosition {number} Cursor position (milliseconds)
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.playbackStart(0, function() {console.log('Action av.start generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.playbackStart = function (cursorPosition, callback, extraProps) {

            _timers.initBaseTime();

            var processedPosition = _utility.value2Number(cursorPosition);

            _context.eventDuration = _timers.getEventDuration(); // duration since play
            _context.previousCursorPosition = processedPosition; // event Cursor Position
            _context.currentCursorPosition = processedPosition; // event Cursor Position
            _context.isPlaying = true;
            _context.isPlaybackActivated = true;

            _timers.stopHeartbeatTimer(false);
            _timers.stopHeartbeatTimer(true);

            _timers.initHeartbeatTimer(function () {
                _heartbeat(true, true);
            }, false);

            _sendEvent('av.start', true, callback, extraProps);

        };

        /**
         * Media playback restarted manually after a pause.
         * @alias Media.playbackResumed
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param cursorPosition {number} Cursor position (milliseconds)
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.playbackResumed(1000, function() {console.log('Action av.resume generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.playbackResumed = function (cursorPosition, callback, extraProps) {

            _timers.initBaseTime();

            var processedPosition = _utility.value2Number(cursorPosition);

            _context.eventDuration = _timers.getEventDuration(); // duration since play
            _context.previousCursorPosition = _context.currentCursorPosition; // event-1 Cursor Position  (usually HB)
            _context.currentCursorPosition = processedPosition; // event Cursor Position
            _context.isPlaying = true;
            _context.isPlaybackActivated = true;

            _timers.stopHeartbeatTimer(false);
            _timers.stopHeartbeatTimer(true);
            _timers.initHeartbeatTimer(function () {
                _heartbeat(true, true);
            }, false);

            _sendEvent('av.resume', true, callback, extraProps);


        };

        /**
         * Media playback paused.
         * @alias Media.playbackPaused
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param cursorPosition {number} Cursor position (milliseconds)
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.playbackPaused(1000, function() {console.log('Action av.pause generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.playbackPaused = function (cursorPosition, callback, extraProps) {

            _timers.initBaseTime();

            var processedPosition = _utility.value2Number(cursorPosition);

            _context.eventDuration = _timers.getEventDuration(); // duration since the last event (usually heartbeat)
            _context.previousCursorPosition = _context.currentCursorPosition; // event-1 Cursor Position  (usually HB)
            _context.currentCursorPosition = processedPosition; // event Cursor Position
            _context.isPlaying = false;
            _context.isPlaybackActivated = true;

            _timers.stopHeartbeatTimer(false);
            _timers.stopHeartbeatTimer(true);

            _sendEvent('av.pause', true, callback, extraProps);


        };

        /**
         * Media playback stopped.
         * @alias Media.playbackStopped
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param cursorPosition {number} Cursor position (milliseconds)
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.playbackStopped(1000, function() {console.log('Action av.stop generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.playbackStopped = function (cursorPosition, callback, extraProps) {

            _timers.initBaseTime();

            var processedPosition = _utility.value2Number(cursorPosition);

            _context.eventDuration = _timers.getEventDuration(); // duration since the last event (usually heartbeat)
            _context.previousCursorPosition = _context.currentCursorPosition; // event-1 Cursor Position  (usually HB)
            _context.currentCursorPosition = processedPosition; // event Cursor Position
            _context.isPlaying = false;
            _context.isPlaybackActivated = false;

            _timers.stopHeartbeatTimer(false);
            _timers.stopHeartbeatTimer(true);
            _timers.resetProperties();

            _restoreDelayConfiguration(false);
            _restoreDelayConfiguration(true);

            _sendEvent('av.stop', true, callback, extraProps);

            _resetSession();

        };

        /**
         * Measuring seek event.
         * @alias Media.seek
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param oldCursorPosition {number} Starting position (milliseconds)
         * @param newCursorPosition {number} Ending position (milliseconds)
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.seek(1000, 2000, function() {console.log('Action av.forward generated');}, {'customProp': 'customValue'});
         * myMedia.seek(2000, 1000, function() {console.log('Action av.backward generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.seek = function (oldCursorPosition, newCursorPosition, callback, extraProps) {

            var processedOldPosition = _utility.value2Number(oldCursorPosition);
            var processedNewPosition = _utility.value2Number(newCursorPosition);

            if (processedOldPosition > processedNewPosition) {
                _thisMedia.seekBackward(processedOldPosition, processedNewPosition, callback, extraProps);
            } else {
                _thisMedia.seekForward(processedOldPosition, processedNewPosition, callback, extraProps);
            }

        };

        /**
         * Measuring seek backward.
         * @alias Media.seekBackward
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param oldCursorPosition {number} Starting position (milliseconds)
         * @param newCursorPosition {number} Ending position (milliseconds)
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.seekBackward(2000, 1000, function() {console.log('Action av.backward generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.seekBackward = function (oldCursorPosition, newCursorPosition, callback, extraProps) {

            _thisMedia.seekStart(oldCursorPosition, null, extraProps);

            _context.eventDuration = 0;
            _context.previousCursorPosition = _utility.value2Number(oldCursorPosition); // Cursor position at the beginning of the seek
            _context.currentCursorPosition = _utility.value2Number(newCursorPosition); // Cursor position at the end of the seek

            _sendEvent('av.backward', true, callback, extraProps);

        };

        /**
         * Measuring seek forward.
         * @alias Media.seekForward
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param oldCursorPosition {number} Starting position (milliseconds)
         * @param newCursorPosition {number} Ending position (milliseconds)
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.seekForward(1000, 2000, function() {console.log('Action av.forward generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.seekForward = function (oldCursorPosition, newCursorPosition, callback, extraProps) {

            _thisMedia.seekStart(oldCursorPosition, null, extraProps);

            _context.eventDuration = 0;
            _context.previousCursorPosition = _utility.value2Number(oldCursorPosition); // Cursor position at the beginning of the seek
            _context.currentCursorPosition = _utility.value2Number(newCursorPosition); // Cursor position at the end of the seek

            _sendEvent('av.forward', true, callback, extraProps);

        };

        /**
         * Measuring seek start.
         * Automatically executed when calling seek, seekBackward or seekForward method.
         * @alias Media.seekStart
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param oldCursorPosition {number}
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.seekStart(1000, function() {console.log('Action av.seek.start generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.seekStart = function (oldCursorPosition, callback, extraProps) {

            var processedOldPosition = _utility.value2Number(oldCursorPosition);

            _context.previousCursorPosition = _context.currentCursorPosition;
            _context.currentCursorPosition = processedOldPosition;
            if (_context.isPlaying) {
                _context.eventDuration = _timers.getEventDuration(); // duration since the last event (usually heartbeat)
            } else {
                _context.eventDuration = 0;
            }

            _sendEvent('av.seek.start', true, callback, extraProps);

        };

        /**
         * Measuring media click (especially for ads).
         * @alias Media.adClick
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.adClick(function() {console.log('Action av.ad.click generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.adClick = function (callback, extraProps) {

            _sendEvent('av.ad.click', false, callback, extraProps);

        };

        /**
         * Measuring media skip (especially for ads).
         * @alias Media.adSkip
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.adSkip(function() {console.log('Action av.ad.skip generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.adSkip = function (callback, extraProps) {

            _sendEvent('av.ad.skip', false, callback, extraProps);

        };

        /**
         * Measurement of errors preventing further reading.
         * @alias Media.error
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param message {string}
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.error('Error loading video', function() {console.log('Action av.error generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.error = function (message, callback, extraProps) {

            var newProps = {};
            if (ATInternet.Utils.isObject(extraProps)) {
                newProps = extraProps;
            }
            newProps.av_player_error = String(message);
            _sendEvent('av.error', false, callback, newProps);

        };

        /**
         * Measuring reco or Ad display.
         * @alias Media.display
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.display(function() {console.log('Action av.display generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.display = function (callback, extraProps) {

            _sendEvent('av.display', false, callback, extraProps);

        };

        /**
         * Measuring close action.
         * @alias Media.close
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.close(function() {console.log('Action av.close action');}, {'customProp': 'customValue'});
         */
        _thisMedia.close = function (callback, extraProps) {

            _sendEvent('av.close', false, callback, extraProps);

        };

        /**
         * Measurement of a volume change action.
         * @alias Media.volume
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.volume(function() {console.log('Action av.volume generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.volume = function (callback, extraProps) {

            _sendEvent('av.volume', false, callback, extraProps);

        };

        /**
         * Measurement of activated subtitles.
         * @alias Media.subtitleOn
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.subtitleOn(function() {console.log('Action av.subtitle.on generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.subtitleOn = function (callback, extraProps) {

            _sendEvent('av.subtitle.on', false, callback, extraProps);

        };

        /**
         * Measurement of deactivated subtitles.
         * @alias Media.subtitleOff
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.subtitleOff(function() {console.log('Action av.subtitle.off generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.subtitleOff = function (callback, extraProps) {

            _sendEvent('av.subtitle.off', false, callback, extraProps);

        };

        /**
         * Measuring a full-screen display.
         * @alias Media.fullscreenOn
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.fullscreenOn(function() {console.log('Action av.fullscreen.on generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.fullscreenOn = function (callback, extraProps) {

            _sendEvent('av.fullscreen.on', false, callback, extraProps);

        };

        /**
         * Measuring a full screen deactivation.
         * @alias Media.fullscreenOff
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.fullscreenOff(function() {console.log('Action av.fullscreen.off generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.fullscreenOff = function (callback, extraProps) {

            _sendEvent('av.fullscreen.off', false, callback, extraProps);
        };

        /**
         * Measurement of a quality change action.
         * @alias Media.quality
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.quality(function() {console.log('Action av.quality generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.quality = function (callback, extraProps) {

            _sendEvent('av.quality', false, callback, extraProps);

        };

        /**
         * Measurement of a speed change action.
         * @alias Media.speed
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.speed(2, function() {console.log('Action av.speed generated with twice the reading speed');}, {'customProp': 'customValue'});
         */
        _thisMedia.speed = function (callback, extraProps) {

            _sendEvent('av.speed', false, callback, extraProps);

        };

        /**
         * Measurement of a sharing action.
         * @alias Media.share
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @public
         * @example
         * var myMedia = new tag.avInsights.Media();
         * myMedia.share(function() {console.log('Action av.share generated');}, {'customProp': 'customValue'});
         */
        _thisMedia.share = function (callback, extraProps) {

            _sendEvent('av.share', false, callback, extraProps);

        };

        /* -------- Media init -------- */

        _initContext();
        _initHeartbeats();
        _initSessionID();
        _initTimers();
        _initProperties();

        // For unit tests on private elements !!!
        /* @if test */
        _thisMedia._utility = _utility;
        _thisMedia._properties = _properties;
        _thisMedia._context = _context;
        _thisMedia._initHeartbeats = _initHeartbeats;
        _thisMedia._initTimers = _initTimers;
        _thisMedia._initProperties = _initProperties;
        _thisMedia._initContext = _initContext;
        _thisMedia._initSessionID = _initSessionID;
        _thisMedia._resetSession = _resetSession;
        _thisMedia._restoreDelayConfiguration = _restoreDelayConfiguration;
        _thisMedia._saveDelayConfiguration = _saveDelayConfiguration;
        _thisMedia._resetDelayConfiguration = _resetDelayConfiguration;
        _thisMedia._sortDelayConfiguration = _sortDelayConfiguration;
        _thisMedia._updateDelayConfiguration = _updateDelayConfiguration;
        _thisMedia._addOptionalProperties = _addOptionalProperties;
        _thisMedia._sendEvent = _sendEvent;
        _thisMedia.Timers = Timers;
        _thisMedia._timers = _timers;
        /* @endif */
    };

    /**
     * [Object added by plugin {@link ATInternet.Tracker.Plugins.AvInsights AvInsights}] Tags to manage media measurement.
     * @name avInsights
     * @inner
     * @type {Object}
     * @memberof! ATInternet.Tracker.Tag
     * @property {function} set Tag helper, see details here {@link ATInternet.Tracker.Tag#avInsights.Media}
     * @public
     */
    tag.avInsights = {};

    /**
     *[Helper added by plugin {@link ATInternet.Tracker.Plugins.AvInsights AvInsights}] Media object.
     * @alias avInsights.Media
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @example
     * var myMedia = new tag.avInsights.Media();
     * @public
     */
    tag.avInsights.Media = Media;

    /**
     * Launch plugin when all dependencies are loaded.
     * @memberof ATInternet.Tracker.Plugins.AvInsights#
     * @function
     * @private
     */
    var _init = function () {
        var dependencies = ['EventRoot'];
        tag.plugins.waitForDependencies(dependencies, function () {
            // Plugin configuration.
            tag.configPlugin('AvInsights', dfltPluginCfg || {}, function (newConf) {
                _config = newConf;
            });
        });
    };

    _init();

    // For unit tests on private elements !!!
    /* @if test */
    this.Utility = Utility;
    this._init = _init;
    /* @endif */
};
ATInternet.Tracker.addPlugin('AvInsights');