<?xml version="1.0" encoding="UTF-8" standalone="no"?><addon xmlns="http://icplayer.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="MultiAudio" xsi:schemaLocation="http://icplayer.com ../../tools/addon.xsd">
	<model>
		<property isDefault="true" name="Files" nameLabel="MultiAudio_property_files" type="list">
			<property name="ID" nameLabel="MultiAudio_property_id" type="string"/>
			<property name="Mp3" nameLabel="MultiAudio_property_mp3" type="audio"/>
			<property name="Ogg" nameLabel="MultiAudio_property_ogg" type="audio"/>
			<property name="Enable loop" nameLabel="MultiAudio_property_enable_loop" type="boolean"/>
			<property isLocalized="true" name="Text" nameLabel="MultiAudio_property_text" type="html"/>
		</property>
		<property name="Interface" nameLabel="MultiAudio_property_interface" type="{None, Default controls, Display time, Draggable items}"/>
		<property isLocalized="true" name="Narration" nameLabel="MultiAudio_property_narration" type="narration"/>
		<property displayName="Lang attribute" name="langAttribute" nameLabel="MultiAudio_property_lang_attribute" type="string"/>
		<property displayName="Speech texts" name="speechTexts" nameLabel="MultiAudio_property_property_speech_texts" type="staticlist">

			<property name="Selected" nameLabel="MultiAudio_property_selected" type="staticrow">
				<property name="Selected" nameLabel="MultiAudio_property_selected" type="string"/>
			</property>

			<property name="Deselected" nameLabel="MultiAudio_property_deselected" type="staticrow">
				<property name="Deselected" nameLabel="MultiAudio_property_deselected" type="string"/>
			</property>

			<property name="Empty" nameLabel="MultiAudio_property_empty" type="staticrow">
				<property name="Empty" nameLabel="MultiAudio_property_empty" type="string"/>
			</property>

		</property>
	</model>
<css>.wrapper-addon-audio audio {
    bottom: 0;
    position: absolute;
}

.wrapper-addon-audio {
    height: 100%;
    position: relative;
}

.multiaudio-item-wrapper {
    width: 110px;
    height: 45px;
    display: block;
    background-color: #3792cb;
    padding: 5px;
    margin: 2px;
}

.multiaudio-item-wrapper.multiaudio-item-has-text {
    min-width: 110px;
    min-height: 45px;
    width: fit-content;
    height: auto;
    display:flex;
    flex-wrap: wrap;
    padding: 3px;
}

.multiaudio-selected {
    border: 1px solid orange;
    z-index: 100;
}

.multiaudio-item-wrapper .multiaudio-item-grab-area {
    background-color: rgba(0,0,0,0.15);
    width: 32px;
    height: 44px;
    margin-right: 5px;
    display:inline-block;
    cursor: pointer;
}

.multiaudio-item-wrapper.multiaudio-item-has-text  .multiaudio-item-grab-area {
    background-color: white;
    min-width: 32px;
    min-height: 44px;
    width: auto;
    height: 100%;
    margin: 3px;
}

.multiaudio-item-wrapper .multiaudio-item-button {
    width: 71px;
    height: 44px;
    display:inline-block;
    background: white;
}

.multiaudio-item-wrapper.multiaudio-item-has-text .multiaudio-item-button {
    margin: 3px;
}

.multiaudio-item-wrapper .multiaudio-item-icon {
    width: 100%;
    height: 100%;
    display:block;
    background: #3792cb;
    mask: url('resources/volume_up-24px.svg') no-repeat center;
    -webkit-mask: url('resources/volume_up-24px.svg') no-repeat center;
}

.multiaudio-item-wrapper.playing .multiaudio-item-icon {
    width: 100%;
    height: 100%;
    display:block;
    background: #3792cb;
    mask: url('resources/volume_off-24px.svg') no-repeat center;
    -webkit-mask: url('resources/volume_off-24px.svg') no-repeat center;
}
</css><view>&lt;div class="wrapper-addon-audio"&gt;

&lt;/div&gt;
</view><preview>&lt;div class="wrapper-addon-audio"&gt;

