/**
 * @파일 tech.js
 */

'../component'에서 컴포넌트 가져오기;
'../utils/merge-options.js'에서 mergeOptions 가져오기;
import * as Fn from '../utils/fn.js';
'../utils/log.js'에서 로그 가져오기;
import { createTimeRange } from '../utils/time-ranges.js';
import { bufferedPercent } from '../utils/buffer.js';
'../media-error.js'에서 MediaError 가져오기;
'글로벌/창'에서 창 가져오기;
'글로벌/문서'에서 문서 가져오기;
'../utils/obj'에서 {isPlain} 가져오기;
import * as TRACK_TYPES from '../tracks/track-types';
'../utils/string-cases.js'에서 {toTitleCase, toLowerCase} 가져오기;
'videojs-vtt.js'에서 vtt 가져오기;
import * as Guid from '../utils/guid.js';

/**
 * `{src: 'url', type: 'mimetype'}` 또는 문자열과 같은 구조를 포함하는 객체
 * src url만 포함하는 것입니다.
 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
   * `var SourceString = 'http://example.com/some-video.mp4';`
 *
 * @typedef {객체|문자열} Tech~SourceObject
 *
 * @property {문자열} src
 * 소스 URL
 *
 * @property {문자열} 유형
 * 소스의 MIME 유형
 */

/**
 * 새로운 {@link TextTrack}을 생성하기 위해 {@link Tech}에서 사용하는 기능.
 *
 * @사적인
 *
 * @param {기술} 자신
 * Tech 클래스의 인스턴스입니다.
 *
 * @param {문자열} 종류
 * `TextTrack` 종류(자막, 캡션, 설명, 챕터 또는 메타데이터)
 *
 * @param {문자열} [레이블]
 * 텍스트 트랙을 식별하는 레이블
 *
 * @param {문자열} [언어]
 * 두 글자 언어 약어
 *
 * @param {객체} [옵션={}]
 * 추가 텍스트 추적 옵션이 있는 개체
 *
 * @return {텍스트 트랙}
 * 생성된 텍스트 트랙.
 */
함수 createTrackHelper(self, kind, label, language, options = {}) {
  const 트랙 = self.textTracks();

  options.kind = 종류;

  if(레이블) {
    options.label = 라벨;
  }
  if (언어) {
    options.language = 언어;
  }
  options.tech = 자기;

  const track = new TRACK_TYPES.ALL.text.TrackClass(options);

  tracks.addTrack(트랙);

  리턴 트랙;
}

/**
 * 이것은 다음과 같은 미디어 재생 기술 컨트롤러의 기본 클래스입니다.
 * {@링크 HTML5}
 *
 * @extends 컴포넌트
 */
