Source: managers/VideoManager.js

import document from 'global/document';
import Promise from 'es6-promise';

import { isValidUrl, getYtId } from '../utils';

const HT_PARAMS_PATTERN = /([src|subtitle]+)=([a-zA-Z0-9:/.\-_?=&]+)/g;
const SUPPORTED_SUBTITLES = ['vtt', 'srt'];
const parseHTParams = (params) => {
  const obj = {};
  let m;

  while ((m = HT_PARAMS_PATTERN.exec(params)) !== null) {
    if (m.index === HT_PARAMS_PATTERN.lastIndex) {
      HT_PARAMS_PATTERN.lastIndex++;
    }

    obj[m[1]] = m[2];
  }

  return obj;
};

class VideoItem {

  videoManager = null;

  constructor(videoManager, elem) {

    this.videoManager = videoManager;

    const dataHtValue = elem.getAttribute('data-ht');
    const data = parseHTParams(dataHtValue);

    // Pega o type e o source
    const source = this.getSource(data);

    if (source.type == 'youtube') this.createYtPlayer(elem, source, this.getSubtitle(data));
    else this.createGenericPlayer(elem, source, this.getSubtitle(data));
  }

  /** 
   * Verifica se uma legenda é compativel 
   * @param subtitle - Objeto com o src e type da legenda
   */
  checkSubtitle = (subtitle) => {
    return subtitle && SUPPORTED_SUBTITLES.indexOf(subtitle.type, 0) != -1;
  }

  /**
   * Cria um novo player em mp4
   * @param elem - Elemento atual do video
   * @param source - Source com type e src
   * @param subtitle - Subtitle com type e src
   */
  createGenericPlayer = (elem, source, subtitle) => {
    // Cria um player sem legenda caso a legenda seja incompativel
    if (!this.checkSubtitle(subtitle)) {
      this.createPlayerWithoutSubtitle(elem, source);
      return;
    }

    this.downloadSubtitle(subtitle).then((vttData) => {
      this.videoManager.htVideo.createAccessibilePlayer(elem, source.src, source.type, vttData);
    }).catch(() => { this.createPlayerWithoutSubtitle(elem, source, subtitle); });
  }

  /**
   * Cria um novo player do youtube
   * @param elem - Elemento atual do video
   * @param source - Source com type e src
   * @param subtitle - Subtitle com type e src
   */
  createYtPlayer = (elem, source, subtitle) => {
    // Cria uma solicitação de legenda do tipo yt no ht-translation caso nenhuma legenda tenha sido informada
    if (!subtitle) {
      subtitle = {
        'type': 'yt',
        // Verifica se o src do video é uma url valida, senão consideramos um id do youtube
        'src': isValidUrl(source.src) ? getYtId(source.src) : source.src
      };
    }

    // Cria um player sem legenda caso a legenda seja incompativel
    if (subtitle.type != 'yt' && !this.checkSubtitle(subtitle)) {
      this.createPlayerWithoutSubtitle(elem, source, subtitle);
      return;
    }

    // Baixa a legenda
    this.downloadSubtitle(subtitle).then((vttData) => {
      this.videoManager.htVideo.createAccessibilePlayer(elem, source.src, source.type, vttData);
    }).catch(() => { this.createPlayerWithoutSubtitle(elem, source); });
  }

  /** Cria um player sem legenda, criado quando uma legenda não é compativel */
  createPlayerWithoutSubtitle = (elem, source, subtitle) => {
    this.videoManager.htVideo.createAccessibilePlayer(elem, source.src, source.type, 'Formato de legenda não suportado', true);
    this.videoManager.ht.logger.warn('Tradutor de vídeos | Formato de legenda não suportado', source, subtitle);
  }

  /**
   * Obtém o source de um objeto ht-data
   * @param data - Objeto ht-data
   */
  getSource = (data) => {
    if (data.src) {
      const [type, ...sourceUrl] = data.src.split(':');
      const src = sourceUrl.join(':');
      return { 'type': type, 'src': src };
    }
  }

  /**
   * Obtém a legenda de um objeto ht-data
   * @param data - Objeto ht-data
   */
  getSubtitle = (data) => {
    if (data.subtitle) {
      const [type, ...sourceUrl] = data.subtitle.split(':');
      const src = sourceUrl.join(':');
      return { 'type': type, 'src': src };
    }
  }

  /**
   * Baixa a legenda através do nosso serviço
   * @param subtitle - Legenda com src e type
   */
  downloadSubtitle = (subtitle) => {
    // Pega o arquivo local caso vtt ou srt, pra outros tipos consulta o serviço 
    const url = subtitle.type == 'yt' ? `${TRANSLATE_SUBTITLE_URL}?type=${subtitle.type}&src=${subtitle.src}` : subtitle.src;

    return new Promise((resolve, reject) => {
      const xmlHttp = new XMLHttpRequest();
      xmlHttp.open('GET', url);
      xmlHttp.onload = () => {
        const data = xmlHttp.responseText;
        if (xmlHttp.status == 200) resolve(data);
        else reject();
      };
      xmlHttp.onerror = reject;
      xmlHttp.send();
    });
  }
}

class VideoManager {
  ht = null;
  htVideo = null;
  hasProblem = false;
  currentVideo = null;

  static vjsMinVersion = 5;

