/**
* @file 텍스트-트랙.js
*/
'./text-track-cue-list'에서 TextTrackCueList 가져오기;
import * as Fn from '../utils/fn.js';
import {TextTrackKind, TextTrackMode} from './track-enums';
'../utils/log.js'에서 로그 가져오기;
'글로벌/창'에서 창 가져오기;
'./track.js'에서 트랙 가져오기;
import { isCrossOrigin } from '../utils/url.js';
'@videojs/xhr'에서 XHR 가져오기;
'../utils/merge-options'에서 병합 가져오기;
/**
* webvtt 파일 내용을 가져와 큐로 구문 분석합니다.
*
* @param {문자열} srcContent
* webVTT 파일 내용
*
* @param {TextTrack} 트랙
* 큐를 추가할 TextTrack. 큐는 srcContent에서 가져옵니다.
*
* @사적인
*/
const parseCues = function(srcContent, track) {
const 파서 = 새 window.WebVTT.Parser(
창문,
창.vttjs,
window.WebVTT.StringDecoder()
);
const 오류 = [];
parser.oncue = 함수(큐) {
track.addCue(큐);
};
parser.onparsingerror = 함수(오류) {
오류.푸시(오류);
};
parser.onflush = 함수() {
트랙.트리거({
유형: '로드데이터',
대상: 추적
});
};
parser.parse(srcContent);
if (오류. 길이 > 0) {
if (창.콘솔 && window.console.groupCollapsed) {
window.console.groupCollapsed(`${track.src}에 대한 텍스트 트랙 구문 분석 오류`);
}
오류.forEach((오류) => log.error(오류));
if (창.콘솔 && window.console.groupEnd) {
window.console.groupEnd();
}
}
parser.flush();
};
/**
* 지정된 URL에서 `TextTrack`을 로드합니다.
*
* @param {문자열} 소스
* 트랙을 로드할 URL.
*
* @param {TextTrack} 트랙
* 큐를 추가할 트랙. `url` 끝에 있는 콘텐츠에서 가져옵니다.
*
* @사적인
*/
const loadTrack = 함수(src, 트랙) {
const 선택 = {
우리: src
};
const crossOrigin = isCrossOrigin(src);
if(크로스오리진) {
opts.cors = crossOrigin;
}
const withCredentials = track.tech_.crossOrigin() === '사용 자격 증명';
if (withCredentials) {
opts.withCredentials = withCredentials;
}
XHR(opts, Fn.bind(this, function(err, response, responseBody) {
경우 (오류) {
return log.error(err, 응답);
}
track.loaded_ = 참;
// vttjs가 로드되었는지 확인합니다. 그렇지 않으면 로드가 완료될 때까지 기다립니다.
// 참고: 이것은 alt/video.novtt.js 빌드에만 사용됩니다.
if (typeof window.WebVTT !== '함수') {
경우 (track.tech_) {
// eslint 오류를 정의하기 전에 사용을 방지하기 위해 loadHandler를 정의합니다.
// 여기에 let으로
track.tech_.any(['vttjsloaded', 'vttjserror'], (이벤트) => {
if (event.type === 'vttjserror') {
log.error(`vttjs 로드 실패, ${track.src} 처리 시도 중단`);
반품;
}
return parseCues(responseBody, track);
});
}
} else {
parseCues(responseBody, track);
}
}));
};
/**
* 단일 `TextTrack`의 표현입니다.
*
* @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
* @extends 트랙
*/
클래스 TextTrack 확장 트랙 {
/**
* 이 클래스의 인스턴스를 만듭니다.
*
* @param {객체} 옵션={}
* 옵션 이름 및 값의 개체
*
* @param {기술} options.tech
* 이 TextTrack을 소유한 기술에 대한 참조입니다.
*
* @param {TextTrack~Kind} [options.kind='자막']
* 유효한 텍스트 트랙 종류입니다.
*
* @param {TextTrack~Mode} [options.mode='비활성화됨']
* 유효한 텍스트 트랙 모드.
*
* @param {string} [options.id='vjs_track_' + Guid.newGUID()]
* 이 TextTrack의 고유 ID입니다.
*
* @param {문자열} [옵션.라벨='']
* 이 트랙의 메뉴 레이블입니다.
*
* @param {문자열} [옵션.언어='']
* 유효한 두 문자 언어 코드입니다.
*
* @param {문자열} [options.srclang='']
* 유효한 두 문자 언어 코드입니다. 대안이지만 우선 순위가 낮음
* `options.language` 버전
*
* @param {문자열} [옵션.src]
* TextTrack 큐에 대한 URL.
*
* @param {부울} [옵션.기본값]
* 이 트랙이 기본적으로 켜짐 또는 꺼짐인지 여부.
*/
생성자(옵션 = {}) {
if (!options.tech) {
throw new Error('기술이 제공되지 않았습니다.');
}
const 설정 = 병합(옵션, {
친절한: TextTrackKind[options.kind] || '자막',
언어: options.language || options.srclang || ''
});
let 모드 = TextTrackMode[settings.mode] || '장애가 있는';
const default_ = 설정.기본;
if (settings.kind === 'metadata' || settings.kind === '챕터') {
모드 = '숨김';
}
슈퍼(설정);
this.tech_ = 설정.기술;
this.cues_ = [];
this.activeCues_ = [];
this.preload_ = this.tech_.preloadTextTracks !== 거짓;
const cues = new TextTrackCueList(this.cues_);
const activeCues = new TextTrackCueList(this.activeCues_);
변경하자 = 거짓;
this.timeupdateHandler = Fn.bind(this, function(event = {}) {
if (this.tech_.isDisposed()) {
반품;
}
if (!this.tech_.isReady_) {
if (event.type !== 'timeupdate') {
this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
}
반품;
}
// 업데이트 자체의 부작용에 대한 this.activeCues 액세스
// getter 함수로서의 특성 때문입니다. 제거하지 마십시오.
// 업데이트 중지!
// uglify에서 삭제되지 않도록 setter를 사용합니다(pure_getters 규칙).
this.activeCues = this.activeCues;
경우 (변경) {
this.trigger('큐체인지');
변경 = 거짓;
}
if (event.type !== 'timeupdate') {
this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
}
});
const disposeHandler = () => {
this.stopTracking();
};
this.tech_.one('dispose', disposeHandler);
if (모드 !== '비활성화') {
this.startTracking();
}
Object.defineProperties(이것, {
/**
* @memberof TextTrack
* @member {부울} 기본값
* 이 트랙이 기본적으로 켜짐 또는 꺼짐으로 설정된 경우. 이후 변경 불가
* 창조.
* @사례
*
* @읽기전용
*/
기본: {
얻다() {
default_ 반환;
},
세트() {}
},
/**
* @memberof TextTrack
* @member {문자열} 모드
* 이 TextTrack의 모드를 유효한 {@link TextTrack~Mode}로 설정합니다. 할 것이다
* 유효하지 않은 모드로 설정하면 설정되지 않습니다.
* @사례
*
* @fires TextTrack#modechange
*/
모드: {
얻다() {
반환 모드;
},
설정(신규 모드) {
if (!TextTrackMode[newMode]) {
반품;
}
if (모드 === newMode) {
반품;
}
모드 = newMode;
if (!this.preload_ && 모드 !== '비활성화' && this.cues.length === 0) {
// 주문형 로드.
loadTrack(this.src, this);
}
this.stopTracking();
if (모드 !== '비활성화') {
this.startTracking();
}
/**
* 이 트랙에서 모드가 변경될 때 발생하는 이벤트입니다. 이를 통해
* 이 트랙을 보유하고 있는 TextTrackList가 적절하게 작동합니다.
*
* > 메모: 이것은 사양의 일부가 아닙니다!
*
* @event TextTrack#modechange
* @type {이벤트대상~이벤트}
*/
this.trigger('모드 변경');
}
},
/**
* @memberof TextTrack
* @member {TextTrackCueList} 큐
* 이 TextTrack에 대한 텍스트 트랙 큐 목록입니다.
* @사례
*/
단서: {
얻다() {
if (!this.loaded_) {
null을 반환합니다.
}
반환 신호;
},
세트() {}
},
/**
* @memberof TextTrack
* @member {TextTrackCueList} 활성 큐
* 이 TextTrack에 대해 현재 활성화된 목록 텍스트 트랙 큐.
* @사례
*/
활성 큐: {
얻다() {
if (!this.loaded_) {
null을 반환합니다.
}
// 할 것이 없다
if (this.cues.length === 0) {
활성 큐를 반환합니다.
}
const ct = this.tech_.currentTime();
const 활성 = [];
for (let i = 0, l = this.cues.length; i < 엘; i++) {
const cue = this.cues[i];
if (cue.startTime < = CT && cue.endTime > = CT) {
active.push(큐);
} 그렇지 않으면 (cue.startTime === cue.endTime &&
cue.startTime < = CT &&
cue.startTime + 0.5 > = CT) {
active.push(큐);
}
}
변경 = 거짓;
if (active.length !== this.activeCues_.length) {
변경됨 = 참;
} else {
에 대한 (하자 i = 0; i < 활성 길이; i++) {
if (this.activeCues_.indexOf(active[i]) === -1) {
변경됨 = 참;
}
}
}
this.activeCues_ = 활성;
activeCues.setCues_(this.activeCues_);
활성 큐를 반환합니다.
},
// /!\ 이 setter를 비워 둡니다(위의 timeupdate 핸들러 참조).
세트() {}
}
});
경우 (settings.src) {
this.src = 설정.src;
if (!this.preload_) {
// 트랙은 주문형으로 로드됩니다.
// 다른 목적을 위해 로드된 것처럼 작동합니다.
this.loaded_ = 참;
}
if (this.preload_ || (settings.kind !== '자막' && settings.kind !== '캡션')) {
loadTrack(this.src, this);
}
} else {
this.loaded_ = 참;
}
}
시작 추적() {
// requestAnimationFram 폴백이 있는 requestVideoFrameCallback을 기반으로 하는 보다 정확한 신호
this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
// 또한 rVFC/rAF가 중지되는 경우 timeupdate를 듣습니다(창은 백그라운드, 오디오는 비디오 엘).
this.tech_.on('timeupdate', this.timeupdateHandler);
}
stopTracking() {
경우 (이.rvf_) {
this.tech_.cancelVideoFrameCallback(this.rvf_);
this.rvf_ = 정의되지 않음;
}
this.tech_.off('timeupdate', this.timeupdateHandler);
}
/**
* 내부 큐 목록에 큐를 추가합니다.
*
* @param {TextTrack~Cue} 큐
* 내부 목록에 추가할 단서
*/
addCue(originalCue) {
let cue = originalCue;
만약 (window.vttjs && !(window.vttjs.VTTCue의 원본 큐 인스턴스)) {
cue = new window.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
for (originalCue의 const 소품) {
if (!(큐의 소품)) {
cue[prop] = originalCue[prop];
}
}
// `id`가 복사되었는지 확인합니다.
cue.id = originalCue.id;
cue.originalCue_ = originalCue;
}
const 트랙 = this.tech_.textTracks();
에 대한 (하자 i = 0; i < 트랙.길이; i++) {
if (트랙[i] !== 이것) {
track[i].removeCue(cue);
}
}
this.cues_.push(큐);
this.cues.setCues_(this.cues_);
}
/**
* 내부 목록에서 큐 제거
*
* @param {TextTrack~Cue} removeCue
* 내부 목록에서 제거할 신호
*/
removeCue(제거 큐) {
let i = this.cues_.length;
동안 (i--) {
const cue = this.cues_[i];
if (cue === removeCue || (cue.originalCue_ && cue.originalCue_ === removeCue)) {
this.cues_.splice(i, 1);
this.cues.setCues_(this.cues_);
부서지다;
}
}
}
}
/**
* cuechange - 트랙에 있는 하나 이상의 큐가 활성화되었거나 활성이 중지되었습니다.
*/
TextTrack.prototype.allowedEvents_ = {
cuechange: '큐체인지'
};
기본 TextTrack 내보내기;