클래스 기술 확장 구성 요소 {

  /**
  * 이 기술의 인스턴스를 만듭니다.
  *
  * @param {객체} [옵션]
  * 플레이어 옵션의 키/값 저장소.
  *
  * @param {Component~ReadyCallback} 준비 완료
  * `HTML5` Tech가 준비되면 호출하는 콜백 함수.
  */
  생성자(옵션 = {}, 준비 = function() {}) {
    // 우리는 기술자가 사용자 활동을 자동으로 보고하는 것을 원하지 않습니다.
    // 이것은 addControlsListeners에서 수동으로 수행됩니다.
    options.reportTouchActivity = 거짓;
    슈퍼(널, 옵션, 준비);

    this.onDurationChange_ = (e) => this.onDurationChange(e);
    this.trackProgress_ = (e) => this.trackProgress(e);
    this.trackCurrentTime_ = (e) => this.trackCurrentTime(e);
    this.stopTrackingCurrentTime_ = (e) => this.stopTrackingCurrentTime(e);
    this.disposeSourceHandler_ = (e) => this.disposeSourceHandler(e);

    this.queuedHanders_ = new Set();

    // 현재 소스가 재생되었는지 여부를 추적합니다.
    // 매우 제한된 playing() 구현
    this.hasStarted_ = 거짓;
    this.on('재생 중', function() {
      this.hasStarted_ = 참;
    });
    this.on('loadstart', function() {
      this.hasStarted_ = 거짓;
    });

    TRACK_TYPES.ALL.names.forEach((이름) => {
      const props = TRACK_TYPES.ALL[이름];

      if (옵션 && 옵션[props.getterName]) {
        this[props.privateName] = 옵션[props.getterName];
      }
    });

    // 브라우저/기술자가 보고하지 않는 경우 진행 상황을 수동으로 추적합니다.
    if (!this.featuresProgressEvents) {
      this.manualProgressOn();
    }

    // 브라우저/기술자가 보고하지 않는 경우 시간 업데이트를 수동으로 추적합니다.
    if (!this.featuresTimeupdateEvents) {
      this.manualTimeUpdatesOn();
    }

    ['텍스트', '오디오', '비디오'].forEach((트랙) => {
      if (옵션[`네이티브${트랙}트랙`] === 거짓) {
        this[`featuresNative${track}트랙`] = 거짓;
      }
    });

    if (options.nativeCaptions === false || options.nativeTextTracks === false) {
      this.featuresNativeTextTracks = 거짓;
    } 그렇지 않으면 (options.nativeCaptions === 참 || options.nativeTextTracks === 참) {
      this.featuresNativeTextTracks = 참;
    }

    if (!this.featuresNativeTextTracks) {
      this.emulateTextTracks();
    }

    this.preloadTextTracks = options.preloadTextTracks !== 거짓;

    this.autoRemoteTextTracks_ = new TRACK_TYPES.ALL.text.ListClass();

    this.initTrackListeners();

    // 네이티브 컨트롤을 사용하지 않는 경우에만 구성요소 탭 이벤트를 켭니다.
    if (!options.nativeControlsForTouch) {
      this.emitTapEvents();
    }

    if (이.생성자) {
      this.name_ = this.constructor.name || '알 수 없는 기술';
    }
  }

  /**
   * 플레이어가 허용하는 방식으로 소스 세트를 트리거하는 특수 기능
   * 플레이어나 기술이 아직 준비되지 않은 경우 다시 트리거합니다.
   *
   * @fires Tech#sourceset
   * @param {string} src 소스 변경 시점의 소스 문자열.
   */
  triggerSourceset(src) {
    if (!this.isReady_) {
      // 초기 준비 시 소스 세트를 트리거해야 합니다.
      // 플레이어가 볼 수 있도록 준비 후 1ms.
      this.one('준비', () => this.setTimeout(() => this.triggerSourceset(src), 1));
    }

    /**
     * 미디어 요소를 유발하는 기술에 소스가 설정되면 실행됩니다.
     * 다시 로드합니다.
     *
     * @see {@link Player#event:sourceset}
     * @event Tech#sourceset
     * @type {이벤트대상~이벤트}
     */
    this.trigger({
      소스,
      유형: '소스셋'
    });
  }

  /* 지원되지 않는 이벤트 유형에 대한 폴백
  ==================================================== ============================== */

  /**
   * 기본적으로 지원하지 않는 브라우저에 대해 `progress` 이벤트를 폴리필합니다.
   *
   * @see {@link Tech#trackProgress}
   */
  manualProgressOn() {
    this.on('durationchange', this.onDurationChange_);

    this.manualProgress = 참;

    // 소스가 로드되기 시작하면 진행 상황을 감시하도록 트리거합니다.
    this.one('준비', this.trackProgress_);
  }

  /**
   * 생성된 `progress` 이벤트에 대한 폴리필을 끕니다.
   * {@link Tech#manualProgressOn}
   */
  manualProgressOff() {
    this.manualProgress = 거짓;
    this.stopTrackingProgress();

    this.off('durationchange', this.onDurationChange_);
  }

  /**
   * 버퍼링된 백분율이 변경될 때 `progress` 이벤트를 트리거하는 데 사용됩니다. 그것
   * 500밀리초마다 호출되는 간격 함수를 설정하여
   * 버퍼 종료 비율이 변경되었습니다.
   *
   * > 이 함수는 {@link Tech#manualProgressOn}에 의해 호출됩니다.
   *
   * @param {EventTarget~Event} 이벤트
   * 이것을 실행하게 만든 `ready` 이벤트.
   *
   * @listens Tech#ready
   * @fires Tech#progress
   */
  trackProgress(이벤트) {
    this.stopTrackingProgress();
    this.progressInterval = this.setInterval(Fn.bind(this, function() {
      // 버퍼링된 양이 지난 시간보다 크지 않으면 트리거하지 않음

      const numBufferedPercent = this.bufferedPercent();

      if (this.bufferedPercent_ !== numBufferedPercent) {
        /**
         * {@link Player#progress} 참조
         *
         * @event 기술#진행
         * @type {이벤트대상~이벤트}
         */
        this.trigger('진행');
      }

      this.bufferedPercent_ = numBufferedPercent;

      if (numBufferedPercent === 1) {
        this.stopTrackingProgress();
      }
    }), 500);
  }

  /**
   * 호출하여 `durationchange` 이벤트에서 내부 기간을 업데이트합니다.
   * {@link Tech#duration}.
   *
   * @param {EventTarget~Event} 이벤트
   * 이것을 실행하게 만든 `durationchange` 이벤트.
   *
   * @listens Tech#durationchange
   */
  onDurationChange(이벤트) {
    this.duration_ = this.duration();
  }

  /**
   * 버퍼링을 위한 `TimeRange` 개체를 가져오고 만듭니다.
   *
   * @return {시간 범위}
   * 생성된 시간 범위 개체입니다.
   */
  버퍼링() {
    return createTimeRange(0, 0);
  }

  /**
   * 현재 버퍼링된 현재 비디오의 백분율을 가져옵니다.
   *
   * @return {숫자}
   * 0에서 1까지의 숫자로 소수 비율을 나타냅니다.
   * 버퍼링된 비디오.
   *
   */
  bufferedPercent() {
    return bufferedPercent(this.buffered(), this.duration_);
  }

  /**
   * 생성된 `progress` 이벤트에 대한 폴리필을 끕니다.
   * {@link Tech#manualProgressOn}
   * 설정한 간격을 지워 수동으로 진행 이벤트 추적을 중지합니다.
   * {@link Tech#trackProgress}.
   */
  stopTrackingProgress() {
    this.clearInterval(this.progressInterval);
  }

  /**
   * 지원하지 않는 브라우저에 대해 `timeupdate` 이벤트를 폴리필합니다.
   *
   * @see {@link Tech#trackCurrentTime}
   */
  manualTimeUpdatesOn() {
    this.manualTimeUpdates = 참;

    this.on('재생', this.trackCurrentTime_);
    this.on('일시정지', this.stopTrackingCurrentTime_);
  }

  /**
   * 생성된 `timeupdate` 이벤트에 대한 폴리필을 끕니다.
   * {@link Tech#manualTimeUpdatesOn}
   */
  manualTimeUpdatesOff() {
    this.manualTimeUpdates = 거짓;
    this.stopTrackingCurrentTime();
    this.off('재생', this.trackCurrentTime_);
    this.off('일시중지', this.stopTrackingCurrentTime_);
  }

  /**
   * 현재 시간을 추적하고 'timeupdate'를 트리거하는 간격 기능을 설정합니다.
   * 250밀리초.
   *
   * @listens Tech#play
   * @triggers Tech#timeupdate
   */
  trackCurrentTime() {
    if (이.currentTimeInterval) {
      this.stopTrackingCurrentTime();
    }
    this.currentTimeInterval = this.setInterval(function() {
      /**
       * 250ms 간격으로 트리거되어 비디오에서 시간이 흐르고 있음을 나타냅니다.
       *
       * @event Tech#timeupdate
       * @type {이벤트대상~이벤트}
       */
      this.trigger({ type: 'timeupdate', target: this, manualTriggered: true });

    // 42 = 24fps // 250은 Webkit이 사용하는 것입니다. // FF는 15를 사용합니다.
    }, 250);
  }

  /**
   * {@link Tech#trackCurrentTime}에서 생성된 간격 함수를 중지하여
   * `timeupdate` 이벤트가 더 이상 발생하지 않습니다.
   *
   * @listens {Tech#pause}
   */
  stopTrackingCurrentTime() {
    this.clearInterval(this.currentTimeInterval);

    // #1002 - 다음 시간 업데이트가 발생하기 직전에 동영상이 종료되면
    // 진행률 표시줄이 끝까지 도달하지 않습니다.
    this.trigger({ type: 'timeupdate', target: this, manualTriggered: true });
  }

  /**
   * 모든 이벤트 폴리필을 끄고 `Tech`의 {@link AudioTrackList}를 지우고,
   * {@link VideoTrackList} 및 {@link TextTrackList}, 그리고 이 기술을 폐기하십시오.
   *
   * @fires 컴포넌트#dispose
   */
  폐기() {

    // 기술자 간에 재사용할 수 없기 때문에 모든 트랙을 지웁니다.
    this.clearTracks(TRACK_TYPES.NORMAL.names);

    // 수동 진행률 또는 시간 업데이트 추적을 끕니다.
    if (이.수동 진행) {
      this.manualProgressOff();
    }

    if (this.manualTimeUpdates) {
      this.manualTimeUpdatesOff();
    }

    super.dispose();
  }

  /**
   * 이름이 지정된 단일 `TrackList` 또는 `TrackLists` 배열을 지웁니다.
   *
   * > 메모: 소스 핸들러가 없는 기술자는 `비디오` 소스 간에 이것을 호출해야 합니다.
   *         & '오디오' 트랙. 당신은 트랙 사이에 사용하고 싶지 않아!
   *
   * @param {문자열[]|문자열} 유형
   * 지울 TrackList 이름, 유효한 이름은 `video`, `audio` 및
   * `텍스트`.
   */
  clearTracks(유형) {
    유형 = [].concat(유형);
    // 기술자 간에 재사용할 수 없기 때문에 모든 트랙을 지웁니다.
    types.forEach((유형) => {
      const list = this[`${type}Tracks`]() || [];
      let i = list.length;

      동안 (i--) {
        const track = list[i];

        if (유형 === '텍스트') {
          this.removeRemoteTextTrack(트랙);
        }
        list.removeTrack(트랙);
      }
    });
  }

  /**
   * addRemoteTextTrack을 통해 추가된 모든 TextTrack을 제거합니다.
   * 자동 가비지 수집용으로 표시됨
   */
  cleanupAutoTextTracks() {
    const list = this.autoRemoteTextTracks_ || [];
    let i = list.length;

    동안 (i--) {
      const track = list[i];

      this.removeRemoteTextTrack(트랙);
    }
  }

  /**
   * 기술을 재설정하면 모든 소스가 제거되고 내부 readyState가 재설정됩니다.
   *
   * @추상적인
   */
  초기화() {}

  /**
   * 기술에서 `crossOrigin` 값을 가져옵니다.
   *
   * @추상적인
   *
   * @see {Html5#crossOrigin}
   */
  크로스오리진() {}

  /**
   * 기술에서 `crossOrigin` 값을 설정하십시오.
   *
   * @추상적인
   *
   * @param {string} crossOrigin crossOrigin 값
   * @see {Html5#setCrossOrigin}
   */
  setCrossOrigin() {}

  /**
   * Tech에서 오류를 얻거나 설정하십시오.
   *
   * @param {MediaError} [오류]
   * Tech에서 설정 오류
   *
   * @return {MediaError|null}
   * 기술의 현재 오류 개체 또는 없는 경우 null입니다.
   */
  오류(오류) {
    if (err !== 정의되지 않음) {
      this.error_ = new MediaError(err);
      this.trigger('오류');
    }
    반환 this.error_;
  }

  /**
   * 현재 소스에 대해 재생된 `TimeRange`를 반환합니다.
   *
   * > 메모: 이 구현은 불완전합니다. 재생된 `TimeRange`를 추적하지 않습니다.
   * 소스가 전혀 재생되지 않았는지 여부만 확인합니다.
   *
   * @return {시간 범위}
   * - 이 동영상이 재생된 경우 단일 시간 범위
   * - 그렇지 않은 경우 빈 범위 집합입니다.
   */
  플레이() {
    if (this.hasStarted_) {
      return createTimeRange(0, 0);
    }
    return createTimeRange();
  }

  /**
   * 재생 시작
   *
   * @추상적인
   *
   * @see {Html5#play}
   */
  놀다() {}

  /**
   * 스크러빙 여부 설정
   *
   * @추상적인
   *
   * @see {Html5#setScrubbing}
   */
  setScrubbing() {}

  /**
   * 스크러빙 여부 확인
   *
   * @추상적인
   *
   * @see {Html5#scrubbing}
   */
  스크러빙() {}

  /**
   * {@link Tech#manualTimeUpdatesOn}이 발생한 경우 수동 시간 업데이트가 발생합니다.
   * 이전에 호출되었습니다.
   *
   * @fires Tech#timeupdate
   */
  setCurrentTime() {
    // 수동 시간 업데이트의 정확도 향상
    if (this.manualTimeUpdates) {
      /**
       * 수동 `timeupdate` 이벤트.
       *
       * @event Tech#timeupdate
       * @type {이벤트대상~이벤트}
       */
      this.trigger({ type: 'timeupdate', target: this, manualTriggered: true });
    }
  }

  /**
   * {@link VideoTrackList}, {@link {AudioTrackList} 및
   * {@link TextTrackList} 이벤트.
   *
   * 이렇게 하면 `addtrack` 및 `removetrack`에 대한 {@link EventTarget~EventListeners}가 추가됩니다.
   *
   * @fires Tech#audiotrackchange
   * @fires Tech#videotrackchange
   * @fires Tech#texttrackchange
   */
  initTrackListeners() {
    /**
      * 기술 {@link AudioTrackList}에서 트랙이 추가되거나 제거될 때 트리거됨
      *
      * @event Tech#audiotrackchange
      * @type {이벤트대상~이벤트}
      */

    /**
      * 기술 {@link VideoTrackList}에서 트랙이 추가되거나 제거될 때 트리거됨
      *
      * @event Tech#videotrackchange
      * @type {이벤트대상~이벤트}
      */

    /**
      * 기술 {@link TextTrackList}에서 트랙이 추가되거나 제거될 때 트리거됨
      *
      * @event Tech#texttrackchange
      * @type {이벤트대상~이벤트}
      */
    TRACK_TYPES.NORMAL.names.forEach((이름) => {
      const 소품 = TRACK_TYPES.NORMAL[이름];
      const trackListChanges = () => {
        this.trigger(`${name}trackchange`);
      };

      const track = this[props.getterName]();

      tracks.addEventListener('removetrack', trackListChanges);
      Tracks.addEventListener('addtrack', trackListChanges);

      this.on('처리', () => {
        tracks.removeEventListener('removetrack', trackListChanges);
        Tracks.removeEventListener('addtrack', trackListChanges);
      });
    });
  }

  /**
   * 필요한 경우 vtt.js를 사용하여 TextTracks 에뮬레이트
   *
   * @fires Tech#vttjs로드됨
   * @fires Tech#vttjserror
   */
  addWebVttScript_() {
    경우 (window.WebVTT) {
      반품;
    }

    // 처음에 Tech.el_은 구성 요소 시스템이 완료될 때까지 대기하는 dummy-div의 자식입니다.
    // Tech.el_이 DOM의 일부인 지점에서 Tech가 준비되었음을 알립니다.
    // WebVTT 스크립트를 삽입하기 전에
    if (document.body.contains(this.el())) {

      // 사용 가능하고 vtt.js 스크립트 위치가 전달되지 않은 경우 require를 통해 로드
      // 옵션으로. novtt 빌드는 위의 요청 호출을 빈 개체로 바꿉니다.
      // 검사가 항상 실패하면 이 문제가 발생합니다.
      if (!this.options_['vtt.js'] && 일반(vtt) && 객체.키(vtt).길이 > 0) {
        this.trigger('vttjsloaded');
        반품;
      }

      // 스크립트 위치 옵션을 통해 vtt.js를 로드하거나 위치가 없는 cdn이
      // 통과
      const 스크립트 = document.createElement('스크립트');

      script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
      script.onload = () => {
        /**
         * vtt.js가 로드되면 시작됩니다.
         *
         * @event Tech#vttjs로드됨
         * @type {이벤트대상~이벤트}
         */
        this.trigger('vttjsloaded');
      };
      script.onerror = () => {
        /**
         * 오류로 인해 vtt.js가 로드되지 않았을 때 발생
         *
         * @event Tech#vttjs로드됨
         * @type {이벤트대상~이벤트}
         */
        this.trigger('vttjserror');
      };
      this.on('처리', () => {
        script.onload = null;
        script.onerror = null;
      });
      // 하지만 아직 로드되지 않았으며 주입 전에 true로 설정하여
      // 삽입된 window.WebVTT가 바로 로드되면 덮어쓰지 않습니다.
      window.WebVTT = 참;
      this.el().parentNode.appendChild(스크립트);
    } else {
      this.ready(this.addWebVttScript_);
    }

  }

  /**
   * 텍스트 트랙 에뮬레이트
   *
   */
  emulateTextTracks() {
    const 트랙 = this.textTracks();
    const remoteTracks = this.remoteTextTracks();
    const handleAddTrack = (e) => tracks.addTrack(e.track);
    const handleRemoveTrack = (e) => tracks.removeTrack(e.track);

    remoteTracks.on('addtrack', handleAddTrack);
    remoteTracks.on('removetrack', handleRemoveTrack);

    this.addWebVttScript_();

    const 업데이트디스플레이 = () => this.trigger('texttrackchange');

    const textTracksChanges = () => {
      업데이트디스플레이();

      에 대한 (하자 i = 0; i < 트랙.길이; i++) {
        const 트랙 = 트랙[i];

        track.removeEventListener('cuechange', updateDisplay);
        if (track.mode === '표시') {
          track.addEventListener('cuechange', updateDisplay);
        }
      }
    };

    textTracksChanges();
    tracks.addEventListener('변경', textTracksChanges);
    tracks.addEventListener('addtrack', textTracksChanges);
    tracks.addEventListener('removetrack', textTracksChanges);

    this.on('처리', function() {
      remoteTracks.off('addtrack', handleAddTrack);
      remoteTracks.off('removetrack', handleRemoveTrack);
      tracks.removeEventListener('변경', textTracksChanges);
      tracks.removeEventListener('addtrack', textTracksChanges);
      tracks.removeEventListener('removetrack', textTracksChanges);

      에 대한 (하자 i = 0; i < 트랙.길이; i++) {
        const 트랙 = 트랙[i];

        track.removeEventListener('cuechange', updateDisplay);
      }
    });
  }

  /**
   * 원격 {@link TextTrack} 객체를 생성하고 반환합니다.
   *
   * @param {문자열} 종류
   * `TextTrack` 종류(자막, 캡션, 설명, 챕터 또는 메타데이터)
   *
   * @param {문자열} [레이블]
   * 텍스트 트랙을 식별하는 레이블
   *
   * @param {문자열} [언어]
   * 두 글자 언어 약어
   *
   * @return {텍스트 트랙}
   * 생성되는 TextTrack.
   */
  addTextTrack(종류, 라벨, 언어) {
    if (! 종류) {
      throw new Error('TextTrack 종류가 필요하지만 제공되지 않았습니다.');
    }

    return createTrackHelper(this, kind, label, language);
  }

  /**
   * addRemoteTextTrack에서 사용할 에뮬레이트된 TextTrack 만들기
   *
   * 이것은 다음에서 상속받은 클래스에 의해 재정의됩니다.
   * 기본 또는 사용자 정의 TextTracks를 생성하기 위한 기술.
   *
   * @param {객체} 옵션
   * 객체는 TextTrack을 초기화하는 옵션을 포함해야 합니다.
   *
   * @param {문자열} [옵션.종류]
   * `TextTrack` 종류(자막, 캡션, 설명, 챕터 또는 메타데이터).
   *
   * @param {문자열} [옵션.레이블].
   * 텍스트 트랙을 식별하는 레이블
   *
   * @param {문자열} [옵션.언어]
   * 두 글자 언어 약어.
   *
   * @return {HTMLTrackElement}
   * 생성되는 트랙 요소.
   */
  createRemoteTextTrack(옵션) {
    const 트랙 = mergeOptions(옵션, {
      기술: 이
    });

    return new TRACK_TYPES.REMOTE.remoteTextEl.TrackClass(track);
  }

  /**
   * 원격 텍스트 추적 개체를 만들고 html 추적 요소를 반환합니다.
   *
   * > 메모: 이것은 에뮬레이트된 {@link HTMLTrackElement} 또는 네이티브 것일 수 있습니다.
   *
   * @param {객체} 옵션
   * 자세한 속성은 {@link Tech#createRemoteTextTrack}을 참조하세요.
   *
   * @param {부울} [manualCleanup=true]
   * - false인 경우: TextTrack이 비디오에서 자동으로 제거됩니다.
   * 소스가 변경될 때마다 요소
   * - 참인 경우: TextTrack은 수동으로 정리해야 합니다.
   *
   * @return {HTMLTrackElement}
   * Html 트랙 요소.
   *
   * @deprecated 이 함수의 기본 기능은 동일합니다.
   * 앞으로 "manualCleanup=false"로. manualCleanup 매개변수는
   * 또한 제거됩니다.
   */
  addRemoteTextTrack(옵션 = {}, manualCleanup) {
    const htmlTrackElement = this.createRemoteTextTrack(옵션);

    if (manualCleanup !== 참 && manualCleanup !== 거짓) {
      // 사용 중단 경고
      log.warn('manualCleanup' 매개변수를 'true'로 명시적으로 설정하지 않고 addRemoteTextTrack을 호출하는 것은 더 이상 사용되지 않으며 향후 video.js 버전에서는 'false'로 기본 설정됩니다.');
      manualCleanup = 참;
    }

    // HTMLTrackElement 및 TextTrack을 원격 목록에 저장
    this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
    this.remoteTextTracks().addTrack(htmlTrackElement.track);

    if (manualCleanup !== 참) {
      // 존재하지 않는 경우 TextTrackList를 생성합니다.
      this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track));
    }

    htmlTrackElement 반환;
  }

  /**
   * 원격 `TextTrackList`에서 원격 텍스트 트랙을 제거합니다.
   *
   * @param {TextTrack} 트랙
   * `TextTrackList`에서 제거할 `TextTrack`
   */
  removeRemoteTextTrack(트랙) {
    const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);

    // 원격 목록에서 HTMLTrackElement 및 TextTrack 제거
    this.remoteTextTrackEls().removeTrackElement_(trackElement);
    this.remoteTextTracks().removeTrack(트랙);
    this.autoRemoteTextTracks_.removeTrack(트랙);
  }

  /**
   * W3C의 미디어에서 지정한 대로 사용 가능한 미디어 재생 품질 메트릭을 가져옵니다.
   * 재생 품질 API.
   *
   * @see [사양]{@link https://wicg.github.io/media-playback-quality}
   *
   * @return {객체}
   * 지원되는 미디어 재생 품질 메트릭이 있는 개체
   *
   * @추상적인
   */
  getVideoPlaybackQuality() {
    반품 {};
  }

  /**
   * 항상 다른 창 위에 플로팅 비디오 창 만들기 시도
   * 사용자가 다른 사용자와 상호 작용하는 동안 미디어를 계속 소비할 수 있도록
   * 콘텐츠 사이트 또는 기기의 애플리케이션.
   *
   * @see [사양]{@link https://wicg.github.io/picture-in-picture}
   *
   * @return {약속|정의되지 않음}
   * 브라우저가 지원하는 경우 Picture-in-Picture 창에 대한 약속
   * 약속(또는 하나가 옵션으로 전달됨). 그것은 정의되지 않은 반환
   * 그렇지 않으면.
   *
   * @추상적인
   */
  requestPictureInPicture() {
    const PromiseClass = this.options_.Promise || 창. 약속;

    경우 (PromiseClass) {
      반환 PromiseClass.reject();
    }
  }

  /**
   * 'disablePictureInPicture' 값을 확인하는 방법 < 동영상> 재산.
   * 기술이 pip를 지원하지 않는 경우 비활성화된 것으로 간주되어야 하므로 기본값은 true입니다.
   *
   * @추상적인
   */
  disablePictureInPicture() {
    true를 반환합니다.
  }

  /**
   * 'disablePictureInPicture'를 설정하거나 해제하는 방법 < 동영상> 재산.
   *
   * @추상적인
   */
  setDisablePictureInPicture() {}

  /**
   * requestAnimationFrame을 사용한 requestVideoFrameCallback의 폴백 구현
   *
   * @param {함수} cb
   * @return {숫자} 요청 ID
   */
  requestVideoFrameCallback(cb) {
    const id = Guid.newGUID();

    if (!this.isReady_ || this.paused()) {
      this.queuedHanders_.add(id);
      this.one('재생 중', () => {
        if (this.queuedHanders_.has(id)) {
          this.queuedHanders_.delete(id);
          cb();
        }
      });
    } else {
      this.requestNamedAnimationFrame(id, cb);
    }

    반환 ID;
  }

  /**
   * cancelVideoFrameCallback의 폴백 구현
   *
   * @param {number} id 취소할 콜백의 id
   */
  cancelVideoFrameCallback(id) {
    if (this.queuedHanders_.has(id)) {
      this.queuedHanders_.delete(id);
    } else {
      this.cancelNamedAnimationFrame(id);
    }
  }

  /**
   * `테크`에서 포스터를 설정하는 방법.
   *
   * @추상적인
   */
  setPoster() {}

  /**
   * 'playsinline'의 존재를 확인하는 방법 < 동영상> 기인하다.
   *
   * @추상적인
   */
  플레이인라인() {}

  /**
   * 'playsinline'을 설정하거나 해제하는 방법 < 동영상> 기인하다.
   *
   * @추상적인
   */
  setPlaysinline() {}

  /**
   * 기본 오디오 트랙을 강제로 무시하려고 시도합니다.
   *
   * @param {boolean} override - true로 설정하면 기본 오디오가 무시됩니다.
   * 그렇지 않으면 기본 오디오가 잠재적으로 사용됩니다.
   *
   * @추상적인
   */
  overrideNativeAudioTracks() {}

  /**
   * 기본 비디오 트랙을 강제로 무시하려고 시도합니다.
   *
   * @param {boolean} override - true로 설정하면 기본 동영상이 무시됩니다.
   * 그렇지 않으면 네이티브 비디오가 잠재적으로 사용됩니다.
   *
   * @추상적인
   */
  overrideNativeVideoTracks() {}

  /*
   * 기술이 주어진 MIME 유형을 지원할 수 있는지 확인하십시오.
   *
   * 기본 기술은 어떤 유형도 지원하지 않지만 소스 핸들러는
   * 이것을 덮어씁니다.
   *
   * @param {문자열} 유형
   * 지원 여부를 확인할 mimetype
   *
   * @return {문자열}
   * 'probably', 'maybe' 또는 빈 문자열
   *
   * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
   *
   * @추상적인
   */
  canPlayType() {
    반품 '';
  }

  /**
   * 이 기술에서 지원하는 유형인지 확인하십시오.
   *
   * 기본 기술은 어떤 유형도 지원하지 않지만 소스 핸들러는
   * 이것을 덮어씁니다.
   *
   * @param {문자열} 유형
   * 확인할 미디어 유형
   * @return {string} 네이티브 동영상 요소의 응답을 반환합니다.
   */
  정적 canPlayType() {
    반품 '';
  }

  /**
   * 기술이 주어진 소스를 지원할 수 있는지 확인
   *
   * @param {객체} srcObj
   * 소스 객체
   * @param {객체} 옵션
   * 기술자에게 전달된 옵션
   * @return {string} 'probably', 'maybe' 또는 '' (빈 문자열)
   */
  정적 canPlaySource(srcObj, 옵션) {
    return Tech.canPlayType(srcObj.type);
  }

  /*
   * 인수가 Tech인지 여부를 반환합니다.
   * `Html5`와 같은 클래스 또는 `player.tech_`와 같은 인스턴스를 전달할 수 있습니다.
   *
   * @param {객체} 컴포넌트
   * 확인할 항목
   *
   * @return {부울}
   * 기술 여부
   * - 기술인 경우 참
   * - 그렇지 않은 경우 거짓
   */
  정적 isTech(구성 요소) {
    return component.prototype instanceof 기술 ||
           Tech의 구성 요소 인스턴스 ||
           구성 요소 === 기술;
  }

  /**
   * videojs의 공유 목록에 `Tech`를 등록합니다.
   *
   * @param {문자열} 이름
   * 등록할 `Tech`의 이름입니다.
   *
   * @param {객체} 기술
   * 등록할 `Tech` 클래스입니다.
   */
  정적 registerTech(이름, 기술) {
    if (!Tech.techs_) {
      Tech.techs_ = {};
    }

    if (!Tech.isTech(기술)) {
      throw new Error(`기술 ${이름}은 기술이어야 함`);
    }

    if (!Tech.canPlayType) {
      throw new Error('기술자는 정적 canPlayType 메서드를 가지고 있어야 합니다.');
    }
    if (!Tech.canPlaySource) {
      throw new Error('기술자는 정적 canPlaySource 메서드를 가지고 있어야 합니다.');
    }

    name = toTitleCase(이름);

    Tech.techs_[이름] = 기술;
    Tech.techs_[toLowerCase(이름)] = 기술;
    if (이름 !== '기술') {
      // 카멜 케이스 techOrder에서 사용하기 위한 techName
      Tech.defaultTechOrder_.push(이름);
    }
    반환 기술;
  }

  /**
   * 이름으로 공유 목록에서 `Tech`를 가져옵니다.
   *
   * @param {문자열} 이름
   * 가져올 기술의 `camelCase` 또는 `TitleCase` 이름
   *
   * @return {기술|정의되지 않음}
   * 요청한 이름의 기술이 없는 경우 `Tech` 또는 정의되지 않음.
   */
  정적 getTech(이름) {
    if (!이름) {
      반품;
    }

    if (Tech.techs_ && Tech.techs_[이름]) {
      return Tech.techs_[이름];
    }

    name = toTitleCase(이름);

    만약 (창 && window.videojs && window.videojs[이름]) {
      log.warn(`videojs.registerTech(name, tech)를 사용하여 등록해야 할 때 ${name} 기술이 videojs 개체에 추가되었습니다.`);
      return window.videojs[이름];
    }
  }
}