  /**
   * @param {HT} ht 
   */
  constructor(ht) {
    this.ht = ht;
    this.ht.logger.debug('init videoManager');

    this.ht.once('errorOnAuth', () => {
      this.hasProblem = true;
      this.htVideo.error = true;
      if (this.ht.pageTranslatorManager) this.ht.pageTranslatorManager.activated = true;
    });

    this.loadDependencies().then(() => {
      this.ht.startHugo().then((hugo) => {
        this.htVideo = new HTVideo(hugo);
        this.htVideo.on('hugoViewChanged', this.onVideoActivated);
        this.ht.emit('videoManagerReady');
        this.search();
      });
    }).catch(() => {
      this.ht.logger.error('Falha ao baixar dependencias do tradutor de vídeos');
    });
  }

  _loadDependenciesPromise = null;
  /** Carrega todas as dependencias */
  loadDependencies = () => {
    if (this._loadDependenciesPromise) return this._loadDependenciesPromise;
    return this._loadDependenciesPromise = new Promise((resolve, reject) => {
      if (!window.videojs)
        this.loadVideoJsDependencies()
          .then(() => this.loadVideoPlayerScript())
          .then(() => resolve())
          .catch(() => reject());
      else {
        if (window.videojs.VERSION && window.videojs.VERSION.split('.')[0] < VideoManager.vjsMinVersion)
          this.ht.logger.warn(`A versão ${window.videojs.VERSION} do videojs pode apresentar instabilidade com o Tradutor de Vídeos. Considere atualizar para versão ${VideoManager.vjsMinVersion}.0.0 ou superior`);

        this.loadVideoPlayerScript()
          .then(() => resolve())
          .catch(() => reject());
      }
    });
  };

  _loadVideoPlayerPromise = null;
  loadVideoPlayerScript = () => {
    if (this._loadVideoPlayerPromise) return this._loadVideoPlayerPromise;
    return this._loadVideoPlayerPromise = new Promise((resolve, reject) => {
      const scriptElem = document.createElement('script');
      scriptElem.src = VIDEO_PLAYER_SCRIPT_URL;
      scriptElem.onload = () => {
        this.ht.logger.debug('ht video carregado');
        resolve();
      };
      scriptElem.onerror = reject;
      this.ht.config.parentElement.appendChild(scriptElem);
    });
  };

  loadVideoJsDependencies = () => (
    this.loadVideojsScript().then(() => this.loadVideojsStyle())
  )

  _loadVideojsScriptPromise = null;
  loadVideojsScript = () => {
    if (this._loadVideojsScriptPromise) return this._loadVideojsScriptPromise;
    return this._loadVideojsScriptPromise = new Promise((resolve, reject) => {
      const scriptElem = document.createElement('script');
      scriptElem.src = VIDEOJS_SCRIPT_URL;
      scriptElem.onload = () => {
        this.ht.logger.debug(`Versão ${videojs.VERSION} do videojs injetada`);
        resolve();
      };
      scriptElem.onerror = reject;
      document.head.appendChild(scriptElem);
    });
  }

  _loadVideojsStylePromise = null;
  loadVideojsStyle = () => {
    if (this._loadVideojsStylePromise) return this._loadVideojsStylePromise;
    return this._loadVideojsStylePromise = new Promise((resolve, reject) => {
      const styleElem = document.createElement('link');
      styleElem.type = 'text/css';
      styleElem.rel = 'stylesheet';
      styleElem.setAttribute('rel', 'stylesheet');
      styleElem.onload = () => {
        this.ht.logger.debug('CSS do videojs injetado');
        resolve();
      };
      styleElem.onerror = reject;
      styleElem.href = VIDEOJS_CSS_URL;
      document.head.appendChild(styleElem);
    });
  }

  /**
   * Adiciona os componentes de acessibilidade em um player existente do videojs
   * @param vjsPlayer - Player atual do videojs
   */
  makeVideojsAccessibile = (vjsPlayer) => {
    this.ht.logger.debug('Componentes adicionados', vjsPlayer);
    this.htVideo.makeVideojsAccessibile(vjsPlayer);
  }

  /**
   * Chamado quando a opção de Libras é ativada em um video
   * @param video - Video que teve foi ativado
   */
  onVideoActivated = (video) => {
    this.ht.logger.debug('ativado', video);
    this.currentVideo = video;
    this.ht._auth();
    if (this.ht.pageTranslatorManager) this.ht.pageTranslatorManager.activated = false;
  }


  /**
   * Desativa o vídeo acessivel atual
   */
  disableCurrentAcessibleVideo = () => {
    if (this.currentVideo) {
      this.currentVideo.signLanguageButton.toggleActive(false);
      this.currentVideo.pause();
    }
  }

  /**
   * @function
   */
  search = () => {
    const elems = Array.prototype.map.bind(document.querySelectorAll('video[data-ht]'))(elem => (elem));

    elems.forEach((elem) => { this.getItem(elem); });
  };

  elemItems = [];
  /**
   * @function
   */
  getItem = (elem) => {
    const elemItem = this.elemItems.filter(elemItem => (elemItem.elem === elem))[0];
    if (elemItem) return elemItem.item;

    this.elemItems.push({ elem, item: new VideoItem(this, elem) });
    return this.getItem(elem);
  };
}

export default VideoManager;