&lt;/div&gt;
</preview><presenter>function AddonMultiAudio_create(){
    var presenter = function(){};
    var AUDIO_FILES_MISSING = "This addon needs at least 1 audio file.";
    var eventBus;
    var currentTimeAlreadySent;
    var deferredSyncQueue = window.DecoratorUtils.DeferredSyncQueue(deferredQueueDecoratorChecker);
    presenter.currentAudio = 0;
    presenter.audio = {};
    presenter.files = [];
    presenter.visible = true;
    presenter.defaultVisibility = true;
    presenter.globalView = null;
    presenter.globalModel = null;
    presenter.playerController = null;
    presenter.addonID = null;
    presenter.type = 'multiaudio';
    presenter.draggableItems = {};
    presenter.isWCAGOn = false;
    presenter.selectedItemID = '';
    presenter.currentDraggableItemID = '';

    presenter.setPlayerController = function(controller) {
        presenter.playerController = controller;
    };

    function getTextVoiceObject(text, lang) {return window.TTSUtils.getTextVoiceObject(text, lang);}
    
    presenter.onEventReceived = function(eventName, eventData) {
        if (eventName == "ValueChanged") {
            if (eventData.value == 'dropdownClicked' &amp;&amp; !presenter.audio.playing &amp;&amp; !isTemporarilyPaused()) {
                this.audio.load();
            }
        }else if (eventName == "ItemConsumed") {
            if (presenter.globalModel["Interface"] == "Draggable items") {
                var itemID = getItemIdFromEvent(eventData.item);
                if (itemID != null) {
                    removeDraggableItem(itemID);
                }
            }
        } else if (eventName == "ItemReturned") {
            if (presenter.globalModel["Interface"] == "Draggable items") {
                var itemID = getItemIdFromEvent(eventData.item);
                if (itemID != null) {
                    createDraggableItem(itemID);
                }
            }
        } else if (eventName == "itemDragged") {
            if (presenter.globalModel["Interface"] == "Draggable items") {
                var itemID = getItemIdFromEvent(eventData.item);
                presenter.fireSelectedDraggableEvent(itemID);
                hideDraggableItem(itemID);
            }
        } else if (eventName == "itemStopped") {
            if (presenter.globalModel["Interface"] == "Draggable items") {
                var itemID = getItemIdFromEvent(eventData.item);
                if (itemID != null) {
                    showDraggableItem(itemID);
                }
            }
        } else if (eventName == "ItemSelected" &amp;&amp; eventData.item !== null) { // when ImageSource deselects item, then item is null
            var itemID = getItemIdFromEvent(eventData.item);
            this.applySelectedClass(itemID);
        }
    };

    function isTemporarilyPaused() {
        return (presenter.audio.paused
            &amp;&amp; presenter.audio.readyState &gt; 2
            &amp;&amp; presenter.audio.currentTime &gt; 0
            &amp;&amp; !presenter.audio.ended
        );
    }

    function getItemIdFromEvent(eventDataItem) {
        var addonAndItemIds = eventDataItem.split('-');
        if (addonAndItemIds.length != 2) return null;
        if (addonAndItemIds[0] != presenter.addonID) return null;
        return addonAndItemIds[1];
    }

    presenter.applySelectedClass = function(itemID) {
        if (presenter.globalModel["Interface"] != "Draggable items") return;
        var keys = Object.keys(presenter.draggableItems);
        for (var i = 0; i &lt; keys.length; i++) {
            var key = keys[i];
            var item = presenter.draggableItems[key];
            if (key == itemID) {
                item.addClass('multiaudio-selected');
            } else {
                item.removeClass('multiaudio-selected');
            }
        }
    }

    function getEventObject(_item, _value, _score) {
    	return {
            source : presenter.addonID,
            item : _item + '',
            value : _value + '',
            score : _score + ''
        };
    }

    function deferredQueueDecoratorChecker () {
        return presenter.isLoaded;
    }
    
    presenter.createEventData = function (data) {
    	return getEventObject(data.currentItem, data.currentTime, '');
    };

    presenter.createOnEndEventData = function (data) {
        return getEventObject(data.currentItem, 'end', '');
    };
    
    presenter.createOnPlayingEventData = function (data) {
        return getEventObject(data.currentItem, 'playing', '');
    };

    presenter.createOnPauseEventData = function (data) {
        return getEventObject(data.currentItem, 'pause', '');
    };
    
    presenter.sendEventAndSetCurrentTimeAlreadySent = function (eventData, currentTime) {
        eventBus.sendEvent('ValueChanged', eventData);
        currentTimeAlreadySent = currentTime;
    };

    presenter.getAudioCurrentTime = function () {
        return this.audio.currentTime;
    };

    presenter.onTimeUpdateSendEventCallback = function() {

        var ua = navigator.userAgent;
        if( ua.indexOf("Android") &gt;= 0 )
        {
            var androidversion = parseFloat(ua.slice(ua.indexOf("Android")+8));
            if (androidversion == 4.4)
            {
                var duration = parseInt(presenter.audio.duration, 10);
                duration = isNaN(duration) ? 0 : duration;
                var currentTime2 = parseInt(presenter.audio.currentTime, 10);

                if(duration == currentTime2){
                    presenter.sendOnEndEvent();
                }
            }
        }

        var currentTime = presenter.formatTime(presenter.getAudioCurrentTime());
        var currentItem = presenter.currentAudio+1;
        if (currentTime !== currentTimeAlreadySent) { // to prevent duplicated value
            var eventData = presenter.createEventData({'currentTime' : currentTime, 'currentItem': currentItem});
            presenter.sendEventAndSetCurrentTimeAlreadySent(eventData, currentTime);
        }
    };
    
    presenter.addAttributeLoop = function(audio) {
        $(audio).on("ended", function() {
            this.currentTime = 0;
            this.play();
        });
    };

    presenter.prepareAudio = function(){
        this.audio = document.createElement("audio");
        var audioWrapper = presenter.globalView.find(".wrapper-addon-audio");
        audioWrapper.html("");
        audioWrapper.append(this.audio);
        return audioWrapper;
    };
    
    presenter.sendOnEndEvent = function () {
        var currentItem = presenter.currentAudio+1;
        var eventData = presenter.createOnEndEventData({'currentItem': currentItem});
        eventBus.sendEvent('ValueChanged', eventData);
    };
    
    presenter.sendOnPlayingEvent = function () {
        var currentItem = presenter.currentAudio+1;
        var eventData = presenter.createOnPlayingEventData({'currentItem': currentItem});
            eventBus.sendEvent('ValueChanged', eventData);
    };

    presenter.sendOnPausedEvent = function () {
        var currentItem = presenter.currentAudio+1;
        var eventData = presenter.createOnPauseEventData({'currentItem': currentItem});
            eventBus.sendEvent('ValueChanged', eventData);
    };

    presenter.playingEventSent = false;
    presenter.createView = function(view, model){
        var interfaceType = model["Interface"];
        var audioWrapper = this.prepareAudio();
        this.audio.addEventListener('timeupdate', function() {
            presenter.onTimeUpdateSendEventCallback();

            var ua = navigator.userAgent;
            if( ua.indexOf("Android") &gt;= 0 )
            {
                var androidversion = parseFloat(ua.slice(ua.indexOf("Android")+8));
                if (androidversion == 5)
                {
                    if(presenter.audio.currentTime &gt; 0 &amp;&amp; !presenter.playingEventSent){
                        presenter.sendOnPlayingEvent();
                        presenter.playingEventSent = true;
                    }
                }
            }
        }, false);
        this.audio.addEventListener('playing', function () {
            var ua = navigator.userAgent;
            if( ua.indexOf("Android") &gt;= 0 ){
                var androidversion = parseFloat(ua.slice(ua.indexOf("Android")+8));
                if (androidversion == 5){
                    //do nothing
                }else{
                    presenter.sendOnPlayingEvent();
                }
            }else{
                presenter.sendOnPlayingEvent();
            }
        }, false);
        this.audio.addEventListener('play', function () {
        }, false);
        this.audio.addEventListener('click', function(e) {
            e.stopPropagation();
        }, false);
        this.audio.addEventListener('ended', function() {
            presenter.stop();
            presenter.sendOnEndEvent();
            presenter.playingEventSent = false;
        }, false);

        this.audio.addEventListener('pause', function() {
                if (interfaceType == "Default controls") {
                    presenter.sendOnPausedEvent();
                }
            }, false);

        if (!presenter.isLoaded) {
            this.audio.addEventListener("loadeddata", function() {
                presenter.isLoaded = true;

                deferredSyncQueue.resolve();
            });
        }

        switch(interfaceType) {
            case "Default controls":
                $(this.audio).attr("controls", "controls").attr("preload", "auto");
                break;
            case "Display time":
                this.createCurrentAndDuration(audioWrapper);
                this.audio.addEventListener('loadeddata', onLoadedMetadataCallback, false);
                this.audio.addEventListener('timeupdate', onTimeUpdateCallback, false);
                break;
            case "Draggable items":
                presenter.createDraggableItems(model['Files']);
                break;
        }

        Object.defineProperty(presenter.audio, 'playing', {
            get: function () {
                return !!(this.currentTime &gt; 0 &amp;&amp; !this.paused &amp;&amp; !this.ended &amp;&amp; this.readyState &gt; 2);
            }
        });
    };

    presenter.createDraggableItems = function(filesModel) {
        for (var i=0; i &lt; filesModel.length; i++) {
            createDraggableItem(filesModel[i].ID);
        }
        if (presenter.selectedItemID.length &gt; 0) {
            presenter.applySelectedClass(presenter.selectedItemID);
        }
    };

    function createDraggableItem (itemID) {
        if (itemID in presenter.draggableItems) return;

        var $el = $('&lt;div&gt;&lt;/div&gt;');
            $el.attr('data-audio-id', itemID);
            $el.attr('data-addon-id', presenter.addonID);
            $el.addClass('multiaudio-item-wrapper');

            var $grab = $('&lt;div&gt;&lt;/div&gt;');
            $grab.addClass('multiaudio-item-grab-area');
            $el.append($grab);

            var itemText = presenter.getTextFromFileID(itemID);
            if (presenter.playerController) {
                itemText = presenter.playerController.getTextParser().parseAltTexts(itemText);
            } else {
                itemText = window.TTSUtils.parsePreviewAltText(itemText);
            }
            if ($("&lt;span&gt;" + itemText + "&lt;/span&gt;").text().length &gt; 0) {
                var $text = $('&lt;span&gt;&lt;/span&gt;');
                $text.addClass('multiaudio-item-text');
                $text.html(itemText);
                $grab.append($text);
                $el.addClass("multiaudio-item-has-text");
            }

            $grab.click(function(){presenter.handleGrabAreaClick(itemID)});

            var $button = $('&lt;div&gt;&lt;/div&gt;');
            $button.addClass('multiaudio-item-button');
            $el.append($button);

            var $icon = $('&lt;div&gt;&lt;/div&gt;');
            $icon.addClass('multiaudio-item-icon');
            $button.append($icon);

            $button.click(draggableItemButtonClickHandler);

            $el.draggable({
                revert: true,
                helper: "original",
                handle: ".multiaudio-item-grab-area",
                start : function(event, ui) {
                    presenter.fireSelectedDraggableEvent(itemID);
                    if (presenter.draggableItems[itemID].hasClass('playing')) {
                        presenter.stop();
                        presenter.draggableItems[itemID].removeClass('playing');
                    }
                },
                drag : function(event, ui) {
                    ui.position.left = ui.position.left / getScale().X;
                    ui.position.top = ui.position.top / getScale().Y;
                }
            });

            presenter.globalView.find(".wrapper-addon-audio").append($el);
            presenter.draggableItems[itemID] = $el;
    }

    function getScale() {
        if (presenter.playerController) {
            const scaleInformation = presenter.playerController.getScaleInformation();
            if (scaleInformation.baseScaleX !== 1.0 ||
                scaleInformation.baseScaleY !== 1.0 ||
                scaleInformation.scaleX !== 1.0 ||
                scaleInformation.scaleY !== 1.0
            ) {
                return {X: scaleInformation.scaleX, Y: scaleInformation.scaleY};
            }
        }

        const $content = $("#content");
        if ($content.size() &gt; 0) {
            const contentElem = $content[0];
            const scaleX = contentElem.getBoundingClientRect().width / contentElem.offsetWidth;
            const scaleY = contentElem.getBoundingClientRect().height / contentElem.offsetHeight;
            return {X: scaleX, Y: scaleY};
        } else {
            return {X: 1.0, Y: 1.0};
        }
    }

    presenter.getTextFromFileID = function(itemID) {
        // This method is used by the multiplegap addon while creating the draggable audio widgets
        var item = null;
        for (var i = 0; i &lt; presenter.globalModel.Files.length; i++) {
            var tmpItem = presenter.globalModel.Files[i];
            if (tmpItem.ID === itemID) {
                item = tmpItem;
            }
        }
        if (item === null) return null;
        return item["Text"];
    }

    function removeDraggableItem(itemID) {
        if (!(itemID in presenter.draggableItems)) return;

        presenter.draggableItems[itemID].remove();
        delete presenter.draggableItems[itemID];
        if (presenter.selectedItemID == itemID) {
            presenter.selectedItemID = '';
        }
    }

    function removeDraggableItems() {
        presenter.globalView.find('.multiaudio-item-wrapper').remove();
        presenter.draggableItems = {};
        presenter.selectedItemID = '';
    }

    function hideDraggableItem(itemID) {
        if (itemID in presenter.draggableItems) {
            presenter.draggableItems[itemID].css('display','none');
        }
    }

    function showDraggableItem(itemID) {
        if (itemID in presenter.draggableItems) {
            presenter.draggableItems[itemID].css('display','');
        }
    }

    // itemID is optional, if left empty a "deselecting" event will be fired
    presenter.fireSelectedDraggableEvent = function(itemID) {
        var eventData = {
            source : presenter.addonID,
            type : 'audio',
            item : "",
            value : ""
        };
        if (itemID != null) {
            eventData.item = presenter.addonID + '-' + itemID;
            eventData.value = itemID + '';
        }
        eventBus.sendEvent('ItemSelected', eventData);
    };

    presenter.handleGrabAreaClick = function(itemID) {
        if (presenter.draggableItems[itemID].hasClass('ui-draggable-dragging')) return;
        if (presenter.draggableItems[itemID].hasClass('multiaudio-selected')) {
            presenter.fireSelectedDraggableEvent();
            presenter.selectedItemID = '';
            readDeselected();
        } else {
            presenter.fireSelectedDraggableEvent(itemID);
            presenter.selectedItemID = itemID;
            readSelected();
        }
    };

    function draggableItemButtonClickHandler (event) {
        var $parent = $(event.currentTarget).parent();
        var itemID = $parent.attr('data-audio-id');
        if ($parent.hasClass('playing')) {
            presenter.stop();
            presenter.jumpToID(itemID);
        } else {
            $parent.addClass('playing');
            presenter.jumpToID(itemID);
            presenter.play();

        }
    }

    presenter.createCurrentAndDuration = function(audioWrapper) {
        var currentTime = document.createElement("span");
        var durationTime = document.createElement("span");
        $(currentTime).attr("id", "currentTime");
        $(durationTime).attr("id", "durationTime");
        audioWrapper.html(currentTime).append(durationTime);
    };

    presenter.formatTime = function(seconds) {
        var minutes = Math.floor(seconds / 60);
        minutes = (minutes &gt;= 10) ? minutes : "0" + minutes;
        seconds = Math.floor(seconds % 60);
        seconds = (seconds &gt;= 10) ? seconds : "0" + seconds;
        return minutes + ":" + seconds;
    };

    function onLoadedMetadataCallback() {
        var duration = parseInt(presenter.audio.duration, 10);
        duration = isNaN(duration) ? 0 : duration;
        presenter.displayTimer(0, duration);
    }

    function onTimeUpdateCallback() {
        var duration = parseInt(presenter.audio.duration, 10);
        duration = isNaN(duration) ? 0 : duration;
        var currentTime = presenter.audio.currentTime;
        presenter.displayTimer(currentTime, duration);
    }

    presenter.displayTimer = function(current, duration) {
        presenter.globalView.find('#currentTime').html(presenter.formatTime(current) + ' / ');
        presenter.globalView.find('#durationTime').html(presenter.formatTime(duration));
    };

    presenter.loadFiles = function(audio, model){
        this.files = model["Files"];
        var oggFile = this.files[this.currentAudio]["Ogg"];
        var mp3File = this.files[this.currentAudio]["Mp3"];
        var loop = !!(this.files[this.currentAudio]["Enable loop"] == "True");
        var canPlayMp3 = false;
        var canPlayOgg = false;

        var validated = this.validateFiles(this.files[this.currentAudio]);

        if (!validated) {
            this.globalView.find(".wrapper-addon-audio").html(AUDIO_FILES_MISSING);
        }

        if (loop) {
            presenter.addAttributeLoop(audio);
        }

        if(audio.canPlayType) {
            canPlayMp3 = !!audio.canPlayType &amp;&amp; "" != audio.canPlayType('audio/mpeg');
            canPlayOgg = !!audio.canPlayType &amp;&amp; "" != audio.canPlayType('audio/ogg; codecs="vorbis"');
            if(canPlayMp3){
                $(audio).attr("src", mp3File);
            } else if (canPlayOgg) {
                $(audio).attr("src", oggFile);
            }
        } else {
            $(audio).append("Your browser doesn't support audio.");
        }

        audio.load();

    };

    function upgradeModel(model) {
        var upgradedModel = upgradeFileText(model);
        upgradedModel = upgradeTextToSpeechSupport(upgradedModel);
        return upgradedModel;
    }

    function upgradeFileText(model) {
        var upgradedModel = {};
        $.extend(true, upgradedModel, model);
        for (var i = 0; i &lt; upgradedModel["Files"].length; i++) {
            if (upgradedModel["Files"][i]["Text"] === undefined) {
                upgradedModel["Files"][i]["Text"] = "";
            }
        }
        return upgradedModel;
    }

    function upgradeTextToSpeechSupport(model) {
        var upgradedModel = {};
        $.extend(true, upgradedModel, model);

        if (upgradedModel['speechTexts'] === undefined) {
            upgradedModel['speechTexts'] = {
                Selected: {Selected: "Selected"},
                Deselected: {Deselected: "Deselected"},
                Empty: {Empty: "Empty"}
            };
        }

        if (upgradedModel['langAttribute'] === undefined) {
            upgradedModel['langAttribute'] = "";
        }

        return upgradedModel;
    }

    function getSpeechTextProperty (rawValue, defaultValue) {
            var value = rawValue.trim();

            if (value === undefined || value === null || value === '') {
                return defaultValue;
            }

            return value;
        }

    presenter.getSpeechTexts = function(speechTextsModel) {
        var speechTexts = {
            selected:  'Selected',
            deselected: 'Deselected',
            empty: 'Empty'
        };

        if (!speechTextsModel) {
            return speechTexts;
        }

        speechTexts = {
            selected:        getSpeechTextProperty(speechTextsModel['Selected']['Selected'], speechTexts.selected),
            deselected:        getSpeechTextProperty(speechTextsModel['Deselected']['Deselected'], speechTexts.deselected),
            empty:        getSpeechTextProperty(speechTextsModel['Empty']['Empty'], speechTexts.empty)
        };

        return speechTexts;
    };

    presenter.run = function(view, model){
        this.initialize(view, model, false);
        eventBus = presenter.playerController.getEventBus();
        presenter.addonID = model.ID;
        eventBus.addEventListener('ValueChanged', this);
        eventBus.addEventListener('ItemSelected', this);
        eventBus.addEventListener('ItemConsumed', this);
        eventBus.addEventListener('ItemReturned', this);
        eventBus.addEventListener('itemStopped', this);
        eventBus.addEventListener('itemDragged', this);
    };

    presenter.createPreview = function(view, model){
        this.initialize(view, model, true);
    };

    presenter.initialize = function(view, model, isPreview) {
        var upgradedModel = upgradeModel(model);
        this.globalModel = upgradedModel;
        this.speechTexts = presenter.getSpeechTexts(upgradedModel['speechTexts']);
        this.globalView = $(view);
        this.createView(view, upgradedModel);
        presenter.view = view;
        if (!isPreview) {
        	this.loadFiles(this.audio, upgradedModel);
            MutationObserverService.createDestroyObserver(presenter.addonID, presenter.destroy, presenter.view);
            MutationObserverService.setObserver();
        }
        this.visible = !!(upgradedModel['Is Visible'] == 'True');
        this.defaultVisibility = this.visible;
    };

    presenter.destroy = function AddonMultiAudio_destroy() {
        presenter.audio.pause();
    };

    presenter.executeCommand = function(name, params) {
        var commands = {
            'show': presenter.show,
            'hide': presenter.hide,
            'play': presenter.play,
            'stop': presenter.stop,
            'next': presenter.next,
            'previous': presenter.previous,
            'jumpTo': presenter.jumpToCommand,
            'jumpToID': presenter.jumpToIDCommand,
            'pause': presenter.pause
        };

        Commands.dispatch(commands, name, params, presenter);
    };

    presenter.setVisibility = function(isVisible) {
        $(presenter.globalView).css("visibility", isVisible ? "visible" : "hidden");
    };

    presenter.play = deferredSyncQueue.decorate(function() {
        if (!this.audio.playing) {
            this.audio.play();
        }
        if (presenter.globalModel["Interface"] == "Draggable items") {
            presenter.setCurrentDraggableItemToPlay();
        }
    });

    presenter.setCurrentDraggableItemToPlay = function() {
        var itemID = presenter.files[presenter.currentAudio].ID;
        $(presenter.view).find(".multiaudio-item-wrapper").each(function(){
            var $this = $(this);
            if ($this.attr('data-audio-id') == itemID) {
                $this.addClass('playing');
            } else {
                $this.removeClass('playing');
            }
        });
    };

    presenter.stop = deferredSyncQueue.decorate(function() {
        if (!presenter.audio.paused) {
            presenter.audio.pause();
            presenter.playingEventSent = false;
            presenter.sendOnPausedEvent();
        }

        presenter.audio.currentTime = 0;
        if (presenter.globalModel["Interface"] == "Draggable items") {
            presenter.stopDraggableItems();
        }
    });

    presenter.stopDraggableItems = function() {
        $(presenter.view).find('.multiaudio-item-wrapper.playing').removeClass('playing');
    };

    presenter.pause = function() {
        if (!presenter.audio.paused) {
            presenter.audio.pause();
            presenter.playingEventSent = false;
            presenter.sendOnPausedEvent();
        }
        presenter.stopDraggableItems();
    };

    presenter.show = function() {
        this.setVisibility(true);
        this.visible = true;
        if(audioStarted(this.audio)) {
            this.audio.play();
        }
    };

    function increaseVolume() {
        var volume = presenter.audio.volume;
        volume += 0.1;
        if (volume &gt; 1.0) volume = 1.0;
        presenter.audio.volume = volume;
    };

    function decreaseVolume() {
        var volume = presenter.audio.volume;
        volume -= 0.1;
        if (volume &lt; 0.0) volume = 0.0;
        presenter.audio.volume = volume;
    };

    function forward() {
        presenter.audio.currentTime += 5;
    }

    function backward() {
            presenter.audio.currentTime -= 5;
        }

    function playPause() {
        if (presenter.audio.paused) {
            presenter.play();
        } else {
            presenter.pause();
        }
    }

    function playStop() {
            if (presenter.audio.paused) {
                presenter.play();
            } else {
                presenter.stop();
            }
        }

    presenter.hide = function() {
        this.setVisibility(false);
        this.visible = false;
        if(audioStarted(this.audio)){
            this.audio.pause();
        }
    };

    function audioStarted(audio) {
        return audio.currentTime &gt; 0;
    }
    
    presenter.reset = function() {
        this.visible = this.defaultVisibility;
        if (this.visible) {
            this.show();
        } else {
            this.hide();
        }
        this.stop();
        this.currentAudio = 0;
        presenter.loadFiles(this.audio, this.globalModel);

        if (presenter.globalModel["Interface"] == "Draggable items") {
            removeDraggableItems();
            for (var i = 0; i &lt; presenter.globalModel.Files.length; i++) {
                createDraggableItem(presenter.globalModel.Files[i].ID);
            }
        }
    };

    presenter.jumpTo = function(audioNumber) {
        var newAudio = parseInt(audioNumber, 10) - 1;
        if (0 &lt;= newAudio &amp;&amp; newAudio &lt; this.files.length) {
            presenter.sendOnPausedEvent();
            this.currentAudio = newAudio;
            presenter.isLoaded = false;
            presenter.loadFiles(this.audio, this.globalModel);
        }
    };

    presenter.jumpToCommand = function(params) {
        presenter.jumpTo(params[0]);
        presenter.playingEventSent = false;
    };

    presenter.previous = function() {
        if (this.currentAudio &gt; 0) {
            presenter.sendOnPausedEvent();
            this.currentAudio--;
            this.initialize(this.globalView[0], this.globalModel);
        }
    };

    presenter.next = function() {
        if (this.currentAudio &lt; this.files.length - 1) {
            presenter.sendOnPausedEvent();
            this.currentAudio++;
            this.initialize(this.globalView[0], this.globalModel);
        }
    };

    presenter.previousDraggableItem = function() {
        var itemIDs = Object.keys(presenter.draggableItems);
        if (itemIDs.length == 0){
            presenter.currentDraggableItemID = '';
            return;
        }
        if (presenter.currentDraggableItemID.length == 0) {
            presenter.currentDraggableItemID = itemIDs[itemIDs.length - 1];
            presenter.jumpToID(presenter.currentDraggableItemID);
            updateWCAGSelectedClass();
        } else {
            var index = itemIDs.indexOf(presenter.currentDraggableItemID);
            if (index &gt; 0) {
                presenter.currentDraggableItemID = itemIDs[index-1];
                presenter.jumpToID(presenter.currentDraggableItemID);
                updateWCAGSelectedClass();
            }
        }
        }

    presenter.nextDraggableItem = function() {
        var itemIDs = Object.keys(presenter.draggableItems);
        if (itemIDs.length == 0){
            presenter.currentDraggableItemID = '';
            return;
        }
        if (presenter.currentDraggableItemID.length == 0) {
            presenter.currentDraggableItemID = itemIDs[0];
            presenter.jumpToID(presenter.currentDraggableItemID);
            updateWCAGSelectedClass();
        } else {
            var index = itemIDs.indexOf(presenter.currentDraggableItemID);
            if (index &lt; itemIDs.length -1 &amp;&amp; index != -1) {
                presenter.currentDraggableItemID = itemIDs[index+1];
                presenter.jumpToID(presenter.currentDraggableItemID);
                updateWCAGSelectedClass();
            }
        }
    }

    function updateWCAGSelectedClass() {
        if (presenter.globalModel["Interface"] != "Draggable items") return;

        clearWCAGSelectedClass();
        if (presenter.currentDraggableItemID.length &gt; 0) {
            presenter.draggableItems[presenter.currentDraggableItemID].addClass('keyboard_navigation_active_element');
        }
    }

    function clearWCAGSelectedClass() {
        var activeClassName = 'keyboard_navigation_active_element';
        presenter.globalView.find('.'+activeClassName).removeClass(activeClassName);
    }

    presenter.jumpToID = function(id) {
        for (var i = 0; i &lt; this.files.length; i++) {
            if (id === this.files[i].ID) {
                this.jumpTo(i + 1);  // Audio numbers are counted from 1 to n
                break;
            }
        }
    };

    presenter.jumpToIDCommand = function(params) {
        presenter.jumpToID(params[0]);
    };

    presenter.getState = function() {
        var state = {
            'visible' : "" + this.visible,
            'currentAudio' : "" + this.currentAudio,
            'currentTime'   : "" + this.audio.currentTime
        };
        var draggableKeys = Object.keys(presenter.draggableItems);
        if (draggableKeys.length != 0) {
            state['draggableItems'] = Object.keys(presenter.draggableItems).join(',')
        }
        return this.convertStateToString(state);
    };

    presenter.convertStateToString = function(state) {
        var stateString = "";
        $.each(state, function(key, value){
            stateString += "[" + key + ":" + value + "]";
        });
        return stateString;
    };

    presenter.convertStringToState = function(stateString) {
        var state = {};
        var pattern = /\w+:[\w|\,]+/g;
        var stateElements = stateString.match(pattern);
        for (var i = 0; i &lt; stateElements.length; i++) {
            var keyAndValue = stateElements[i].split(":");
            var key = keyAndValue[0];
            state[key] = keyAndValue[1];
        }
        return state;
    };

    presenter.setState = function(stateString) {
        var state = this.convertStringToState(stateString);
        var visible = !!(state["visible"] == "true");
        var currentAudio = parseInt(state["currentAudio"]);
        var currentTime = parseInt(state["currentTime"]);

        if (visible) {
            this.show();
        } else {
            this.hide();
        }

        this.currentAudio = currentAudio;
        presenter.loadFiles(this.audio, this.globalModel);

        if (presenter.globalModel["Interface"] == "Draggable items") {
            var keys = Object.keys(presenter.draggableItems);
            if ('draggableItems' in state) {
                var loadedDraggableItems = state['draggableItems'].split(',');
                for (var i = 0; i &lt; keys.length; i++) {
                    if (loadedDraggableItems.indexOf(keys[i]) == -1) {
                        removeDraggableItem(keys[i]);
                    }
                }
            } else {
                for (var i = 0; i &lt; keys.length; i++) {
                    removeDraggableItem(keys[i]);
                }
            }
        }
    };

    presenter.validateFiles = function(files) {
        return !(!files["Ogg"] &amp;&amp; !files["Mp3"]);
    };

    presenter.setWCAGStatus = function (isOn) {
            presenter.isWCAGOn = isOn;
            if (!isOn) {
                clearWCAGSelectedClass();
                presenter.currentDraggableItemID = '';
            }
        };

    presenter.keyboardController = function (keycode, isShift, event) {
        event.preventDefault();
        if (presenter.globalModel["Interface"] == "Draggable items") {
            presenter.draggableKeyboardController(keycode, isShift, event);
        } else {
            presenter.audioKeyboardController(keycode, isShift, event);
        }
    };

    presenter.draggableKeyboardController = function (keycode, isShift, event) {
        switch (keycode) {
            case 9: // TAB
                if (isShift) {
                    presenter.previousDraggableItem();
                } else {
                    presenter.nextDraggableItem();
                }
                readCurrentDraggableFileText();
                break;
            case 13: //ENTER
                if (isShift) {
                    presenter.stop();
                    clearWCAGSelectedClass();
                    presenter.currentDraggableItemID = '';
                } else {
                    if (presenter.currentDraggableItemID.length &gt; 0) {
                        playStop();
                        updateWCAGSelectedClass();
                    }
                }
                break;
            case 32: //SPACE
                var itemID = presenter.files[presenter.currentAudio].ID;
                presenter.handleGrabAreaClick(itemID);
                break;
            case 38: // UP
                presenter.previousDraggableItem();
                readCurrentDraggableFileText();
                updateWCAGSelectedClass();
                break;
            case 40: // DOWN
                presenter.nextDraggableItem();
                readCurrentDraggableFileText();
                updateWCAGSelectedClass();
                break;
            case 37: // LEFT
                presenter.previousDraggableItem();
                readCurrentDraggableFileText();
                updateWCAGSelectedClass();
                break;
            case 39: // RIGHT
                presenter.nextDraggableItem();
                readCurrentDraggableFileText();
                updateWCAGSelectedClass();
                break;
            case 27: // ESC
                presenter.stop();
                clearWCAGSelectedClass();
                presenter.currentDraggableItemID = '';
                break;
        }
    }

    presenter.audioKeyboardController = function (keycode, isShift, event) {
        switch (keycode) {
            case 9: // TAB
                if (isShift) {
                    presenter.previous();
                } else {
                    presenter.next();
                }
                readCurrentAudioFileText();
                break;
            case 13: //ENTER
                if (!isShift) {
                    presenter.pause();
                    readCurrentAudioFileText();
                }
                break;
            case 32: // SPACE
                playPause();
                break;
            case 38: // UP
                increaseVolume();
                break;
            case 40: // DOWN
                decreaseVolume();
                break;
            case 37: // LEFT
                backward();
                break;
            case 39: // RIGHT
                forward();
                break;
            case 27: // ESC
                presenter.stop();
                break;
        }
    }

    function readSelected() {
        var textVoiceArray = [];
        textVoiceArray.push(getTextVoiceObject(presenter.speechTexts.selected, ""));
        speak(textVoiceArray);
    }

    function readDeselected() {
        var textVoiceArray = [];
        textVoiceArray.push(getTextVoiceObject(presenter.speechTexts.deselected, ""));
        speak(textVoiceArray);
    }

    function readCurrentDraggableFileText() {
        var textVoiceArray = [];
        if (Object.keys(presenter.draggableItems).length == 0) {
            textVoiceArray.push(getTextVoiceObject(presenter.speechTexts.empty, ""));
        } else {
            var item = presenter.files[presenter.currentAudio];
            textVoiceArray.push(getTextVoiceObject(item.Text, presenter.globalModel['langAttribute']));
            if (presenter.selectedItemID == item.ID) {
                textVoiceArray.push(getTextVoiceObject(presenter.speechTexts.selected, ""));
            }
        }
        speak(textVoiceArray);
    }

    function readCurrentAudioFileText() {
        var item = presenter.files[presenter.currentAudio];
        var textVoiceArray = [getTextVoiceObject(item.Text, presenter.globalModel['langAttribute'])];
        speak(textVoiceArray);
    }

    presenter.getTextToSpeechOrNull = function () {
        if (presenter.playerController) {
            return presenter.playerController.getModule('Text_To_Speech1');
        }

        return null;
    };

    function speak (data) {
        var tts = presenter.getTextToSpeechOrNull();
        if (tts &amp;&amp; presenter.isWCAGOn) {
            tts.speak(data);
        }
    };

    return presenter;
}
</presenter></addon>