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} isObject Utility helper, see details here {@link ATInternet.Tracker.Tag#Utility.isObject}
     * @property {function} processNumber Utility helper, see details here {@link ATInternet.Tracker.Tag#Utility.processNumber}
     * @property {function} processPosition Utility helper, see details here {@link ATInternet.Tracker.Tag#Utility.processPosition}
     * @property {function} flatten2Object Utility helper, see details here {@link ATInternet.Tracker.Tag#Utility.flatten2Object}
     * @property {function} object2Flatten Utility helper, see details here {@link ATInternet.Tracker.Tag#Utility.object2Flatten}
     * @property {function} getFormattedObject Utility helper, see details here {@link ATInternet.Tracker.Tag#Utility.getFormattedObject}
     * @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'
        };

        /**
         * Check if the the parameter is a defined object.
         * @alias Utility.isObject
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @param contextualObject {Object} Object from context
         * @return {boolean}
         * @private
         */
        _thisUtil.isObject = function (contextualObject) {
            return ((contextualObject !== null) && (typeof contextualObject === 'object') && !(contextualObject instanceof Array));
        };

        /**
         * Manage heartbeat values.
         * @alias Utility.processNumber
         * @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.processNumber = function (val, min) {
            var _val = parseInt(val, 10);
            if (!!_val)
                return Math.max(_val, min);
            return 0;
        };

        /**
         * Manage cursor position values.
         * @alias Utility.processPosition
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @param position {*} Cursor position
         * @return {number}
         * @private
         */
        _thisUtil.processPosition = function (position) {
            var validPosition = 0;
            if (!isNaN(Number(position))) {
                validPosition = Number(position);
            }
            return validPosition;
        };

        /**
         * Create object from flatten property.
         * @alias Utility.flatten2Object
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @param destination {Object} Final object
         * @param sourceKey {string} Key to process
         * @param sourceValue {*} Value to set
         * @private
         */
        _thisUtil.flatten2Object = function (destination, sourceKey, sourceValue) {

            var levels = sourceKey.split('_');
            var parentObject = destination;
            var currentLevel;

            // Scrolling through data object
            for (var i = 0; i < levels.length - 1; i++) {
                currentLevel = levels[i];
                if (!parentObject[currentLevel]) {
                    parentObject[currentLevel] = {};
                }
                parentObject = parentObject[currentLevel];
            }

            // Update parent object if necessary
            var parentValue = parentObject._val;
            if (typeof parentValue !== 'undefined') {
                delete parentObject._val;
                parentObject['$'] = {
                    _val: parentValue
                };
            }

            // Adding current value
            var sourceCopyValue = ATInternet.Utils.cloneSimpleObject(sourceValue);
            if (parentObject[levels[i]]) {
                parentObject[levels[i]]['$'] = sourceCopyValue;
            } else {
                parentObject[levels[i]] = sourceCopyValue;
            }

        };

        /**
         * Adding flatten property with value from source object into destination.
         * @alias Utility.object2Flatten
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @param source {Object} Source object
         * @param parentPath {string|null} Path from parent
         * @param destination {Object} Final object
         * @private
         */
        _thisUtil.object2Flatten = function (source, parentPath, destination) {
            var path;
            for (var sourceKey in source) {
                if (source.hasOwnProperty(sourceKey)) {
                    if (sourceKey !== '_val') {
                        path = (parentPath ? parentPath + '_' : '') + sourceKey;
                    } else {
                        path = parentPath;
                    }
                    if (_thisUtil.isObject(source[sourceKey])) {
                        _thisUtil.object2Flatten(source[sourceKey], path, destination);
                    } else {
                        destination[path] = destination[path] || {};
                        destination[path]._val = source[sourceKey];
                    }
                }
            }
        };

        /**
         * Get formatted content object from flatten source.
         * @alias Utility.getFormattedObject
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @param source {Object} Source object
         * @private
         */
        _thisUtil.getFormattedObject = function (source) {
            var content = {};
            for (var key in source) {
                if (source.hasOwnProperty(key)) {
                    if (typeof source[key]._val === 'undefined') {
                        content[key] = _thisUtil.getFormattedObject(source[key]);
                    } else {
                        content[key] = source[key]._val;
                    }
                }
            }
            return content;
        };

    };

    /**
     * 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}

     * @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} 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) {

        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
                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 () {
            if (_context.sessionId === '') {
                _context.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 = '';
        };

        /**
         * 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
         * @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);

                var _heartbeatObject = {};
                if (_utility.isObject(heartbeat)) {
                    _heartbeatObject = heartbeat;
                } else if (!isNaN(heartbeat)) {
                    _heartbeatObject[0] = heartbeat;
                } else {
                    _heartbeatObject = ATInternet.Utils.jsonParse(heartbeat);
                }

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

        /**
         * Init heartbeats.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @function
         * @private
         */
        var _initHeartbeats = function () {
            _initHeartbeat(false, heartbeatValue);
            _initHeartbeat(true, bufferHeartbeatValue);
        };

        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 = {_val: _context.previousCursorPosition};
            object2Send.av_position = {_val: _context.currentCursorPosition};
            object2Send.av_duration = {_val: _context.eventDuration};
            object2Send.av_previous_event = {_val: _context.previousEvent};
        };

        /**
         * Get final formatted object from properties.
         * @memberof ATInternet.Tracker.Plugins.AvInsights#
         * @param object2Send {Object}
         * @function
         * @private
         */
        var _getFinalObject = function (object2Send) {
            var content = {};
            for (var key in object2Send) {
                if (object2Send.hasOwnProperty(key)) {
                    _utility.flatten2Object(content, key, object2Send[key]);
                }
            }
            return _utility.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) {

            var object2Send = {};

            // Process media properties
            var propertiesCopy = ATInternet.Utils.cloneSimpleObject(_properties);
            _utility.object2Flatten(propertiesCopy, null, object2Send);

            // Adding session ID
            _initSessionId();
            object2Send.av_session_id = {_val: _context.sessionId};

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

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

            // 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).
             * @alias Timers.getEventDuration
             * @memberof ATInternet.Tracker.Plugins.AvInsights#
             * @function
             * @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(true);
                    }, 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('content_id', 'fg456');
         */
        _thisMedia.set = function (propKey, propValue) {
            _properties[propKey] = _properties[propKey] || {};
            _properties[propKey]._val = propValue;
        };

        /**
         * 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('content_id'); // 'fg456'
         */
        _thisMedia.get = function (propKey) {
            var property = null;
            if (typeof _properties[propKey] !== 'undefined') {
                property = _properties[propKey]._val;
            }
            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) {
            if (typeof _properties[propKey] !== 'undefined') {
                delete _properties[propKey];
            }
        };

        /**
         * 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({'content_id': 'fg456', 'content': 'My Content'});
         */
        _thisMedia.setProps = function (properties) {
            if (_utility.isObject(properties)) {
                _utility.object2Flatten(properties, null, _properties);
            } 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(); // {'content_id': 'fg456', 'content': 'My Content'}
         */
        _thisMedia.getProps = function () {
            var properties = null;
            for (var property in _properties) {
                if (_properties.hasOwnProperty(property)) {
                    properties = properties || {};
                    properties[property] = _properties[property]._val;
                }
            }
            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 withTimer {boolean} With or without timer management
         * @param [callback] {function} Function to execute after hit sending
         * @param [extraProps] {Object} Additional custom settings
         * @function
         * @private
         */
        var _heartbeat = function (withTimer, 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 = _context.previousCursorPosition + _context.eventDuration; // event Cursor Position

            withTimer && _timers.initHeartbeatTimer(_heartbeat, 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 withTimer {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 (withTimer, callback, extraProps) {

            _timers.initBaseTime();

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

            withTimer && _timers.initHeartbeatTimer(_bufferHeartbeat, 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 withTimer {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 (withTimer, 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)

            withTimer && _timers.initHeartbeatTimer(_rebufferHeartbeat, true);

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

        };

        // 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(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 [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 (callback, extraProps) {

            _heartbeat(false, 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.processPosition(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.processPosition(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(_rebufferHeartbeat, true);
                _sendEvent('av.rebuffer.start', true, callback, extraProps);
            } else {
                _timers.initHeartbeatTimer(_bufferHeartbeat, 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.processPosition(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(_heartbeat, 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.processPosition(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(_heartbeat, 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.processPosition(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.processPosition(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.processPosition(oldCursorPosition);
            var processedNewPosition = _utility.processPosition(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);

            _context.eventDuration = 0;
            _context.previousCursorPosition = _utility.processPosition(oldCursorPosition); // Cursor position at the beginning of the seek
            _context.currentCursorPosition = _utility.processPosition(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);

            _context.eventDuration = 0;
            _context.previousCursorPosition = _utility.processPosition(oldCursorPosition); // Cursor position at the beginning of the seek
            _context.currentCursorPosition = _utility.processPosition(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.processPosition(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 (_utility.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(function() {console.log('Action av.speed generated');}, {'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();
        _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._resetSession = _resetSession;
        _thisMedia._restoreDelayConfiguration = _restoreDelayConfiguration;
        _thisMedia._saveDelayConfiguration = _saveDelayConfiguration;
        _thisMedia._resetDelayConfiguration = _resetDelayConfiguration;
        _thisMedia._sortDelayConfiguration = _sortDelayConfiguration;
        _thisMedia._updateDelayConfiguration = _updateDelayConfiguration;
        _thisMedia._initSessionId = _initSessionId;
        _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 = ['Event'];
        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');