/**
 * {@link VideoTrackList} 받기
 *
 * @returns {VideoTrackList}
 * @method Tech.prototype.videoTracks
 */

/**
 * {@link AudioTrackList} 받기
 *
 * @returns {오디오트랙리스트}
 * @method Tech.prototype.audioTracks
 */

/**
 * {@link TextTrackList} 받기
 *
 * @returns {TextTrackList}
 * @method Tech.prototype.textTracks
 */

/**
 * 원격 요소 가져오기 {@link TextTrackList}
 *
 * @returns {TextTrackList}
 * @method Tech.prototype.remoteTextTracks
 */

/**
 * 원격 요소 가져오기 {@link HtmlTrackElementList}
 *
 * @returns {HtmlTrackElementList}
 * @method Tech.prototype.remoteTextTrackEls
 */

TRACK_TYPES.ALL.names.forEach(기능(이름) {
  const props = TRACK_TYPES.ALL[이름];

  Tech.prototype[props.getterName] = function() {
    this[props.privateName] = this[props.privateName] || 새로운 props.ListClass();
    return this[props.privateName];
  };
});

/**
 * 관련 텍스트 트랙 목록
 *
 * @type {TextTrackList}
 * @사적인
 * @property Tech#textTracks_
 */

/**
 * 관련 오디오 트랙 목록.
 *
 * @type {오디오트랙리스트}
 * @사적인
 * @property Tech#audioTracks_
 */

