(function (options) {
    var _width = options.width;
    var _left = options.left;
    var _top = options.top;
    var _logLevel = 1;
    var _height = options.height;
    var _showPanel = options.showPanel === true;
    var _showConsole = options.showConsole === true;
    var _logPanel = document.createElement("div");
    if (document.body) {
        appendLogPanel();
    } else {
        addLoadEvent(appendLogPanel);
    }
    function addLoadEvent(fn) {
        if (window.addEventListener) {
            window.addEventListener("load", fn, false);
        }
        else if (document.attachEvent) {
            window.attachEvent("onload", fn);
        }
        else {
            window['on' + event] = fn;
        }
    }

    function appendLogPanel() {
        var css = "width:" + _width + "px; \
                    height:" + _height + "px; \
                    position:absolute; \
                    border:1px solid #ccc; \
                    background-color:#fff; \
                    left:" + _left + "px; \
                    top:" + _top + "px; \
                    word-wrap: break-word; \
                    overflow-y:scroll; \
                    display:" + (_showPanel ? "" : "none");
        _logPanel.style.cssText = css;
        document.body.appendChild(_logPanel);
    }

    function formatDate(pattern) {
        function fs(num) {
            return num < 10 ? "0" + num : num.toString();
        }

        var s = pattern.replace(/yyyy/g, this.getFullYear());
        s = s.replace(/MM/g, fs(this.getMonth() + 1));
        s = s.replace(/dd/g, fs(this.getDate()));
        s = s.replace(/hh/ig, fs(this.getHours()));
        s = s.replace(/mm/g, fs(this.getMinutes()));
        s = s.replace(/ss/g, fs(this.getSeconds()));
        var milliseconds = this.getMilliseconds();
        s = s.replace(/fff/g, milliseconds < 10 ? "00" + milliseconds : milliseconds < 100 ? "0" + milliseconds : milliseconds);
        return s;
    }

    function showMessage(msg, perfix, color) {
        if (_showPanel) {
            msg = formatDate.call(new Date(), "yyyy-MM-dd HH:mm:ss.fff") + " " + perfix + " : " + msg;
            var msgPanel = document.createElement("span");
            msgPanel.style.color = color;
            if (msgPanel.innerText !== undefined) {
                msgPanel.innerText = msg;
            } else {
                msgPanel.textContent = msg;
            }
            _logPanel.appendChild(msgPanel);
            _logPanel.appendChild(document.createElement("br"));
            _logPanel.scrollTop = _logPanel.scrollHeight;
        }
    }

    var LogCatLevel = {DEBUG: 3, INFO: 4, WARN: 5, ERROR: 6};

    function showLogCat(level, msg, tag) {
        if (window.LogCatInterface) {
            window.LogCatInterface.log(level, msg, tag || "SpeechJS Log");
        }
    }

    function reload(option) {
        if (option && _logPanel) {
            if (typeof option.width === "number") {
                _width = option.width;
                _logPanel.style.width = _width + "px";
            }
            if (typeof option.height === "number") {
                _height = option.height;
                _logPanel.style.height = _height + "px";
            }
            if (typeof option.left === "number") {
                _left = option.left;
                _logPanel.style.left = _left + "px";
            }
            if (typeof option.top === "number") {
                _top = option.top;
                _logPanel.style.top = _top + "px";
            }
            if (typeof option.showPanel === "boolean") {
                _showPanel = option.showPanel;
                if (_showPanel) {
                    _logPanel.style.display = "";
                } else {
                    _logPanel.style.display = "none";
                }
            }
            if (typeof option.showConsole === "boolean") {
                _showConsole = option.showConsole;
            }
        }
    }

    window.LogUtil = {debug: function (msg, tag) {
        showMessage(msg, "DEBUG", "gray");
        if (_showConsole && window.console && window.console.log) {
            window.console.log(msg);
        }
        showLogCat(LogCatLevel.DEBUG, msg, tag);
    }, info: function (msg, tag) {
        showMessage(msg, "INFO", "green");
        if (_showConsole && window.console && window.console.info) {
            window.console.info(msg);
        }
        showLogCat(LogCatLevel.INFO, msg, tag);
    }, warn: function (msg, tag) {
        showMessage(msg, "WARN", "orange");
        if (_showConsole && window.console && window.console.warn) {
            window.console.warn(msg);
        }
        showLogCat(LogCatLevel.WARN, msg, tag);
    }, error: function (msg, tag) {
        showMessage(msg, "ERROR", "red");
        if (_showConsole && window.console && window.console.error) {
            window.console.error(msg);
        }
        showLogCat(LogCatLevel.ERROR, msg, tag);
    }, reload: reload, clear: function () {
        _logPanel.innerHTML = "";
    }};
})({width: 300, height: 450, left: 630, top: 10, showPanel: false, showConsole: false});
(function (win) {
    var $this = win.SpeechJS = {};
    LogUtil.info("--------- SpeechJS 1.0.0.7 -----------");
    $this.SimpleAjax = new SimpleAjax();
    $this.Common = new Common();
    function SimpleAjax() {
        function ajax(option) {
            if (!option) {
                return;
            }
            var xmlhttp;
            if (window.XMLHttpRequest) {
                xmlhttp = new window.XMLHttpRequest();
            } else {
                xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            var url = encodeURI(option.url);
            var isAsync = option.async !== false;
            xmlhttp.open(option.method || "GET", url, isAsync);
            xmlhttp.send();
            if (isAsync) {
                xmlhttp.onreadystatechange = onReadyStateChange;
            } else {
                onReadyStateChange();
            }
            function onReadyStateChange() {
                if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
                    if (option.success) {
                        option.success(xmlhttp.responseText, xmlhttp.status, xmlhttp);
                    }
                } else if (xmlhttp.readyState == 4 && xmlhttp.status != 200) {
                    if (option.error) {
                        option.error(xmlhttp.responseText, xmlhttp.status, xmlhttp);
                    }
                }
                ;
            }
        }

        return{ajax: ajax};
    }

    function Common() {
        function clone(obj) {
            if (obj === undefined) {
                return undefined;
            }
            var objClone;
            if (obj.constructor === Object) {
                objClone = new obj.constructor();
            } else {
                objClone = new obj.constructor(obj.valueOf());
            }
            for (var key in obj) {
                if (objClone[key] !== obj[key]) {
                    if (typeof obj[key] === 'object') {
                        objClone[key] = $this.Common.clone(obj[key]);
                    } else {
                        objClone[key] = obj[key];
                    }
                }
            }
            objClone.toString = obj.toString;
            objClone.valueOf = obj.valueOf;
            return objClone;
        }

        function extend(objA, objB, safe) {
            if (typeof objA !== "object" || typeof objB !== "object") {
                return;
            }
            for (var prop in objB) {
                if (safe) {
                    if (!objA.hasOwnProperty(prop)) {
                        objA[prop] = objB[prop];
                    }
                }
                else {
                    objA[prop] = objB[prop];
                }
            }
        }

        function stopPropagation() {
            if (this.stopPropagation) {
                this.stopPropagation();
            } else {
                this.cancelBubble = true;
            }
        }

        function addEvent(ele, event, handleFun, data) {
            var func;
            if (data !== undefined) {
                func = (function (passData) {
                    return function () {
                        handleFun.call(ele, arguments[0], passData);
                    };
                }(data));
            } else {
                func = handleFun;
            }
            if (document.addEventListener) {
                ele.addEventListener(event, function () {
                    func.apply(ele, arguments);
                }, false);
            }
            else if (document.attachEvent) {
                ele.attachEvent("on" + event, function () {
                    arguments.data = data || "";
                    func.apply(ele, arguments);
                });
            }
            else {
                ele['on' + event] = function () {
                    func.apply(ele, arguments);
                };
            }
        }

        function getElementsByClassName(dom, className) {
            if (dom.getElementsByClassName) {
                return dom.getElementsByClassName(className);
            } else {
                var returnElements = [];
                var els = dom.all;
                var i = els.length;
                var pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");
                for (var idx = 0; idx < i; idx++) {
                    if (pattern.test(els[idx].className)) {
                        returnElements.push(els[idx]);
                    }
                }
                return returnElements;
            }
        }

        return{clone: clone, extend: extend, addEvent: addEvent, getElementsByClassName: getElementsByClassName};
    }

    function AbstractClassBase() {
        return{__class__: {check: function () {
            for (var prop in this) {
                if (prop.length > 2 && prop.substr(0, 2) === "__") {
                    continue;
                }
                if (prop.length > 1 && prop.substr(0, 1) === "$") {
                    continue;
                }
                if (!this.hasOwnProperty(prop) && typeof(this[prop]) === "function" && prop !== "constructor") {
                    if (!this.__proxy__.hasOwnProperty(prop) || typeof(this.__proxy__[prop]) !== "function") {
                        LogUtil.error(prop + " need to be implement!");
                    }
                }
            }
        }}};
    }

    function Speecher(settings) {
        var _this = this;
        var _exSettings;
        var _presenter;
        var _recorder;
        var _player;

        function extendSettings(option) {
            var exSetting = option;
            if (!exSetting.events) {
                exSetting.events = {};
            }
            var speecherId = option.id || "speecher-" + new Date().getTime();
            while ($this.getSpeecher(speecherId) !== undefined) {
                LogUtil.warn(speecherId + " already exist, regenerate one");
                speecherId = "speecher-" + new Date().getTime();
            }
            exSetting.id = speecherId;
            return exSetting;
        }

        function initRecorder(option) {
            var exOption = $this.Common.clone(option);
            if (!exOption.events) {
                exOption.events = {};
            }
            var eventsProxy = {onTrackInfo: function (e) {
                var exTrackInfo = e;
                if (_presenter) {
                    exTrackInfo = _presenter.TTS.extenndTrackInfo(e, _presenter.DOM);
                    _presenter.handleTrackInfo(exTrackInfo);
                }
                if (option.events && option.events.onTrackInfo) {
                    option.events.onTrackInfo.call(this, exTrackInfo);
                }
            }, onEvalResult: function (e) {
                if (option.events && option.events.onEvalResult) {
                    option.events.onEvalResult.call(this, e);
                }
            }};
            $this.Common.extend(exOption.events, eventsProxy);
            var record = RecorderFactory.getRecorder(exOption);
            return record;
        };
        function initPlayer(option) {
            var exOption = $this.Common.clone(option);
            if (!exOption.events) {
                exOption.events = {};
            }
            var eventsProxy = {onPlaying: function (e) {
                if (_presenter) {
                    _presenter.highlight(e.position * 1000);
                }
                if (option.events && option.events.onPlaying) {
                    option.events.onPlaying.call(this, e);
                }
            }};
            $this.Common.extend(exOption.events, eventsProxy);
            var player = PlayerFactory.getPlayer(exOption);
            return player;
        }

        function initPresenter(option) {
            var presenterOption = option.presenterOption;
            if (presenterOption) {
                presenterOption.events = presenterOption.events || {};
                var ttsSentences = new TTSSentences({ttsUrl: option.ttsUrl, ttsContent: option.ttsContent, onBeforeInitTTS: presenterOption.events.onBeforeInitTTS, onInitialized: function (isSuccess) {
                    LogUtil.debug("Speecher TTSSentences onInitialized isSuccess:" + isSuccess);
                    if (isSuccess) {
                        _presenter = PresenterFactory.getPresenter(presenterOption);
                        _presenter.__init__(presenterOption, ttsSentences);
                        _this.Presenter = _presenter;
                    } else {
                        LogUtil.error("TTS Sentences not initialize success!");
                    }
                    if (option.pyUrl || option.pyContent) {
                        handlePyInfo({pyUrl: option.pyUrl, pyContent: option.pyContent}, _presenter.DOM);
                    }
                    ;
                    presenterOption.events.onReady && presenterOption.events.onReady.call(_presenter);
                }});
            } else {
                LogUtil.warn("invalid Presenter initialize option");
            }
        }

        function handlePyInfo(pyInfo, dom) {
            var pyContent;
            if (pyInfo.pyContent) {
                pyContent = pyInfo.pyContent;
            } else if (pyInfo.pyUrl) {
                $this.SimpleAjax.ajax({url: encodeURI(pyInfo.pyUrl), async: false, success: function (result) {
                    pyContent = result;
                }, error: function (result, status) {
                    LogUtil.error("Get py error! status:" + status);
                }});
            }
            if (pyContent) {
                dom.Paper = pyContent;
                var senPaper;
                if (pyContent.indexOf("[content]\r\n") > -1) {
                    senPaper = pyContent.substring("[content]\r\n".length, pyContent.indexOf("\r\n[keywords]") - 1);
                } else {
                    senPaper = pyContent;
                }
                var senSpliter;
                if (new RegExp(/\.\r\n/).test(pyContent)) {
                    senSpliter = ".";
                } else {
                    senSpliter = "!";
                }
                if (new RegExp(senSpliter + '{2,}').test(senPaper)) {
                    throw new Error("试卷文件-多余的句子分隔符");
                }
                var senPaperArray = senPaper.split(senSpliter);
                if (senPaperArray.length !== dom.Sentences.length) {
                    LogUtil.warn("评测试卷与DOM不匹配...");
                    throw new Error("评测试卷与DOM不匹配..");
                } else {
                    var gloablSenIndex = 0;
                    for (var pIdx = 0; pIdx < dom.Paragraphs.length; pIdx++) {
                        var paraPaper = "";
                        var paragraph = dom.Paragraphs[pIdx];
                        for (var sIdx = 0; sIdx < paragraph.Sentences.length; sIdx++, gloablSenIndex++) {
                            var sen = paragraph.Sentences[sIdx];
                            sen.Paper = senPaperArray[gloablSenIndex];
                            paraPaper += sen.Paper + senSpliter;
                        }
                        paragraph.Paper = paraPaper;
                    }
                }
            }
        }

        function init(option) {
            var exSettings = _exSettings = extendSettings(option);
            LogUtil.debug("Speecher init | enter, id:" + exSettings.id);
            _this.id = exSettings.id;
            LogUtil.debug("Speecher init Presenter begin");
            initPresenter(exSettings);
            LogUtil.debug("Speecher initRecorder begin");
            var recorderOption = exSettings.recorderOption;
            if (recorderOption) {
                _recorder = initRecorder(recorderOption);
                _this.Recorder = _recorder;
            } else {
                LogUtil.warn("invalid Recorder initialize option");
            }
            LogUtil.debug("Speecher initPlayer begin");
            var playerOption = exSettings.playerOption;
            if (playerOption) {
                _player = initPlayer(playerOption);
                _this.Player = _player;
            } else {
                LogUtil.warn("invalid Player initialize option");
            }
        }

        function switchRecorder(option) {
            _this.Recorder = initRecorder(option);
        }

        function switchPlayer(option) {
            _this.Player = initPlayer(option);
        }

        init(settings);
        var publicMethods = {switchRecorder: switchRecorder, switchPlayer: switchPlayer};
        $this.Common.extend(_this, publicMethods);
        return _this;
    }

    function TTSSentences(option) {
        var _this = this;
        var _allSentences = [];
        var _allWords = [];
        var _allEvalWords = [];
        if (option.ttsUrl) {
            $this.SimpleAjax.ajax({url: option.ttsUrl, success: function (result) {
                initTTSContent(result);
            }, error: function (result, status) {
                LogUtil.error("Get tts error! status:" + status);
                if (option.onInitialized) {
                    option.onInitialized(false);
                }
            }});
        } else {
            initTTSContent(option.ttsContent);
        }
        function initTTSContent(ttsContent) {
            var ttsInfo;
            if (typeof ttsContent === "string") {
                ttsInfo = eval("(" + ttsContent + ")");
            } else {
                ttsInfo = ttsContent;
            }
            if (option.onBeforeInitTTS) {
                var result = option.onBeforeInitTTS.call(_this, ttsInfo);
                if (result !== false) {
                    initTTSInfo(ttsInfo);
                    ;
                }
            } else {
                initTTSInfo(ttsInfo);
            }
            if (option.onInitialized) {
                setTimeout(function () {
                    option.onInitialized(true);
                }, 0);
            }
            _this.getIndexByTime = getIndexByTime;
            _this.extenndTrackInfo = extenndTrackInfo;
        }

        function initTTSInfo(ttsJSON) {
            var sentences = ttsJSON.Sentences;
            var paragraphs = [];
            var paragraph = {Sentences: [], GlobalIndex: 0, Words: []};
            paragraphs.push(paragraph);
            var currParagraphsIndex = 0;
            var senIndex = 0;
            var senGlobalIndex = 0;
            var wordGlobalIndex = 0;
            for (var index = 0; index < sentences.length; index++) {
                var sen = sentences[index];
                sen.Words = [];
                sen.ParagraphIndex = currParagraphsIndex;
                if (sen.Text === "||。") {
                    currParagraphsIndex++;
                    paragraph = {Sentences: [], GlobalIndex: currParagraphsIndex, Words: []};
                    paragraphs.push(paragraph);
                    sentences.splice(index--, 1);
                    senIndex = 0;
                    continue;
                } else {
                    paragraph.Sentences.push(sen);
                    sen.Index = senIndex++;
                    sen.GlobalIndex = senGlobalIndex++;
                    sen.Words = [];
                    sen.ValidWords = [];
                    _allSentences.push(sen);
                    if (sen.Phrases.length < 1) {
                        throw new Error("TTS 句子中没有单词, 段落索引:" + currParagraphsIndex + ", 句子全局索引:" + senGlobalIndex);
                    }
                    for (var wordIndex = 0; wordIndex < sen.Phrases.length; wordIndex++) {
                        var word = sen.Phrases[wordIndex];
                        word.Index = wordIndex;
                        word.GlobalIndex = wordGlobalIndex++;
                        word.SentenceIndex = sen.GlobalIndex;
                        word.ParagraphIndex = currParagraphsIndex;
                        _allWords.push(word);
                        sen.Words.push(word);
                        if (word.PY != "" && word.PT.indexOf("</Font>") < 0) {
                            sen.ValidWords.push(word);
                            _allEvalWords.push(word);
                        }
                        paragraph.Words.push(word);
                    }
                    delete sen.Phrases;
                }
            }
            for (var pIdx = 0; pIdx < paragraphs.length; pIdx++) {
                paragraph = paragraphs[pIdx];
                if (paragraph.Sentences.length < 1) {
                    throw new Error("TTS 段落中没有句子, 段落索引:" + pIdx);
                }
                paragraph.ST = paragraph.Sentences[0].ST;
                paragraph.ET = paragraph.Sentences[paragraph.Sentences.length - 1].ET;
            }
            _this.Paragraphs = paragraphs;
            _this.AllSentences = _allSentences;
            _this.AllWords = _allWords;
            _this.AllEvalWords = _allEvalWords;
        }

        function getIndexByTime(position) {
            var wordIndex = 0;
            var sen = undefined;
            for (var senIndex = 0; senIndex < _this.AllSentences.length; senIndex++) {
                sen = _this.AllSentences[senIndex];
                if (sen.ET * 1000 < position) {
                    continue;
                }
                var avrageWordTime = (sen.ET - sen.ST) * 1000 / sen.ValidWords.length;
                if (position < sen.ST * 1000) {
                    wordIndex = -1;
                } else {
                    wordIndex = Math.floor((position - sen.ST * 1000) / avrageWordTime);
                }
                break;
            }
            return{ParagraphIndex: sen === undefined ? -1 : sen.ParagraphIndex, SentenceIndex: senIndex > _this.AllSentences.length - 1 ? _this.AllSentences.length - 1 : senIndex, WordIndex: wordIndex > sen.ValidWords.length - 1 ? sen.ValidWords.length - 1 : wordIndex};
        }

        function extenndTrackInfo(trackInfo, dom) {
            var exTrackInfo = {TrackType: trackInfo.TrackType, ReadStatus: trackInfo.ReadStatus, OriginalTrackInfo: trackInfo};
            var sentencs = dom.Sentences;
            var words = dom.Words;
            if (trackInfo.TrackType === 2) {
                exTrackInfo.SentenceIndex = trackInfo.Index;
                exTrackInfo.ParagraphIndex = sentencs[trackInfo.Index].ParagraphIndex;
            }
            else if (trackInfo.TrackType === 3) {
                exTrackInfo.WordIndex = trackInfo.Index;
                exTrackInfo.SentenceIndex = words[trackInfo.Index].SentenceIndex;
                exTrackInfo.ParagraphIndex = words[trackInfo.Index].ParagraphIndex;
            }
            ;
            return exTrackInfo;
        }

        return _this;
    }

    var PresenterFactory = {getPresenter: function (option) {
        LogUtil.debug("PresenterFactory::getPresenter | enter");
        var presenter = undefined;
        if (option.customPresenter) {
            LogUtil.debug("PresenterFactory::create custom presenter, typeof custom presenter:" + typeof option.customPresenter);
            presenter = new option.customPresenter(option);
        } else {
            LogUtil.debug("PresenterFactory::create default presenter name:" + option.presenter);
            switch (option.presenter) {
                case $this.Presenters.Chapter:
                    presenter = new ChapterPresenter(option);
                    break;
                case $this.Presenters.Sentence:
                    presenter = new SentencePresenter(option);
                    break;
                case $this.Presenters.Words:
                    presenter = new WordPresenter(option);
                    break;
                default:
                    LogUtil.error("Unkonw presenter category:" + option.category);
                    break;
            }
        }
        LogUtil.debug("PresenterFactory::getPresenter | leave");
        return presenter;
    }};

    function PresenterBase() {
        var _this = this;
        var _proxy, _events;
        _this.__proxy__ = _proxy = {};
        _this.__events__ = _events = {};
        _this.TTS = {};
        _this.DOM = {};
        function cacheDOM(contentPanel) {
            var senGlobalIndex = 0;
            var wordGlobalIndex = 0;
            var paragraphPanels = $this.Common.getElementsByClassName(contentPanel, $this.Defines.ParagraphClass);
            var sentencePanels = [];
            var wordPanels = [];
            for (var pIndex = 0; pIndex < paragraphPanels.length; pIndex++) {
                var paragraph = paragraphPanels[pIndex];
                paragraph.GlobalIndex = pIndex;
                paragraph.Sentences = [];
                paragraph.Words = [];
                var sentences = $this.Common.getElementsByClassName(paragraph, $this.Defines.SentenceClass);
                for (var sIndex = 0; sIndex < sentences.length; sIndex++, senGlobalIndex++) {
                    var sen = sentences[sIndex];
                    sen.GlobalIndex = senGlobalIndex;
                    sen.ParagraphIndex = pIndex;
                    sen.Words = [];
                    var words = $this.Common.getElementsByClassName(sen, $this.Defines.WordClass);
                    paragraph.Sentences.push(sen);
                    sentencePanels.push(sen);
                    for (var wIndex = 0; wIndex < words.length; wIndex++) {
                        var word = words[wIndex];
                        word.GlobalIndex = wordGlobalIndex++;
                        word.SentenceIndex = senGlobalIndex;
                        word.ParagraphIndex = pIndex;
                        wordPanels.push(word);
                        sen.Words.push(word);
                        paragraph.Words.push(word);
                    }
                }
            }
            return{Paragraphs: paragraphPanels, Sentences: sentencePanels, Words: wordPanels};
        }

        function registerEvents(dom, events) {
            for (var pIdx = 0; pIdx < dom.Paragraphs.length; pIdx++) {
                var paragraph = dom.Paragraphs[pIdx];
                $this.Common.addEvent(paragraph, "click", function () {
                    if (events && events.onParagraphPanelClick) {
                        events.onParagraphPanelClick.apply(this, arguments);
                    }
                }, _this.TTS.Paragraphs[pIdx]);
                for (var sIdx = 0; sIdx < paragraph.Sentences.length; sIdx++) {
                    var sentence = paragraph.Sentences[sIdx];
                    $this.Common.addEvent(sentence, "click", function () {
                        if (events && events.onSentencePanelClick) {
                            events.onSentencePanelClick.apply(this, arguments);
                        }
                    }, _this.TTS.Paragraphs[pIdx].Sentences[sIdx]);
                    for (var wIdx = 0; wIdx < sentence.Words.length; wIdx++) {
                        $this.Common.addEvent(sentence.Words[wIdx], "click", function () {
                            if (events && events.onWordPanelClick) {
                                events.onWordPanelClick.apply(this, arguments);
                            }
                        }, _this.TTS.Paragraphs[pIdx].Sentences[sIdx].Words[wIdx]);
                    }
                }
            }
        };
        _this.__init__ = function (option, ttsInfo) {
            var events = option.events;
            var contentPanel = document.getElementById(option.contentPanelId);
            _this.TTS = ttsInfo;
            contentPanel.innerHTML = _this.buildHTML(ttsInfo);
            if (option.cacheDOM == false) {
                return;
            }
            _this.DOM = cacheDOM(contentPanel);
            if (option.registerEvents == false) {
                return;
            }
            registerEvents(_this.DOM, events);
        };
        _this.$handleEvalResult = function (evalResult) {
            var words = [];
            for (var senIdx = 0; senIdx < evalResult.Sentences.length; senIdx++) {
                var sen = evalResult.Sentences[senIdx];
                for (var wIdx = 0; wIdx < sen.Words.length; wIdx++) {
                    var word = sen.Words[wIdx];
                    words.push(word);
                }
            }
            _this._evalResultWords = words;
        };
        _this.$getReadWord = function (position) {
            var word;
            for (var wIdx = 0; wIdx < _this._evalResultWords.length; wIdx++) {
                word = _this._evalResultWords[wIdx];
                if (word.BegPos <= position && position < word.EndPos) {
                    return _this.DOM.Words[word.GlobalIndex];
                }
            }
            return undefined;
        };
        _this.buildHTML = function (ttsInfo) {
            return _proxy.buildHTML(ttsInfo);
        };
        _this.handleTrackInfo = function (exTrackInfo) {
            _proxy.handleTrackInfo(exTrackInfo);
        };
        _this.highlight = function (postion) {
            _proxy.highlight(postion);
        };
        _this.clearHighlight = function () {
            _proxy.clearHighlight();
        };
        _events.onBeforeHighlight = function () {
            return true;
        };
        _events.onHighlight = function () {
        };
        return _this;
    }

    PresenterBase.prototype = new AbstractClassBase();
    PresenterBase.prototype.constructor = PresenterBase;
    $this.PresenterBase = PresenterBase;
    function ChapterPresenter() {
        var _this = this;
        this.__proxy__.buildHTML = function (tts) {
            var paragraphs = tts.Paragraphs;
            var html = "";
            for (var paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
                var paragraph = paragraphs[paragraphIndex];
                html += '<p class="' + $this.Defines.ParagraphClass + ' speech-chap-para" pIndex="' + paragraphIndex + '">';
                for (var senIndex = 0; senIndex < paragraph.Sentences.length; senIndex++) {
                    var sen = paragraph.Sentences[senIndex];
                    html += '<span class="' + $this.Defines.SentenceClass + '" sIdx="' + senIndex + '">';
                    var words = sen.Words;
                    for (var wIndex = 0; wIndex < words.length; wIndex++) {
                        var word = words[wIndex];
                        if (word.PY == "" && word.PT.indexOf("</Font>") > -1) {
                            html += '<span wIdx="' + wIndex + '">' + word.PT.replace("</Font>", "") + ' </span>';
                        }
                        else {
                            html += '<span class="' + SpeechJS.Defines.WordClass + '" wIdx="' + wIndex + '" ttsIdx="' + word.GlobalIndex + '">' + word.PT + ' </span>';
                        }
                    }
                    html += '</span>';
                }
                html += '</p>';
            }
            return html;
        };
        this.__proxy__.highlight = function (position) {
            LogUtil.debug("ChapterPresenter highlight:" + position);
            var indexInfo = _this.TTS.getIndexByTime(position);
            if (indexInfo.ParagraphIndex > -1 && indexInfo.SentenceIndex > -1 && indexInfo.WordIndex > -1) {
                var sen = _this.DOM.Sentences[indexInfo.SentenceIndex];
                sen.Words[indexInfo.WordIndex].style.color = "#0013ec";
            }
        };
        this.__proxy__.handleTrackInfo = function (trackInfo) {
            if (trackInfo.TrackType === 2) {
            }
            else if (trackInfo.TrackType === 3) {
                var wordPanel = _this.DOM.Words[trackInfo.WordIndex];
                if (wordPanel !== undefined) {
                    var css;
                    switch (trackInfo.ReadStatus) {
                        case 0:
                            css = "unread";
                            break;
                        case 1:
                            css = "checking";
                            break;
                        case 2:
                        case 3:
                            css = "read";
                            break;
                        default:
                            css = "unknow";
                            break;
                    }
                    if (css == "read") {
                        wordPanel.style.color = "blue";
                    }
                }
            }
        };
        this.__proxy__.clearHighlight = function () {
            LogUtil.debug("ChapterPresenter clearHighlight");
            for (var index = 0; index < _this.DOM.Words.length; index++) {
                _this.DOM.Words[index].style.color = "#000";
            }
        };
        this.__class__.check.call(this);
    }

    ChapterPresenter.prototype = new PresenterBase();
    ChapterPresenter.prototype.constructor = ChapterPresenter;
    function SentencePresenter() {
        this.__proxy__.buildHTML = function (ttsInfo) {
            var paragraphs = ttsInfo.Paragraphs;
            var html = '<div class="' + $this.Defines.ParagraphClass + '">';
            for (var paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
                var paragraph = paragraphs[paragraphIndex];
                for (var senIndex = 0; senIndex < paragraph.Sentences.length; senIndex++) {
                    var sen = paragraph.Sentences[senIndex];
                    html += '<span class="' + $this.Defines.SentenceClass + ' speech-sentence-sen" sIdx="' + senIndex + '">';
                    var words = sen.Words;
                    for (var wIndex = 0; wIndex < words.length; wIndex++) {
                        var word = words[wIndex];
                        if (word.PY == "" && word.PT.indexOf("</Font>") > -1) {
                            html += '<span wIdx="' + wIndex + '">' + word.PT.replace("</Font>", "") + ' </span>';
                        }
                        else {
                            html += '<span class="' + SpeechJS.Defines.WordClass + '" wIdx="' + wIndex + '" ttsIdx="' + word.GlobalIndex + '">' + word.PT + ' </span>';
                        }
                    }
                    html += '</span>';
                }
            }
            html += '</div>';
            return html;
        };
        this.__proxy__.handleTrackInfo = function (trackInfo) {
        };
        this.__proxy__.highlight = function () {
        };
        this.__proxy__.clearHighlight = function () {
        };
        this.__class__.check.call(this);
    }

    SentencePresenter.prototype = new PresenterBase();
    SentencePresenter.prototype.constructor = SentencePresenter;
    function WordPresenter() {
        this.__proxy__.buildHTML = function (ttsInfo) {
            var paragraphs = ttsInfo.Paragraphs;
            var html = "<div class='" + $this.Defines.ParagraphClass + "'><div class='" + $this.Defines.SentenceClass + "'>";
            for (var paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
                var paragraph = paragraphs[paragraphIndex];
                for (var senIndex = 0; senIndex < paragraph.Sentences.length; senIndex++) {
                    var sen = paragraph.Sentences[senIndex];
                    var words = sen.Words;
                    for (var wIndex = 0; wIndex < words.length; wIndex++) {
                        html += '<span class="' + $this.Defines.WordClass + ' speech-words-word" wIdx="' + wIndex + '">' + words[wIndex].PT + ' </span>';
                    }
                }
            }
            html += "</div></div>";
            return html;
        };
        this.__proxy__.handleTrackInfo = function (trackInfo) {
            LogUtil.debug("WordPresenter", trackInfo);
        };
        this.__proxy__.highlight = function () {
        };
        this.__proxy__.clearHighlight = function () {
        };
        this.__class__.check.call(this);
    }

    WordPresenter.prototype = new PresenterBase();
    WordPresenter.prototype.constructor = WordPresenter;
    var RecorderFactory = {getRecorder: function (option) {
        var recorder;
        switch (option.adapter) {
            case $this.Adapters.WPFRecorder:
                recorder = new WPFRecorderAdapter({onSoundEnergy: option.events.onSoundEnergy || function () {
                }, onRecordBegin: option.onRecordBegin || function () {
                }, onTrackInfo: option.onTrackInfo || function () {
                }});
                break;
            case $this.Adapters.SpeechRecorder:
                recorder = new win.SpeechRecorderAdapter(option);
                break;
            case $this.Adapters.AndroidRecorder:
                recorder = new AndroidRecorderAdapter(option);
                break;
            default:
                throw new Error("SpeechJS - unknow recorder adapter");
        }
        return recorder;
    }};

    function RecorderAdapterBase() {
        var _recordAdapter = this;
        var _events;
        _recordAdapter.__events__ = _events = {};
        _recordAdapter.__proxy__ = {};
        _recordAdapter.record = function (option) {
            return this.__proxy__.record(option);
        };
        _recordAdapter.stop = function (isForce) {
            return this.__proxy__.stop(isForce);
        };
        _events.onReady = function () {
        };
        _events.onBeforeRecord = function () {
            return true;
        };
        _events.onRecordBegin = function () {
        };
        _events.onRecordEnd = function () {
        };
        _events.onRecording = function () {
        };
        _events.onTrackInfo = function () {
        };
        _events.onEvalResult = function () {
        };
        _events.onError = function () {
        };
        return _recordAdapter;
    }

    RecorderAdapterBase.prototype = new AbstractClassBase();
    RecorderAdapterBase.prototype.constructor = RecorderAdapterBase;
    $this.RecorderAdapterBase = RecorderAdapterBase;
    var PlayerFactory = {getPlayer: function (option) {
        var player = undefined;
        switch (option.adapter) {
            case $this.Adapters.WPFPlayer:
                player = new WPFPlayerAdapter({onPlayBegin: option.onPlayBegin || function () {
                }, onPlaying: option.onPlaying || function () {
                }});
                break;
            case $this.Adapters.JWPlayer:
                player = new JWPlayerAdapter(option);
            case $this.Adapters.MediaPlayer:
                break;
            case $this.Adapters.AndroidPlayer:
                player = new AndroidPlayerAdapter(option);
                break;
            case $this.Adapters.SmartBookPlayer:
                player = new SmartBookPlayerAdapter(option);
                break;
            default:
                throw new Error("SpeechJS - unknow player adapter");
        }
        return player;
    }};

    function PlayerAdapterBase() {
        var playerAdapter = this;
        var _events;
        playerAdapter.__proxy__ = {};
        playerAdapter.__events__ = _events = {};
        playerAdapter.loadAudio = function (url) {
            this.__proxy__.loadAudio(url);
        };
        playerAdapter.play = function () {
            this.__proxy__.play();
        };
        playerAdapter.playRange = function (beginPos, endPos) {
            this.__proxy__.playRange(beginPos, endPos);
        };
        playerAdapter.pause = function () {
            this.__proxy__.pause();
        };
        playerAdapter.stop = function () {
            this.__proxy__.stop();
        };
        playerAdapter.getVolume = function () {
            return this.__proxy__.getVolume();
        };
        playerAdapter.setVolume = function (volume) {
            return this.__proxy__.setVolume(volume);
        };
        playerAdapter.setMute = function (isMute) {
            this.__proxy__.setMute(isMute);
        };
        _events.onReady = function () {
        };
        _events.onPlayBegin = function () {
        };
        _events.onBuffering = function () {
        };
        _events.onPlaying = function () {
        };
        _events.onPause = function () {
        };
        _events.onStop = function () {
        };
        _events.onError = function () {
        };
        return playerAdapter;
    }

    PlayerAdapterBase.prototype = new AbstractClassBase();
    PlayerAdapterBase.prototype.constructor = PlayerAdapterBase;
    $this.PlayerAdapterBase = PlayerAdapterBase;
    (function (speechJS) {
        speechJS.Adapters = {SpeechRecorder: "SpeechRecorder", WPFRecorder: "WPFRecorder", AndroidRecorder: "AndroidRecorder", JWPlayer: "JWPlayer", MediaPlayer: "MediaPlayer", WPFPlayer: "WPFPlayer", AndroidPlayer: "AndroidPlayer", SmartBookPlayer: "SmartBookPlayer"};
        speechJS.Presenters = {Chapter: "Chapter", Sentence: "Sentence", Words: "Words"};
        speechJS.Defines = {ParagraphClass: "spc-para", SentenceClass: "spc-sent", WordClass: "spc-word"};
        var _speechers = [];
        speechJS.getSpeecher = function (speecherId) {
            if (speecherId === undefined) {
                return undefined;
            }
            for (var i = 0; i < _speechers.length; i++) {
                if (_speechers[i].id == speecherId) {
                    return speechJS.Speechers[i];
                }
            }
            return undefined;
        };
        speechJS.load = function (option) {
            var speecher = new Speecher(option);
            _speechers.push(speecher);
            return speecher;
        };
        speechJS.Speechers = _speechers;
    })($this);
})(window);