text: /**
   * The copyright in this software is being made available under the BSD License,
   * included below. This software may be subject to other third party and contributor
   * rights, including patent rights, and no such rights are granted under this license.
   *
   * Copyright (c) 2013, Dash Industry Forum.
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without modification,
   * are permitted provided that the following conditions are met:
   *  * Redistributions of source code must retain the above copyright notice, this
   *  list of conditions and the following disclaimer.
   *  * Redistributions in binary form must reproduce the above copyright notice,
   *  this list of conditions and the following disclaimer in the documentation and/or
   *  other materials provided with the distribution.
   *  * Neither the name of Dash Industry Forum nor the names of its
   *  contributors may be used to endorse or promote products derived from this software
   *  without specific prior written permission.
   *
   *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
   *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
   *  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
   *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
   *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   *  POSSIBILITY OF SUCH DAMAGE.
   */
  import Constants from '../constants/Constants';
  import FactoryMaker from '../../core/FactoryMaker';
  import TextSourceBuffer from './TextSourceBuffer';
  import TextTracks from './TextTracks';
  import VTTParser from '../utils/VTTParser';
  import TTMLParser from '../utils/TTMLParser';
  import EventBus from '../../core/EventBus';
  import Events from '../../core/events/Events';
  
  function TextController() {
  
      let context = this.context;
      let instance;
      let textSourceBuffer;
  
      let errHandler,
          dashManifestModel,
          manifestModel,
          mediaController,
          videoModel,
          streamController,
          textTracks,
          vttParser,
          ttmlParser,
          eventBus,
          defaultLanguage,
          lastEnabledIndex,
          textDefaultEnabled, // this is used for default settings (each time a file is loaded, we check value of this settings )
          allTracksAreDisabled, // this is used for one session (when a file has been loaded, we use this settings to enable/disable text)
          forceTextStreaming;
  
      function setup() {
  
          defaultLanguage = '';
          lastEnabledIndex = -1;
          textDefaultEnabled = true;
          forceTextStreaming = false;
          textTracks = TextTracks(context).getInstance();
          vttParser = VTTParser(context).getInstance();
          ttmlParser = TTMLParser(context).getInstance();
          textSourceBuffer = TextSourceBuffer(context).getInstance();
          eventBus = EventBus(context).getInstance();
  
          textTracks.initialize();
          eventBus.on(Events.TEXT_TRACKS_QUEUE_INITIALIZED, onTextTracksAdded, instance);
  
          resetInitialSettings();
      }
  
      function setConfig(config) {
          if (!config) {
              return;
          }
          if (config.errHandler) {
              errHandler = config.errHandler;
          }
          if (config.dashManifestModel) {
              dashManifestModel = config.dashManifestModel;
          }
          if (config.manifestModel) {
              manifestModel = config.manifestModel;
          }
          if (config.mediaController) {
              mediaController = config.mediaController;
          }
          if (config.videoModel) {
              videoModel = config.videoModel;
          }
          if (config.streamController) {
              streamController = config.streamController;
          }
          if (config.textTracks) {
              textTracks = config.textTracks;
          }
          if (config.vttParser) {
              vttParser = config.vttParser;
          }
          if (config.ttmlParser) {
              ttmlParser = config.ttmlParser;
          }
  
          // create config for source buffer
          textSourceBuffer.setConfig({
              errHandler: errHandler,
              dashManifestModel: dashManifestModel,
              manifestModel: manifestModel,
              mediaController: mediaController,
              videoModel: videoModel,
              streamController: streamController,
              textTracks: textTracks,
              vttParser: vttParser,
              ttmlParser: ttmlParser
          });
      }
  
      function getTextSourceBuffer() {
          return textSourceBuffer;
      }
  
      function getAllTracksAreDisabled() {
          return allTracksAreDisabled;
      }
  
      function addEmbeddedTrack(mediaInfo) {
          textSourceBuffer.addEmbeddedTrack(mediaInfo);
      }
  
      function setTextDefaultLanguage(lang) {
          if (typeof lang !== 'string') {
              return;
          }
  
          defaultLanguage = lang;
      }
  
      function getTextDefaultLanguage() {
          return defaultLanguage;
      }
  
      function onTextTracksAdded(e) {
          let tracks = e.tracks;
          let index = e.index;
  
          tracks.some((item, idx) => {
              if (item.lang === defaultLanguage) {
                  this.setTextTrack(idx);
                  index = idx;
                  return true;
              }
          });
  
          if (!textDefaultEnabled) {
              // disable text at startup
              this.setTextTrack(-1);
          }
  
          lastEnabledIndex = index;
          eventBus.trigger(Events.TEXT_TRACKS_ADDED, {
              enabled: isTextEnabled(),
              index: index,
              tracks: tracks
          });
      }
  
      function setTextDefaultEnabled(enable) {
          if (typeof enable !== 'boolean') {
              return;
          }
          textDefaultEnabled = enable;
  
          if (!textDefaultEnabled) {
              // disable text at startup
              this.setTextTrack(-1);
          }
      }
  
      function getTextDefaultEnabled() {
          return textDefaultEnabled;
      }
  
      function enableText(enable) {
          if (typeof enable !== 'boolean') {
              return;
          }
  
          if (isTextEnabled() !== enable) {
              // change track selection
              if (enable) {
                  // apply last enabled tractk
                  this.setTextTrack(lastEnabledIndex);
              }
  
              if (!enable) {
                  // keep last index and disable text track
                  lastEnabledIndex = this.getCurrentTrackIdx();
                  this.setTextTrack(-1);
              }
          }
      }
  
      function isTextEnabled() {
          let enabled = true;
          if (allTracksAreDisabled && !forceTextStreaming) {
              enabled = false;
          }
          return enabled;
      }
  
      // when set to true NextFragmentRequestRule will allow schedule of chunks even if tracks are all disabled. Allowing streaming to hidden track for external players to work with.
      function enableForcedTextStreaming(enable) {
          if (typeof enable !== 'boolean') {
              return;
          }
          forceTextStreaming = enable;
      }
  
      function setTextTrack(idx) {
          //For external time text file,  the only action needed to change a track is marking the track mode to showing.
          // Fragmented text tracks need the additional step of calling TextController.setTextTrack();
          let config = textSourceBuffer.getConfig();
          let fragmentModel = config.fragmentModel;
          let fragmentedTracks = config.fragmentedTracks;
          let videoModel = config.videoModel;
          let mediaInfosArr,
              streamProcessor;
  
          allTracksAreDisabled = idx === -1 ? true : false;
  
          let oldTrackIdx = textTracks.getCurrentTrackIdx();
          if (oldTrackIdx !== idx) {
              textTracks.setModeForTrackIdx(oldTrackIdx, Constants.TEXT_HIDDEN);
              textTracks.setCurrentTrackIdx(idx);
              textTracks.setModeForTrackIdx(idx, Constants.TEXT_SHOWING);
  
              let currentTrackInfo = textTracks.getCurrentTrackInfo();
  
              if (currentTrackInfo && currentTrackInfo.isFragmented && !currentTrackInfo.isEmbedded) {
                  for (let i = 0; i < fragmentedTracks.length; i++) {
                      let mediaInfo = fragmentedTracks[i];
                      if (currentTrackInfo.lang === mediaInfo.lang && currentTrackInfo.index === mediaInfo.index &&
                          (mediaInfo.id ? currentTrackInfo.label === mediaInfo.id : currentTrackInfo.label === mediaInfo.index)) {
                          let currentFragTrack = mediaController.getCurrentTrackFor(Constants.FRAGMENTED_TEXT, streamController.getActiveStreamInfo());
                          if (mediaInfo !== currentFragTrack) {
                              fragmentModel.abortRequests();
                              fragmentModel.removeExecutedRequestsBeforeTime();
                              textSourceBuffer.remove();
                              textTracks.deleteCuesFromTrackIdx(oldTrackIdx);
                              mediaController.setTrack(mediaInfo);
                              textSourceBuffer.setCurrentFragmentedTrackIdx(i);
                          } else if (oldTrackIdx === -1) {
                              //in fragmented use case, if the user selects the older track (the one selected before disabled text track)
                              //no CURRENT_TRACK_CHANGED event will be trigger, so dashHandler current time has to be updated and the scheduleController
                              //has to be restarted.
                              const streamProcessors = streamController.getActiveStreamProcessors();
                              for (let i = 0; i < streamProcessors.length; i++) {
                                  if (streamProcessors[i].getType() === Constants.FRAGMENTED_TEXT) {
                                      streamProcessor = streamProcessors[i];
                                      break;
                                  }
                              }
                              streamProcessor.getIndexHandler().setCurrentTime(videoModel.getTime());
                              streamProcessor.getScheduleController().start();
                          }
                      }
                  }
              } else if (currentTrackInfo && !currentTrackInfo.isFragmented) {
                  const streamProcessors = streamController.getActiveStreamProcessors();
                  for (let i = 0; i < streamProcessors.length; i++) {
                      if (streamProcessors[i].getType() === Constants.TEXT) {
                          streamProcessor = streamProcessors[i];
                          mediaInfosArr = streamProcessor.getMediaInfoArr();
                          break;
                      }
                  }
  
                  if (streamProcessor && mediaInfosArr) {
                      for (let i = 0; i < mediaInfosArr.length; i++) {
                          if (mediaInfosArr[i].index === currentTrackInfo.index && mediaInfosArr[i].lang === currentTrackInfo.lang) {
                              streamProcessor.selectMediaInfo(mediaInfosArr[i]);
                              break;
                          }
                      }
                  }
              }
          }
      }
  
      function getCurrentTrackIdx() {
          return textTracks.getCurrentTrackIdx();
      }
  
      function resetInitialSettings() {
          allTracksAreDisabled = false;
      }
  
      function reset() {
          resetInitialSettings();
          textSourceBuffer.resetEmbedded();
          textSourceBuffer.reset();
      }
  
      instance = {
          setConfig: setConfig,
          getTextSourceBuffer: getTextSourceBuffer,
          getAllTracksAreDisabled: getAllTracksAreDisabled,
          addEmbeddedTrack: addEmbeddedTrack,
          getTextDefaultLanguage: getTextDefaultLanguage,
          setTextDefaultLanguage: setTextDefaultLanguage,
          setTextDefaultEnabled: setTextDefaultEnabled,
          getTextDefaultEnabled: getTextDefaultEnabled,
          enableText: enableText,
          isTextEnabled: isTextEnabled,
          setTextTrack: setTextTrack,
          getCurrentTrackIdx: getCurrentTrackIdx,
          enableForcedTextStreaming: enableForcedTextStreaming,
          reset: reset
      };
      setup();
      return instance;
  }
  
  TextController.__dashjs_factory_name = 'TextController';
  export default FactoryMaker.getSingletonFactory(TextController);
  