/**
 * 관련 비디오 트랙 목록.
 *
 * @type {VideoTrackList}
 * @사적인
 * @property Tech#videoTracks_
 */

/**
 * `Tech`가 볼륨 조절을 지원하는지 여부를 나타내는 부울입니다.
 *
 * @유형 {부울}
 * @기본
 */
Tech.prototype.featuresVolumeControl = 참;

/**
 * `Tech`가 음소거 볼륨을 지원하는지 여부를 나타내는 부울입니다.
 *
 * @type {볼린}
 * @기본
 */
Tech.prototype.featuresMuteControl = 참;

/**
 * `Tech`가 전체 화면 크기 조정 제어를 지원하는지 여부를 나타내는 부울입니다.
 * 요청 전체 화면을 사용하여 플러그인 크기를 조정하면 플러그인이 다시 로드됨
 *
 * @유형 {부울}
 * @기본
 */
Tech.prototype.featuresFullscreenResize = 거짓;

/**
 * 'Tech'가 비디오 재생 속도 변경을 지원하는지 여부를 나타내는 부울
 * 연극. 예:
 * - 플레이어가 2배(2배) 빠르게 플레이하도록 설정
 * - 플레이어가 0.5배(절반) 빠르게 플레이하도록 설정
 *
 * @유형 {부울}
 * @기본
 */
