PageSourceSearch

https://gsk-china.com/nuxtassets/js/talkify.js

js gsk-china.com collected 2026-10-02 10:24:56 UTC 172,943 bytes, 4,962 lines download raw bytes

1/**
2 * Customised version of talkify.js for GSK
3 */
4
5(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
6var talkify = require('./src/talkify.js');
7var talkifyConfig = require('./src/talkify-config.js');
8var talkifyUtils = require('./src/talkify-utils.js');
9var talkifyMessageHub = require('./src/talkify-messagehub.js');
10var talkifyHttp = require('./src/talkify-ajax.js');
11var TalkifyTextextractor = require('./src/talkify-textextractor.js');
12var TalkifyWordHighlighter = require('./src/talkify-word-highlighter.js');
13var BasePlayer = require('./src/talkify-player-core.js');
14var Html5Player = require('./src/talkify-html5-speechsynthesis-player.js');
15var TtsPlayer = require('./src/talkify-player.js');
16var talkifyPlaylist = require('./src/talkify-playlist.js');
17var talkifyControlcenterCore = require('./src/control-centers/talkify-controlcenter-core.js');
18var talkifyPlaybar = require('./src/control-centers/talkify-controlcenter-classic.js');
19var talkifyModernControlcenter = require('./src/control-centers/talkify-controlcenter-modern.js');
20var talkifyLocalControlcenter = require('./src/control-centers/talkify-controlcenter-local.js');
21var talkifyKeyCommands = require('./src/talkify-keyboard-commands.js');
22var talkifyVoiceCommands = require('./src/talkify-speech-recognition.js');
23var talkifyFormReader = require('./src/talkify-formreader.js');
24var talkifyTableReader = require('./src/table-reader/talkify-tablereader.js');
25var talkifyTestSelectionActivator = require('./src/talkify-text-selection-activator.js');
26var talkifyAutoScroll = require('./src/talkify-autoscroll.js');
27},{"./src/control-centers/talkify-controlcenter-classic.js":2,"./src/control-centers/talkify-controlcenter-core.js":3,"./src/control-centers/talkify-controlcenter-local.js":4,"./src/control-centers/talkify-controlcenter-modern.js":5,"./src/table-reader/talkify-tablereader.js":6,"./src/talkify-ajax.js":7,"./src/talkify-autoscroll.js":8,"./src/talkify-config.js":9,"./src/talkify-formreader.js":10,"./src/talkify-html5-speechsynthesis-player.js":11,"./src/talkify-keyboard-commands.js":12,"./src/talkify-messagehub.js":13,"./src/talkify-player-core.js":14,"./src/talkify-player.js":15,"./src/talkify-playlist.js":16,"./src/talkify-speech-recognition.js":17,"./src/talkify-text-selection-activator.js":18,"./src/talkify-textextractor.js":19,"./src/talkify-utils.js":20,"./src/talkify-word-highlighter.js":21,"./src/talkify.js":22}],2:[function(require,module,exports){
28talkify = talkify || {};
29talkify.controlcenters = talkify.controlcenters || {};
30
31talkify.controlcenters.classic = function () {
32    this.html = '<div class="talkify-control-center classic attached">\
33        <ul>  \
34        <li class="talkify-drag-area talkify-detached">  \
35        <i class="fa fa-grip-horizontal"></i>  \
36        </li>  \
37        <li>  \
38        <button class="talkify-play-button talkify-disabled" title="Play">  \
39        <i class="fas fa-play-circle"></i>  \
40        </button>  \
41        <button class="talkify-pause-button talkify-disabled" title="Pause">  \
42        <i class="fas fa-pause-circle"></i>  \
43        </button>  \
44        <i class="fa fa-circle-notch fa-spin talkify-audio-loading"></i> \
45        <i class="fas fa-exclamation-triangle talkify-audio-error" title="An error occurred at playback"></i> \
46        </li>  \
47        <li class="progress-wrapper">  \
48        <span class="talkify-time-elapsed-element">00:00</span> \
49        <progress value="0.0" max="1.0"></progress>  \
50        <span class="talkify-time-duration-element">00:00</span> \
51        </li>  \
52        <li>  \
53        <button class="talkify-volume-button" title="Volume">  \
54        <i class="fa fa-volume-up"></i>  \
55        <div class="volume-slider">  \
56        <input type="range" value="10" min="0" max="10" title="Adjust playback volume">  \
57        </div>  \
58        </button></li>  \
59       </ul></div>';
60
61}
62},{}],3:[function(require,module,exports){
63talkify = talkify || {};
64talkify.playbar = function (parent, correlationId, controlcenter) {
65    var mainFlags = ["de-DE", "fr-FR", "en-US", "zh-CN", "es-ES", "it-IT", "ja-JP", "ko-KR", "sv-SE", "nb-NO", "da-DK", "ru-RU", "nl-NL", "pl-PL", "tr-TR", "is-IS", "uk-UA", "sk-SK", "pt-PT", "ro-RO", "cy-GB", "bg-BG", "cs-CZ", "el-GR", "fi-FI", "he-IL", "hi-IN", "hr-HR", "hu-HU", "id-ID", "ms-MY", "sl-SI", "th-TH", "vi-VN", "ar-EG", "ar-SA", "ta-IN", "te-IN", "en-GB-WLS", "ca-ES", "gu-IN", "ml-IN", "bn-IN", "kn-IN", "fil-PH"];
66
67    var settings = {
68        parentElement: parent || talkify.config.ui.audioControls.container || document.body,
69        controlCenterName: controlcenter || talkify.config.ui.audioControls.controlcenter
70    }
71
72    var playElement, pauseElement, rateElement, volumeElement, progressElement, voiceElement, elapsedElement, durationElement, textHighlightingElement, wrapper, voicePicker;
73    var attachElement, detatchedElement, dragArea, loader, erroroccurredElement, textInteractionElement, pitchElement, wordBreakElement, wordBreakElementWrapper;
74    var pitchElementWrapper, nextItemElement, previousItemElement, voiceNameElement, enhancedVisibilityElement;
75    var flagElement, phonationNormalElement, phonationSoftElement, phonationWhisperElement, phonationDropDown, downloadElement, downloadLoadingElement, downloadErrorElement;
76    var voices = [];
77
78    var noopElement = document.createElement("div");
79    playElement = pitchElementWrapper = nextItemElement = enhancedVisibilityElement = voiceNameElement = previousItemElement = phonationNormalElement = phonationSoftElement = phonationWhisperElement = phonationDropDown = flagElement = wordBreakElement = wordBreakElementWrapper = pitchElement = dragArea = textInteractionElement = voiceWrapperElement = attachElement = elapsedElement = durationElement = detatchedElement = pauseElement = loader = erroroccurredElement = progressElement = textHighlightingElement = noopElement;
80    rateElement = volumeElement = [];
81
82    function hide(element) {
83        if (!element) {
84            return;
85        }
86
87        var arr = Array.isArray(element) ?
88            element :
89            NodeList.prototype.isPrototypeOf(element) ?
90                Array.from(element) :
91                [element];
92
93        arr.forEach(function (x) {
94            if (x.classList.contains("talkify-hidden")) {
95                return;
96            }
97
98            x.className += " talkify-hidden";
99        });
100    }
101
102    function show(element) {
103        if (!element) {
104            return;
105        }
106
107        var arr = Array.isArray(element) ?
108            element :
109            NodeList.prototype.isPrototypeOf(element) ?
110                Array.from(element) :
111                [element];
112
113        arr.forEach(function (x) {
114            x.className = x.className.replace("talkify-hidden", "");
115        });
116    }
117
118    function play() {
119        hide(loader);
120        hide(playElement);
121        hide(erroroccurredElement);
122        show(pauseElement);
123    }
124
125    function pause() {
126        hide(loader);
127        hide(pauseElement);
128        hide(erroroccurredElement);
129        show(playElement);
130    }
131
132    function onError() {
133        hide(loader);
134        hide(pauseElement);
135        hide(playElement);
136        show(erroroccurredElement);
137    }
138
139    function addClass(element, c) {
140        var arr = Array.isArray(element) ?
141            element :
142            NodeList.prototype.isPrototypeOf(element) ?
143                Array.from(element) :
144                [element];
145
146        arr.forEach(function (x) {
147            if (x.classList.contains(c)) {
148                return;
149            }
150
151            x.className += (" " + c);
152        });
153    }
154
155    function removeClass(element, c) {
156        var arr = Array.isArray(element) ?
157            element :
158            NodeList.prototype.isPrototypeOf(element) ?
159                Array.from(element) :
160                [element];
161
162        arr.forEach(function (x) {
163            x.className = x.className.replace(c, "");
164        });
165    }
166
167    function createElement(type, classes) {
168        var element = document.createElement(type);
169
170        element.className = classes;
171
172        return element;
173    }
174
175    var groupBy = function (xs, keyFn) {
176        return xs.reduce(function (rv, x) {
177            var key = keyFn(x);
178
179            (rv[key] = rv[key] || []).push(x);
180            return rv;
181        }, {});
182    };
183
184    function createVoicePicker(voices, remoteVoices) {
185        var mainUl = createElement("ul", "voice-selector");
186
187        if (!voices.length) {
188            return mainUl;
189        }
190
191        var byLanguage = groupBy(voices, function (v) {
192            return remoteVoices ? v.language : v.lang;
193        });
194
195        for (var prop in byLanguage) {
196            if (!byLanguage.hasOwnProperty(prop)) {
197                continue;
198            }
199
200            var sortedVoices = byLanguage[prop].sort(function (a, b) {
201                return remoteVoices ? (a.culture - b.culture) : (a.name - b.name);
202            });
203
204            var defaultVoice = remoteVoices ?
205                sortedVoices.filter(function (x) { return x.isStandard; })[0] :
206                (sortedVoices.filter(function (x) { return x.localService; }
206)[0] || sortedVoices[0]);
207
208            var foo = sortedVoices.find(function (v) {
209                return mainFlags.indexOf(v.culture || v.lang) !== -1;
210            });
211
212            if (!foo) {
213                continue;
214            }
215
216            var mainCulture = foo.culture || foo.lang;
217
218            var mainFlag = "https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.3.0/flags/4x3/" + mainCulture.split("-")[1].toLowerCase() + ".svg";
219
220            var li = createElement("li");
221
222            var flagImg = createElement("img", "talkify-flag");
223            flagImg.src = mainFlag;
224
225            var label = createElement("label", "talkify-clickable");
226            label.innerHTML = sortedVoices[0].language || sortedVoices[0].lang;
227            label.htmlFor = "chk_" + prop;
228
229            var checkbox = createElement("input", "");
230            checkbox.id = "chk_" + prop;
231            checkbox.type = "checkbox";
232            checkbox.style = "display: none";
233
234            li.appendChild(flagImg);
235            li.appendChild(label);
236            li.appendChild(checkbox);
237
238            var innerUl = createElement("ul", "language");
239
240            li.appendChild(innerUl);
241
242            for (var j = 0; j < sortedVoices.length; j++) {
243                var voice = sortedVoices[j];
244                var isDefault = !!defaultVoice && voice === defaultVoice;
245
246                var innerLi = createElement("li", "talkify-clickable");
247                innerLi.setAttribute("data-default", isDefault);
248                innerLi.setAttribute("data-voice", remoteVoices ? JSON.stringify(voice) : voice.name);
249                innerLi.setAttribute("data-voice-name", voice.name);
250
251                var d = createElement("div", "");
252
253                if (voice.isNeural) {
254                    var i = createElement("i", "fas fa-network-wired");
255                    i.setAttribute("title", "Neural voice");
256                }
257                else if (voice.isExclusive) {
258                    var i = createElement("i", "fas fa-star");
259                    i.setAttribute("title", "Exclusive voice");
260                }
261                else if (voice.isPremium) {
262                    var i = createElement("i", "far fa-star");
263                    i.setAttribute("title", "Premium voice");
264                } else {
265                    var i = createElement("i", "far fa-check-circle");
266                    i.setAttribute("title", "Standard voice");
267                }
268
269                var span = createElement("span");
270                span.innerHTML = voice.name;
271
272                d.appendChild(i);
273                d.appendChild(span);
274
275                var flagDiv = createElement("div");
276                var svg = "https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.3.0/flags/4x3/" + (voice.culture || voice.lang).split("-")[1].toLowerCase() + ".svg";
277
278                var svgImg = createElement("img", "talkify-flag");
279                svgImg.src = svg;
280
281                flagDiv.appendChild(svgImg);
282
283                innerLi.appendChild(d);
284                innerLi.appendChild(flagDiv);
285                innerUl.appendChild(innerLi);
286            }
287
288            mainUl.appendChild(li);
289        }
290
291        return mainUl;
292    }
293
294    function render() {
295        var existingControl = document.getElementsByClassName("talkify-control-center")[0];
296
297        if (existingControl) {
298            existingControl.parentNode.removeChild(existingControl);
299        }
300
301        var controlcenter = new talkify.controlcenters[settings.controlCenterName]();
302
303        var div = document.createElement('div');
304        div.innerHTML = controlcenter.html.trim();
305
306        wrapper = div.firstChild;
307
308        settings.parentElement.appendChild(wrapper);
309
310        playElement = wrapper.getElementsByClassName("talkify-play-button")[0] || noopElement;
311        pauseElement = wrapper.getElementsByClassName("talkify-pause-button")[0] || noopElement;
312        loader = wrapper.getElementsByClassName("talkify-audio-loading")[0] || noopElement;
313        erroroccurredElement = wrapper.getElementsByClassName("talkify-audio-error")[0] || noopElement;
314        rateElement = wrapper.querySelectorAll(".talkify-rate-button input[type=range]") || [];
315        volumeElement = wrapper.querySelectorAll(".talkify-volume-button input[type=range]") || [];
316        progressElement = wrapper.getElementsByTagName("progress")[0] || noopElement;
317        textHighlightingElement = wrapper.getElementsByClassName("talkify-cc-button")[0] || noopElement;      
318        durationElement = wrapper.getElementsByClassName("talkify-time-duration-element")[0] || noopElement;
319        elapsedElement = wrapper.getElementsByClassName("talkify-time-elapsed-element")[0] || noopElement;        
320        detatchedElement = wrapper.getElementsByClassName("talkify-detatch-handle")[0] || noopElement;
321        attachElement = wrapper.getElementsByClassName("talkify-attach-handle")[0] || noopElement;
322        voiceWrapperElement = wrapper.querySelector(".talkify-voice-selector") || noopElement;
323        dragArea = wrapper.getElementsByClassName("talkify-drag-area")[0] || noopElement;
324        textInteractionElement = wrapper.getElementsByClassName("talkify-text-interaction-button")[0] || noopElement;
325        pitchElementWrapper = wrapper.querySelector(".talkify-pitch-element") || noopElement;
326        pitchElement = pitchElementWrapper.getElementsByTagName("input")[0] || noopElement;
327        wordBreakElementWrapper = wrapper.querySelector(".talkify-wordbreak-element") || noopElement;
328        wordBreakElement = wordBreakElementWrapper.getElementsByTagName("input")[0] || noopElement;
329        flagElement = wrapper.getElementsByClassName("talkify-selected-voice-flag")[0] || noopElement;
330        phonationDropDown = wrapper.querySelector(".talkify-phonation-element") || noopElement;
331        phonationWhisperElement = wrapper.querySelector("#talkify-phonation-whisper") || noopElement;
332        phonationSoftElement = wrapper.querySelector("#talkify-phonation-soft") || noopElement;
333        phonationNormalElement = wrapper.querySelector("#talkify-phonation-normal") || noopElement;
334        nextItemElement = wrapper.getElementsByClassName("talkify-step-forward-button")[0] || noopElement;
335        previousItemElement = wrapper.getElementsByClassName("talkify-step-backward-button")[0] || noopElement;
336        voiceNameElement = document.querySelector(".talkify-voice-selector span") || noopElement;
337        enhancedVisibilityElement = document.querySelector(".talkify-enhanced-visibility-button") || noopElement;
338        downloadElement = document.querySelectorAll('.talkify-download-button') || [];
339        downloadLoadingElement = document.querySelectorAll('.talkify-download-loading') || [];
340        downloadErrorElement = document.querySelectorAll('.talkify-download-error') || [];
341
342        settings.parentElement.appendChild(wrapper);
343
344        talkify.messageHub.publish(correlationId + ".controlcenter.attached", wrapper.getBoundingClientRect());
345
346        hide(loader);
347        hide(downloadLoadingElement);
348        hide(downloadErrorElement);
349        addClass(downloadElement, "talkify-disabled");
350
351        if (!talkify.config.ui.audioControls.downloadEnabled) {
352            hide(downloadElement);
353
354            downloadElement = downloadErrorElement = downloadLoadingElement = noopElement;
355        }
356
357        pause();
358    }
359
360    function setupBindings() {
361        var controlCenter = document.getElementsByClassName("talkify-control-center")[0];
362
363        playElement.addEventListener("click", function () {
364            if (playElement.classList.contains("talkify-disabled")) {
365                return;
366            }
367
368            talkify.messageHub.publish(correlationId + ".controlcenter.request.play");
369        });
370
371        pauseElement.addEventListener("click", function () {
372            if (pauseElement.classList.contains("talkify-disabled")) {
373                return;
374            }
375            talkify.messageHub.publish(correlationId + ".controlcenter.request.pause");
376        });
377
378        rateElement.forEach(function (x) {
379            x.addEventListener("change", function () {
380                talkify.messageHub.publish(correlationId + ".controlcenter.request.rate", parseFloat(this.value));
381            })
382        });
383
384        volumeElement.forEach(function (x) {
385            x.addEventListener("change", function (e) {
386                talkify.messageHub.publish(correlationId + ".controlcenter.request.volume", parseInt(this.value));
387            });
388        });
389
390        pitchElement.addEventListener("change", function () {
391            talkify.messageHub.publish(correlationId + ".controlcenter.request.pitch", parseInt(this.value));
392        });
393
394        wordBreakElement.addEventListener("change", function () {
395            talkify.messageHub.publish(correlationId + ".controlcenter.request.wordbreak", parseInt(this.value));
396        });
397
398        phonationDropDown.addEventListener("change", function () {
399            if (this.value === "normal") {
400                talkify.messageHub.publish(correlationId + ".controlcenter.request.phonation.normal");
401            } else if (this.value === "soft") {
402                talkify.messageHub.publish(correlationId + ".controlcenter.request.phonation.soft");
403            } else if (this.value === "whisper") {
404                talkify.messageHub.publish(correlationId + ".controlcenter.request.phonation.whisper");
405            }
406        });
407
408        phonationWhisperElement.addEventListener("click", function () {
409            talkify.messageHub.publish(correlationId + ".controlcenter.request.phonation.whisper");
410        });
411
412        phonationSoftElement.addEventListener("click", function () {
413            talkify.messageHub.publish(correlationId + ".controlcenter.request.phonation.soft");
414        });
415
416        phonationNormalElement.addEventListener("click", function () {
417            talkify.messageHub.publish(correlationId + ".controlcenter.request.phonation.normal");
418        });
419
420        textHighlightingElement.addEventListener("click", function (e) {
421            if (textHighlightingElement.classList.contains("talkify-disabled")) {
422                removeClass(textHighlightingElement, "talkify-disabled");
423                talkify.messageHub.publish(correlationId + ".controlcenter.texthighlightoggled", true);
424            } else {
425                addClass(textHighlightingElement, "talkify-disabled");
426                talkify.messageHub.publish(correlationId + ".controlcenter.texthighlightoggled", false);
427            }
428        });
429
430        textInteractionElement.addEventListener("click", function (e) {
431            if (textInteractionElement.classList.contains("talkify-disabled")) {
432                talkify.messageHub.publish(correlationId + ".controlcenter.request.textinteractiontoggled", true);
433            } else {
434                talkify.messageHub.publish(correlationId + ".controlcenter.request.textinteractiontoggled", false);
435            }
436        });
437
438        enhancedVisibilityElement.addEventListener("click", function (e) {
439            if (enhancedVisibilityElement.classList.contains("talkify-disabled")) {
440                talkify.messageHub.publish(correlationId + ".controlcenter.request.enhancedvisibility", true);
441            } else {
442                talkify.messageHub.publish(correlationId + ".controlcenter.request.enhancedvisibility", false);
443            }
444        });
445
446        nextItemElement.addEventListener("click", function () {
447            if (nextItemElement.classList.contains("talkify-disabled")) {
448                return;
449            }
450
451            talkify.messageHub.publish(correlationId + ".controlcenter.request.playnext");
452        });
453
454        previousItemElement.addEventListener("click", function () {
455            if (previousItemElement.classList.contains("talkify-disabled")) {
456                return;
457            }
458
459            talkify.messageHub.publish(correlationId + ".controlcenter.request.playprevious");
460        });
461
462        progressElement.addEventListener("click", function (e) {
463            var clickedValue = (e.offsetX * this.max) / this.offsetWidth;
464
465            if (clickedValue > 1.0) {
466                clickedValue = 1.0;
467            }
468
469            if (clickedValue < 0.0) {
470                clickedValue = 0.0;
471            }
472
473            talkify.messageHub.publish(correlationId + ".controlcenter.request.seek", clickedValue);
474        });
475
476        attachElement.addEventListener("click", function () {
477            addClass(controlCenter, "attached");
478            removeClass(controlCenter, "detached");
479
480            talkify.messageHub.publish(correlationId + ".controlcenter.attached", controlCenter.getBoundingClientRect());
481        });
482
483        detatchedElement.addEventListener("click", function () {
484            removeClass(controlCenter, "attached");
485            addClass(controlCenter, "detached");
486
487            talkify.messageHub.publish(correlationId + ".controlcenter.detatched", controlCenter.getBoundingClientRect());
488        });
489
490        dragArea.addEventListener("mousedown", onMouseDown);
491        document.addEventListener("mouseup", onMouseUp);
492
493        if (talkify.config.ui.audioControls.downloadEnabled) {
494            downloadElement.forEach(function (x) {
495                x.addEventListener("click", function () {
496                    talkify.messageHub.publish(correlationId + ".controlcenter.request.download");
497                });
498            });
499        }
500
501        function onMouseUp(e) {
502            document.removeEventListener("mousemove", onMouseMove);
503        }
504
505        function onMouseDown(e) {
506            document.addEventListener("mousemove", onMouseMove);
507        }
508
509        function onMouseMove(e) {
510            e.preventDefault();
511
512            var dArea = dragArea.getBoundingClientRect();
513            var leftOffset = (dArea.width / 2) + dArea.x - controlCenter.getBoundingClientRect().x;
514
515            controlCenter.style.top = e.clientY - (controlCenter.offsetHeight / 2) + "px";
516            controlCenter.style.left = e.clientX - leftOffset + "px";
517        }
518    }
519
520    function initialize() {
521        if (settings.controlCenterName === "native") {
522            return;
523        }
524
525        render();
526        setupBindings();
527
528        talkify.messageHub.subscribe("controlcenter", [correlationId + ".player.*.pause", correlationId + ".player.*.disposed", correlationId + ".player.html5.ended"], pause);
529        talkify.messageHub.subscribe("controlcenter", [correlationId + ".player.*.play", correlationId + ".player.*.resume"], play);
530        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.*.disposed", dispose);
531        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.*.loading", function () {
532            hide(playElement);
533            hide(pauseElement);
534            hide(erroroccurredElement);
535            show(loader);
536        });
537
538        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.*.loaded", function () {
539            removeClass(pauseElement, "talkify-disabled");
540            removeClass(playElement, "talkify-disabled");
541            show(playElement);
542            hide(loader);
543        });
544
545        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.*.texthighlight.enabled", function () {
546            removeClass(textHighlightingElement, "talkify-disabled");
547        });
548
549        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.*.texthighlight.disabled", function () {
550            addClass(textHighlightingElement, "talkify-disabled");
551        });
552
553        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.*.ratechanged", function (rate) {
554            rateElement.forEach(function (x) {
555                x.value = rate;
556            });
557        });
558
559        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.*.voiceset", function (voice) {
560            featureToggle(voice);
561            setVoiceName(voice);
562        });
563
564        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.tts.error", onError);
565
566        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.tts.timeupdated", updateClock);
567        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.html5.timeupdated", function (value) {
568            progressElement.setAttribute("value", value);
569        });
570
571        talkify.messageHub.subscribe("controlcenter", correlationId + ".playlist.loaded", function () {
572            removeClass(playElement, "talkify-disabled");
573            removeClass(downloadElement, "talkify-disabled");
574        });
575
576        talkify.messageHub.subscribe("controlcenter", correlationId + ".playlist.textinteraction.enabled", function () {
577            removeClass(textInteractionElement, "talkify-disabled");
578        });
579
580        talkify.messageHub.subscribe("controlcenter", correlationId + ".playlist.textinteraction.disabled", function () {
581            addClass(textInteractionElement, "talkify-disabled");
582        });
583
584        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.tts.download.started", function () {
585            hide(downloadElement);
586            show(downloadLoadingElement);
587        });
588
589        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.tts.download.completed", function () {
590            show(downloadElement);
591            hide(downloadLoadingElement);
592        });
593
594        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.tts.download.error", function (obj) {
595            hide(downloadLoadingElement);
596            show(downloadErrorElement);
597
598            downloadErrorElement.title = obj.statusText;
599        });
600
601        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.tts.wordbreakchanged", function (ms) {
602            wordBreakElement.value = ms;
603        });
604
605        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.*.pitchchanged", function (value) {
606            pitchElement.value = value;
607        });
608
609        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.tts.phonationchanged", function (phonation) {
610            phonationDropDown.value = phonation === "soft" ? "soft" : "normal";
611        });
612
613        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.tts.whisperchanged", function (whisper) {
614            phonationDropDown.value = whisper ? "whisper" : "normal";
615        });
616
617        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.tts.created", function () {
618            getRemoteVoices();
619        });
620
621        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.html5.created", function () {
622            if (!talkify.config.ui.audioControls.voicepicker.enabled) {
623                return;
624            }
625
626            getLocalVoices();
627        });
628
629        talkify.messageHub.subscribe("controlcenter", correlationId + ".player.*.enhancedvisibilityset", function (value) {
630            if (value) {
631                removeClass(enhancedVisibilityElement, "talkify-disabled");
632            } else {
633                addClass(enhancedVisibilityElement, "talkify-disabled");
634            }
635        });
636
637
638        talkify.messageHub.subscribe("controlcenter", correlationId + ".playlist.playing", function (msg) {
639            removeClass(nextItemElement, "talkify-disabled");
640            removeClass(previousItemElement, "talkify-disabled");
641
642            if (msg.isLast) {
643                addClass(nextItemElement, "talkify-disabled");
644            }
645
646            if (msg.isFirst) {
647                addClass(previousItemElement, "talkify-disabled");
648            }
649        });
650
651
652    };
653
654    function getLocalVoices() {
655        window.speechSynthesis.getVoices();
656        setTimeout(function () {
657            voices = window.speechSynthesis.getVoices();
658            voicePicker = createVoicePicker(filterVoicesByConfig(voices), false);
659
660            wrapper.getElementsByClassName("talkify-voice-selector")[0].appendChild(voicePicker);
661
662            voicePicker.querySelectorAll(".language > li").forEach(function (item) {
663                item.addEventListener('click', function (e) {
664                    var voice = voices.find(function (v) { return v.name === e.currentTarget.dataset.voice });
665
666                    talkify.messageHub.publish(correlationId + ".controlcenter.request.setvoice", voice);
667                });
668            });
669
670            if (voiceNameElement.textContent) {
671                var backendVoice = voices.find(function (v) {
672                    return v.name === voiceNameElement.textContent;
673                });
674
675                if (backendVoice) {
676                    featureToggle(backendVoice);
677                    setVoiceName(backendVoice);
678                }
679            }
680        }, 100);
681    }
682
683    function getRemoteVoices() {
684        talkify.http.get(talkify.config.remoteService.speechBaseUrl + "/voices")
685            .then(function (data) {
686                voices = window.talkify.toLowerCaseKeys(data);
687
688                if (voiceNameElement.textContent) {
689                    var backendVoice = voices.find(function (v) {
690                        return v.name === voiceNameElement.textContent;
691                    });
692
693                    if (backendVoice) {
694                        featureToggle(backendVoice);
695                        setVoiceName(backendVoice);
696                    }
697                }
698
699                if (!talkify.config.ui.audioControls.voicepicker.enabled) {
700                    return;
701                }
702
703                voicePicker = createVoicePicker(filterVoicesByConfig(voices), true);
704
705                wrapper.getElementsByClassName("talkify-voice-selector")[0].appendChild(voicePicker);
706
707                voicePicker.querySelectorAll(".language > li").forEach(function (item) {
708                    item.addEventListener('click', function (e) {
709                        var voice = JSON.parse(e.currentTarget.dataset.voice);
710
711                        talkify.messageHub.publish(correlationId + ".controlcenter.request.setvoice", window.talkify.toLowerCaseKeys(voice));
712                    });
713                });
714            });
715    }
716
717    function filterVoicesByConfig(voices) {
718        var filter = talkify.config.ui.audioControls.voicepicker.filter;
719
720        if (!filter) {
721            return voices;
722        }
723
724        return voices.filter(function (voice) {
725            var active = true;
726
727            if (filter.byCulture.length) {
728                active = filter.byCulture.indexOf(voice.culture || voice.lang) !== -1;
729            }
730
731            if (active && filter.byLanguage.length) {
732                active = filter.byLanguage.indexOf(voice.language) !== -1;
733            }
734
735            if (active && filter.byClass.length) {
736                if (filter.byClass.indexOf("Standard") !== -1 && voice.isStandard) {
737                    return true;
738                }
739
740                if (filter.byClass.indexOf("Premium") !== -1 && voice.isPremium) {
741                    return true;
742                }
743
744                if (filter.byClass.indexOf("Exclusive") !== -1 && voice.isExclusive) {
745                    return true;
746                }
747
748                if (filter.byClass.indexOf("Neural") !== -1 && voice.isNeural) {
749                    return true;
750                }
751
752                return false;
753            }
754
755            return active;
756        });
757    }
758
759    function updateClock(timeInfo) {
760        var currentTime = timeInfo.currentTime;
761        var duration = timeInfo.duration;
762        //TODO: Over tunnels duration === NaN. Look @ http://stackoverflow.com/questions/10868249/html5-audio-player-duration-showing-nan
763        progressElement.setAttribute("value", currentTime / duration);
764
765        if (!elapsedElement || !durationElement) {
766            return;
767        }
768
769        var currentminutes = Math.floor(currentTime / 60);
770        var currentseconds = Math.round(currentTime) - (currentminutes * 60);
771
772        var totalminutes = !!duration ? Math.floor(duration / 60) : 0;
773        var totalseconds = !!duration ? Math.round(duration) - (totalminutes * 60) : 0;
774
775        elapsedElement.textContent = ((currentminutes < 10) ? "0" + currentminutes : currentminutes) + ":" + ((currentseconds < 10) ? "0" + currentseconds : currentseconds);
776        durationElement.textContent = ((totalminutes < 10) ? "0" + totalminutes : totalminutes) + ":" + ((totalseconds < 10) ? "0" + totalseconds : totalseconds);
777    }
778
779    function isTalkifyHostedVoice(voice) {
780        return voice && voice.constructor.name !== "SpeechSynthesisVoice";
781    }
782
783    function featureToggle(voice) {
784        show(progressElement);
785        show(textHighlightingElement);
786
787        if (!voice) {
788            return;
789        }
790
791        var backendVoice = voices.find(function (v) {
792            return v.name === voice.name;
793        });
794
795        if (backendVoice) {
796            voice = backendVoice;
797        }
798
799        voice.canUseWordBreak ? show(wordBreakElementWrapper) : hide(wordBreakElementWrapper);
800
801        voice.canWhisper ? show(phonationWhisperElement) : hide(phonationWhisperElement);
802        voice.canSpeakSoftly ? show(phonationSoftElement) : hide(phonationSoftElement);
803
804        if (voice.canSpeakSoftly) {
805
806        }
807
808        if (isTalkifyHostedVoice(voice)) {
809            voice.canUsePitch ? show(pitchElementWrapper) : hide(pitchElementWrapper);
810            return;
811        }
812
813        show(pitchElementWrapper);
814        hide(elapsedElement);
815        hide(durationElement);        
816
817        if (!voice.localService) {
818            hide(progressElement);
819            hide(textHighlightingElement);
820        }
821    }
822
823    function setVoiceName(voice) {
824        if (!voice) {
825            voiceNameElement.textContent = "Automatic voice detection";
826            return;
827        }
828
829        var backendVoice = voices.find(function (v) {
830            return v.name === voice.name;
831        });
832
833        if (backendVoice) {
834            voice = backendVoice;
835        }
836
837        var split = (voice.culture || voice.lang || "").split("-");
838
839        if (split.length > 1) {
840            flagElement.src = "https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.3.0/flags/4x3/" + split[1].toLowerCase() + ".svg";
841            show(flagElement);
842        } else {
843            hide(flagElement);
844        }
845
846        voiceNameElement.textContent = voice.name;
847    }
848
849    function dispose() {
850        var existingControl = document.getElementsByClassName("talkify-control-center")[0];
851
852        if (existingControl) {
853            existingControl.parentNode.removeChild(existingControl);
854        }
855
856        talkify.messageHub.unsubscribe("controlcenter", [correlationId + ".player.*.pause", correlationId + ".player.*.disposed"]);
857        talkify.messageHub.unsubscribe("controlcenter", [correlationId + ".player.*.play", correlationId + ".player.*.resume"]);
858        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.*.disposed");
859        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.*.loaded");
860        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.*.loading");
861        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.*.texthighlight.enabled");
862        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.*.texthighlight.disabled");
863        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.*.ratechanged");
864        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.*.voiceset");
865        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.tts.timeupdated");
866        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.html5.timeupdated");
867        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".playlist.loaded");
868        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.tts.error");
869        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".playlist.textinteraction.enabled");
870        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".playlist.textinteraction.disabled");
871        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.tts.wordbreakchanged");
872        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.*.pitchchanged");
873        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.tts.phonationchanged");
874        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.tts.whisperchanged");
875        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".playlist.playing");
876        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.html5.ended");
877        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.tts.created");
878        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.html5.created");
879        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.*.enhancedvisibilityset");
880        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.tts.download.error");
881        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.tts.download.started");
882        talkify.messageHub.unsubscribe("controlcenter", correlationId + ".player.tts.download.completed");
883    }
884
885    initialize();
886
887    return {
888        setMaxRate: function (value) {
889            rateElement.forEach(function (x) {
890                x.setAttribute("max", value);
891            });
892
893            return this;
894        },
895        setMinRate: function (value) {
896            rateElement.forEach(function (x) {
897                x.setAttribute("min", value);
898            });
899
900            return this;
901        },
902        setRateStep: function (value) {
903            rateElement.forEach(function (x) {
904                x.setAttribute("step", value);
905            });
906
907            return this;
908        },
909        setMaxPitch: function (value) {
910            pitchElement.setAttribute("max", value);
911
912            return this;
913        },
914        setMinPitch: function (value) {
915            pitchElement.setAttribute("min", value);
916
917            return this;
918        },
919        setPitchStep: function (value) {
920            pitchElement.setAttribute("step", value);
921
922            return this;
923        },
924        dispose: dispose
925    }
926}
927},{}],4:[function(require,module,exports){
928talkify = talkify || {};
929talkify.controlcenters = talkify.controlcenters || {};
930
931talkify.controlcenters.local = function () {
932    this.html =
933        '<div class="talkify-control-center local">\
934            <div>\
935                <div class="talkify-brand">T</i></div>\
936                <button class="talkify-step-backward-button" title="Previous">\
937                    <i class="fa fa-step-backward"></i>\
938                </button>\
939                <button class="talkify-play-button" title="Listen to text">\
940                    <i class="fas fa-play"></i>\
941                </button>\
942                <div class="talkify-audio-loading">\
943                    <i class="fas fa-dharmachakra fa-spin"></i>\
944                </div>\
945                <button class="talkify-pause-button" title="Pause">\
946                    <i class="fas fa-pause"></i>\
947                </button>\
948                <button class="talkify-audio-error" title="An error occurred at playback"><i class="fas fa-exclamation-triangle"></i></button>\
949                <button class="talkify-step-forward-button" title="Next">\
950                    <i class="fa fa-step-forward"></i>\
951                </button>\
952                <button class="talkify-cc-button" title="Toggle text highlighting">\
953                    <i class="fa fa-closed-captioning"></i>\
954                </button>\
955                <button class="talkify-text-interaction-button" title="Toggle text interaction">\
956                    <i class="fas fa-hand-point-up"></i>\
957                </button>\
958                <button class="talkify-enhanced-visibility-button" title="Toggle enhanced visibility">\
959                    <i class="fas fa-eye"></i>\
960                </button>\
961                <button class="talkify-download-button" title="Download">\
962                    <i class="fas fa-download"></i>\
963                </button>\
964                <div class="talkify-download-loading">\
965                    <i class="fas fa-dharmachakra fa-spin"></i>\
966                </div>\
967                <div class="talkify-download-error">\
968                    <i class="fas fa-exclamation-triangle"></i>\
969                </div>\
970                <button title="More settings">\
971                    <label for="talkify-local-settings"><i class="fas fa-cog"></i></label>\
972                </button>\
973            </div>\
974            <input type="checkbox" style="display:none;" id="talkify-local-settings"/>\
975            <div class="talkify-columns talkify-more-settings">\
976                <div title="Adjust playback volume">\
977                    <i class="fas fa-volume-up"></i>\
978                    <div class="volume-slider talkify-volume-button">\
979                        <input type="range" value="10" min="0" max="10">\
980                    </div>\
981                </div>\
982                <div title="Adjust playback rate">\
983                    <i class="fa fa-tachometer-alt"></i>\
984                    <div class="rate-slider talkify-rate-button">\
985                        <input type="range" value="5" min="0" max="10">\
986                    </div>\
987                </div>\
988            </div>\
989        </div>';
990};
991},{}],5:[function(require,module,exports){
992talkify = talkify || {};
993talkify.controlcenters = talkify.controlcenters || {};
994
995talkify.controlcenters.modern = function (parent, correlationId) {
996
997    this.html = '<div class="talkify-control-center modern attached"> \
998    <progress value="0" max="1.0"></progress>\
999    <ul class="player-controls">\
1000        <li style="display: flex; justify-content: center;">\
1001            <button data-type="drag" class="talkify-detached talkify-drag-area" title="Drag player">\
1002                <i class="fa fa-grip-horizontal"></i>\
1003            </button>\
1004            <button class="talkify-attached talkify-detatch-handle" title="Detatch player">\
1005                <i class="fa fa-window-maximize"></i>\
1006            </button>\
1007            <div class="player-settings">\
1008                <button>\
1009                    <label for="player-settings-toggle" title="More settings">\
1010                        <i class="fas fa-ellipsis-h"></i>\
1011                    </label>\
1012                </button>\
1013                <input type="checkbox" id="player-settings-toggle" style="display: none;" />\
1014                <ul>\
1015                    <li>\
1016                        <button class="modern-talkify-control-center-accent" title="Playback mode">\
1017                            <i class="fas fa-music"></i>\
1018                        </button>\
1019                        <select class="talkify-phonation-element">\
1020                            <option id="talkify-phonation-normal" value="normal">Normal tone</option>\
1021                            <option id="talkify-phonation-soft" value="soft">Softer tone</option>\
1022                            <option id="talkify-phonation-whisper" value="whisper">Whisper</option>\
1023                        </select>\
1024                    </li>\
1025                    <li class="talkify-wordbreak-element">\
1026                        <button class="modern-talkify-control-center-accent" title="Word break">\
1027                            <i class="fas fa-arrows-alt-h"></i>\
1028                        </button>\
1029                        <div>\
1030                            <input type="range" value="0" min="0" max="3000" title="Adjust pause between words">\
1031                        </div>\
1032                    </li>\
1033                    <li class="talkify-pitch-element">\
1034                        <button class="modern-talkify-control-center-accent" title="Adjust pitch">\
1035                            <i class="fas fa-arrows-alt-h pitch-icon"></i>\
1036                        </button>\
1037                        <div>\
1038                            <input type="range" value="0" min="-7" max="7" title="Adjust pitch">\
1039                        </div>\
1040                    </li>\
1041                    <li class="talkify-cc-button">\
1042                        <button class="modern-talkify-control-center-accent"\
1043                            title="Toggle text highlighting">\
1044                            <i class="fa fa-closed-captioning"></i>\
1045                        </button>\
1046                        <div>Text highlighting</div>\
1047                    </li>\
1048                    <li class="talkify-text-interaction-button">\
1049                        <button class="modern-talkify-control-center-accent" title="Toggle text interaction">\
1050                            <i class="fas fa-hand-point-up"></i>\
1051                        </button>\
1052                        <div>Text interaction</div>\
1053                    </li>\
1054                    <li class="talkify-enhanced-visibility-button">\
1055                        <button class="modern-talkify-control-center-accent" title="Toggle enhanced visibility">\
1056                            <i class="fas fa-eye"></i>\
1057                        </button>\
1058                        <div>Enhanced visibility</div>\
1059                    </li>\
1060                    <li class="talkify-detached talkify-rate-button">\
1061                        <button class="modern-talkify-control-center-accent" title="Adjust playback rate">\
1062                            <i class="fas fa-tachometer-alt"></i>\
1063                        </button>\
1064                        <div>\
1065                            <input type="range" value="5" min="0" max="10" title="Adjust playback rate">\
1066                        </div>\
1067                    </li>\
1068                    <li class="talkify-detached talkify-volume-button">\
1069                        <button class="modern-talkify-control-center-accent" title="Adjust playback volume">\
1070                            <i class="fas fa-volume-up"></i>\
1071                        </button>\
1072                        <div class="detached-volume-slider">\
1073                            <input type="range" value="10" min="0" max="10" title="Adjust playback volume">\
1074                        </div>\
1075                    </li>\
1076                    <li class="talkify-download-button mobile">\
1077                        <button title="Download" class="modern-talkify-control-center-accent">\
1078                            <i class="fas fa-download"></i>\
1079                        </button>\
1080                        <div>Download</div>\
1081                    </li>\
1082                    <li class="talkify-detached talkify-attach-handle">\
1083                        <button class="modern-talkify-control-center-accent" title="Attach player">\
1084                            <i class="fa fa-window-minimize"></i>\
1085                        </button>\
1086                        <div>Dock player</div>\
1087                    </li>\
1088                </ul>\
1089            </div>\
1090        </li>\
1091        <li class="talkify-playback-controls">\
1092            <button class="talkify-rate-button talkify-attached" title="Rate of speech">\
1093                <i class="fa fa-tachometer-alt"></i>\
1094                <div class="rate-slider">\
1095                    <input type="range" value="5" min="0" max="10" title="Adjust playback rate">\
1096                </div>\
1097            </button>\
1098            <button class="talkify-step-backward-button" title="Previous">\
1099                <i class="fa fa-step-backward"></i>\
1100            </button>\
1101            <button class="talkify-play-button" title="Play">\
1102                <i class="fa fa-play"></i>\
1103            </button>\
1104            <button class="talkify-audio-loading"></button>\
1105            <button class="talkify-pause-button" title="Pause">\
1106                    <i class="fa fa-pause"></i>\
1107            </button>\
1108            <button class="talkify-audio-error" title="An error occurred at playback"><i class="fas fa-exclamation-triangle"></i></button>\
1109            <button class="talkify-step-forward-button" title="Next">\
1110                <i class="fa fa-step-forward"></i>\
1111            </button>\
1112            <button class="talkify-volume-button talkify-attached" title=" Volume">\
1113                <i class="fa fa-volume-up"></i>\
1114                <div class="volume-slider">\
1115                    <input type="range" value="10" min="0" max="10" title="Adjust playback volume">\
1116                </div>\
1117            </button>\
1118            <button class="talkify-download-button desktop" title="Download">\
1119                    <i class="fas fa-download"></i>\
1120            </button>\
1121            <div class="talkify-download-loading desktop">\
1122                <i class="fas fa-dharmachakra fa-spin"></i>\
1123            </div>\
1124            <div class="talkify-download-error desktop">\
1125                <i class="fas fa-exclamation-triangle"></i>\
1126            </div>\
1127        <li class="talkify-attached">\
1128            <div class="talkify-voice-selector ">\
1129                <label for="voice-selector-toggle">\
1130                <img class="talkify-selected-voice-flag talkify-flag talkify-hidden" src="">\
1131                <span></span>\
1132                </label><input type="checkbox" id="voice-selector-toggle" style="display: none;" />\
1133            </div>\
1134        </li>\
1135    </ul>\
1136</div>';
1137}
1138},{}],6:[function(require,module,exports){
1139talkify = talkify || {};
1140
1141talkify.tableReader = function () {
1142    function addTables(config) {
1143        var mapped = config.map(function (cfg) {
1144            var tablesToRead = [];
1145
1146            if ((typeof cfg.table) === "string") {
1147                tablesToRead = document.querySelectorAll(cfg.table);
1148            } else {
1149                tablesToRead = cfg.table;
1150            }
1151
1152            cfg.tables = Array.prototype.slice.call(tablesToRead);
1153
1154            return cfg;
1155        });
1156
1157        markTables(mapped);
1158    }
1159
1160    function getElements(obj) {
1161        if (!obj) {
1162            return null;
1163        }
1164
1165        if ((typeof obj) === "string") {
1166            return Array.from(document.querySelectorAll(obj));
1167        } else {
1168            return obj;
1169        }
1170    }
1171
1172    function markTables(configurations) {
1173        for (var j = 0; j < configurations.length; j++) {
1174            var config = configurations[j];
1175
1176            for (var k = 0; k < config.tables.length; k++) {
1177                var table = config.tables[k];
1178
1179                table.classList.add("talkify-tts-table");
1180
1181                var headerCells = getElements(config.headerCells) || table.querySelectorAll('th');
1182                var bodyCells = getElements(config.bodyCells) || table.querySelectorAll('td');
1183
1184                var cols = headerCells.length;
1185                var currentHeader = 0;
1186
1187                for (var i = 0; i < bodyCells.length; i++) {
1188                    if (i % cols === 0) {
1189                        currentHeader = 0;
1190                    }
1191
1192                    if(!bodyCells[i].innerText.trim()){
1193                        currentHeader++;
1194                        continue;
1195                    }
1196
1197                    var headerCell = headerCells[currentHeader];
1198
1199                    if (headerCell.innerText.trim()) {
1200                        bodyCells[i].setAttribute("data-talkify-prefix", headerCell.innerText);
1201                    }
1202
1203                    bodyCells[i].classList.add("talkify-tts-tablecell");
1204
1205                    currentHeader++;
1206                }
1207            }
1208        }
1209    }
1210
1211    return {
1212        markTables: addTables
1213    }
1214}();
1215},{}],7:[function(require,module,exports){
1216talkify = talkify || {};
1217talkify.http = (function ajax() {
1218    var get = function (url) {
1219        var keypart = (url.indexOf('?') !== -1 ? "&key=" : "?key=") + talkify.config.remoteService.apiKey;
1220
1221        return fetch(window.talkify.config.remoteService.host + url + keypart)
1222            .then(function (res) {
1223                return res.json();
1224            });
1225    };
1226
1227    return {
1228        get: get
1229    };
1230})();
1231},{}],8:[function(require,module,exports){
1232talkify = talkify || {};
1233
1234talkify.autoScroll = function () {
1235    function activate() {
1236        talkify.messageHub.unsubscribe("autoscroll", "*.player.*.loaded");
1237
1238        talkify.messageHub.subscribe("autoscroll", "*.player.*.loaded", function (item) {
1239            var y = item.element.getBoundingClientRect().top + window.pageYOffset + talkify.config.autoScroll.offsetpx;
1240
1241            window.scrollTo({ top: y, behavior: 'smooth' });
1242        });
1243    }
1244
1245    return {
1246        activate: activate
1247    }
1248}();
1249
1250},{}],9:[function(require,module,exports){
1251talkify = talkify || {};
1252talkify.config = {
1253    debug: false,
1254    useSsml: false,
1255    maximumTextLength: 5000,
1256    ui:
1257    {
1258        audioControls: {
1259            enabled: true,
1260            controlcenter: "native", //["classic", "modern", "local", "native"]
1261            container: document.body,
1262            downloadEnabled: true,
1263            voicepicker: {
1264                enabled: true, //where applicable
1265                filter: {
1266                    byClass: [], //example: ["Standard", "Premium", "Exclusive"]
1267                    byCulture: [], //example: ["en-EN", "en-AU"]
1268                    byLanguage: [] //example: ["English", "Spanish"]
1269                }
1270            }
1271        }
1272    },
1273    formReader: {
1274        voice: null,
1275        rate: 0,
1276        remoteService: true,
1277        requiredText: "This field is required",
1278        valueText: "You have entered {value} as: {label}.",
1279        selectedText: "You have selected {label}.",
1280        notSelectedText: "{label} is not selected."
1281    },
1282    remoteService: {
1283        active: true,
1284        host: 'https://talkify.net',
1285        apiKey: '',
1286        speechBaseUrl: '/api/speech/v2',
1287        languageBaseUrl: '/api/language/v1'
1288    },
1289    keyboardCommands: {
1290        enabled: false,
1291        commands: {
1292            playPause: 32,
1293            next: 39,
1294            previous: 37
1295        }
1296    },
1297    voiceCommands: {
1298        enabled: false,
1299        keyboardActivation: {
1300            enabled: true,
1301            key: 77
1302        },
1303        commands: {
1304            playPause: ["play", "pause", "stop", "start"],
1305            next: ["play next", "next"],
1306            previous: ["play previous", "previous", "back", "go back"]
1307        }
1308    },
1309    autoScroll: {
1310        offsetpx: 100
1311    }
1312}
1313},{}],10:[function(require,module,exports){
1314talkify = talkify || {};
1315
1316talkify.formReader = function () {
1317    var player;
1318    var timeout;
1319
1320    function setupForm(formElement) {
1321        var elements = formElement.elements;
1322
1323        for (var i = 0; i < elements.length; i++) {
1324            elements[i].addEventListener("focus", onFocus);
1325        }
1326    }
1327
1328    function removeForm(formElement) {
1329        var elements = formElement.elements;
1330
1331        for (var i = 0; i < elements.length; i++) {
1332            elements[i].removeEventListener("focus", onFocus);
1333        }
1334    }
1335
1336    function onFocus(e) {
1337        if (timeout) {
1338            clearTimeout(timeout);
1339        }
1340
1341        var me = this;
1342
1343        timeout = setTimeout(function () {
1344            if (!player) {
1345                player = talkify.config.formReader.remoteService ? new talkify.TtsPlayer() : new talkify.Html5Player();
1346            }
1347
1348            var config = talkify.config.formReader;
1349
1350            if (config.voice) {
1351                player.forceVoice({ name: config.voice });
1352            }
1353
1354            player.setRate(config.rate);
1355
1356            if (me.type === "button" || me.type === "submit") {
1357                player.playText(me.value || me.innerText);
1358                return;
1359            }
1360
1361            var requiredText = me.attributes.required ? config.requiredText : "";
1362
1363            var label = findLabelFor(me);
1364
1365            var text = getTextForCheckboxes(me, label) || getTextForSelects(me, label) || getTextForInputs(me, label) || "";
1366
1367            player.playText(text + ". " + requiredText);
1368        }, 100);
1369    }
1370
1371    function getTextForCheckboxes(element, label) {
1372        var config = talkify.config.formReader;
1373
1374        if (element.type === "checkbox") {
1375            var labelText = label ? label.innerText : "checkbox";
1376
1377            if (element.checked) {
1378                return config.selectedText.replace("{label}", labelText);
1379            } else {
1380                return config.notSelectedText.replace("{label}", labelText);
1381            }
1382        }
1383
1384        return null;
1385    }
1386
1387    function getTextForSelects(element, label) {
1388        var config = talkify.config.formReader;
1389
1390        if (element.tagName.toLowerCase() === "select") {
1391            var labelText = label ? label.innerText : "option";
1392
1393            var value = element.options[element.options.selectedIndex].text;
1394
1395            return config.valueText.replace("{value}", value).replace("{label}", labelText);
1396        }
1397
1398        return null;
1399    }
1400
1401    function getTextForInputs(element, label) {
1402        var config = talkify.config.formReader;
1403
1404        if (!label) {
1405            return element.value;
1406        }
1407
1408        if (element.value) {
1409            return config.valueText.replace("{value}", element.value).replace("{label}", label.innerText);
1410        } else {
1411            return label.innerText + ".";
1412        }
1413    }
1414
1415    function findLabelFor(input) {
1416        var labels = document.getElementsByTagName('label');
1417        for (var i = 0; i < labels.length; i++) {
1418            if (labels[i].htmlFor === input.id) {
1419                return labels[i];
1420            }
1421        }
1422
1423        return null;
1424    }
1425
1426    return {
1427        addForm: function (formElement) {
1428            setupForm(formElement);
1429        },
1430        removeForm: function(formElement) {
1431            removeForm(formElement);
1432        }
1433    };
1434}();
1435},{}],11:[function(require,module,exports){
1436//TODO: Verify all events. Especially for this player. Trigger play, pause, stop and add console outputs and see what happens
1437talkify = talkify || {};
1438
1439talkify.Html5Player = function () {
1440    this.isStopped = false;
1441    this.volume = 1;
1442    this.pitch = 1;
1443
1444    this.currentContext = {
1445        item: null,
1446        utterances: [],
1447        currentUtterance: null
1448    };
1449
1450    var timeupdater;
1451
1452    var me = this;
1453
1454    this.playbar = {
1455        instance: null
1456    };
1457
1458    this.audioSource = {
1459        play: function () {
1460            if (me.currentContext.item) {
1461                playCurrentContext();
1462            } else {
1463                talkify.messageHub.publish(me.correlationId + ".player.html5.unplayable");
1464            }
1465        },
1466        pause: function () {
1467            window.speechSynthesis.pause();
1468
1469            talkify.messageHub.publish(me.correlationId + ".player.html5.pause");
1470        },
1471        ended: function () { return !window.speechSynthesis.speaking; },
1472        isPlaying: function () { return window.speechSynthesis.speaking; },
1473        paused: function () { return !window.speechSynthesis.speaking; },
1474        currentTime: function () { return 0; },
1475        cancel: function (asPause) {
1476            if (asPause) {
1477                stop();
1478            } else {
1479                window.speechSynthesis.cancel();
1480            }
1481        },
1482        stop: function () {
1483            stop();
1484        },
1485        dispose: function () {
1486            talkify.messageHub.unsubscribe("html5player", me.correlationId + ".controlcenter.request.play");
1487            talkify.messageHub.unsubscribe("html5player", me.correlationId + ".controlcenter.request.pause");
1488            talkify.messageHub.unsubscribe("html5player", me.correlationId + ".controlcenter.request.volume");
1489            talkify.messageHub.unsubscribe("html5player", me.correlationId + ".controlcenter.request.rate");
1490            talkify.messageHub.unsubscribe("html5player", me.correlationId + ".controlcenter.request.pitch");
1491
1492            if (timeupdater) {
1493                clearInterval(timeupdater);
1494            }
1495        }
1496    };
1497
1498    talkify.BasePlayer.call(this, this.audioSource, this.playbar);
1499
1500    this.playAudio = function (item) {
1501        me.currentContext.item = item;
1502        me.currentContext.utterances = [];
1503        me.currentContext.currentUtterance = null;
1504
1505        playCurrentContext();
1506    };
1507
1508    this.setVolume = function (volume) {
1509        me.volume = volume;
1510
1511        return this;
1512    };
1513
1514    this.usePitch = function (pitch) {
1515        me.pitch = pitch;
1516
1517        talkify.messageHub.publish(me.correlationId + ".player.html5.pitchchanged", pitch);
1518
1519        return this;
1520    };
1521
1522    talkify.messageHub.subscribe("html5player", me.correlationId + ".controlcenter.request.play", function () { me.audioSource.play(); });
1523    talkify.messageHub.subscribe("html5player", me.correlationId + ".controlcenter.request.pause", function () { me.pause(); });
1524    talkify.messageHub.subscribe("html5player", me.correlationId + ".controlcenter.request.volume", function (volume) { me.volume = volume / 10; });
1525    
1526    talkify.messageHub.subscribe("html5player", me.correlationId + ".controlcenter.request.rate", function (rate) {
1527        me.settings.rate = rate;
1528        talkify.messageHub.publish(me.correlationId + ".player.html5.ratechanged", rate);
1529    });
1530
1531    talkify.messageHub.subscribe("html5player", me.correlationId + ".controlcenter.request.pitch", function (pitch) {
1532        me.pitch = pitch;
1533
1534        talkify.messageHub.publish(me.correlationId + ".player.html5.pitchchanged", me.pitch);
1535    });
1536
1537    function playCurrentContext() {
1538        if (timeupdater) {
1539            clearInterval(timeupdater);
1540        }
1541
1542        var item = me.currentContext.item;
1543
1544        var chuncks = chunckText(item.text);
1545
1546        me.currentContext.utterances = [];
1547        me.isStopped = false;
1548
1549        chuncks.forEach(function (chunck) {
1550            var utterance = new window.SpeechSynthesisUtterance();
1551
1552            utterance.text = chunck;
1553            utterance.lang = me.settings.lockedLanguage || me.settings.referenceLanguage.Culture;
1554            utterance.rate = me.settings.rate;
1555            utterance.volume = me.volume;
1556            utterance.pitch = me.pitch;
1557
1558            me.currentContext.utterances.push(utterance);
1559        });
1560
1561        var wordIndex = 0;
1562        var previousCharIndex = 0;
1563        var words = extractWords(item.text);
1564
1565        me.currentContext.utterances[me.currentContext.utterances.length - 1].onend = function (e) {
1566            talkify.messageHub.publish(me.correlationId + ".player.html5.utterancecomplete", item);
1567
1568            if (timeupdater) {
1569                clearInterval(timeupdater);
1570            }
1571
1572            if (!me.currentContext.currentUtterance) {
1573                return;
1574            }
1575
1576            if (me.currentContext.currentUtterance.text !== e.currentTarget.text) {
1577                return;
1578            }
1579
1580            if (!me.isStopped) {
1581                talkify.messageHub.publish(me.correlationId + ".player.html5.ended", item);
1582            }
1583        };
1584
1585        me.currentContext.utterances.forEach(function (u, index) {
1586            if (index === 0) {
1587                u.onstart = function (e) {
1588                    me.currentContext.currentUtterance = e.utterance;
1589                    talkify.messageHub.publish(me.correlationId + ".player.html5.loaded", me.currentContext.item);
1590                    talkify.messageHub.publish(me.correlationId + ".player.html5.play", { item: me.currentContext.item, positions: [], currentTime: 0 });
1591
1592                    if (timeupdater) {
1593                        clearInterval(timeupdater);
1594                    }
1595
1596                    timeupdater = setInterval(function () {
1597                        talkify.messageHub.publish(me.correlationId + ".player.html5.timeupdated", (wordIndex + 1) / words.length);
1598                    }, 100);
1599                };
1600            } else {
1601                u.onstart = function (e) {
1602                    me.currentContext.currentUtterance = e.utterance;
1603                };
1604            }
1605
1606            u.onpause = function () {
1607                if (timeupdater) {
1608                    clearInterval(timeupdater);
1609                }
1610
1611                talkify.messageHub.publish(me.correlationId + ".player.html5.pause");
1612            };
1613
1614            u.onresume = function () { };
1615
1616            u.onboundary = function (e) {
1617                if (e.name !== "word" || !words[wordIndex]) {
1618                    return;
1619                }
1620
1621                if (!me.settings.useTextHighlight || !u.voice.localService) {
1622                    return;
1623                }
1624
1625                var fromIndex = 0;
1626
1627                for (var k = 0; k < wordIndex; k++) {
1628                    fromIndex += words[k].length + 1;
1629                }
1630
1631                var isCommaOrSimilair = previousCharIndex + 1 === e.charIndex;
1632
1633                if (isCommaOrSimilair) {
1634                    previousCharIndex = e.charIndex;
1635                    return;
1636                }
1637
1638                me.wordHighlighter.highlight(item, words[wordIndex], fromIndex);
1639                wordIndex++;
1640                previousCharIndex = e.charIndex;
1641            };
1642
1643            getVoice().then(function (voice) {
1644                if (words.length && me.settings.useTextHighlight && voice.localService) {
1645                    me.wordHighlighter.highlight(item, words[0], 0);
1646                }
1647
1648                u.voice = voice;
1649
1650                console.log(u); //Keep this, speech bugs out otherwise
1651
1652                window.speechSynthesis.cancel();
1653
1654                talkify.messageHub.publish(me.correlationId + ".player.html5.voiceset", voice);
1655
1656                window.setTimeout(function () {
1657                    window.speechSynthesis.speak(u);
1658                }, 100);
1659            });
1660        });
1661    };
1662
1663    function chunckText(text) {
1664        var language = me.settings.lockedLanguage || me.settings.referenceLanguage.Culture;
1665        var chunckSize = language.indexOf('zh-') > -1 ? 70 :
1666            language.indexOf('ko-') > -1 ? 130 : 200;
1667
1668        var chuncks = [];
1669        var sentences = text.split(/(\?|\.|。)+/g); //('.');
1670        var currentChunck = '';
1671
1672        sentences.forEach(function (sentence) {
1673            if (sentence === '' || sentence === '.' || sentence === '。' || sentence === '?') {
1674                if (currentChunck) {
1675                    currentChunck += sentence;
1676                }
1677
1678                return;
1679            }
1680
1681            if (currentChunck && ((currentChunck.length + sentence.length) > chunckSize)) {
1682                chuncks.push(currentChunck);
1683                currentChunck = '';
1684            }
1685
1686            if (sentence.length > chunckSize) {
1687                var words = extractWords(sentence, language);
1688
1689                words.forEach(function (word) {
1690                    if (currentChunck.length + word.length > chunckSize) {
1691                        chuncks.push(currentChunck);
1692                        currentChunck = '';
1693                    }
1694
1695                    currentChunck += word.trim() + ' ';
1696                });
1697
1698                if (currentChunck.trim()) {
1699                    chuncks.push(currentChunck.trim() + '.');
1700                    currentChunck = '';
1701                }
1702
1703                return;
1704            }
1705
1706            currentChunck += sentence;
1707        });
1708
1709        chuncks.push(currentChunck);
1710
1711        return chuncks;
1712    };
1713
1714    function extractWords(text, language) {
1715        var wordRegex = new RegExp(/[&\$\-|]|([("\-&])*(\b[^\s]+[.:,"-)!&?]*)/g);
1716
1717        if (language) {
1718            if (language.indexOf('zh-') > -1) {
1719                return text.split(',');
1720            }
1721
1722            if (language.indexOf('ko-') > -1) {
1723                return text.split('.');
1724            }
1725        }
1726
1727        var words = [];
1728        var m;
1729
1730        while ((m = wordRegex.exec(text)) !== null) {
1731            if (m.index === wordRegex.lastIndex) {
1732                wordRegex.lastIndex++;
1733            }
1734
1735            words.push(m[0]);
1736        }
1737
1738        return words;
1739    };
1740
1741    function selectVoiceToPlay(voices) {
1742        var matchingVoices = [];
1743        var voice = null;
1744
1745        var language = me.settings.lockedLanguage || me.settings.referenceLanguage.Culture;
1746
1747        for (var i = 0; i < voices.length; i++) {
1748            if (voices[i].lang === language) {
1749                matchingVoices.push(voices[i]);
1750                voice = voices[i];
1751            }
1752        }
1753
1754        for (var j = 0; j < matchingVoices.length; j++) {
1755            if (matchingVoices[j].localService) {
1756                voice = matchingVoices[j];
1757
1758                break;
1759            }
1760        }
1761
1762        if (!voice && matchingVoices.length) {
1763            voice = matchingVoices[0];
1764        }
1765
1766        if (!voice && voices.length) {
1767            voice = voices[0];
1768        }
1769
1770        return voice;
1771    };
1772
1773    function getVoice() {    
1774        if (me.forcedVoice) {
1775            return Promise.resolve(me.forcedVoice);
1776        }
1777
1778        if (window.speechSynthesis.getVoices().length) {
1779            var voices = window.speechSynthesis.getVoices();
1780
1781            return Promise.resolve(selectVoiceToPlay(voices));
1782        }
1783
1784        return new Promise(function(resolve){
1785            window.speechSynthesis.onvoiceschanged = function () {
1786                var voices = window.speechSynthesis.getVoices();
1787    
1788                resolve(selectVoiceToPlay(voices));
1789            };
1790        });
1791    };
1792
1793    function stop() {
1794        me.isStopped = true;
1795        talkify.messageHub.publish(me.correlationId + ".player.html5.pause");
1796        window.speechSynthesis.cancel();
1797
1798        if (timeupdater) {
1799            clearInterval(timeupdater);
1800        }
1801
1802        if (me.currentContext.utterances.indexOf(me.currentContext.currentUtterance) < me.currentContext.utterances.length - 1) {
1803            console.log('Not the last, finishing anyway...');
1804            talkify.messageHub.publish(me.correlationId + ".player.html5.utterancecomplete", me.currentContext.
1804item);
1805        }
1806    };
1807
1808    if (me.playbar.instance) {
1809        me.playbar.instance
1810            .setMinRate(0)
1811            .setMaxRate(2)
1812            .setRateStep(0.2)
1813            .setMinPitch(0)
1814            .setMaxPitch(2)
1815            .setPitchStep(0.2);
1816    }
1817
1818    talkify.messageHub.publish(this.correlationId + ".player.html5.created");
1819};
1820
1821talkify.Html5Player.prototype.constructor = talkify.Html5Player;
1822},{}],12:[function(require,module,exports){
1823talkify = talkify || {};
1824
1825talkify.KeyboardCommands = function (keyboadCommands) {
1826    if (!keyboadCommands.enabled) {
1827        return {
1828            onPrevious: function () { },
1829            onNext: function () { },
1830            onPlayPause: function () { },
1831            dispose: function () { }
1832        }
1833    }
1834
1835
1836    var onNextCallback = function () { };
1837    var onPreviousCallback = function () { };
1838    var onPlayPauseCallback = function () { };
1839
1840    document.addEventListener("keyup", keyupEventHandler);
1841
1842    function keyupEventHandler(e) {
1843        if (!e.ctrlKey) {
1844            return;
1845        }
1846
1847        var key = e.keyCode ? e.keyCode : e.which;
1848
1849        if (key === keyboadCommands.commands.previous) {
1850            onPreviousCallback();
1851        } else if (key === keyboadCommands.commands.next) {
1852            onNextCallback();
1853        } else if (key === keyboadCommands.commands.playPause) {
1854            onPlayPauseCallback();
1855        }
1856    }
1857
1858    return {
1859        onPrevious: function (callback) {
1860            onPreviousCallback = callback;
1861        },
1862        onNext: function (callback) {
1863            onNextCallback = callback;
1864        },
1865        onPlayPause: function (callback) {
1866            onPlayPauseCallback = callback;
1867        },
1868        dispose: function () {
1869            document.removeEventListener("keyup", keyupEventHandler);
1870        }
1871    }
1872};
1873},{}],13:[function(require,module,exports){
1874talkify = talkify || {};
1875talkify.messageHub = function () {
1876    var subscribers = {};
1877
1878    function publish(topic, message) {
1879        if (topic.indexOf("timeupdate") === -1) {
1880            talkify.log("Publishing", topic);
1881        }
1882
1883        var topics = topic.split('.');
1884        var candidates = [];
1885
1886        Object.keys(subscribers).forEach(function (subscriberKey) {
1887            if(subscriberKey === '*'){
1888                candidates.push(subscriberKey);
1889                return;
1890            }
1891
1892            var s = subscriberKey.split('.');
1893
1894            if (s.length != topics.length) {
1895                return;
1896            }
1897
1898            var match = true;
1899
1900            for (var i = 0; i < s.length; i++) {
1901                if (topics[i] === '*') {
1902                    match = true;
1903                }
1904                else if (s[i] === topics[i]) {
1905                    match = true;
1906                } else if (s[i] === "*") {
1907                    match === true;
1908                } else {
1909                    match = false;
1910                    break;
1911                }
1912            }
1913
1914            if (match) {
1915                candidates.push(subscriberKey);
1916            }
1917        });
1918
1919        if (candidates.length === 0) {
1920            console.warn("No subscriber found", topic)
1921        }
1922
1923        candidates.forEach(function (c) {
1924            subscribers[c].forEach(function (subscriber) {
1925                if (c.indexOf("timeupdate") === -1) {
1926                    talkify.log("Calling subscriber", subscriber, c, message);
1927                }
1928
1929                subscriber.fn(message, topic);
1930            });
1931        })
1932
1933    }
1934
1935    function subscribe(key, topic, fn) {
1936        topic = Array.isArray(topic) ? topic : [topic];
1937
1938        for (var i = 0; i < topic.length; i++) {
1939            subscribers[topic[i]] = subscribers[topic[i]] || [];
1940
1941            subscribers[topic[i]].push({ key: key, fn: fn });
1942        }
1943    }
1944
1945    function unsubscribe(key, topic) {
1946        topic = Array.isArray(topic) ? topic : [topic];
1947
1948        talkify.log("Unsubscribing", key, topic);
1949
1950        Object.keys(subscribers).filter(function (subscriberKey) {
1951            return topic.indexOf(subscriberKey) > -1 ;
1952        }
1952).forEach(function (subscriberKey) {
1953            subscribers[subscriberKey] = subscribers[subscriberKey].filter(function (subscriber) {
1954                return subscriber.key !== key;
1955            });
1956        });
1957    }
1958
1959    return {
1960        publish: publish,
1961        subscribe: subscribe,
1962        unsubscribe: unsubscribe
1963    }
1964}();
1965},{}],14:[function(require,module,exports){
1966talkify = talkify || {};
1967talkify.BasePlayer = function (_audiosource, _playbar, options) {
1968    this.correlationId = talkify.generateGuid();
1969    talkify.messageHub.publish(this.correlationId + ".player.*.creating");
1970
1971    options = options || {};
1972    options.controlcenter = options.controlcenter || {};
1973
1974    this.audioSource = _audiosource;
1975    this.wordHighlighter = new talkify.wordHighlighter(this.correlationId);
1976
1977    var me = this;
1978
1979    this.settings = {
1980        useTextHighlight: false,
1981        referenceLanguage: { Culture: "", Language: -1 },
1982        lockedLanguage: null,
1983        rate: 1
1984    };
1985
1986    this.playbar = _playbar;
1987    this.forcedVoice = null;
1988
1989    if (talkify.config.ui.audioControls.enabled) {
1990        this.playbar.instance = talkify.playbar(options.controlcenter.container, this.correlationId, options.controlcenter.name);
1991    }
1992
1993    //Does infact prevent the usage of 2 simultanous players (which is not supported anyway)
1994    talkify.messageHub.subscribe("core-player", "*.player.*.creating", function () {
1995        me.dispose();
1996    });
1997
1998    talkify.messageHub.subscribe("core-player", this.correlationId + ".player.*.loaded", function (item) {
1999        item.isLoading = false;
2000    });
2001
2002    talkify.messageHub.subscribe("core-player", this.correlationId + ".player.*.ended", function (item) {
2003        item.isPlaying = false;
2004    });
2005
2006    talkify.messageHub.subscribe("core-player", this.correlationId + ".controlcenter.texthighlightoggled", function (enabled) {
2007        me.settings.useTextHighlight = enabled;
2008    });
2009
2010    talkify.messageHub.subscribe("core-player", this.correlationId + ".controlcenter.request.setvoice", function (voice) {
2011        me.forceVoice(voice);
2012    });
2013
2014    talkify.messageHub.subscribe("core-player", this.correlationId + ".controlcenter.request.enhancedvisibility", function (value) {
2015        talkify.messageHub.publish(me.correlationId + ".player.*.enhancedvisibilityset", value);
2016    });
2017
2018    talkify.messageHub.publish(this.correlationId + ".player.*.ratechanged", me.settings.rate);
2019    talkify.messageHub.publish(this.correlationId + ".player.*.enhancedvisibilityset", false);
2020
2021    this.withReferenceLanguage = function (refLang) {
2022        this.settings.referenceLanguage = refLang;
2023
2024        return this;
2025    };
2026
2027    this.enableTextHighlighting = function () {
2028        this.settings.useTextHighlight = true;
2029
2030        talkify.messageHub.publish(this.correlationId + ".player.*.texthighlight.enabled");
2031
2032        return this;
2033    };
2034
2035    this.disableTextHighlighting = function () {
2036        this.settings.useTextHighlight = false;
2037
2038        talkify.messageHub.publish(this.correlationId + ".player.*.texthighlight.disabled");
2039
2040        return this;
2041    };
2042
2043    this.setRate = function (r) {
2044        this.settings.rate = r;
2045
2046        talkify.messageHub.publish(this.correlationId + ".player.*.ratechanged", r);
2047
2048        return this;
2049    };
2050
2051    this.subscribeTo = function (subscriptions) {
2052        talkify.messageHub.subscribe("core-player", this.correlationId + ".player.*.pause", subscriptions.onPause || function () { });
2053        talkify.messageHub.subscribe("core-player", this.correlationId + ".player.*.resume", subscriptions.onResume || function () { });
2054        talkify.messageHub.subscribe("core-player", this.correlationId + ".player.*.play", subscriptions.onPlay || function () { });
2055        talkify.messageHub.subscribe("core-player", this.correlationId + ".player.*.loaded", subscriptions.onItemLoaded || function () { });
2056        talkify.messageHub.subscribe("core-player", [this.correlationId + ".wordhighlighter.complete", this.correlationId + ".player.html5.utterancecomplete"], subscriptions.onItemFinished || function () { });
2057        talkify.messageHub.subscribe("core-player", this.correlationId + ".player.*.prepareplay", subscriptions.onBeforeItemPlaying || function () { });
2058        talkify.messageHub.subscribe("core-player", this.correlationId + ".controlcenter.texthighlightoggled", subscriptions.onTextHighligtChanged || function () { });
2059
2060        return this;
2061    };
2062
2063    this.playItem = function (item) {
2064        if (item && item.isPlaying) {
2065            if (this.audioSource.paused()) {
2066                this.audioSource.play();
2067            } else {
2068                this.audioSource.pause();
2069            }
2070        }
2071
2072        talkify.messageHub.publish(this.correlationId + ".player.*.prepareplay", item);
2073
2074        item.isLoading = true;
2075        item.isPlaying = true;
2076        item.element.classList.add("playing");
2077        
2078        this.playAudio(item);
2079    };
2080
2081    this.createItems = function (text) {
2082        var safeMaxQuerystringLength = window.talkify.config.maximumTextLength || 1000;
2083
2084        var items = [];
2085
2086        //TODO: Smart split, should really split at the first end of sentence (.) that is < safeLength
2087        if (text.length > safeMaxQuerystringLength) {
2088            var f = text.substr(0, safeMaxQuerystringLength);
2089
2090            items.push(template(f));
2091
2092            items = items.concat(this.createItems(text.substr(safeMaxQuerystringLength, text.length - 1)));
2093
2094            return items;
2095        }
2096
2097        items.push(template(text));
2098
2099        return items;
2100
2101        function template(t) {
2102            //Null-objects
2103            var element = document.createElement("span");
2104            var clone = element.cloneNode(true);
2105
2106            return {
2107                text: t,
2108                preview: t.substr(0, 40),
2109                element: element,
2110                originalElement: clone,
2111                isPlaying: false,
2112                isLoading: false
2113            };
2114        }
2115    };
2116
2117    this.playText = function (text) {
2118        if (!text) {
2119            return;
2120        }
2121
2122        var items = this.createItems(text);
2123
2124        var currentItem = 0;
2125
2126        talkify.messageHub.subscribe("core-player.playText", this.correlationId + ".player.*.ended", function () {
2127            currentItem++;
2128
2129            if (currentItem >= items.length) {
2130                talkify.messageHub.unsubscribe("core.playText", this.correlationId + ".player.*.ended");
2131                return;
2132            }
2133
2134            this.playItem(items[currentItem]);
2135        });
2136
2137        this.playItem(items[currentItem]);
2138    };
2139
2140    this.paused = function () {
2141        return this.audioSource.paused();
2142    };
2143
2144    this.isPlaying = function () {
2145        return this.audioSource.isPlaying();
2146    };
2147
2148    this.play = function () {
2149        this.audioSource.play();
2150    };
2151
2152    this.pause = function () {
2153        this.audioSource.pause();
2154        var me = this;
2155
2156        if (!me.audioSource.paused() && me.audioSource.cancel) {
2157            me.audioSource.cancel(true);
2158        }
2159    };
2160
2161    this.dispose = function () {
2162        talkify.messageHub.publish(this.correlationId + ".player.tts.disposed");
2163        this.audioSource.stop();
2164
2165        this.audioSource.dispose();
2166        this.wordHighlighter.dispose();
2167
2168        talkify.messageHub.unsubscribe("core-player", this.correlationId + ".player.*.loaded");
2169        talkify.messageHub.unsubscribe("core-player", this.correlationId + ".player.*.ended");
2170        talkify.messageHub.unsubscribe("core-player", this.correlationId + ".controlcenter.texthighlightoggled");
2171        talkify.messageHub.unsubscribe("core-player", this.correlationId + ".player.*.pause");
2172        talkify.messageHub.unsubscribe("core-player", this.correlationId + ".player.*.resume");
2173        talkify.messageHub.unsubscribe("core-player", this.correlationId + ".player.*.play");
2174        talkify.messageHub.unsubscribe("core-player", [this.correlationId + ".wordhighlighter.complete", this.correlationId + ".player.html5.utterancecomplete"]);
2175        talkify.messageHub.unsubscribe("core-player", this.correlationId + ".player.*.prepareplay");
2176        talkify.messageHub.unsubscribe("core-player", this.correlationId + ".controlcenter.texthighlightoggled");
2177        talkify.messageHub.unsubscribe("core-player", this.correlationId + ".controlcenter.request.setvoice");
2178        talkify.messageHub.unsubscribe("core-player", this.correlationId + ".controlcenter.request.enhancedvisibility");
2179        talkify.messageHub.unsubscribe("core-player", "*.player.*.creating");
2180    };
2181
2182    this.forceLanguage = function (culture) {
2183        this.settings.lockedLanguage = culture;
2184
2185        return this;
2186    };
2187
2188    this.forceVoice = function (voice) {
2189        this.forcedVoice = voice !== undefined ? voice : null;
2190
2191        this.settings.lockedLanguage = (voice && (voice.lang || voice.culture)) || this.settings.lockedLanguage;
2192
2193        talkify.messageHub.publish(this.correlationId + ".player.*.voiceset", voice);
2194
2195        return this;
2196    };
2197
2198    this.enableEnhancedTextVisibility = function () {
2199        talkify.messageHub.publish(this.correlationId + ".player.*.enhancedvisibilityset", true);
2200
2201        return this;
2202    }
2203
2204    this.disableEnhancedTextVisibility = function () {
2205        talkify.messageHub.publish(this.correlationId + ".player.*.enhancedvisibilityset", false);
2206
2207        return this;
2208    }
2209};
2210},{}],15:[function(require,module,exports){
2211talkify = talkify || {};
2212
2213talkify.TtsPlayer = function (options) {
2214    if (!talkify.config.remoteService.active) {
2215        throw "This player needs to communicate to a remote service. To enable this player please set flag talkify.config.remoteService.active to true.";
2216    }
2217
2218    var me = this;
2219    var audioElement, timeupdater;
2220    var currentVoice, currentPitch, currentWordBreak, currentRate;
2221
2222    this.currentContext = {
2223        item: null,
2224        positions: []
2225    };
2226
2227    this.playbar = {
2228        instance: null
2229    };
2230
2231    this.audioSource = {
2232        play: function () {
2233            audioElement.play();
2234        },
2235        pause: function () {
2236            audioElement.pause();
2237        },
2238        isPlaying: function () {
2239            return audioElement.duration > 0 && !audioElement.paused;
2240        },
2241        paused: function () { return audioElement.paused; },
2242        currentTime: function () { return audioElement.currentTime; },
2243        stop: function () {
2244            audioElement.pause();
2245            audioElement.currentTime = 0;
2246        },
2247        dispose: function () {
2248            if (timeupdater) {
2249                clearInterval(timeupdater);
2250            }
2251
2252            var existingElement = document.getElementById("talkify-audio");
2253
2254            if (existingElement) {
2255                existingElement.outerHTML = "";
2256            }
2257
2258            talkify.messageHub.unsubscribe("tts-player", me.correlationId + ".controlcenter.request.play");
2259            talkify.messageHub.unsubscribe("tts-player", me.correlationId + ".controlcenter.request.pause");
2260            talkify.messageHub.unsubscribe("tts-player", me.correlationId + ".controlcenter.request.seek");
2261            talkify.messageHub.unsubscribe("tts-player", me.correlationId + ".controlcenter.request.volume");
2262            talkify.messageHub.unsubscribe("tts-player", me.correlationId + ".controlcenter.request.rate");
2263            talkify.messageHub.unsubscribe("tts-player", me.correlationId + ".controlcenter.request.wordbreak");
2264            talkify.messageHub.unsubscribe("tts-player", me.correlationId + ".controlcenter.request.pitch");
2265            talkify.messageHub.unsubscribe("tts-player", me.correlationId + ".controlcenter.request.phonation.normal");
2266            talkify.messageHub.unsubscribe("tts-player", me.correlationId + ".controlcenter.request.phonation.soft");
2267            talkify.messageHub.unsubscribe("tts-player", me.correlationId + ".controlcenter.request.phonation.whisper");
2268        }
2269    };
2270
2271    talkify.BasePlayer.call(this, this.audioSource, this.playbar, options);
2272
2273    this.settings.whisper = false;
2274    this.settings.soft = false;
2275    this.settings.wordbreakms = 0;
2276    this.settings.volumeDb = 0.0;
2277    this.settings.pitch = 0;
2278
2279    function setupBindings() {
2280        audioElement.addEventListener("pause", onPause);
2281        audioElement.addEventListener("play", onPlay);
2282        audioElement.addEventListener("seeked", onSeek);
2283    }
2284
2285    function onSeek() {
2286        talkify.messageHub.publish(me.correlationId + ".player.tts.seeked", { time: this.currentTime, item: me.currentContext.item, positions: me.currentContext.positions });
2287
2288        if (me.audioSource.paused() && me.audioSource.currentTime() > 0.1) {
2289            me.audioSource.play();
2290        }
2291    }
2292
2293    function onPause() {
2294        if (timeupdater) {
2295            clearInterval(timeupdater);
2296        }
2297
2298        talkify.messageHub.publish(me.correlationId + ".player.tts.pause");
2299    }
2300
2301    function onPlay() {
2302        if (timeupdater) {
2303            clearInterval(timeupdater);
2304        }
2305
2306        timeupdater = setInterval(function () {
2307            talkify.messageHub.publish(me.correlationId + ".player.tts.timeupdated", { currentTime: audioElement.currentTime, duration: audioElement.duration });
2308        }, 50);
2309
2310        if (!me.currentContext.item) {
2311            talkify.messageHub.publish(me.correlationId + ".player.tts.unplayable");
2312
2313            me.audioSource.pause();
2314        }
2315
2316        if (!me.currentContext.positions.length) {
2317            talkify.messageHub.publish(me.correlationId + ".player.tts.play", { item: me.currentContext.item, positions: [], currentTime: me.audioSource.currentTime() });
2318            return;
2319        }
2320
2321        if (me.audioSource.currentTime() > 0.1) {
2322            talkify.messageHub.publish(me.correlationId + ".player.tts.resume", { currentTime: me.audioSource.currentTime() });
2323        } else {
2324            var interval = setInterval(function () {
2325                if (me.audioSource.currentTime() > 0) {
2326                    clearInterval(interval);
2327
2328                    talkify.messageHub.publish(me.correlationId + ".player.tts.play", { item: me.currentContext.item, positions: me.currentContext.positions, currentTime: me.audioSource.currentTime() });
2329                }
2330            }, 20);
2331        }
2332    }
2333
2334    function initialize() {
2335        if (timeupdater) {
2336            clearInterval(timeupdater);
2337        }
2338
2339        audioElement = null;
2340        var existingElement = document.getElementById("talkify-audio");
2341
2342        if (existingElement) {
2343            existingElement.outerHTML = "";
2344        }
2345
2346        var mp3Source = document.createElement("source");
2347        var wavSource = document.createElement("source");
2348        audioElement = document.createElement("audio");
2349
2350        audioElement.appendChild(mp3Source);
2351        audioElement.appendChild(wavSource);
2352
2353        mp3Source.type = "audio/mpeg";
2354        wavSource.type = "audio/wav";
2355        audioElement.id = "talkify-audio";
2356        audioElement.autoplay = false;
2357
2358        (talkify.config.ui.audioControls.container || document.body).appendChild(audioElement);
2359
2360        var clonedAudio = audioElement.cloneNode(true);
2361        audioElement.parentNode.replaceChild(clonedAudio, audioElement);
2362
2363        audioElement = clonedAudio;
2364
2365        talkify.messageHub.subscribe("tts-player", me.correlationId + ".controlcenter.request.play", function () { me.play(); });
2366        talkify.messageHub.subscribe("tts-player", me.correlationId + ".controlcenter.request.pause", function () { audioElement.pause(); });
2367        talkify.messageHub.subscribe("tts-player", me.correlationId + ".controlcenter.request.seek", function (position) {
2368            var pos = audioElement.duration * position;
2369
2370            if (isNaN(audioElement.duration)) {
2371                return;
2372            }
2373
2374            audioElement.currentTime = pos;
2375        });
2376
2377        talkify.messageHub.subscribe("tts-player", me.correlationId + ".controlcenter.request.volume", function (volume) { audioElement.volume = volume / 10; });
2378        talkify.messageHub.subscribe("tts-player", me.correlationId + ".controlcenter.request.rate", function (rate) {
2379            me.settings.rate = rate;
2380
2381            talkify.messageHub.publish(me.correlationId + ".player.tts.ratechanged", me.settings.rate);
2382        });
2383
2384        talkify.messageHub.subscribe("tts-player", me.correlationId + ".controlcenter.request.wordbreak", function (ms) {
2385            me.useWordBreak(ms);
2386        });
2387
2388        talkify.messageHub.subscribe("tts-player", me.correlationId + ".controlcenter.request.pitch", function (value) {
2389            me.usePitch(value);
2390        });
2391
2392        talkify.messageHub.subscribe("tts-player", me.correlationId + ".controlcenter.request.phonation.soft", function () {
2393            me.normalTone();
2394            me.usePhonation("soft");
2395        });
2396
2397        talkify.messageHub.subscribe("tts-player", me.correlationId + ".controlcenter.request.phonation.normal", function () {
2398            me.normalTone();
2399            me.usePhonation("normal");
2400        });
2401
2402        talkify.messageHub.subscribe("tts-player", me.correlationId + ".controlcenter.request.phonation.whisper", function () {
2403            me.usePhonation("normal");
2404            me.whisper();
2405        });
2406
2407        if (me.playbar.instance) {
2408            me.playbar.instance
2409                .setMinRate(-5)
2410                .setMaxRate(5)
2411                .setRateStep(1)
2412                .setMinPitch(-10)
2413                .setMaxPitch(10)
2414                .setPitchStep(1);
2415        }
2416
2417        talkify.messageHub.publish(this.correlationId + ".player.tts.created");
2418    }
2419
2420    function getPositions(requestId) {
2421        return talkify.http.get(talkify.config.remoteService.speechBaseUrl + "/marks?id=" + requestId);
2422    };
2423
2424    initialize.apply(this);
2425
2426    this.downloadAudio = function (text) {
2427        var textType = "text";
2428        var voice = (me.forcedVoice ? me.forcedVoice.name : "");
2429        var pitch = me.settings.pitch;
2430        var wordbreak = me.settings.wordbreakms;
2431        var rate = me.settings.rate;
2432        var textToPlay = textType === "ssml" ?
2433            encodeURIComponent(text.replace(/\n/g, " ")) :
2434            encodeURIComponent(text.replace(/\n/g, " "));
2435
2436        var obj = {
2437            text: text,
2438            rate: rate,
2439            volume: me.settings.volumeDb,
2440            voice: voice,
2441            whisper: me.settings.whisper,
2442            wordBreakMs: wordbreak,
2443            soft: me.settings.soft,
2444            pitch: pitch
2445        };
2446
2447        var xhr = new XMLHttpRequest();
2448        var url = talkify.config.remoteService.host + talkify.config.remoteService.speechBaseUrl + "?key=" + talkify.config.remoteService.apiKey;
2449        xhr.open("POST", url, true);
2450        xhr.setRequestHeader("Content-Type", "application/json");
2451        xhr.onreadystatechange = function () {
2452            if (xhr.readyState === 4 && xhr.status >= 400 && xhr.status <= 599) {
2453                talkify.messageHub.publish(me.correlationId + ".player.tts.download.error", xhr);
2454            }
2455
2456            if (xhr.readyState === 4 && xhr.status === 200) {
2457                var link = document.createElement('a');
2458
2459                link.href = URL.createObjectURL(xhr.response);
2460                link.download = "talkify";
2461
2462                document.body.appendChild(link);
2463
2464                link.dispatchEvent(
2465                    new MouseEvent('click', {
2466                        bubbles: true,
2467                        cancelable: true,
2468                        view: window
2469                    })
2470                );
2471
2472                setTimeout(function () {
2473                    document.body.removeChild(link);
2474                }, 1000);
2475
2476                talkify.messageHub.publish(me.correlationId + ".player.tts.download.completed", null);
2477            }
2478        };
2479        var data = JSON.stringify(obj);
2480        xhr.responseType = 'blob';
2481
2482        talkify.messageHub.publish(me.correlationId + ".player.tts.download.started", null);
2483
2484        xhr.send(data);
2485    }
2486
2487    this.playAudio = function (item) {
2488        talkify.messageHub.publish(me.correlationId + ".player.tts.loading", item);
2489
2490        me.currentContext.item = item;
2491        me.currentContext.positions = [];
2492
2493        audioElement.controls = talkify.config.ui.audioControls.enabled && talkify.config.ui.audioControls.controlcenter === "native";
2494        audioElement.onloadeddata = null;
2495        audioElement.onended = null;
2496
2497        var sources = audioElement.getElementsByTagName("source");
2498
2499        var textType = talkify.config.useSsml && item.ssml ? "ssml" : "text";
2500
2501        var textToPlay = textType === "ssml" ?
2502            encodeURIComponent(item.ssml.replace(/\n/g, " ")) :
2503            encodeURIComponent(item.text.replace(/\n/g, " "));
2504
2505        var voice = item.voice || (this.forcedVoice ? this.forcedVoice.name : "");
2506
2507        if (voice !== currentVoice) {
2508            talkify.messageHub.publish(this.correlationId + ".player.tts.voiceset", { name: voice });
2509            currentVoice = voice;
2510        }
2511
2512        var pitch = (item.pitch || this.settings.pitch);
2513
2514        if (pitch !== currentPitch) {
2515            talkify.messageHub.publish(me.correlationId + ".player.tts.pitchchanged", pitch);
2516            currentPitch = pitch;
2517        }
2518
2519        var wordbreak = (item.wordbreakms || this.settings.wordbreakms);
2520
2521        if (wordbreak !== currentWordBreak) {
2522            talkify.messageHub.publish(me.correlationId + ".player.tts.wordbreakchanged", wordbreak);
2523            currentWordBreak = wordbreak;
2524        }
2525
2526        var rate = (item.rate || this.settings.rate);
2527
2528        if (rate !== currentRate) {
2529            talkify.messageHub.publish(me.correlationId + ".player.tts.ratechanged", rate);
2530            currentRate = rate;
2531        }
2532
2533        var requestId = talkify.generateGuid();
2534
2535        var audioUrl = talkify.config.remoteService.host +
2536            talkify.config.remoteService.speechBaseUrl +
2537            "?texttype=" + textType +
2538            "&text=" + textToPlay +
2539            "&fallbackLanguage=" + this.settings.referenceLanguage.Language +
2540            "&voice=" + (voice) +
2541            "&rate=" + rate +
2542            "&key=" + talkify.config.remoteService.apiKey +
2543            "&whisper=" + (item.whisper || this.settings.whisper) +
2544            "&soft=" + (item.soft || this.settings.soft) +
2545            "&wordbreakms=" + wordbreak +
2546            "&volume=" + this.settings.volumeDb +
2547            "&pitch=" + pitch;
2548
2549        if (me.settings.useTextHighlight) {
2550            audioUrl += "&marksid=" + requestId;
2551        }
2552
2553        sources[0].src = audioUrl + "&format=mp3";
2554        sources[1].src = audioUrl + "&format=wav";
2555
2556        sources[1].onerror = function (e) {
2557            talkify.messageHub.publish(me.correlationId + ".player.tts.error", null);
2558        }
2559
2560        audioElement.load();
2561
2562        audioElement.onloadeddata = function () {
2563            me.audioSource.pause();
2564
2565            if (!me.settings.useTextHighlight) {
2566                talkify.messageHub.publish(me.correlationId + ".player.tts.loaded", me.currentContext.
2566item);
2567                me.audioSource.play();
2568                return;
2569            }
2570
2571            getPositions(requestId).then(function (positions) {
2572                me.currentContext.positions = positions || [];
2573
2574                talkify.messageHub.publish(me.correlationId + ".player.tts.loaded", me.currentContext.item);
2575                me.audioSource.play();
2576            });
2577        };
2578
2579        audioElement.onended = function () {
2580            talkify.messageHub.publish(me.correlationId + ".player.tts.ended", item);
2581        };
2582    };
2583
2584    this.usePhonation = function (phonation) {
2585        this.settings.soft = phonation === "soft";
2586
2587        talkify.messageHub.publish(me.correlationId + ".player.tts.phonationchanged", phonation);
2588
2589        return this;
2590    };
2591
2592    this.whisper = function () {
2593        this.settings.whisper = true;
2594
2595        talkify.messageHub.publish(me.correlationId + ".player.tts.whisperchanged", true);
2596
2597        return this;
2598    };
2599
2600    this.normalTone = function () {
2601        this.settings.whisper = false;
2602
2603        talkify.messageHub.publish(me.correlationId + ".player.tts.whisperchanged", false);
2604
2605        return this;
2606    };
2607
2608    this.useWordBreak = function (ms) {
2609        this.settings.wordbreakms = Math.max(0, ms);
2610
2611        talkify.messageHub.publish(me.correlationId + ".player.tts.wordbreakchanged", this.settings.wordbreakms);
2612
2613        return this;
2614    };
2615
2616    this.useVolumeBaseline = function (volumeDb) {
2617        this.settings.volumeDb = volumeDb;
2618
2619        talkify.messageHub.publish(me.correlationId + ".player.tts.volumechanged", volumeDb);
2620
2621        return this;
2622    };
2623
2624    this.usePitch = function (pitch) {
2625        this.settings.pitch = pitch;
2626
2627        talkify.messageHub.publish(me.correlationId + ".player.tts.pitchchanged", pitch);
2628
2629        return this;
2630    };
2631
2632    setupBindings();
2633};
2634
2635talkify.TtsPlayer.prototype.constructor = talkify.TtsPlayer;
2636},{}],16:[function(require,module,exports){
2637talkify = talkify || {};
2638talkify.playlist = function () {
2639    var defaults = {
2640        useGui: false,
2641        useTextInteraction: false,
2642        domElements: null,
2643        exclusions: [],
2644        rootSelector: "body",
2645        events: {
2646            onEnded: null,
2647            onVoiceCommandListeningStarted: null,
2648            onVoiceCommandListeningEnded: null
2649        }
2650    };
2651
2652    var s = JSON.parse(JSON.stringify(defaults));
2653
2654    var p = null;
2655
2656    function isSupported() {
2657        var a = document.createElement("audio");
2658
2659        return (typeof a.canPlayType === "function" && (a.canPlayType("audio/mpeg") !== "" || a.canPlayType("audio/wav") !== ""));
2660    }
2661
2662    function implementation(_settings, player) {
2663        var textextractor = new talkify.textextractor();
2664
2665        var playlist = {
2666            queue: [],
2667            currentlyPlaying: null,
2668            refrenceText: "",
2669            referenceLanguage: { Culture: '', Language: -1 }
2670        };
2671
2672        var settings = _settings;
2673        var playerHasBeenReplaced = false;
2674
2675        var commands = [
2676            new talkify.KeyboardCommands(talkify.config.keyboardCommands),
2677            new talkify.SpeechCommands(talkify.config.voiceCommands)
2678        ];
2679
2680        var voiceCommands = commands[1];
2681
2682        for (var k = 0; k < commands.length; k++) {
2683            commands[k].onNext(playNext);
2684            commands[k].onPrevious(playPrevious);
2685            commands[k].onPlayPause(function () {
2686                if (player.paused()) {
2687                    player.play();
2688                } else {
2689                    pause();
2690                }
2691            });
2692        }
2693
2694        voiceCommands.onListeningStarted(settings.events.onVoiceCommandListeningStarted);
2695        voiceCommands.onListeningEnded(settings.events.onVoiceCommandListeningEnded);
2696
2697        setupHubSubscribers();
2698
2699        function setupHubSubscribers() {
2700            talkify.messageHub.subscribe("playlist", player.correlationId + ".player.*.ended", function (endedItem) {
2701                if (playlist.queue.indexOf(endedItem) === -1) {
2702                    return;
2703                }
2704
2705                var item = getNextItem();
2706
2707                if (!item) {
2708                    settings.events.onEnded();
2709                    resetPlaybackStates();
2710                    return;
2711                }
2712
2713                playItem(item);
2714            });
2715
2716            talkify.messageHub.subscribe("playlist", player.correlationId + ".player.*.unplayable", function () {
2717                if (playlist.currentlyPlaying) {
2718                    playItem(playlist.currentlyPlaying);
2719                    return;
2720                }
2721
2722                playFromBeginning();
2723            });
2724
2725            talkify.messageHub.subscribe("playlist", player.correlationId + ".controlcenter.request.textinteractiontoggled", function (enabled) {
2726                if (enabled) {
2727                    enableTextInteraction();
2728                } else {
2729                    disableTextInteraction();
2730                }
2731            });
2732
2733            talkify.messageHub.subscribe("playlist", player.correlationId + ".controlcenter.request.playnext", playNext);
2734            talkify.messageHub.subscribe("playlist", player.correlationId + ".controlcenter.request.playprevious", playPrevious);
2735            talkify.messageHub.subscribe("playlist", player.correlationId + ".controlcenter.request.download", downloadAudio);
2736        }
2737
2738        function downloadAudio() {
2739            if (!player) {
2740                return;
2741            }
2742
2743            if (player.downloadAudio) {
2744                var separators = ['\.', '\?', '!', '。'];
2745
2746                var text = playlist.queue.map(function (x) {
2747                    if (separators.indexOf(x.text.trim().substr(-1)) !== -1) {
2748                        return x.text;
2749                    }
2750
2751                    return x.text + ".";
2752                });
2753
2754                player.downloadAudio(text.join(" "));
2755            }
2756        }
2757
2758        function playNext() {
2759            var item = getNextItem();
2760
2761            if (!item) {
2762                return;
2763            }
2764
2765            playItem(item);
2766        }
2767
2768        function playPrevious() {
2769            var item = getPreviousItem();
2770
2771            if (!item) {
2772                return;
2773            }
2774
2775            playItem(item);
2776        }
2777
2778        function reset() {
2779            playlist.queue = [];
2780            player.withReferenceLanguage({ Culture: '', Language: -1 });
2781            playlist.currentlyPlaying = null;
2782            playlist.refrenceText = "";
2783        }
2784
2785        function insertAt(index, items) {
2786            playlist.queue = playlist.queue.slice(0, index)
2787                .concat(items)
2788                .concat(playlist.queue.slice(index));
2789        }
2790
2791        function push(items) {
2792            playlist.queue = playlist.queue.concat(items);
2793        }
2794
2795        function resetPlaybackStates() {
2796            for (var j = 0; j < playlist.queue.length; j++) {
2797                var item = playlist.queue[j];
2798
2799                //TODO: Call player.resetItem?
2800                item.isPlaying = false;
2801                item.isLoading = false;
2802                item.element.classList.remove("playing");
2803            }
2804        };
2805
2806        function isPlaying() {
2807            for (var i = 0; i < playlist.queue.length; i++) {
2808                if (playlist.queue[i].isPlaying) {
2809                    return true;
2810                }
2811            }
2812
2813            return false;
2814        }
2815
2816        function domElementExistsInQueue(element) { //TODO: might need to look at construct as <a><h3></h3></a> and whether "a" is "h3" since it is just a wrapper
2817            for (var j = 0; j < playlist.queue.length; j++) {
2818                var item = playlist.queue[j];
2819
2820                if (!item) {
2821                    continue;
2822                }
2823
2824                if (element === item.element) {
2825                    return true;
2826                }
2827            }
2828
2829            return false;
2830        }
2831
2832        function playItem(item) {
2833            if (!playerHasBeenReplaced && item && item.isPlaying) {
2834                if (player.paused()) {
2835                    player.play();
2836                } else {
2837                    player.pause();
2838                }
2839            }
2840
2841            playerHasBeenReplaced = false;
2842
2843            resetPlaybackStates();
2844
2845            if (playlist.currentlyPlaying) {
2846                playlist.currentlyPlaying.element.innerHTML = playlist.currentlyPlaying.originalElement.innerHTML;
2847            }
2848
2849            playlist.currentlyPlaying = item;
2850
2851            var previous = getPreviousItem();
2852            var next = getNextItem();
2853
2854            talkify.messageHub.publish(player.correlationId + ".playlist.playing", { isFirst: !previous, isLast: !next });
2855
2856            p = player.playItem(item);
2857        };
2858
2859        function createItems(text, ssml, element) {
2860            var safeMaxQuerystringLength = window.talkify.config.maximumTextLength || 1000;
2861
2862            var items = [];
2863
2864            if (text.length > safeMaxQuerystringLength) {
2865                var chuncks = getSafeTextChunks(text, safeMaxQuerystringLength);
2866
2867                element.innerHTML = '';
2868
2869                for (var i = 0; i < chuncks.length; i++) {
2870                    var p = document.createElement("p");
2871                    p.textContent = chuncks[i];
2872
2873                    element.appendChild(p);
2874
2875                    items = items.concat(template(chuncks[i], null, p));
2876                }
2877
2878                return items;
2879            }
2880
2881            items = items.concat(template(text, ssml, element));
2882
2883            return items;
2884
2885            function template(t, s, el) {
2886                el = el || document.createElement("span");
2887                var clone = el.cloneNode(true);
2888
2889                var wordbreakms = el.getAttribute("data-talkify-wordbreakms");
2890                var whisper = el.getAttribute("data-talkify-whisper");
2891                var phonation = el.getAttribute("data-talkify-phonation");
2892                var voice = el.getAttribute("data-talkify-voice") || null;
2893                var pitch = el.getAttribute("data-talkify-pitch") || null;
2894                var rate = el.getAttribute("data-talkify-rate") || null;
2895                var prefix = el.getAttribute("data-talkify-prefix") || null;
2896
2897                var response = [];
2898
2899                var text = prefix ? prefix + ": " + t : t;
2900                var ssml = prefix ? prefix + ": " + s : s;
2901
2902                response.push({
2903                    text: text,
2904                    ssml: ssml,
2905                    prefix: prefix,
2906                    preview: t.substr(0, 40),
2907                    element: el,
2908                    originalElement: clone,
2909                    isPlaying: false,
2910                    isLoading: false,
2911                    wordbreakms: wordbreakms ? parseInt(wordbreakms) : null,
2912                    whisper: whisper ? whisper === "true" : null,
2913                    soft: phonation ? phonation === "soft" : null,
2914                    voice: voice,
2915                    pitch: pitch ? parseInt(pitch) : null,
2916                    rate: rate ? parseInt(rate) : null
2917                });
2918
2919                return response;
2920            }
2921        }
2922
2923        function getSafeTextChunks(text, safeMaxQuerystringLength) {
2924            var chuncks = [];
2925            var remaining = text;
2926
2927            var chunck = getNextChunck(text, safeMaxQuerystringLength);
2928            chuncks.push(chunck);
2929
2930            while (chunck) {
2931                remaining = remaining.replace(chunck, "");
2932
2933                chunck = getNextChunck(remaining, safeMaxQuerystringLength);
2934
2935                if (chunck) {
2936                    chuncks.push(chunck);
2937                }
2938            }
2939
2940            return chuncks;
2941        }
2942
2943        function getNextChunck(text, safeMaxQuerystringLength) {
2944            if (!text) {
2945                return null;
2946            }
2947
2948            if (text.length < safeMaxQuerystringLength) {
2949                return text;
2950            }
2951
2952            var breakAt = text.substr(0, safeMaxQuerystringLength).lastIndexOf('.');
2953            breakAt = breakAt > -1 ? breakAt : text.substr(0, safeMaxQuerystringLength).lastIndexOf('。');
2954            breakAt = breakAt > -1 ? breakAt : safeMaxQuerystringLength;
2955
2956            return text.substr(0, breakAt + 1);
2957        }
2958
2959        function play(item) {
2960            if (!item) {
2961                if (playlist.queue.length === 0) {
2962                    return;
2963                }
2964
2965                playFromBeginning();
2966
2967                return;
2968            }
2969
2970            playItem(item);
2971        }
2972
2973        function pause() {
2974            player.pause();
2975        }
2976
2977        function setupItemForUserInteraction(item) {
2978            item.element.style.cursor = "pointer";
2979            item.element.classList.add("talkify-highlight");
2980
2981            removeEventListeners("click", item.element);
2982            addEventListener("click", item.element, textInteractionEventListener);
2983
2984            function textInteractionEventListener() {
2985                play(item);
2986            }
2987        }
2988
2989        function removeUserInteractionForItem(item) {
2990            item.element.style.cursor = "inherit";
2991            item.element.classList.remove("talkify-highlight");
2992
2993            removeEventListeners("click", item.element);
2994        }
2995
2996        function markTables() {
2997            if (!settings.tableConfig) {
2998                return;
2999            }
3000
3001            talkify.tableReader.markTables(settings.tableConfig);
3002        }
3003
3004        function extractTables() {
3005            if (!settings.tableConfig) {
3006                return [];
3007            }
3008
3009            return Array.from(document.querySelectorAll('.talkify-tts-table'));
3010        }
3011
3012        function initialize() {
3013            reset();
3014
3015            markTables();
3016
3017            if (!settings.domElements) {
3018                settings.domElements = textextractor.extract(settings.rootSelector, settings.exclusions);
3019            }
3020
3021            for (var i = 0; i < settings.domElements.length; i++) {
3022                var text, ssml;
3023                var element = null;
3024
3025                if (typeof settings.domElements[i] === "string") {
3026                    text = settings.domElements[i];
3027                } else {
3028                    element = settings.domElements[i];
3029
3030                    ssml = convertToSsml(element);
3031
3032                    text = element.innerText.trim();
3033                    
3034                    // Customisation: prevent voice reading attributions ("[1], [2] etc")
3035                    text = text.replace(/(\[\d*\])/g, '');
3036                }
3037
3038                if (text === "") {
3039                    continue;
3040                }
3041
3042                push(createItems(text, ssml, element));
3043
3044                if (text.length > playlist.refrenceText.length) {
3045                    playlist.refrenceText = text;
3046                }
3047            }
3048
3049            var tables = extractTables();            
3050
3051            for (var t = 0; t < tables.length; t++) {
3052                var cells = Array.from(tables[t].querySelectorAll(".talkify-tts-tablecell"));
3053
3054                insertChunckOfElements(cells);
3055            }
3056
3057            if (settings.useTextInteraction) {
3058                for (var j = 0; j < playlist.queue.length; j++) {
3059                    var item = playlist.queue[j];
3060
3061                    if (j > 0) {
3062                        var isSameAsPrevious = item.element === playlist.queue[j - 1].element;
3063
3064                        if (isSameAsPrevious) {
3065                            continue;
3066                        }
3067                    }
3068
3069                    setupItemForUserInteraction(item);
3070                }
3071            }
3072
3073            talkify.messageHub.publish(player.correlationId + ".playlist.loaded");
3074
3075            if (settings.useTextInteraction) {
3076                talkify.messageHub.publish(player.correlationId + ".playlist.textinteraction.enabled");
3077            } else {
3078                talkify.messageHub.publish(player.correlationId + ".playlist.textinteraction.disabled");
3079            }
3080        }
3081
3082        function convertToSsml(element) {
3083            if (!talkify.config.useSsml) {
3084                return null;
3085            }
3086
3087            var ssmlMappings = {
3088                h1: {
3089                    start: '###emphasis level="strong">',
3090                    end: '###/emphasis>',
3091                    trim: false
3092                },
3093                h2: {
3094                    start: '###emphasis level="strong">',
3095                    end: '###/emphasis>',
3096                    trim: false
3097                },
3098                h3: {
3099                    start: '###emphasis level="strong">',
3100                    end: '###/emphasis>',
3101                    trim: false
3102                },
3103                b: {
3104                    start: '###emphasis level="strong">',
3105                    end: '###/emphasis>',
3106                    trim: false
3107                },
3108                strong: {
3109                    start: '###emphasis level="strong">',
3110                    end: '###/emphasis>',
3111                    trim: false
3112                },
3113                em: {
3114                    start: '###emphasis level="strong">',
3115                    end: '###/emphasis>',
3116                    trim: false
3117                },
3118                i: {
3119                    start: '###emphasis level="reduced">',
3120                    end: '###/emphasis>',
3121                    trim: false
3122                },
3123                br: {
3124                    start: '###break strength="x-strong">###/break>',
3125                    end: '',
3126                    trim: true
3127                }
3128            };
3129
3130            var htmlEntities = {};
3131            htmlEntities["&nbsp;"] = " ";
3132            htmlEntities["&lt;"] = "<";
3133            htmlEntities["&gt;"] = ">";
3134            htmlEntities["&qout;"] = "\"";
3135            htmlEntities["&apos;"] = "'";
3136            htmlEntities["&amp;"] = "&";
3137
3138            var ssml = element.innerHTML.replace(/ +/g, " ").replace(/(\r\n|\n|\r)/gm, "").trim();
3139
3140            for (var key in htmlEntities) {
3141                ssml = ssml.replace(new RegExp(key, 'g'), htmlEntities[key]);
3142            }
3143
3144            for (var key in ssmlMappings) {
3145                var mapping = ssmlMappings[key];
3146
3147                var startTagMatches = ssml.match(new RegExp('<' + key + '+(>|.*?[^?]>)', 'gi')) || [];
3148
3149                for (var j = 0; j < startTagMatches.length; j++) {
3150                    if (startTagMatches[j] !== '<' + key + '>' && startTagMatches[j].indexOf('<' + key + ' ') !== 0) {
3151                        continue;
3152                    }
3153
3154                    ssml = ssml.replace(startTagMatches[j], mapping.start);
3155
3156                    if (mapping.trim) {
3157                        ssml = ssml.split(mapping.start).map(function (x) { return x.trim() }).join(mapping.start);
3158                    }
3159                }
3160
3161                ssml = ssml.split('</' + key + '>').map(function (x, i) { return mapping.trim ? x.trim() : x; }).join(mapping.end);
3162            }
3163
3164            ssml = ssml.replace(/<[^>]*>?/gm, ''); //removes html-tags
3165            ssml = ssml.replace(/\s+/g, ' ');
3165 //removes multiple whitespaces
3166            ssml = ssml.split('###').join('<');
3167
3168            return ssml;
3169        }
3170
3171        function getNextItem() {
3172            var currentQueuePosition = playlist.queue.indexOf(playlist.currentlyPlaying);
3173
3174            if (currentQueuePosition === playlist.queue.length - 1) {
3175                return null;
3176            }
3177
3178            return playlist.queue[currentQueuePosition + 1];
3179        }
3180
3181        function getPreviousItem() {
3182            var currentQueuePosition = playlist.queue.indexOf(playlist.currentlyPlaying);
3183
3184            if (currentQueuePosition === 0) {
3185                return null;
3186            }
3187
3188            return playlist.queue[currentQueuePosition - 1];
3189        }
3190
3191        function playFromBeginning() {
3192            if (!talkify.config.remoteService.active || !hasTalkifyPlayer()) {
3193                onComplete({ Cultures: [], Language: -1 });
3194
3195                return;
3196            }
3197
3198            var text = playlist.refrenceText.length <= 1000 ? playlist.refrenceText : playlist.refrenceText.substr(0, 1000);
3199
3200            talkify.http.get(talkify.config.remoteService.languageBaseUrl + "/detect?text=" + text)
3201                .then(function (data) {
3202                    onComplete(data);
3203                })
3204                .catch(function(){
3205                    onComplete({ Cultures: [], Language: -1 });
3206                });
3207
3208            function onComplete(refLang) {
3209                playlist.referenceLanguage = { Culture: refLang.Cultures[0], Language: refLang.Language };
3210                player.withReferenceLanguage(playlist.referenceLanguage);
3211
3212                playItem(playlist.queue[0]);
3213            }
3214        }
3215
3216        function hasTalkifyPlayer(voice) {
3217            return player instanceof talkify.TtsPlayer;
3218        }
3219
3220
3221        function insertChunckOfElements(elements) {
3222            if (!elements || elements.length === 0) {
3223                return;
3224            }
3225
3226            if (!playlist.queue.length) {
3227                playlist.queue = elements.map(function (x) {
3228                    var text = x.innerText.trim();
3229                    var ssml = convertToSsml(x);
3230
3231                    return createItems(text, ssml, x);
3232                }).flat();
3233
3234                return;
3235            }
3236
3237            var baseline = elements[0];
3238            var documentPositionFollowing = 4;
3239
3240            for (var j = 0; j < playlist.queue.length; j++) {
3241                var item = playlist.queue[j];
3242
3243                var isSelectionAfterQueueItem = baseline.compareDocumentPosition(item.element) == documentPositionFollowing;
3244                var shouldAddToBottom = j === playlist.queue.length - 1;
3245
3246                if (isSelectionAfterQueueItem || shouldAddToBottom) {
3247                    var queueItems = elements.map(function (x) {
3248                        var text = x.innerText.trim();
3249                        var ssml = convertToSsml(x);
3250
3251                        return createItems(text, ssml, x);
3252                    }).flat();
3253
3254                    var insertAtIndex = isSelectionAfterQueueItem ? j : j + 1;
3255
3256                    insertAt(insertAtIndex, queueItems);
3257
3258                    return;
3259                }
3260            }
3261        }
3262
3263        function insertElement(element) {
3264            if (!element) {
3265                return [];
3266            }
3267
3268            var items = [];
3269
3270            var text = element.innerText;
3271
3272            if (text.trim() === "") {
3273                return items;
3274            }
3275
3276            if (domElementExistsInQueue(element)) {
3277                return items;
3278            }
3279
3280            var documentPositionFollowing = 4;
3281
3282            for (var j = 0; j < playlist.queue.length; j++) {
3283                var item = playlist.queue[j];
3284
3285                var isSelectionAfterQueueItem = element.compareDocumentPosition(item.element) == documentPositionFollowing;
3286
3287                if (isSelectionAfterQueueItem) {
3288                    var queueItems = createItems(text, null, element);
3289
3290                    insertAt(j, queueItems);
3291
3292                    items = items.concat(queueItems);
3293
3294                    break;
3295                }
3296
3297                var shouldAddToBottom = j === playlist.queue.length - 1;
3298
3299                if (shouldAddToBottom) {
3300                    var qItems = createItems(text, null, element);
3301
3302                    push(qItems);
3303
3304                    items = items.concat(qItems);
3305
3306                    break;
3307                }
3308            }
3309
3310            return items;
3311        }
3312
3313        function replayCurrent() {
3314            if (!playlist.currentlyPlaying) {
3315                return;
3316            }
3317
3318            playlist.currentlyPlaying.isPlaying = false;
3319            play(playlist.currentlyPlaying);
3320        }
3321
3322        //TODO: Extract and reuse?
3323        function removeEventListeners(eventType, element) {
3324            if (!element.trackedEvents || !element.trackedEvents[eventType]) {
3325                return;
3326            }
3327
3328            for (var i = 0; i < element.trackedEvents[eventType].length; i++) {
3329                element.removeEventListener(eventType, element.trackedEvents[eventType][i]);
3330            }
3331        }
3332
3333        function addEventListener(eventType, element, listener) {
3334            if (!element.trackedEvents) {
3335                element.trackedEvents = [];
3336            }
3337
3338            if (!element.trackedEvents[eventType]) {
3339                element.trackedEvents[eventType] = [];
3340            }
3341
3342            element.trackedEvents[eventType].push(listener);
3343            element.addEventListener(eventType, listener);
3344        }
3345
3346        function enableTextInteraction() {
3347            settings.useTextInteraction = true;
3348
3349            for (var i = 0; i < playlist.queue.length; i++) {
3350                setupItemForUserInteraction(playlist.queue[i]);
3351            }
3352
3353            talkify.messageHub.publish(player.correlationId + ".playlist.textinteraction.enabled");
3354        }
3355
3356        function disableTextInteraction() {
3357            settings.useTextInteraction = false;
3358
3359            for (var i = 0; i < playlist.queue.length; i++) {
3360                removeUserInteractionForItem(playlist.queue[i]);
3361            }
3362
3363            talkify.messageHub.publish(player.correlationId + ".playlist.textinteraction.disabled");
3364        }
3365
3366        initialize();
3367
3368        return {
3369            getQueue: function () { return playlist.queue; },
3370            play: play,
3371            pause: pause,
3372            replayCurrent: replayCurrent,
3373            insert: insertElement,
3374            isPlaying: isPlaying,
3375            enableTextInteraction: enableTextInteraction,
3376            disableTextInteraction: disableTextInteraction,
3377            setPlayer: function (p) {
3378                talkify.messageHub.unsubscribe("playlist", player.correlationId + ".player.*.ended");
3379                talkify.messageHub.unsubscribe("playlist", player.correlationId + ".player.tts.unplayable");
3380                talkify.messageHub.unsubscribe("playlist", player.correlationId + ".controlcenter.request.textinteractiontoggled");
3381                talkify.messageHub.unsubscribe("playlist", player.correlationId + ".controlcenter.request.playnext");
3382                talkify.messageHub.unsubscribe("playlist", player.correlationId + ".controlcenter.request.playprevious");
3383                talkify.messageHub.unsubscribe("playlist", player.correlationId + ".controlcenter.request.download");
3384
3385                player = p;
3386                player.withReferenceLanguage(playlist.referenceLanguage);
3387                playerHasBeenReplaced = true;
3388
3389                setupHubSubscribers();
3390
3391                replayCurrent();
3392            },
3393            dispose: function () {
3394                resetPlaybackStates();
3395
3396                for (var i = 0; i < playlist.queue.length; i++) {
3397                    var item = playlist.queue[i];
3398
3399                    removeUserInteractionForItem(item);
3400                }
3401
3402                for (var i = 0; i < commands.length; i++) {
3403                    commands[i].dispose();
3404                }
3405
3406                talkify.messageHub.unsubscribe("playlist", player.correlationId + ".player.*.ended");
3407                talkify.messageHub.unsubscribe("playlist", player.correlationId + ".player.tts.unplayable");
3408                talkify.messageHub.unsubscribe("playlist", player.correlationId + ".controlcenter.request.textinteractiontoggled");
3409                talkify.messageHub.unsubscribe("playlist", player.correlationId + ".controlcenter.request.playnext");
3410                talkify.messageHub.unsubscribe("playlist", player.correlationId + ".controlcenter.request.playprevious");
3411            },
3412            startListeningToVoiceCommands: function () {
3413                voiceCommands.start();
3414            },
3415            stopListeningToVoiceCommands: function () {
3416                voiceCommands.stop();
3417            }
3418        }
3419    }
3420
3421    return {
3422        begin: function () {
3423            s = JSON.parse(JSON.stringify(defaults));
3424            p = null;
3425
3426            return {
3427                withTextInteraction: function () {
3428                    s.useTextInteraction = true;
3429
3430                    return this;
3431                },
3432                withTalkifyUi: function () {
3433                    s.useGui = true;
3434
3435                    return this;
3436                },
3437                excludeElements: function (elementsSelectors) {
3438                    s.exclusions = elementsSelectors;
3439
3440                    return this;
3441                },
3442                withRootSelector: function (rootSelector) {
3443                    s.rootSelector = rootSelector;
3444
3445                    return this;
3446                },
3447                withElements: function (elements) {
3448                    s.domElements = elements;
3449
3450                    return this;
3451                },
3452                withTables: function (tableConfig) {
3453                    s.tableConfig = tableConfig;
3454
3455                    return this;
3456                },
3457                usingPlayer: function (player) {
3458                    p = player;
3459
3460                    return this;
3461                },
3462                subscribeTo: function (events) {
3463                    s.events.onEnded = events.onEnded || function () { };
3464                    s.events.onVoiceCommandListeningStarted = events.onVoiceCommandListeningStarted || function () { };
3465                    s.events.onVoiceCommandListeningEnded = events.onVoiceCommandListeningEnded || function () { };
3466
3467
3468                    return this;
3469                },
3470                build: function () {
3471                    if (!isSupported()) {
3472                        throw new Error("Not supported. The browser needs to support mp3 or wav HTML5 Audio.");
3473                    }
3474
3475                    if (!p) {
3476                        throw new Error("A player must be provided. Please use the 'usingPlayer' method to provide one.");
3477                    }
3478
3479                    s.events.onEnded = s.events.onEnded || function () { };
3480                    s.events.onVoiceCommandListeningStarted = s.events.onVoiceCommandListeningStarted || function () { };
3481                    s.events.onVoiceCommandListeningEnded = s.events.onVoiceCommandListeningEnded || function () { };
3482
3483                    return new implementation(s, p);
3484                }
3485            }
3486        }
3487
3488    };
3489};
3490},{}],17:[function(require,module,exports){
3491talkify = talkify || {};
3492
3493talkify.SpeechCommands = function (speechCommandConfig) {
3494    if (!speechCommandConfig.enabled || !window.webkitSpeechRecognition) {
3495        var noop = function () { };
3496
3497        return {
3498            onPrevious: noop,
3499            onNext: noop,
3500            onPlayPause: noop,
3501            start: noop,
3502            onListeningStarted: noop,
3503            onListeningEnded: noop,
3504            dispose: noop
3505        }
3506    }
3507    
3508    var SpeechRecognition = window.webkitSpeechRecognition;
3509
3510    var isListening = false;
3511    var onNextCallback = function () { };
3512    var onPreviousCallback = function () { };
3513    var onPlayPauseCallback = function () { };
3514    var onListeningStartedCallback = function () { };
3515    var onListeningEndedCallback = function () { };
3516
3517    var recognition = new SpeechRecognition();
3518
3519    recognition.lang = 'en-US';
3520    recognition.interimResults = false;
3521    recognition.maxAlternatives = 1;
3522
3523    recognition.onstart = function () {
3524        isListening = true;
3525        onListeningStartedCallback();
3526    }
3527
3528    recognition.onresult = function (event) {
3529        var transcript = event.results[event.results.length - 1][0].transcript;
3530
3531        var matchingCommandName = evaluate(transcript, speechCommandConfig.commands);
3532
3533        if (speechCommandConfig.commands[matchingCommandName] === speechCommandConfig.commands.playPause) {
3534            onPlayPauseCallback();
3535        } else if (speechCommandConfig.commands[matchingCommandName] === speechCommandConfig.commands.next) {
3536            onNextCallback();
3537        } else if (speechCommandConfig.commands[matchingCommandName] === speechCommandConfig.commands.previous) {
3538            onPreviousCallback();
3539        }
3540    }
3541
3542    recognition.onspeechend = function () {
3543        recognition.stop();
3544        isListening = false;
3545        onListeningEndedCallback();
3546    }
3547
3548    function evaluate(transcript, commands) {
3549        var wordsInTranscript = transcript.split(' ');
3550        var possibleMatches = [];
3551
3552        for (var key in commands) {
3553            if (!commands.hasOwnProperty(key)) {
3554                continue;
3555            }
3556
3557            var phrases = speechCommandConfig.commands[key];
3558
3559            for (var i = 0; i < phrases.length; i++) {
3560                if (phrases[i].toLowerCase() === transcript) {
3561                    //exact match
3562                    return key;
3563                }
3564
3565                var match = phrases[i].split(' ').filter(function (word) {
3566                    return wordsInTranscript.indexOf(word.toLowerCase()) > -1;
3567                })[0];
3568
3569                //any word in phrase mathes
3570                if (match) {
3571                    possibleMatches.push(key);
3572                    break;
3573                }
3574            }
3575        }
3576
3577        if (possibleMatches.length > 0) {
3578            var bestValue = 0;
3579            var bestCommand = null;
3580
3581            for (var j = 0; j < possibleMatches.length; j++) {
3582                var temp = Math.max.apply(Math,
3583                    speechCommandConfig.commands[possibleMatches[j]].map(function (phrase) {
3584                        return levenshtein(phrase, transcript);
3585                    }));
3586
3587                if (temp > bestValue) {
3588                    bestValue = temp;
3589                    bestCommand = possibleMatches[j];
3590                }
3591            }
3592
3593            return bestCommand;
3594        }
3595
3596        return null;
3597
3598    }
3599
3600    function levenshtein(s1, s2) {
3601        var longer = s1;
3602        var shorter = s2;
3603        if (s1.length < s2.length) {
3604            longer = s2;
3605            shorter = s1;
3606        }
3607        var longerLength = longer.length;
3608        if (longerLength === 0) {
3609            return 1.0;
3610        }
3611        return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength);
3612    }
3613
3614    function editDistance(s1, s2) {
3615        s1 = s1.toLowerCase();
3616        s2 = s2.toLowerCase();
3617
3618        var costs = new Array();
3619        for (var i = 0; i <= s1.length; i++) {
3620            var lastValue = i;
3621            for (var j = 0; j <= s2.length; j++) {
3622                if (i === 0)
3623                    costs[j] = j;
3624                else {
3625                    if (j > 0) {
3626                        var newValue = costs[j - 1];
3627                        if (s1.charAt(i - 1) != s2.charAt(j - 1))
3628                            newValue = Math.min(Math.min(newValue, lastValue),
3629                                costs[j]) + 1;
3630                        costs[j - 1] = lastValue;
3631                        lastValue = newValue;
3632                    }
3633                }
3634            }
3635            if (i > 0)
3636                costs[s2.length] = lastValue;
3637        }
3638        return costs[s2.length];
3639    }
3640
3641    if (speechCommandConfig.keyboardActivation.enabled) {
3642        document.addEventListener("keyup",
3643            function (e) {
3644                if (!e.ctrlKey) {
3645                    return;
3646                }
3647
3648                if (isListening) {
3649                    return;
3650                }
3651
3652                var key = e.keyCode ? e.keyCode : e.which;
3653
3654                if (key === speechCommandConfig.keyboardActivation.key) {
3655                    recognition.start();
3656                }
3657            });
3658    }
3659
3660    return {
3661        onPrevious: function (callback) {
3662            onPreviousCallback = callback;
3663        },
3664        onNext: function (callback) {
3665            onNextCallback = callback;
3666        },
3667        onPlayPause: function (callback) {
3668            onPlayPauseCallback = callback;
3669        },
3670        start: function () {
3671            if (isListening) {
3672                return;
3673            }
3674
3675            recognition.start();
3676        },
3677        onListeningStarted: function (callback) {
3678            onListeningStartedCallback = callback;
3679        },
3680        onListeningEnded: function (callback) {
3681            onListeningEndedCallback = callback;
3682        },
3683        dispose: function () {}
3684    }
3685};
3686},{}],18:[function(require,module,exports){
3687talkify = talkify || {};
3688
3689talkify.selectionActivator = function () {
3690    var inlineElements = ['a', 'span', 'b', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'cite', 'code', 'dfn', 'em', 'kbd', 'strong', 'samp', 'var', 'a', 'bdo', 'q', 'sub', 'sup', 'label'];
3691    var forbiddenElementsString = ['img', 'map', 'object', 'script', 'button', 'input', 'select', 'textarea', 'style', 'code', 'rp', 'rt'];
3692    var timeoutId, playlist, player, x, y, activatorHtml, controlcenterHtml, currentSelection;
3693    var orchestrateTimeout;
3694    var activated = false;
3695    var currentContext = {};
3696    var settings = {
3697        exclude: [],
3698        enhancedVisibility: false,
3699        voice: { name: 'Zira' },
3700        highlightText: false,
3701        buttonText: "Listen",
3702        rate: 0
3703    };
3704
3705    var validNodeTypes = [1, 3];
3706
3707    function getElementsInSelection() {
3708        if (!currentSelection) {
3709            return [];
3710        }
3711
3712        var anchorNode = currentContext.anchorNode;
3713
3714        if (!anchorNode) {
3715            return [];
3716        }
3717
3718        var nodes = getNodesInSelection(currentContext.range.commonAncestorContainer, currentContext.leftToRight);
3719
3720        return nodes;
3721    }
3722
3723    function createItemsFromNodes(nodes) {
3724        var items = [];
3725
3726        if (nodes.length === 1) {
3727            return [surroundNode(nodes[0], currentContext.range.startOffset, currentContext.range.endOffset)];
3728        }
3729
3730        for (var i = 0; i < nodes.length; i++) {
3731            if (i === 0) {
3732                if (currentContext.leftToRight) {
3733                    items.push(surroundNode(nodes[i], currentContext.anchorOffset, currentContext.anchorNode.textContent.length))
3734                } else {
3735                    items.push(surroundNode(nodes[i], currentContext.focusOffset, currentContext.focusNode.textContent.length))
3736                }
3737
3738                continue;
3739            }
3740
3741            if (i === nodes.length - 1) {
3742                if (currentContext.leftToRight) {
3743                    items.push(surroundNode(nodes[i], 0, currentContext.focusOffset))
3744                }
3745                else {
3746                    items.push(surroundNode(nodes[i], 0, currentContext.anchorOffset))
3747                }
3748
3749                continue;
3750            }
3751
3752            items.push(surroundNode(nodes[i], 0, 0))
3753        }
3754
3755        return items;
3756    }
3757
3758    function getNodesInSelection(element, leftToRight) {
3759        if (element === document.body.parentElement) {
3760            return [];
3761        }
3762
3763        var nodes = getNodesInBetween(element, leftToRight);
3764
3765        if (!leftToRight) {
3766            nodes.reverse();
3767        }
3768
3769        return nodes;
3770    }
3771
3772    function getNodesInBetween(element, leftToRight) {
3773        var nodes = [];
3774
3775        if (element.nodeType === 3) {
3776            return [element];
3777        }
3778
3779        var inlineGroups = getInlineGroups(element.childNodes);
3780
3781        for (var i = 0; i < element.childNodes.length; i++) {
3782            var node = leftToRight ?
3783                element.childNodes[i] :
3784                element.childNodes[element.childNodes.length - 1 - i];
3785
3786            var tagName = node.tagName != undefined ? node.tagName.toLowerCase() : undefined;
3787
3788            if (forbiddenElementsString.indexOf(tagName) !== -1 || settings.exclude.indexOf(node) !== -1) {
3789                continue;
3790            }
3791
3792            if (validNodeTypes.indexOf(node.nodeType) === -1) {
3793                continue;
3794            }
3795
3796            if (node.textContent.trim() === "") {
3797                continue;
3798            }
3799
3800            var group = inlineGroups.filter(function (x) { return x.indexOf(node) > -1; })[0];
3801
3802            if (group) {
3803                nodes.push(group);
3804
3805                var indexAfterGroup = i + group.length;
3806                i = indexAfterGroup;
3807
3808                continue;
3809            }
3810
3811            if (node.nodeType === 1) {
3812                nodes = nodes.concat(getNodesInBetween(node, leftToRight));
3813            }
3814
3815            if (node.nodeType === 1) {
3816                continue;
3817            }
3818
3819            if (currentSelection.containsNode(node, true)) {
3820                nodes.push(node);
3821            }
3822        }
3823
3824        return nodes;
3825    }
3826
3827    function getInlineGroups(nodes) {
3828        var groups = [];
3829
3830        for (var i = 0; i < nodes.length; i++) {
3831            var node = nodes[i];
3832
3833            if (!currentSelection.containsNode(node, true)) {
3834                continue;
3835            }
3836
3837            var isBlockElement = node.nodeType === 1 && inlineElements.indexOf(node.tagName.toLowerCase()) === -1;
3838
3839            if (isBlockElement) {
3840                continue;
3841            }
3842
3843            if (validNodeTypes.indexOf(node.nodeType) === -1) {
3844                continue;
3845            }
3846
3847            if (node.textContent.trim() === "") {
3848                continue;
3849            }
3850
3851            var group = [node];
3852
3853            while (i < nodes.length &&
3854                node.nextSibling &&
3855                node.nextSibling.textContent.trim() !== "" &&
3856                currentSelection.containsNode(node.nextSibling, true) &&
3857                (node.nextSibling.nodeType === 3 || inlineElements.indexOf((node.nextSibling.tagName || "").toLowerCase()) !== -1)) {
3858                group.push(node.nextSibling);
3859
3860                i++;
3861                node = nodes[i];
3862            }
3863
3864            if (group.length > 1) {
3865                groups.push(group);
3866            }
3867        }
3868
3869        return groups;
3870    }
3871
3872    function surroundNode(node, startOffset, endOffset) {
3873        var range = document.createRange();
3874        var newParent = document.createElement('span');
3875        newParent.classList.add("talkify-selected-text");
3876
3877        if (Array.isArray(node)) {
3878            var originalNodes = [];
3879            node[0].parentNode.insertBefore(newParent, node[0]);
3880
3881            for (var i = 0; i < node.length; i++) {
3882                newParent.appendChild(node[i].cloneNode(true));
3883
3884                originalNodes.push(node[i].cloneNode(true));
3885
3886                node[i].parentNode.removeChild(node[i]);
3887            }
3888
3889            return {
3890                newNode: newParent,
3891                originalNode: originalNodes
3892            };
3893        }
3894
3895        var original = node.cloneNode(true);
3896
3897        if (node.nodeType === 1) {
3898            node.classList.add("talkify-selected-text");
3899
3900            return {
3901                newNode: node,
3902                originalNode: original
3903            };
3904        }
3905
3906        if (startOffset === 0 && endOffset === 0) {
3907            range.setStart(node, 0);
3908            range.setEnd(node, node.textContent.length);
3909            range.surroundContents(newParent);
3910        } else {
3911            range.setStart(node, 0);
3912            range.setEnd(node, node.textContent.length);
3913            range.surroundContents(newParent);
3914        }
3915
3916        return {
3917            originalNode: original,
3918            newNode: newParent
3919        };
3920    }
3921
3922    function deactivate() {
3923        activated = false;
3924
3925        document.removeEventListener('mouseup', onMouseUp, false);
3926        document.removeEventListener('keyup', onMouseUp, false);
3927    }
3928
3929    function activate() {
3930        if (activated) {
3931            console.warn("Selection activator is already activated");
3932            return;
3933        }
3934
3935        activated = true;
3936
3937        if (settings.exclude && NodeList.prototype.isPrototypeOf(settings.exclude)) {
3938            settings.exclude = Array.from(settings.exclude);
3939        }
3940
3941        document.addEventListener('mouseup', onMouseUp, false);
3942        document.addEventListener('keyup', onMouseUp, false);
3943        document.addEventListener('mousemove', onMouseUpdate, false);
3944        document.addEventListener('mouseenter', onMouseUpdate, false);
3945
3946        talkify.domExtensions.recordEvents.begin();
3947    }
3948
3949    function onMouseUp(e) {
3950        currentSelection = window.getSelection();
3951
3952        if (currentSelection.type === "Range" && currentSelection.toString().trim()) {
3953            if (currentContext.anchorNode === currentSelection.anchorNode && currentContext.focusNode === currentSelection.focusNode) {
3954                return;
3955            }
3956
3957            if (activatorHtml && (activatorHtml.contains(currentSelection.focusNode) || activatorHtml.contains(currentSelection.anchorNode))) {
3958                return;
3959            }
3960
3961            if (currentContext && currentContext.active) {
3962                return;
3963            }
3964
3965            if (orchestrateTimeout) {
3966                clearTimeout(orchestrateTimeout);
3967            }
3968
3969            orchestrateTimeout = setTimeout(orchestrateAfterSelection, 200);
3970        }
3971    }
3972
3973    function orchestrateAfterSelection() {
3974        if (currentSelection.rangeCount === 0) {
3975            return;
3976        }
3977
3978        removeActivator();
3979
3980        currentContext = {
3981            anchorNode: currentSelection.anchorNode,
3982            focusNode: currentSelection.focusNode,
3983            anchorOffset: currentSelection.anchorOffset,
3984            focusOffset: currentSelection.focusOffset,
3985            range: currentSelection.getRangeAt(0),
3986            active: false,
3987            elements: []
3988        };
3989
3990        var position = currentContext.anchorNode.compareDocumentPosition(currentContext.focusNode);
3991        currentContext.leftToRight = !(position & Node.DOCUMENT_POSITION_PRECEDING);
3992
3993        currentContext.nodes = getElementsInSelection();
3994
3995        console.log(currentContext.nodes);
3996
3997        if (!currentContext.nodes.length) {
3998            currentContext = {};
3999            return;
4000        }
4001
4002        renderActivator();
4003
4004        activatorHtml.addEventListener('click', activateControlcenter, false);
4005
4006        if (timeoutId) {
4007            clearTimeout(timeoutId);
4008        }
4009
4010        timeoutId = setTimeout(function () {
4011            removeActivator();
4012            currentContext = {};
4013        }, 2000);
4014    }
4015
4016    function activateControlcenter() {
4017        if (timeoutId) {
4018            clearTimeout(timeoutId);
4019        }
4020
4021        currentContext.active = true;
4022
4023        currentContext.elements = createItemsFromNodes(currentContext.nodes);
4024
4025        var elements = currentContext.elements.map(function (x) { return x.newNode; });
4026
4027        if (window.getSelection().empty) {
4028            window.getSelection().empty();
4029        } else if (window.getSelection().removeAllRanges) {
4030            window.getSelection().removeAllRanges();
4031        }
4032
4033        renderControlcenter();
4034        removeActivator();
4035
4036        player = new talkify.TtsPlayer({ controlcenter: { name: "local", container: controlcenterHtml } });
4037
4038        talkify.messageHub.subscribe("text-selection-activator", player.correlationId + ".player.*.disposed", disposeControlCenter);
4039
4040        if (settings.highlightText) {
4041            player.enableTextHighlighting();
4042        }
4043
4044        player.forceVoice(settings.voice);
4045        player.setRate(settings.rate);
4046
4047        if (settings.enhancedVisibility) {
4048            player.enableEnhancedTextVisibility();
4049        }
4050
4051        if (playlist) {
4052            playlist.dispose();
4053        }
4054
4055        playlist = new talkify.playlist()
4056            .begin()
4057            .usingPlayer(player)
4058            .withElements(elements)
4059            .subscribeTo({ onEnded: disposeControlCenter })
4060            .build();
4061    }
4062
4063    function disposeControlCenter() {
4064        if (player) {
4065            talkify.messageHub.unsubscribe("text-selection-activator", player.correlationId + ".player.*.disposed");
4066            player.dispose();
4067        }
4068
4069        if (playlist) {
4070            playlist.dispose();
4071        }
4072
4073        removeControlcenter();
4074
4075        if (currentContext.range.commonAncestorContainer.nodeType !== 1) {
4076            var element = currentContext.elements[0];
4077
4078            if (element) {
4079                element.newNode.parentElement.replaceChild(element.originalNode, element.newNode);
4080            }
4081        }
4082        else {
4083            restoreDom();
4084        }
4085
4086        currentContext = {};
4087    }
4088
4089    function restoreDom() {
4090        var elements = currentContext.elements;
4091
4092        for (var i = 0; i < elements.length; i++) {
4093            var originalNode = elements[i].originalNode;
4094            var currentNode = elements[i].newNode;
4095
4096            if (Array.isArray(originalNode)) {
4097                restoreDomForGroupedNodes(currentNode, originalNode);
4098            }
4099            else {
4100                currentNode.parentElement.replaceChild(originalNode, currentNode);
4101            }
4102
4103            var candidatesForEvents = Array.isArray(originalNode) ?
4104                originalNode.map(function (n) {
4105                    if (n.getElementsByTagName) {
4106                        return [n].concat(Array.from(n.getElementsByTagName('*')))
4107                    }
4108
4109                    return [n];
4110                }).flat() :
4111                [originalNode].concat(originalNode.getElementsByTagName ? Array.from(originalNode.getElementsByTagName('*')) : [])
4112
4113            var events = candidatesForEvents.map(function (x) {
4114                return talkify.domExtensions.recordEvents.getEvents(x);
4115            }).filter(function (x) { return x !== null });
4116
4117            for (var j = 0; j < candidatesForEvents.length; j++) {
4118                var element = candidatesForEvents[j];
4119
4120                var eventsToApply = events
4121                    .filter(function (x) { return element.isEqualNode(x.node) })
4122                    .map(function (x) { return x.events })
4123                    .flat();
4124
4125                for (var k = 0; k < eventsToApply.length; k++) {
4126                    var event = eventsToApply[k];
4127
4128                    element.addEventListener(event.type, event.listener, event.options);
4129                }
4130            }
4131        }
4132    }
4133
4134    function restoreDomForGroupedNodes(currentNode, originalNodes) {
4135        for (var i = 0; i < originalNodes.length; i++) {
4136            var originalNode = originalNodes[i];
4137            currentNode.parentElement.insertBefore(originalNode, currentNode);
4138        }
4139
4140        currentNode.parentElement.removeChild(currentNode);
4141    }
4142
4143    function removeControlcenter() {
4144        if (!controlcenterHtml) {
4145            return;
4146        }
4147
4148        controlcenterHtml.innerHTML = "";
4149        document.body.removeChild(controlcenterHtml);
4150        controlcenterHtml = null;
4151    }
4152
4153    function removeActivator() {
4154        if (!activatorHtml) {
4155            return;
4156        }
4157
4158        activatorHtml.removeEventListener("click", activateControlcenter, false);
4159
4160        activatorHtml.innerHTML = "";
4161
4162        if (activatorHtml.parentElement) {
4163            document.body.removeChild(activatorHtml);
4164        }
4165
4166        activatorHtml = null;
4167    }
4168
4169    function onMouseUpdate(e) {
4170        x = e.clientX;
4171        y = e.clientY;
4172    }
4173
4174    function renderActivator() {
4175        var div = document.createElement('div');
4176        div.classList.add("talkify-activator-wrapper");
4177
4178        div.innerHTML = '<div class="talkify-popup-activator">\
4179                            <button title="Talkify">\
4180                                <i class="fas fa-play"></i>' +
4181            settings.buttonText +
4182            '</button>\
4183                        </div>';
4184
4185        activatorHtml = div;
4186
4187        document.body.appendChild(activatorHtml);
4188
4189        var preferDown = currentContext.leftToRight || y < 50;
4190
4191        if (x >= window.outerWidth - 300) {
4192            x = x - (300 - (window.outerWidth - x));
4193        }
4194
4195        activatorHtml.style.left = x + 'px';
4196        activatorHtml.style.top = (y + (preferDown ? 15 : -45)) + 'px';
4197    }
4198
4199    function renderControlcenter() {
4200        controlcenterHtml = document.createElement('div');
4201        controlcenterHtml.classList.add("talkify-controlcenter-wrapper");
4202
4203        var closeButton = document.createElement('div');
4204        closeButton.classList.add('talkify-close');
4205
4206        closeButton.innerHTML = "<i class='fa fa-times'/>";
4207
4208        closeButton.addEventListener("click", disposeControlCenter);
4209
4210        controlcenterHtml.appendChild(closeButton);
4211
4212        controlcenterHtml.style.left = activatorHtml.style.left;
4213        controlcenterHtml.style.top = activatorHtml.style.top;
4214
4215        document.body.appendChild(controlcenterHtml);
4216    }
4217
4218    return {
4219        withEnhancedVisibility: function () {
4220            settings.enhancedVisibility = true;
4221            return this;
4222        },
4223        withVoice: function (voice) {
4224            settings.voice = voice;
4225            return this;
4226        },
4227        withTextHighlighting: function () {
4228            settings.highlightText = true;
4229            return this;
4230        },
4231        withButtonText: function (text) {
4232            settings.buttonText = text;
4233            return this;
4234        },
4235        withRate: function(rate){
4236            settings.rate = rate;
4237        },
4238        excludeElements: function (domElements) {
4239            settings.exclude = domElements;
4240            return this;
4241        },
4242        activate: activate,
4243        deactivate: deactivate
4244    }
4245}();
4246
4247talkify.domExtensions = {
4248    recordEvents: function () {
4249        var events = [];
4250        var initialized = false;
4251
4252        function addEventListenerDecorator(type, listener, options) {
4253            this._addEventListener(type, listener, options);
4254
4255            events.push({ node: this, type: type, listener: listener, options: options });
4256        }
4257
4258        function initialize() {
4259            if (initialized) {
4260                return;
4261            }
4262
4263            Node.prototype._addEventListener = Node.prototype.addEventListener;
4264            Node.prototype.addEventListener = addEventListenerDecorator;
4265            initialized = true;
4266        }
4267
4268        function getEvents(node) {
4269            var result = events.filter(function (e) {
4270                return e.node.isEqualNode(node);
4271            });
4272
4273            if (!result.length) {
4274                return {
4275                    events: []
4276                };
4277            }
4278
4279            return {
4280                node: result[0].node,
4281                events: result
4282            };
4283        }
4284
4285        return {
4286            getEvents: getEvents,
4287            begin: initialize
4288        }
4289    }()
4290}
4291
4292
4293},{}],19:[function(require,module,exports){
4294talkify = talkify || {};
4295talkify.textextractor = function () {
4296    var validElements = [];
4297
4298    var inlineElements = ['a', 'span', 'b', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'cite', 'code', 'dfn', 'em', 'kbd', 'strong', 'samp', 'var', 'a', 'bdo', 'q', 'sub', 'sup', 'label'];
4299    var forbiddenElementsString = ['img', 'map', 'object', 'script', 'button', 'input', 'select', 'textarea', 'style', 'code', 'nav', '#nav', '#navigation', '.nav', '.navigation', 'footer', 'rp', 'rt']; //removed br...revert?
4300    var forbiddenClasses = ['talkify-tts-table']
4301    var userExcludedElements = [];
4302
4303    function getVisible(elements) {
4304        var result = [];
4305
4306        for (var j = 0; j < elements.length; j++) {
4307            if (!isVisible(elements[j])) {
4308                continue;
4309            }
4310
4311            result.push(validElements[j]);
4312        }
4313
4314        return result;
4315    }
4316
4317    function isVisible(element) {
4318        return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
4319    }
4320
4321    function getStrippedText(text) {
4322        return text.replace(/(\r\n|\n|\r)/gm, "").trim();
4323    }
4324
4325    function isValidTextNode(node) {
4326        if (!node) {
4327            return false;
4328        }
4329
4330        if (node.nodeType === 3) {
4331            return getStrippedText(node.textContent).length >= 10;
4332        }
4333
4334        return false;
4335    }
4336
4337    function isValidAnchor(node) {
4338        var nrOfSiblings = getSiblings(node);
4339
4340        if (nrOfSiblings.length >= 1) {
4341            return true;
4342        }
4343
4344        var previous = node.previousSibling;
4345
4346        if (isValidTextNode(previous)) {
4347            return true;
4348        }
4349
4350        if (isValidTextNode(node.nextSibling)) {
4351            return true;
4352        }
4353
4354        return false;
4355    }
4356
4357    function isValidForGrouping(node) {
4358        if (node.nodeName === "BR") {
4359            return true;
4360        }
4361
4362        var isTextNode = node.nodeType === 3;
4363        var textLength = getStrippedText(node.textContent).length;
4364
4365        return (isTextNode && textLength >= 2) || (!isForbidden(node) && elementIsInlineElement(node));
4366    }
4367
4368    function getConnectedElements(nodes, firstIndex) {
4369        var connectedElements = [];
4370
4371        for (var l = firstIndex; l < nodes.length; l++) {
4372            if (isValidForGrouping(nodes[l])) {
4373                connectedElements.push(nodes[l]);
4374            } else {
4375                break;
4376            }
4377        }
4378
4379        return connectedElements;
4380    }
4381
4382    function group(elements) {
4383        //TODO: wrap in selectable element
4384        wrapping = document.createElement('span');
4385        wrapping.classList.add("superbar");
4386
4387        for (var j = 0; j < elements.length; j++) {
4388            wrapping.appendChild(elements[j].cloneNode(true));
4389        }
4390
4391        return wrapping;
4392    }
4393
4394    function wrapInSelectableElement(node) {
4395        wrapping = document.createElement('span');
4396        wrapping.classList.add("foobar");
4397        wrapping.innerText = node.textContent.trim();
4398        return wrapping;
4399    }
4400
4401    function wrapAndReplace(node) {
4402        var spanElement = wrapInSelectableElement(node);
4403
4404        if (node.parentNode) {
4405            node.parentNode.replaceChild(spanElement, node);
4406        }
4407
4408        return spanElement;
4409    }
4410
4411    function evaluate(nodes) {
4412        if (!nodes || nodes.length === 0) {
4413            return;
4414        }
4415
4416        for (var i = 0; i < nodes.length; i++) {
4417            var node = nodes[i];
4418
4419            if (isForbiddenByClass(node)) {
4420                continue;
4421            }    
4422
4423            if (elementIsParagraphOrHeader(node)) {
4424                validElements.push(node);
4425                continue;
4426            }
4427
4428            if (getForbiddenElements().indexOf(getSafeTagName(node).toLowerCase()) !== -1) {
4429                var forcedElement = (node.nodeType === 1 ? node : node.parentNode).querySelectorAll('h1, h2, h3, h4');
4430
4431                for (var k = 0; k < forcedElement.length; k++) {
4432                    validElements.push(forcedElement[k]);
4433                }
4434
4435                continue;
4436            }
4437
4438            if (getSafeTagName(node).toLowerCase() === 'a' && !isValidAnchor(node)) {
4439                continue;
4440            }
4441
4442            var connectedElements = getConnectedElements(nodes, i);
4443
4444            if (connectedElements.length > 1) {
4445                var wrapping = group(connectedElements);
4446                var isAboveThreshold = getStrippedText(wrapping.innerText).length >= 20;
4447
4448                if (isAboveThreshold) {
4449                    nodes[i].parentNode.replaceChild(wrapping, nodes[i]);
4450
4451                    for (var j = 0; j < connectedElements.length; j++) {
4452                        var parentNode = connectedElements[j].parentNode;
4453
4454                        if (!parentNode) {
4455                            continue;
4456                        }
4457
4458                        connectedElements[j].parentNode.removeChild(connectedElements[j]);
4459                    }
4460
4461                    validElements.push(wrapping);
4462
4463                    continue;
4464                }
4465            }
4466
4467            if (isValidTextNode(node)) {
4468                validElements.push(wrapAndReplace(node));
4469            }
4470
4471            evaluate(node.childNodes);
4472        }
4473    }
4474
4475    function extract(rootSelector, exclusions) {
4476        userExcludedElements = exclusions || [];
4477        validElements = [];
4478
4479        var topLevelElements = document.querySelectorAll(rootSelector + ' > ' + generateExcludesFromForbiddenElements());
4480
4481        for (var i = 0; i < topLevelElements.length; i++) {
4482            var element = topLevelElements[i];
4483
4484            if (elementIsParagraphOrHeader(element)) {
4485                validElements.push(element);
4486
4487                continue;
4488            }
4489
4490            evaluate(topLevelElements[i].childNodes);
4491        }
4492
4493        var result = getVisible(validElements);
4494
4495        return result;
4496    }
4497
4498    function generateExcludesFromForbiddenElements() {
4499        var result = '*';
4500
4501        var forbiddenElements = getForbiddenElements();
4502
4503        for (var i = 0; i < forbiddenElements.length; i++) {
4504            result += ':not(' + forbiddenElements[i] + ')';
4505        }
4506
4507        return result;
4508    }
4509
4510    function elementIsParagraphOrHeader(element) {
4511        if (element.nodeType === 3) {
4512            return false;
4513        }
4514
4515        return ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'].indexOf(getSafeTagName(element).toLowerCase()) != -1;
4516    }
4517
4518    function elementIsInlineElement(element) {
4519        if (element.nodeType === 3) {
4520            return false;
4521        }
4522
4523        return inlineElements.indexOf(getSafeTagName(element).toLowerCase()) != -1;
4524    }
4525
4526    function getSafeTagName(node) {
4527        return node.tagName || '';
4528    }
4529
4530    function getChildren(n, skipMe) {
4531        var r = [];
4532        for (; n; n = n.nextSibling)
4533            if (n.nodeType == 1 && n != skipMe && !isForbidden(n))
4534                r.push(n);
4535        return r;
4536    };
4537
4538    function getSiblings(n) {
4539        if (!n) {
4540            return [];
4541        }
4542
4543        return getChildren(n.parentNode.firstChild, n);
4544    }
4545
4546    function getForbiddenElements() {
4547        return forbiddenElementsString.concat(userExcludedElements);
4548    }
4549
4550    function isForbidden(node) { 
4551        return getForbiddenElements().indexOf(getSafeTagName(node).toLowerCase()) !== -1;
4552    }
4553
4554    function isForbiddenByClass(node){
4555        if(!node.classList){
4556            return false;
4557        }
4558
4559        return !!forbiddenClasses.find(function(x){ return node.closest("." + x) });
4560    }
4561
4562    return {
4563        extract: extract
4564    };
4565};
4566},{}],20:[function(require,module,exports){
4567talkify = talkify || {};
4568
4569talkify.generateGuid = function() {
4570    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
4571        var r = Math.random() * 16 | 0, v = c === "x" ? r : (r & 0x3 | 0x8);
4572        return v.toString(16);
4573    });
4574};
4575
4576talkify.log = function(){
4577    if(talkify.config.debug){
4578        console.log.apply(console, arguments);
4579    }
4580}
4581
4582talkify.toLowerCaseKeys = function(o) {
4583    var newO, origKey, newKey, value;
4584    if (o instanceof Array) {
4585      return o.map(function(value) {
4586          if (typeof value === "object") {
4587            value = talkify.toLowerCaseKeys(value);
4588          }
4589          return value;
4590      })
4591    } else {
4592      newO = {};
4593      for (origKey in o) {
4594        if (o.hasOwnProperty(origKey)) {
4595          newKey = (origKey.charAt(0).toLowerCase() + origKey.slice(1) || origKey).toString();
4596          value = o[origKey];
4597          if (value instanceof Array || (value !== null && value.constructor === Object)) {
4598            value = talkify.toLowerCaseKeys(value);
4599          }
4600          newO[newKey] = value;
4601        }
4602      }
4603    }
4604    return newO;
4605  }
4606
4607},{}],21:[function(require,module,exports){
4608talkify = talkify || {};
4609talkify.wordHighlighter = function (correlationId) {
4610    var currentItem = null;
4611    var currentPositions = [];
4612    var currentPosition = -1;
4613    var currentWordbreakMs = 0;
4614    var useEnhancedView = false;
4615    var enhancedView = null;
4616    var currentControlcenterPosition = null;
4617
4618    talkify.messageHub.subscribe("word-highlighter", correlationId + ".player.tts.seeked", setPosition);
4619    talkify.messageHub.subscribe("word-highlighter", [correlationId + ".player.tts.loading", correlationId + ".player.tts.disposed", correlationId + ".player.tts.ended"],
4620        function () {
4621            cancel();
4622
4623            if (enhancedView) {
4624                document.body.removeChild(enhancedView);
4625                enhancedView = null;
4626            }
4627        });
4628
4629    talkify.messageHub.subscribe("word-highlighter", correlationId + ".player.tts.play", function (message) {
4630        setupWordHightlighting(message.item, message.positions);
4631    });
4632
4633    talkify.messageHub.subscribe("word-highlighter", correlationId + ".player.tts.wordbreakchanged", function (wordbreakms) {
4634        currentWordbreakMs = wordbreakms;
4635    });
4636
4637    talkify.messageHub.subscribe("word-highlighter", correlationId + ".player.tts.timeupdated", function (timeInfo) {
4638        if (!currentPositions.length) {
4639            return;
4640        }
4641
4642        var time = timeInfo.currentTime * 1000;
4643
4644        var currentPos = 0;
4645
4646        if (time < currentPositions[0].Position) {
4647            if (currentPosition === -1) {
4648                return;
4649            }
4650
4651            currentPosition = 0;
4652            highlight(currentItem, currentPositions[0].Word, currentPositions[0].CharPosition);
4653            return;
4654        }
4655
4656        for (var i = 0; i < currentPositions.length; i++) {
4657            if (i === currentPositions.length - 1) {
4658                currentPos = i;
4659                break;
4660            }
4661
4662            var position = currentPositions[i].Position;
4663
4664            if (time >= position && time <= currentPositions[i + 1].Position) {
4665                currentPos = i;
4666                break;
4667            }
4668        }
4669
4670        if (currentPosition === currentPos) {
4671            return;
4672        }
4673
4674        currentPosition = currentPos;
4675
4676        highlight(currentItem, currentPositions[currentPos].Word, currentPositions[currentPos].CharPosition);
4677    });
4678
4679    function adjustPositionsForPrefix(item) {
4680        if (!item.prefix) {
4681            return;
4682        }
4683
4684        var itemPrefix = item.prefix.replace(/\s/g,'').replace(/[.,?!。,]/g, '');;
4685        var prefix = "";
4686
4687        var lastPrefixIndex = 0;
4688        var prefixEndsAtPosition = 0;
4689
4690        for (var i = 0; i < currentPositions.length; i++) {
4691            prefix += currentPositions[i].Word;
4692
4693            if (prefix === itemPrefix) {
4694                lastPrefixIndex = i;
4695                prefixEndsAtPosition = currentPositions[i].CharPosition + currentPositions[i].Word.length + 2; //": "
4696                break;
4697            }
4698        }
4699
4700        currentPositions.splice(0, lastPrefixIndex + 1);
4701
4702        for (var i = 0; i < currentPositions.length; i++) {
4703            currentPositions[i].CharPosition -= prefixEndsAtPosition;
4704        }
4705    }
4706
4707    talkify.messageHub.subscribe("word-highlighter", correlationId + ".player.tts.enhancedvisibilityset", function (value) {
4708        useEnhancedView = value;
4709
4710        if (!value && enhancedView) {
4711            document.body.removeChild(enhancedView);
4712            enhancedView = null;
4713        }
4714    });
4715
4716    talkify.messageHub.subscribe("word-highlighter", correlationId + ".controlcenter.attached", function (position) {
4717        currentControlcenterPosition = position;
4718
4719        adjustRenderPositionToControlCenter();
4720    });
4721
4722    talkify.messageHub.subscribe("word-highlighter", correlationId + ".controlcenter.detatched", function (position) {
4723        currentControlcenterPosition = position;
4724
4725        adjustRenderPositionToControlCenter();
4726    });
4727
4728    function adjustRenderPositionToControlCenter() {
4729        if (enhancedView && currentControlcenterPosition) {
4730            var enhancedViewPosition = enhancedView.getBoundingClientRect();
4731
4732            var hasOverlay = enhancedViewPosition.y >= currentControlcenterPosition.y &&
4733                enhancedViewPosition.y <= (currentControlcenterPosition.y + currentControlcenterPosition.height);
4734
4735            hasOverlay = hasOverlay || (currentControlcenterPosition.y >= enhancedViewPosition.y &&
4736                currentControlcenterPosition.y <= (enhancedViewPosition.y + enhancedViewPosition.height));
4737
4738            if (hasOverlay) {
4739                enhancedView.style.bottom = currentControlcenterPosition.height + "px";
4740            } else {
4741                enhancedView.style.bottom = "";
4742            }
4743        }
4744    }
4745
4746    function adjustPositionsToSsml(text, positions) {
4747        var internalPos = JSON.parse(JSON.stringify(positions));
4748
4749        var lastFound = 0;
4750
4751        for (var i = 0; i < internalPos.length; i++) {
4752            lastFound = text.indexOf(internalPos[i].Word, lastFound);
4753            internalPos[i].CharPosition = lastFound
4754        }
4755
4756        return internalPos;
4757    }
4758
4759    function highlight(item, word, charPosition) {
4760        resetCurrentItem();
4761
4762        currentItem = item;
4763        var text = item.element.innerText.trim();
4764
4765        // Customisation: prevent voice reading attributions ("[1], [2] etc")
4766        text = text.replace(/(\[\d*\])/g, '');
4767
4768        var sentence = findCurrentSentence(item, charPosition);
4769
4770        item.element.innerHTML =
4771            text.substring(0, sentence.start) +
4772            '<span class="talkify-sentence-highlight">' +
4773            text.substring(sentence.start, charPosition) +
4774            '<span class="talkify-word-highlight">' +
4775            text.substring(charPosition, charPosition + word.length) +
4776            '</span>' +
4777            text.substring(charPosition + word.length, sentence.end) +
4778            '</span>' +
4779            text.substring(sentence.end);
4780
4781        renderEnhancedView(text, sentence, charPosition, word);
4782    }
4783
4784    function cancel() {
4785        resetCurrentItem();
4786
4787        currentPositions = [];
4788        currentPosition = -1;
4789    }
4790
4791    function setupWordHightlighting(item, positions, startFrom) {
4792        cancel();
4793
4794        if (!positions.length) {
4795            return;
4796        }
4797
4798        if (item.ssml || item.wordbreakms || currentWordbreakMs) {
4799            currentPositions = adjustPositionsToSsml(item.text, positions);
4800        } else {
4801            currentPositions = positions;
4802        }
4803
4804        adjustPositionsForPrefix(item);
4805
4806        var i = startFrom || 0;
4807
4808        var internalCallback = function () {
4809            currentItem = item;
4810
4811            i++;
4812
4813            if (i >= positions.length) {
4814                window.setTimeout(function () {
4815                    item.element.innerHTML = item.originalElement.innerHTML;
4816
4817                    talkify.messageHub.publish(correlationId + ".wordhighlighter.complete", item);
4818
4819                    if (enhancedView) {
4820                        document.body.removeChild(enhancedView);;
4821                        enhancedView = null;
4822                        initialEnhancedViewPosition = null;
4823                    }
4824                }, 1000);
4825
4826                return;
4827            }
4828        };
4829
4830        internalCallback();
4831    }
4832
4833    function resetCurrentItem() {
4834        if (currentItem) {
4835            currentItem.element.innerHTML = currentItem.originalElement.innerHTML;
4836        }
4837    }
4838
4839    function setPosition(message) {
4840        var diff = 0;
4841        var timeInMs = message.time * 1000;
4842        var nextPosition = 0;
4843
4844        for (var i = 0; i < message.positions.length; i++) {
4845            var pos = message.positions[i];
4846
4847            if (pos.Position < timeInMs) {
4848                continue;
4849            }
4850
4851            diff = pos.Position - timeInMs;
4852            nextPosition = i;
4853
4854            break;
4855        }
4856
4857        var item = currentItem;
4858        var positions = message.positions;
4859
4860        cancel();
4861
4862        setTimeout(function () {
4863            setupWordHightlighting(item, positions, nextPosition);
4864        }, diff);
4865    }
4866
4867    function findCurrentSentence(item, charPosition) {
4868        var text = item.element.innerText.trim();
4869
4870        // Customisation: prevent voice reading attributions ("[1], [2] etc")
4871        text = text.replace(/(\[\d*\])/g, '');
4872
4873        var separators = ['\.', '\?', '!', '。'];
4874        var baseline = text.split(new RegExp('[' + separators.join('') + '](?!\\S)', 'g'));
4875        var result = [];
4876
4877        var currentSentence = "";
4878
4879        for (var i = 0; i < baseline.length; i++) {
4880            currentSentence += baseline[i] + ".";
4881
4882            var isLast = i + 1 === baseline.length;
4883
4884            if (isLast || baseline[i + 1].startsWith(" ") || baseline[i + 1].startsWith("\n")) {
4885                result.push(currentSentence);
4886                currentSentence = "";
4887            }
4888        }
4889
4890        var charactersTraversed = 0;
4891        var sentenceStart = 0;
4892        var sentenceEnd = text.length;
4893
4894        for (var i = 0; i < result.length; i++) {
4895            if (charPosition >= charactersTraversed && charPosition <= charactersTraversed + result[i].length) {
4896                if (charactersTraversed > 0) {
4897                    sentenceStart = charactersTraversed + 1;
4898                }
4899
4900                sentenceEnd = charactersTraversed + result[i].length;
4901                break;
4902            }
4903
4904            charactersTraversed += result[i].length;
4905        }
4906
4907        return {
4908            start: sentenceStart,
4909            end: sentenceEnd
4910        };
4911    }
4912
4913    function dispose() {
4914        talkify.messageHub.unsubscribe("word-highlighter", correlationId + ".player.tts.seeked");
4915        talkify.messageHub.unsubscribe("word-highlighter", [correlationId + ".player.tts.loading", correlationId + ".player.tts.disposed"]);
4916        talkify.messageHub.unsubscribe("word-highlighter", correlationId + ".player.tts.play");
4917        talkify.messageHub.unsubscribe("word-highlighter", correlationId + ".player.tts.timeupdated");
4918        talkify.messageHub.unsubscribe("word-highlighter", correlationId + ".player.tts.enhancedvisibilityset");
4919        talkify.messageHub.unsubscribe("word-highlighter", correlationId + ".controlcenter.attached");
4920        talkify.messageHub.unsubscribe("word-highlighter", correlationId + ".controlcenter.detatched");
4921    }
4922
4923    function renderEnhancedView(text, sentence, charPosition, word) {
4924        if (!useEnhancedView) {
4925            return;
4926        }
4927
4928        var html =
4929            '<p><span class="talkify-sentence-highlight">' +
4930            text.substring(sentence.start, charPosition) +
4931            '<span class="talkify-word-highlight">' +
4932            text.substring(charPosition, charPosition + word.length) +
4933            '</span>' +
4934            text.substring(charPosition + word.length, sentence.end) +
4935            '</span></p>';
4936
4937        if (enhancedView) {
4938            enhancedView.innerHTML = html;
4939
4940            return;
4941        }
4942
4943        enhancedView = document.createElement("div");
4944
4945        enhancedView.classList.add("talkify-enhanced-word-highligher");
4946
4947        enhancedView.innerHTML = html;
4948
4949        document.body.appendChild(enhancedView);
4950
4951        adjustRenderPositionToControlCenter();
4952    }
4953
4954    return {
4955        start: setupWordHightlighting,
4956        highlight: highlight,
4957        dispose: dispose
4958    };
4959};
4960},{}],22:[function(require,module,exports){
4961talkify = {};
4962},{}]},{},[1]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.