Tech.prototype.featuresPlaybackRate = 거짓;

/**
 * `Tech`가 `progress` 이벤트를 지원하는지 여부를 나타내는 부울입니다. 이것은 현재
 * video-js-swf에 의해 트리거되지 않습니다. 다음을 결정하는 데 사용됩니다.
 * {@link Tech#manualProgressOn}을 호출해야 합니다.
 *
 * @유형 {부울}
 * @기본
 */
Tech.prototype.featuresProgressEvents = 거짓;

/**
 * `Tech`가 `sourceset` 이벤트를 지원하는지 여부를 나타내는 부울입니다.
 *
 * 기술자는 이것을 `true`로 설정한 다음 {@link Tech#triggerSourceset}를 사용해야 합니다.
 * 받은 후 가장 빠른 시간에 {@link Tech#event:sourceset} 트리거
 * 새로운 소스입니다.
 *
 * @유형 {부울}
 * @기본
 */
Tech.prototype.featuresSourceset = 거짓;

/**
 * `Tech`가 `timeupdate` 이벤트를 지원하는지 여부를 나타내는 부울입니다. 이것은 현재
 * video-js-swf에 의해 트리거되지 않습니다. 다음을 결정하는 데 사용됩니다.
 * {@link Tech#manualTimeUpdates}를 호출해야 합니다.
 *
 * @유형 {부울}
 * @기본
 */
Tech.prototype.featuresTimeupdateEvents = 거짓;

/**
 * `Tech`가 기본 `TextTrack`을 지원하는지 여부를 나타내는 부울입니다.
 * 브라우저에서 지원하는 경우 기본 `TextTrack`과 통합하는 데 도움이 됩니다.
 *
 * @유형 {부울}
 * @기본
 */
Tech.prototype.featuresNativeTextTracks = 거짓;

/**
 * `Tech`가 `requestVideoFrameCallback`을 지원하는지 여부를 나타내는 부울입니다.
 *
 * @유형 {부울}
 * @기본
 */
Tech.prototype.featuresVideoFrameCallback = 거짓;

/**
 * 소스 핸들러 패턴을 사용하려는 기술자를 위한 기능적 혼합.
 * 소스 처리기는 특정 형식을 처리하기 위한 스크립트입니다.
 * 소스 핸들러 패턴은 적응형 형식(HLS, DASH)에 사용됩니다.
 * 비디오 데이터를 수동으로 로드하고 소스 버퍼(Media Source Extensions)에 공급
 * 예: `Tech.withSourceHandlers.call(MyTech);`
 *
 * @param {기술} _기술
 * 소스 핸들러 기능을 추가하는 기술.
 *
 * @mixes Tech~SourceHandlerAdditions
 */
Tech.withSourceHandlers = function(_Tech) {

  /**
   * 소스 핸들러 등록
   *
   * @param {함수} 핸들러
   * 소스 핸들러 클래스
   *
   * @param {숫자} [인덱스]
   * 아래 인덱스에 등록
   */
  _Tech.registerSourceHandler = 함수(핸들러, 인덱스) {
    let handlers = _Tech.sourceHandlers;

    if (!핸들러) {
      핸들러 = _Tech.sourceHandlers = [];
    }

    if (인덱스 === 정의되지 않음) {
      // 리스트 끝에 추가
      인덱스 = handlers.length;
    }

    handlers.splice(인덱스, 0, 핸들러);
  };

  /**
   * 기술이 주어진 유형을 지원할 수 있는지 확인하십시오. 또한 확인
   * Techs sourceHandlers.
   *
   * @param {문자열} 유형
   * 확인할 mimetype입니다.
   *
   * @return {문자열}
   * 'probably', 'maybe' 또는 '' (빈 문자열)
   */
  _Tech.canPlayType = 기능(유형) {
    const 핸들러 = _Tech.sourceHandlers || [];
    할 수 있습니다;

    에 대한 (하자 i = 0; i < 핸들러.길이; i++) {
      can = 핸들러[i].canPlayType(유형);

      만약 (할 수) {
        반환할 수 있습니다.
      }
    }

    반품 '';
  };

  /**
   * 소스를 지원하는 첫 번째 소스 핸들러를 반환합니다.
   *
   * 할 것: 답 질문: '아마도'보다 '아마도'가 우선되어야 합니다.
   *
   * @param {Tech~SourceObject} 소스
   * 소스 객체
   *
   * @param {객체} 옵션
   * 기술자에게 전달된 옵션
   *
   * @return {SourceHandler|null}
   * 소스를 지원하는 첫 번째 소스 핸들러 또는 null인 경우
   * SourceHandler가 소스를 지원하지 않음
   */
  _Tech.selectSourceHandler = 기능(소스, 옵션) {
    const 핸들러 = _Tech.sourceHandlers || [];
    할 수 있습니다;

    에 대한 (하자 i = 0; i < 핸들러.길이; i++) {
      can = 핸들러[i].canHandleSource(소스, 옵션);

      만약 (할 수) {
        반환 핸들러[i];
      }
    }

    null을 반환합니다.
  };

  /**
   * 기술이 주어진 소스를 지원할 수 있는지 확인하십시오.
   *
   * @param {Tech~SourceObject} srcObj
   * 소스 객체
   *
   * @param {객체} 옵션
   * 기술자에게 전달된 옵션
   *
   * @return {문자열}
   * 'probably', 'maybe' 또는 '' (빈 문자열)
   */
  _Tech.canPlaySource = 함수(srcObj, 옵션) {
    const sh = _Tech.selectSourceHandler(srcObj, 옵션);

    만약 (쉬) {
      sh.canHandleSource(srcObj, 옵션)를 반환합니다.
    }

    반품 '';
  };

  /**
   * 소스 처리기를 사용하는 경우 다음과 같은 구현을 선호합니다.
   * 일반적으로 기술자가 제공하는 모든 기능.
   */
  const 지연 가능 = [
    '찾을 수 있는',
    '찾다',
    '지속'
  ];

  /**
   * `SourceHandler`의 검색 가능 항목을 호출하는 {@link Tech#seekable} 주변의 래퍼
   * 함수가 존재하는 경우 Techs 검색 가능 함수로 폴백합니다.
   *
   * @method _Tech.seekable
   */

  /**
   * `SourceHandler` 기간을 호출하는 {@link Tech#duration} 주변의 래퍼
   * 존재하는 경우 기능, 그렇지 않으면 techs 기간 기능으로 대체됩니다.
   *
   * @method _Tech.duration
   */

  deferrable.forEach(function(fnName) {
    const originalFn = this[fnName];

    if (typeof originalFn !== '함수') {
      반품;
    }

    this[fnName] = 함수() {
      if (이.소스핸들러_ && this.sourceHandler_[fnName]) {
        return this.sourceHandler_[fnName].apply(this.sourceHandler_, 인수);
      }
      return originalFn.apply(this, arguments);
    };
  }, _Tech.prototype);

  /**
   * 소스 객체를 이용하여 소스를 설정하는 함수 생성
   * 및 소스 핸들러.
   * 소스 핸들러가 발견되지 않으면 호출해서는 안 됩니다.
   *
   * @param {Tech~SourceObject} 소스
   * src 및 type 키가 있는 소스 객체
   */
  _Tech.prototype.setSource = 함수(소스) {
    let sh = _Tech.selectSourceHandler(source, this.options_);

    경우 (! 쉬) {
      // 지원되지 않는 소스가 있는 경우 네이티브 소스 핸들러로 폴백
      // 의도적으로 설정
      if (_Tech.nativeSourceHandler) {
        sh = _Tech.nativeSourceHandler;
      } else {
        log.error('현재 소스에 대한 소스 핸들러를 찾을 수 없습니다.');
      }
    }

    // 기존 소스 핸들러 폐기
    this.disposeSourceHandler();
    this.off('dispose', this.disposeSourceHandler_);

    if (sh !== _Tech.nativeSourceHandler) {
      this.currentSource_ = 소스;
    }

    this.sourceHandler_ = sh.handleSource(source, this, this.options_);
    this.one('dispose', this.disposeSourceHandler_);
  };

  /**
   * Tech가 폐기될 때 기존 SourceHandlers 및 수신기를 정리합니다.
   *
   * @listens Tech#dispose
   */
  _Tech.prototype.disposeSourceHandler = 함수() {
    // 소스가 있고 다른 소스가 있는 경우
    // 그런 다음 새로운 것을 로드합니다.
    // 현재 트랙을 모두 지우는 것보다
    if (이.currentSource_) {
      this.clearTracks(['오디오', '비디오']);
      this.currentSource_ = null;
    }

    // 항상 자동 텍스트 트랙을 정리합니다.
    this.cleanupAutoTextTracks();

    if (this.sourceHandler_) {

      if (this.sourceHandler_.dispose) {
        this.sourceHandler_.dispose();
      }

      this.sourceHandler_ = null;
    }
  };

};

// 기본 Tech 클래스는 Component로 등록되어야 합니다. 그것은 유일한
// 컴포넌트로 등록할 수 있는 기술.
Component.registerComponent('기술', 기술);
Tech.registerTech('기술', 기술);

/**
 * 플레이어의 techOrder에 추가해야 하는 기술 목록
 *
 * @사적인
 */
Tech.defaultTechOrder_ = [];

수출 기본 기술;