/**
* @file player.js
*/
// 하위 클래스 컴포넌트
'./component.js'에서 컴포넌트 가져오기;
'../../package.json'에서 {버전} 가져오기;
'글로벌/문서'에서 문서 가져오기;
'글로벌/창'에서 창 가져오기;
'./mixins/evented'에서 이벤트 가져오기;
'./mixins/evented'에서 {isEvented, addEventedCallback} 가져오기;
import * as Events from './utils/events.js';
import * as Dom from './utils/dom.js';
import * as Fn from './utils/fn.js';
import * as Guid from './utils/guid.js';
* './utils/browser.js'에서 브라우저로 가져오기;
'./utils/browser.js'에서 {IE_VERSION, IS_CHROME, IS_WINDOWS} 가져오기;
로그 가져오기, './utils/log.js'에서 { createLogger };
import {toTitleCase, titleCaseEquals} from './utils/string-cases.js';
import { createTimeRange } from './utils/time-ranges.js';
import { bufferedPercent } from './utils/buffer.js';
import * as stylesheet from './utils/stylesheet.js';
'./fullscreen-api.js'에서 FullscreenApi 가져오기;
'./media-error.js'에서 MediaError 가져오기;
'safe-json-parse/tuple'에서 safeParseTuple 가져오기;
'./utils/obj'에서 {assign} 가져오기;
'./utils/merge-options.js'에서 mergeOptions 가져오기;
'./utils/promise'에서 {silencePromise, isPromise} 가져오기;
'./tracks/text-track-list-converter.js'에서 textTrackConverter 가져오기;
'./modal-dialog'에서 ModalDialog 가져오기;
'./tech/tech.js'에서 기술 가져오기;
'./tech/middleware.js'에서 *를 미들웨어로 가져오기;
'./tracks/track-types'에서 {ALL을 TRACK_TYPES}로 가져오기;
import filterSource from './utils/filter-source';
import {getMimetype, findMimetype} from './utils/mimetypes';
'./utils/hooks'에서 {hooks} 가져오기;
'./utils/obj'에서 {isObject} 가져오기;
'키코드'에서 키코드 가져오기;
// 다음 가져오기는 해당 모듈이
// 항상 video.js 패키지에 포함되어 있습니다. 모듈을 가져오면
// 실행하면 video.js에 등록됩니다.
import './tech/loader.js';
import './poster-image.js';
import './tracks/text-track-display.js';
import './loading-spinner.js';
import './big-play-button.js';
import './close-button.js';
import './control-bar/control-bar.js';
import './error-display.js';
import './tracks/text-track-settings.js';
import './resize-manager.js';
import './live-tracker.js';
// 적어도 원본 비디오 태그를 처리하기 위해 Html5 기술을 가져옵니다.
import './tech/html5.js';
// 다음 기술 이벤트는 단순히 다시 트리거됩니다.
// 발생 시 플레이어에서
const TECH_EVENTS_RETRIGGER = [
/**
* 사용자 에이전트가 미디어 데이터를 다운로드하는 동안 발생합니다.
*
* @event 플레이어#progress
* @type {이벤트대상~이벤트}
*/
/**
* {@link Tech}에 의해 트리거된 `progress` 이벤트를 다시 트리거합니다.
*
* @사적인
* @method Player#handleTechProgress_
* @fires Player#progress
* @listens Tech#progress
*/
'진전',
/**
* 오디오/비디오 로드가 중단되면 발생합니다.
*
* @event 플레이어#abort
* @type {이벤트대상~이벤트}
*/
/**
* {@link Tech}에 의해 트리거된 `abort` 이벤트를 다시 트리거합니다.
*
* @사적인
* @method Player#handleTechAbort_
* @fires Player#abort
* @listens Tech#abort
*/
'중단',
/**
* 브라우저가 의도적으로 미디어 데이터를 가져오지 않을 때 발생합니다.
*
* @event 플레이어#suspend
* @type {이벤트대상~이벤트}
*/
/**
* {@link Tech}에 의해 트리거된 `suspend` 이벤트를 다시 트리거합니다.
*
* @사적인
* @method Player#handleTechSuspend_
* @fires Player#suspend
* @listens Tech#suspend
*/
'유예하다',
/**
* 현재 재생 목록이 비어 있을 때 발생합니다.
*
* @event 플레이어#emptied
* @type {이벤트대상~이벤트}
*/
/**
* {@link Tech}에 의해 트리거된 `emptied` 이벤트를 다시 트리거합니다.
*
* @사적인
* @method Player#handleTechEmptied_
* @fires Player#emptied
* @listens Tech#emptied
*/
'비워지다',
/**
* 브라우저가 미디어 데이터를 얻으려고 시도하지만 데이터를 사용할 수 없을 때 발생합니다.
*
* @event Player#stalled
* @type {이벤트대상~이벤트}
*/
/**
* {@link Tech}에 의해 트리거된 `중단` 이벤트를 다시 트리거합니다.
*
* @사적인
* @method Player#handleTechStalled_
* @fires Player#stalled
* @listens Tech#stalled
*/
'정지',
/**
* 브라우저가 오디오/비디오에 대한 메타 데이터를 로드했을 때 발생합니다.
*
* @event Player#loadedmetadata
* @type {이벤트대상~이벤트}
*/
/**
* {@link Tech}에 의해 트리거된 `loadedmetadata` 이벤트를 다시 트리거합니다.
*
* @사적인
* @method Player#handleTechLoadedmetadata_
* @fires Player#loadedmetadata
* @listens Tech#loadedmetadata
*/
'로드된 메타데이터',
/**
* 브라우저가 오디오/비디오의 현재 프레임을 로드했을 때 발생합니다.
*
* @event 플레이어#loadeddata
* @type {이벤트}
*/
/**
* {@link Tech}에 의해 트리거된 `loadeddata` 이벤트를 다시 트리거합니다.
*
* @사적인
* @method Player#handleTechLoad addeddata_
* @fires Player#loadeddata
* @listens Tech#loadeddata
*/
'로드데이터',
/**
* 현재 재생 위치가 변경되면 발생합니다.
*
* @event 플레이어#timeupdate
* @type {이벤트}
*/
/**
* {@link Tech}에 의해 트리거된 `timeupdate` 이벤트를 다시 트리거합니다.
*
* @사적인
* @method Player#handleTechTimeUpdate_
* @fires Player#timeupdate
* @listens Tech#timeupdate
*/
'시간 업데이트',
/**
* 동영상의 고유 크기가 변경되면 실행됩니다.
*
* @event 플레이어#resize
* @type {이벤트}
*/
/**
* {@link Tech}에 의해 트리거된 `resize` 이벤트를 다시 트리거합니다.
*
* @사적인
* @method Player#handleTechResize_
* @fires Player#resize
* @listens Tech#resize
*/
'크기 조정',
/**
* 볼륨이 변경되면 발생
*
* @event 플레이어#volumechange
* @type {이벤트}
*/
/**
* {@link Tech}에 의해 트리거된 `volumechange` 이벤트를 다시 트리거합니다.
*
* @사적인
* @method Player#handleTechVolumechange_
* @fires Player#volumechange
* @listens Tech#volumechange
*/
'볼륨 변경',
/**
* 텍스트 트랙이 변경되면 발생
*
* @event Player#texttrackchange
* @type {이벤트}
*/
/**
* {@link Tech}에 의해 트리거된 `texttrackchange` 이벤트를 다시 트리거합니다.
*
* @사적인
* @method Player#handleTechTexttrackchange_
* @fires Player#texttrackchange
* @listens Tech#texttrackchange
*/
'텍스트트랙체인지'
];
// 재생 속도가 0일 때 대기할 이벤트
// 이것은 카멜 케이스가 아닌 이벤트 이름을 매핑하기 위한 유일한 목적을 위한 해시입니다.
// 카멜 케이스 함수 이름으로
const TECH_EVENTS_QUEUE = {
놀 수있다: '놀 수있다',
canplaythrough: 'CanPlayThrough',
놀이: '놀이',
추구: '찾았다'
};
const BREAKPOINT_ORDER = [
'매우 작은',
'작은',
'작은',
'중간',
'크기가 큰',
'대형',
'거대한'
];
const BREAKPOINT_CLASSES = {};
// 그렙: vjs-layout-tiny
// 그렙: vjs-layout-x-small
// grep: vjs-레이아웃-소형
// grep: vjs-레이아웃-매체
// grep: vjs-레이아웃-라지
// grep: vjs-레이아웃-x-대형
// grep: vjs-레이아웃-거대한
BREAKPOINT_ORDER.forEach(k => {
const v = k.charAt(0) === 'x' ? `x-${k.substring(1)}` : k;
BREAKPOINT_CLASSES[k] = `vjs-layout-${v}`;
});
const DEFAULT_BREAKPOINTS = {
매우 작은: 210,
작은: 320,
작은: 425,
중간: 768,
크기가 큰: 1440,
특대형: 2560,
거대한: 무한대
};
/**
* Video.js 설정 방법 중 하나가 실행될 때 `Player` 클래스의 인스턴스가 생성됩니다.
* 비디오를 초기화하는 데 사용됩니다.
*
* 인스턴스가 생성된 후에는 두 가지 방법으로 전역적으로 액세스할 수 있습니다.
* 1. `videojs('example_video_1');`를 호출하여
* 2. `videojs.players.example_video_1;`을 통해 직접 사용
*
* @extends 컴포넌트
*/
클래스 플레이어 확장 구성 요소 {
/**
* 이 클래스의 인스턴스를 만듭니다.
*
* @param {요소} 태그
* 옵션 구성에 사용되는 원본 비디오 DOM 요소입니다.
*
* @param {객체} [옵션]
* 옵션 이름 및 값의 개체.
*
* @param {Component~ReadyCallback} [준비]
* 준비 콜백 기능.
*/
생성자(태그, 옵션, 준비) {
// 태그 ID가 존재하는지 확인
tag.id = tag.id || options.id || `vjs_video_${Guid.newGUID()}`;
// 옵션 설정
// 옵션 인수는 비디오 태그에 설정된 옵션을 재정의합니다.
// 전역적으로 설정된 옵션을 무시합니다.
// 이 후반부는 로드 순서와 일치합니다.
// (플레이어 앞에 태그가 있어야 함)
options = assign(Player.getTagSettings(tag), 옵션);
// 설정이 필요하므로 자식 초기화를 지연합니다.
// 플레이어 속성이 먼저고 `super()` 전에 `this`를 사용할 수 없습니다.
options.initChildren = 거짓;
// 요소 생성과 동일
options.createEl = 거짓;
// 이벤트 믹스인을 자동 믹스인하지 않음
options.evented = 거짓;
// 플레이어가 자체적으로 터치 활동을 보고하는 것을 원하지 않습니다.
// 구성 요소의 enableTouchActivity 참조
options.reportTouchActivity = 거짓;
// 언어가 설정되지 않은 경우 가장 가까운 lang 속성을 가져옵니다.
if (!옵션.언어) {
if (typeof tag.closest === '함수') {
가장 가까운 const = tag.closest('[lang]');
만약 (가장 가까운 && 가장 가까운.getAttribute) {
options.language =closed.getAttribute('lang');
}
} else {
let 요소 = 태그;
동안 (요소 && element.nodeType === 1) {
if (Dom.getAttributes(요소).hasOwnProperty('lang')) {
options.language = element.getAttribute('lang');
부서지다;
}
요소 = element.parentNode;
}
}
}
// 새 옵션으로 초기화하는 기본 구성 요소 실행
슈퍼(널, 옵션, 준비);
// 문서 수신기에 대한 바인딩된 메서드를 만듭니다.
this.boundDocumentFullscreenChange_ = (e) => this.documentFullscreenChange_(e);
this.boundFullWindowOnEscKey_ = (e) => this.fullWindowOnEscKey(e);
this.boundUpdateStyleEl_ = (e) => this.updateStyleEl_(e);
this.boundApplyInitTime_ = (e) => this.applyInitTime_(e);
this.boundUpdateCurrentBreakpoint_ = (e) => this.updateCurrentBreakpoint_(e);
this.boundHandleTechClick_ = (e) => this.handleTechClick_(e);
this.boundHandleTechDoubleClick_ = (e) => this.handleTechDoubleClick_(e);
this.boundHandleTechTouchStart_ = (e) => this.handleTechTouchStart_(e);
this.boundHandleTechTouchMove_ = (e) => this.handleTechTouchMove_(e);
this.boundHandleTechTouchEnd_ = (e) => this.handleTechTouchEnd_(e);
this.boundHandleTechTap_ = (e) => this.handleTechTap_(e);
// 기본값은 Fullscreen_을 false로
this.isFullscreen_ = 거짓;
// 로거 생성
this.log = createLogger(this.id_);
// 전체 화면 API에 대한 자체 참조를 유지하여 테스트에서 조롱할 수 있도록 합니다.
this.fsApi_ = FullscreenApi;
// 기술이 포스터를 변경할 때 추적
this.isPosterFromTech_ = 거짓;
// 재생 속도가 0일 때 대기 중인 콜백 정보를 보유합니다.
// 검색이 발생합니다.
this.queuedCallbacks_ = [];
// 비동기적으로 로드될 수 있는 새로운 기술을 로드하고 있으므로 API 액세스를 끕니다.
this.isReady_ = 거짓;
// 초기화 상태 hasStarted_
this.hasStarted_ = 거짓;
// 초기화 상태 userActive_
this.userActive_ = 거짓;
// debugEnabled_ 초기화
this.debugEnabled_ = 거짓;
// 초기화 상태 audioOnlyMode_
this.audioOnlyMode_ = 거짓;
// 초기화 상태 audioPosterMode_
this.audioPosterMode_ = 거짓;
// 초기화 상태 audioOnlyCache_
this.audioOnlyCache_ = {
플레이어 높이: null,
숨은 아이들: []
};
// 글로벌 옵션 객체가 실수로 날아간 경우
// 누군가, 유익한 오류로 일찍 보석금을 내십시오.
if (!this.options_ ||
!this.options_.techOrder ||
!this.options_.techOrder.length) {
throw new Error('techOrder가 지정되지 않았습니다. 덮어쓰셨나요' +
' 그냥 변경하는 대신 'videojs.options' +
'무시할 속성?');
}
// 옵션을 설정하는 데 사용된 원래 태그를 저장합니다.
this.태그 = 태그;
// html5 요소를 복원하는 데 사용되는 태그 속성을 저장합니다.
this.tagAttributes = 태그 && Dom.getAttributes(태그);
// 현재 언어 업데이트
this.language(this.options_.language);
// 지원되는 언어 업데이트
if (옵션.언어) {
// 플레이어 옵션 언어를 소문자로 정규화
const languagesToLower = {};
Object.getOwnPropertyNames(options.languages).forEach(function(name) {
languagesToLower[name.toLowerCase()] = options.languages[이름];
});
this.languages_ = languagesToLower;
} else {
this.languages_ = Player.prototype.options_.languages;
}
this.resetCache_();
// 포스터 설정
this.poster_ = options.poster || '';
// 컨트롤 설정
this.controls_ = !!options.controls;
// 옵션에 저장된 원래 태그 설정
// 이제 네이티브 컨트롤이 깜박이지 않도록 즉시 제거합니다.
// nativeControlsForTouch가 true인 경우 HTML5 기술에 의해 다시 켜질 수 있음
tag.controls = 거짓;
tag.removeAttribute('컨트롤');
this.changesrc_ = 거짓;
this.playCallbacks_ = [];
this.playTerminatedQueue_ = [];
// 속성이 옵션을 재정의합니다.
if (tag.hasAttribute('autoplay')) {
this.autoplay(true);
} else {
// 그렇지 않으면 setter를 사용하여 유효성을 검사하고
// 올바른 값을 설정합니다.
this.autoplay(this.options_.autoplay);
}
// 플러그인 확인
if (옵션.플러그인) {
Object.keys(options.plugins).forEach((이름) => {
if (typeof this[이름] !== '함수') {
throw new Error(`플러그인 "${name}"이 존재하지 않습니다`);
}
});
}
/*
* 스크러빙 내부 상태 저장
*
* @사적인
* @return {Boolean} 사용자가 스크러빙하는 경우 참
*/
this.scrubbing_ = 거짓;
this.el_ = this.createEl();
// 이것을 이벤트 객체로 만들고 `el_`을 이벤트 버스로 사용합니다.
evented(이것, {eventBusKey: 'el_'});
// 문서 및 플레이어 fullscreenchange 핸들러를 수신하여 해당 이벤트를 수신합니다.
// isFullscreen을 적절하게 업데이트할 수 있도록 사용자가 수신하기 전에.
// 다른 모든 것보다 먼저 fullscreenchange 이벤트를 수신하여
// 우리의 isFullscreen 메서드는 내부 구성 요소와 외부 구성 요소에 대해 적절하게 업데이트됩니다.
if (this.fsApi_.requestFullscreen) {
Events.on(문서, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
this.on(this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
}
if (this.fluid_) {
this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
}
// 또한 원래 플레이어 옵션을 각 구성 요소 및 플러그인에 전달하려고 합니다.
// 또한 나중에 옵션을 위해 플레이어에 다시 도달할 필요가 없습니다.
// 또한 this.options_의 또 다른 복사본을 만들어야 합니다.
// 무한 루프.
const playerOptionsCopy = mergeOptions(this.options_);
// 플러그인 로드
if (옵션.플러그인) {
Object.keys(options.plugins).forEach((이름) => {
this[이름](options.plugins[이름]);
});
}
// 디버그 모드를 활성화하여 모든 플러그인에 대해 debugon 이벤트를 발생시킵니다.
경우 (옵션. 디버그) {
this.debug(참);
}
this.options_.playerOptions = playerOptionsCopy;
this.middleware_ = [];
this.playbackRates(options.playbackRates);
this.initChildren();
// 오디오 태그 사용 여부에 따라 isAudio 설정
this.isAudio(tag.nodeName.toLowerCase() === '오디오');
// 컨트롤 className을 업데이트합니다. 컨트롤이 처음에 있는 경우 이 작업을 수행할 수 없습니다.
// 요소가 아직 존재하지 않기 때문에 설정합니다.
if (이.컨트롤()) {
this.addClass('vjs-controls-enabled');
} else {
this.addClass('vjs-controls-disabled');
}
// 플레이어 유형에 따라 ARIA 레이블 및 지역 역할 설정
this.el_.setAttribute('역할', '지역');
if (this.isAudio()) {
this.el_.setAttribute('aria-label', this.localize('오디오 플레이어'));
} else {
this.el_.setAttribute('aria-label', this.localize('비디오 플레이어'));
}
if (this.isAudio()) {
this.addClass('vjs-audio');
}
if (this.flexNotSupported_()) {
this.addClass('vjs-no-flex');
}
// 할 것: 이것을 더 똑똑하게 만드십시오. 터치/마우스 사용 간 사용자 상태 전환
// 장치가 터치 및 마우스 이벤트를 모두 가질 수 있으므로 이벤트를 사용합니다.
// 할 것: 창이 모니터 간에 전환될 때 이 검사를 다시 수행하도록 합니다.
// (https://github.com/videojs/video.js/issues/5683 참조)
if (browser.TOUCH_ENABLED) {
this.addClass('vjs-touch-enabled');
}
// iOS Safari에서 호버 처리가 중단되었습니다.
if (!브라우저.IS_IOS) {
this.addClass('vjs-workinghover');
}
// 플레이어를 ID로 쉽게 찾을 수 있도록 합니다.
Player.players[this.id_] = this;
// 플러그인에서 css를 지원하기 위해 주 버전 클래스 추가
const majorVersion = version.split('.')[0];
this.addClass(`vjs-v${majorVersion}`);
// 플레이어가 처음 초기화될 때 활동을 트리거하므로 구성 요소
// 필요한 경우 컨트롤 막대가 표시되는 것처럼
this.userActive(참);
this.reportUserActivity();
this.one('재생', (e) => this.listenForUserActivity_(e));
this.on('stageclick', (e) => this.handleStageClick_(e));
this.on('키다운', (e) => this.handleKeyDown(e));
this.on('언어변경', (e) => this.handleLanguagechange(e));
this.breakpoints(this.options_.breakpoints);
this.responsive(this.options_.responsive);
// 플레이어가 완전히 끝난 후 오디오 모드 메서드를 모두 호출합니다.
// 그들에 의해 트리거된 이벤트를 들을 수 있도록 설정
this.on('준비', () => {
// 먼저 audioPosterMode 메서드를 호출하여
// 두 옵션이 모두 true로 설정된 경우 audioOnlyMode가 우선할 수 있습니다.
this.audioPosterMode(this.options_.audioPosterMode);
this.audioOnlyMode(this.options_.audioOnlyMode);
});
}
/**
* 비디오 플레이어를 파괴하고 필요한 정리를 수행합니다.
*
* 동영상을 동적으로 추가하고 제거하는 경우 특히 유용합니다.
* DOM으로/로부터.
*
* @fires Player#dispose
*/
폐기() {
/**
* 플레이어가 폐기될 때 호출됩니다.
*
* @event 플레이어#dispose
* @type {이벤트대상~이벤트}
*/
this.trigger('처리');
// dispose가 두 번 호출되는 것을 방지합니다.
this.off('처리');
// 모든 플레이어별 문서 리스너가 언바운드인지 확인합니다. 이것은
Events.off(문서, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
Events.off(문서, '키다운', this.boundFullWindowOnEscKey_);
if (이.styleEl_ && this.styleEl_.parentNode) {
this.styleEl_.parentNode.removeChild(this.styleEl_);
this.styleEl_ = null;
}
// 이 플레이어에 대한 참조를 죽입니다.
Player.players[this.id_] = null;
만약 (이.태그 && this.태그.플레이어) {
this.tag.player = null;
}
만약 (이것.el_ && this.el_.player) {
this.el_.player = null;
}
if (this.tech_) {
this.tech_.dispose();
this.isPosterFromTech_ = 거짓;
this.poster_ = '';
}
if (this.playerElIngest_) {
this.playerElIngest_ = null;
}
if(이.태그) {
this.태그 = null;
}
middleware.clearCacheForPlayer(이);
// 트랙 목록에 대한 모든 이벤트 핸들러 제거
// 모든 트랙과 트랙 리스너는 다음에 제거됩니다.
// 기술 폐기
TRACK_TYPES.names.forEach((이름) => {
const 소품 = TRACK_TYPES[이름];
const list = this[props.getterName]();
// 네이티브 리스트가 아닌 경우
// 이벤트 리스너를 수동으로 제거해야 합니다.
만약 (목록 && 목록.꺼짐) {
list.off();
}
});
// 실제 .el_은 여기에서 제거되거나 다음과 같은 경우 대체됩니다.
super.dispose({restoreEl: this.options_.restoreEl});
}
/**
* `Player`의 DOM 요소를 만듭니다.
*
* @return {요소}
* 생성되는 DOM 요소.
*/
createEl() {
let 태그 = this.tag;
엘하자;
let playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
const divEmbed = this.tag.tagName.toLowerCase() === '비디오 js';
if (playerElIngest) {
el = this.el_ = tag.parentNode;
} 그렇지 않으면 (!divEmbed) {
el = this.el_ = super.createEl('div');
}
// ID 및 클래스를 포함하여 태그의 모든 속성을 복사합니다.
// ID는 이제 비디오 태그가 아닌 플레이어 상자를 참조합니다.
const attrs = Dom.getAttributes(tag);
if(divEmbed) {
el = this.el_ = 태그;
tag = this.tag = document.createElement('동영상');
동안 (el.children.length) {
tag.appendChild(el.firstChild);
}
if (!Dom.hasClass(el, 'video-js')) {
Dom.addClass(el, 'video-js');
}
el.appendChild(태그);
playerElIngest = this.playerElIngest_ = 엘;
// 커스텀 `video-js` 요소에서 속성을 이동합니다.
// 새로운 `video` 요소로. 이것은 다음과 같은 것을 움직일 것입니다
// 플레이어 이전에 js를 통해 설정된 `src` 또는 `controls`
// 초기화되었습니다.
Object.keys(el).forEach((k) => {
{
태그[k] = el[k];
} 잡기 (e) {
// 실제로 복사할 수 없는 outerHTML과 같은 속성이 있습니다. 무시하세요.
}
});
}
// 포커스 순서에서 비디오 요소를 제거하려면 tabindex를 -1로 설정합니다.
tag.setAttribute('tabindex', '-1');
attrs.tabindex = '-1';
// #4583에 대한 해결 방법(JAWS+IE가 BPB 또는 재생 버튼을 알리지 않음) 및
// JAWS가 있는 Chrome(Windows)의 동일한 문제.
// https://github.com/FreedomScientific/VFO-standards-support/issues/78 참조
// JAWS가 사용되고 있는지 감지할 수 없지만 이 ARIA 속성은
// JAWS를 사용하지 않는 경우 IE11 또는 Chrome의 동작을 변경하지 않습니다.
if (IE_VERSION || (IS_CHROME && IS_WINDOWS)) {
tag.setAttribute('역할', '응용 프로그램');
attrs.role = '응용 프로그램';
}
// CSS가 너비/높이를 100%로 만들 수 있도록 태그에서 너비/높이 속성을 제거합니다.
tag.removeAttribute('너비');
tag.removeAttribute('높이');
if (attrs의 '너비') {
삭제 attrs.width;
}
if (attrs의 '높이') {
삭제 attrs.height;
}
Object.getOwnPropertyNames(attrs).forEach(function(attr) {
// div 임베드에 있을 때 플레이어 요소에 클래스 속성을 복사하지 마십시오.
// 클래스는 이미 divEmbed 케이스에 적절하게 설정되어 있습니다.
// 그리고 `video-js` 클래스가 손실되지 않도록 하고 싶습니다.
if (!(divEmbed && 속성 === '클래스')) {
el.setAttribute(attr, attrs[attr]);
}
if(divEmbed) {
tag.setAttribute(attr, attrs[attr]);
}
});
// HTML5 재생 기술로 사용하기 위해 태그 ID/클래스 업데이트
// 컨테이너에 삽입한 후에 이 작업을 수행해야 한다고 생각할 수 있으므로 .vjs-tech 클래스
// 100% 너비/높이로 깜박이지 않지만 클래스는 .video-js 부모에만 적용됩니다.
tag.playerId = tag.id;
tag.id += '_html5_api';
tag.className = 'vjs-tech';
// 요소에서 플레이어를 찾을 수 있도록 합니다.
tag.player = el.player = 이;
// 비디오의 기본 상태는 일시 정지입니다.
this.addClass('vjs-paused');
// 너비/높이를 설정하는 데 사용할 플레이어에 스타일 요소를 추가합니다.
// 여전히 CSS로 재정의할 수 있는 방식으로 플레이어의
// 비디오 요소
if (window.VIDEOJS_NO_DYNAMIC_STYLE !== 참) {
this.styleEl_ = stylesheet.createStyleElement('vjs-styles-dimensions');
const defaultsStyleEl = Dom.$('.vjs-styles-defaults');
const 머리 = Dom.$('머리');
head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
}
this.fill_ = 거짓;
this.fluid_ = 거짓;
// 스타일 el을 업데이트할 width/height/aspectRatio 옵션을 전달합니다.
this.width(this.options_.width);
this.height(this.options_.height);
this.fill(this.options_.fill);
this.fluid(this.options_.fluid);
this.aspectRatio(this.options_.aspectRatio);
// 이름에 대한 혼동과 문제를 줄이기 위해 crossOrigin과 crossorigin을 모두 지원합니다.
this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin);
// 비디오/오디오 태그 내의 모든 링크를 숨깁니다.
// IE는 화면 판독기에서 완전히 숨기지 않기 때문입니다.
const 링크 = tag.getElementsByTagName('a');
에 대한 (하자 i = 0; i < 링크 길이; i++) {
const linkEl = 링크.항목(i);
Dom.addClass(linkEl, 'vjs-hidden');
linkEl.setAttribute('숨김', '숨김');
}
// insertElFirst는 networkState가 3에서 2로 깜박이는 것 같습니다.
// 소스가 원래 실패했는지 알 수 있도록 나중을 위해 원본을 추적합니다.
tag.initNetworkState_ = 태그.네트워크상태;
// 비디오 태그를 div(el/box) 컨테이너에 래핑합니다.
if (태그.부모노드 && !playerElIngest) {
tag.parentNode.insertBefore(엘, 태그);
}
// 플레이어 요소의 첫 번째 자식으로 태그를 삽입합니다.
// 그런 다음 수동으로 자식 배열에 추가하여 this.addChild
// 다른 구성 요소에 대해 제대로 작동합니다.
//
// HTML5 설정에서 수정된 iPhone을 깨뜨립니다.
Dom.prependTo(태그, 엘);
this.children_.unshift(태그);
// CSS :lang()이 플레이어와 일치하도록 플레이어에 lang 속성을 설정합니다.
// 문서와 다른 것으로 설정된 경우
this.el_.setAttribute('lang', this.language_);
this.el_.setAttribute('번역', '아니오');
this.el_ = 엘;
반환 엘;
}
/**
* `Player`의 crossOrigin 옵션을 가져오거나 설정합니다. HTML5 플레이어의 경우 이
* `에 `crossOrigin` 속성을 설정합니다.< 동영상> ` CORS를 제어하는 태그
* 행동.
*
* @see [동영상 요소 속성]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
*
* @param {문자열} [값]
* `Player`의 crossOrigin을 설정할 값. 인수가
* 주어진 'anonymous' 또는 'use-credentials' 중 하나여야 합니다.
*
* @return {문자열|정의되지 않음}
* - 가져올 때 `Player`의 현재 crossOrigin 값입니다.
* - 설정 시 정의되지 않음
*/
crossOrigin(값) {
if(!값) {
return this.techGet_('crossOrigin');
}
if (값 !== '익명' && 값 !== '사용 자격 증명') {
log.warn(`crossOrigin은 "${value}"가 지정된 "익명" 또는 "사용 자격 증명"이어야 합니다.`);
반품;
}
this.techCall_('setCrossOrigin', 값);
반품;
}
/**
* `Player`의 너비에 대한 getter/setter. 플레이어의 구성된 값을 반환합니다.
* 현재 너비를 얻으려면 `currentWidth()`를 사용하십시오.
*
* @param {숫자} [값]
* `Player`의 너비를 설정할 값.
*
* @return {숫자}
* 가져올 때 `Player`의 현재 너비입니다.
*/
너비(값) {
return this.dimension('너비', 값);
}
/**
* `Player`의 높이에 대한 getter/setter. 플레이어의 구성된 값을 반환합니다.
* 현재 높이를 얻으려면 `currentheight()`를 사용하십시오.
*
* @param {숫자} [값]
* `Player`의 높이를 설정할 값.
*
* @return {숫자}
* 가져올 때 `Player`의 현재 높이입니다.
*/
높이(값) {
return this.dimension('높이', 값);
}
/**
* `Player`의 너비에 대한 getter/setter & 키.
*
* @param {문자열} 차원
* 이 문자열은 다음과 같을 수 있습니다.
* - '너비'
* - '키'
*
* @param {숫자} [값]
* 첫 번째 인수에 지정된 차원 값.
*
* @return {숫자}
* (너비/높이)를 가져올 때 차원 인수 값입니다.
*/
치수(치수, 값) {
const privDimension = 치수 + '_';
if (값 === 정의되지 않음) {
return this[privDimension] || 0;
}
if (값 === '' || 값 === '자동') {
// 빈 문자열이 주어지면 차원을 자동으로 재설정
this[privDimension] = 정의되지 않음;
this.updateStyleEl_();
반품;
}
const parsedVal = parseFloat(값);
if (isNaN(parsedVal)) {
log.error(`${dimension}에 대해 잘못된 값 "${value}"이(가) 제공됨`);
반품;
}
this[privDimension] = parsedVal;
this.updateStyleEl_();
}
/**
* `Player`의 vjs-fluid `className`에 대한 getter/setter/toggler.
*
* 이 기능을 켜면 채우기 모드가 꺼집니다.
*
* @param {부울} [부울]
* - true 값은 클래스를 추가합니다.
* - false 값은 클래스를 제거합니다.
* - 어떤 값도 게터가 되지 않습니다.
*
* @return {부울|정의되지 않음}
* - 얻을 때 유체의 가치.
* - 설정시 `정의되지 않음`.
*/
유체(부울) {
if (부울 === 정의되지 않음) {
반환 !!this.fluid_;
}
this.fluid_ = !!부울;
if (isEvented(이)) {
this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_);
}
경우 (부울) {
this.addClass('vjs-fluid');
this.fill(false);
addEventedCallback(이것, () => {
this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
});
} else {
this.removeClass('vjs-fluid');
}
this.updateStyleEl_();
}
/**
* `Player`의 vjs-fill `className`에 대한 getter/setter/toggler.
*
* 이 기능을 켜면 유체 모드가 꺼집니다.
*
* @param {부울} [부울]
* - true 값은 클래스를 추가합니다.
* - false 값은 클래스를 제거합니다.
* - 어떤 값도 게터가 되지 않습니다.
*
* @return {부울|정의되지 않음}
* - 얻을 때 유체의 가치.
* - 설정시 `정의되지 않음`.
*/
채우기(부울) {
if (부울 === 정의되지 않음) {
반환 !!this.fill_;
}
this.fill_ = !!부울;
경우 (부울) {
this.addClass('vjs-fill');
this.fluid(false);
} else {
this.removeClass('vjs-fill');
}
}
/**
* 종횡비 가져오기/설정
*
* @param {문자열} [비율]
* 플레이어의 종횡비
*
* @return {문자열|정의되지 않음}
* 가져올 때 현재 종횡비를 반환합니다.
*/
/**
* `Player`의 종횡비에 대한 getter/setter입니다.
*
* @param {문자열} [비율]
* `Player`의 종횡비를 설정할 값입니다.
*
* @return {문자열|정의되지 않음}
* - 가져올 때 `Player`의 현재 종횡비.
* - 설정 시 정의되지 않음
*/
aspectRatio(비율) {
if (비율 === 정의되지 않음) {
this.aspectRatio_를 반환합니다.
}
// 너비:높이 형식 확인
if (!(/^\d+\:\d+$/).test(비율)) {
throw new Error('가로 세로 비율에 대해 잘못된 값이 제공되었습니다. 형식은 너비:높이여야 합니다(예: 16:9).');
}
this.aspectRatio_ = 비율;
// 유동 모드를 원하는 종횡비를 설정하면
// 고정 모드에서는 너비와 높이를 직접 계산할 수 있기 때문입니다.
this.fluid(참);
this.updateStyleEl_();
}
/**
* `Player` 요소의 스타일 업데이트(높이, 너비 및 종횡비).
*
* @사적인
* @listens Tech#loadedmetadata
*/
updateStyleEl_() {
if (window.VIDEOJS_NO_DYNAMIC_STYLE === 참) {
const 너비 = typeof this.width_ === '숫자' ? this.width_ : this.options_.width;
const height = typeof this.height_ === '숫자' ? this.height_ : this.options_.height;
const techEl = this.tech_ && this.tech_.el();
경우 (techEl) {
if (너비 > = 0) {
techEl.width = 폭;
}
만약 (높이 > = 0) {
techEl.height = 높이;
}
}
반품;
}
너비를 보자;
높이를 보자;
let aspectRatio;
let idClass;
// 종횡비는 직접 사용되거나 너비와 높이를 계산하는 데 사용됩니다.
if (this.aspectRatio_ !== 정의되지 않음 && this.aspectRatio_ !== '자동') {
// 특별히 설정된 모든 aspectRatio 사용
aspectRatio = this.aspectRatio_;
} 그렇지 않으면 (this.videoWidth() > 0) {
// 그렇지 않으면 비디오 메타데이터에서 종횡비를 가져오려고 시도합니다.
aspectRatio = this.videoWidth() + ':' + this.videoHeight();
} else {
// 또는 기본값을 사용합니다. 비디오 요소는 2:1이지만 16:9가 더 일반적입니다.
종횡비 = '16:9';
}
// 크기를 계산하는 데 사용할 수 있는 비율을 십진수로 가져옵니다.
const ratioParts = aspectRatio.split(':');
const ratioMultiplier = ratioParts[1] / ratioParts[0];
if (this.width_ !== 정의되지 않음) {
// 특별히 설정된 너비를 사용합니다.
너비 = this.width_;
} 그렇지 않으면 (this.height_ !== 정의되지 않음) {
// 또는 높이가 설정된 경우 종횡비에서 너비를 계산합니다.
너비 = this.height_ / ratioMultiplier;
} else {
// 또는 비디오의 메타데이터를 사용하거나 비디오 엘의 기본값인 300을 사용합니다.
너비 = this.videoWidth() || 300;
}
if (this.height_ !== 정의되지 않음) {
// 특별히 설정된 높이를 사용합니다.
높이 = this.height_;
} else {
// 그렇지 않으면 비율과 너비에서 높이를 계산합니다.
높이 = 너비 * ratioMultiplier;
}
// 알파 문자로 시작하여 CSS 클래스가 유효한지 확인
if ((/^[^a-zA-Z]/).test(this.id())) {
idClass = '치수-' + this.id();
} else {
idClass = this.id() + '-dimensions';
}
// 스타일 요소에 대해 올바른 클래스가 여전히 플레이어에 있는지 확인합니다.
this.addClass(idClass);
stylesheet.setTextContent(this.styleEl_, `
.${idClass} {
폭: ${폭}px;
높이: ${높이}픽셀;
}
.${idClass}.vjs-fluid:not(.vjs-오디오 전용 모드) {
패딩 상단: ${ratioMultiplier * 100}%;
}
`);
}
/**
* 요소를 포함한 재생 {@link Tech} 인스턴스 로드/생성
* 및 API 메서드. 그런 다음 `Player`에 `Tech` 요소를 자식으로 추가합니다.
*
* @param {문자열} 기술 이름
* 재생 기술의 이름
*
* @param {문자열} 소스
* 영상 출처
*
* @사적인
*/
loadTech_(기술자 이름, 소스) {
// 현재 재생 기술을 일시 중지하고 제거합니다.
if (this.tech_) {
this.unloadTech_();
}
const titleTechName = toTitleCase(techName);
const camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);
// 다른 기술을 사용하는 즉시 HTML5 비디오 태그를 제거합니다.
if (titleTechName !== 'Html5' && 이 태그) {
Tech.getTech('Html5').disposeMediaElement(this.tag);
this.tag.player = null;
this.태그 = null;
}
this.techName_ = titleTechName;
// 비동기적으로 로드될 수 있는 새로운 기술을 로드하고 있으므로 API 액세스를 끕니다.
this.isReady_ = 거짓;
let autoplay = this.autoplay();
// autoplay가 문자열(또는 normalizeAutoplay: true인 `true`)인 경우 기술팀에 false를 전달합니다.
// 플레이어가 `loadstart`에서 자동 재생을 처리하기 때문입니다.
if (typeof this.autoplay() === '문자열' || this.autoplay() === 참 && this.options_.normalizeAutoplay) {
자동재생 = 거짓;
}
// 플레이어 옵션에서 기술별 옵션을 가져오고 사용할 소스 및 상위 요소를 추가합니다.
const 기술 옵션 = {
원천,
자동 재생,
'nativeControlsForTouch': this.options_.nativeControlsForTouch,
'playerId': this.id(),
'techId': `${this.id()}_${camelTechName}_api`,
'playsinline': this.options_.playsinline,
'사전 로드': this.options_.preload,
'루프': this.options_.loop,
'disablePictureInPicture': this.options_.disablePictureInPicture,
'음소거됨': this.options_.muted,
'포스터': this.poster(),
'언어': this.언어(),
'playerElIngest': this.playerElIngest_ || 거짓,
'vtt.js': this.options_['vtt.js'],
'canOverridePoster': !!this.options_.techCanOverridePoster,
'enableSourceset': this.options_.enableSourceset,
'약속': this.options_.Promise
};
TRACK_TYPES.names.forEach((이름) => {
const 소품 = TRACK_TYPES[이름];
techOptions[props.getterName] = this[props.privateName];
});
assign(techOptions, this.options_[titleTechName]);
assign(techOptions, this.options_[camelTechName]);
assign(techOptions, this.options_[techName.toLowerCase()]);
if(이.태그) {
techOptions.tag = this.tag;
}
만약 (출처 && source.src === this.cache_.src && this.cache_.currentTime > 0) {
techOptions.startTime = this.cache_.currentTime;
}
// 기술 인스턴스 초기화
const TechClass = Tech.getTech(techName);
if (!TechClass) {
throw new Error(`'${titleTechName}'이라는 이름의 기술이 없습니다! '${titleTechName}'은 videojs.registerTech()를 사용하여 등록해야 합니다'`);
}
this.tech_ = new TechClass(techOptions);
// player.triggerReady는 항상 비동기이므로 이것이 비동기일 필요는 없습니다.
this.tech_.ready(Fn.bind(this, this.handleTechReady_), true);
textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_);
// 모든 HTML5 정의 이벤트를 수신하고 플레이어에서 트리거합니다.
TECH_EVENTS_RETRIGGER.forEach((이벤트) => {
this.on(this.tech_, 이벤트, (e) => this[`handleTech${toTitleCase(event)}_`](e));
});
Object.keys(TECH_EVENTS_QUEUE).forEach((이벤트) => {
this.on(this.tech_, 이벤트, (eventObj) => {
if (this.tech_.playbackRate() === 0 && this.tech_.seeking()) {
this.queuedCallbacks_.push({
콜백: this[`handleTech${TECH_EVENTS_QUEUE[event]}_`].bind(this),
이벤트: eventObj
});
반품;
}
this[`handleTech${TECH_EVENTS_QUEUE[event]}_`](eventObj);
});
});
this.on(this.tech_, 'loadstart', (e) => this.handleTechLoadStart_(e));
this.on(this.tech_, 'sourceset', (e) => this.handleTechSourceset_(e));
this.on(this.tech_, '대기 중', (e) => this.handleTechWaiting_(e));
this.on(this.tech_, '종료', (e) => this.handleTechEnded_(e));
this.on(this.tech_, '탐구', (e) => this.handleTechSeeking_(e));
this.on(this.tech_, '재생', (e) => this.handleTechPlay_(e));
this.on(this.tech_, 'firstplay', (e) => this.handleTechFirstPlay_(e));
this.on(this.tech_, '일시중지', (e) => this.handleTechPause_(e));
this.on(this.tech_, 'durationchange', (e) => this.handleTechDurationChange_(e));
this.on(this.tech_, 'fullscreenchange', (e, 데이터) => this.handleTechFullscreenChange_(e, 데이터));
this.on(this.tech_, '전체 화면 오류', (e, 오류) => this.handleTechFullscreenError_(e, err));
this.on(this.tech_, 'enterpictureinpicture', (e) => this.handleTechEnterPictureInPicture_(e));
this.on(this.tech_, 'leavepictureinpicture', (e) => this.handleTechLeavePictureInPicture_(e));
this.on(this.tech_, '오류', (e) => this.handleTechError_(e));
this.on(this.tech_, 'posterchange', (e) => this.handleTechPosterChange_(e));
this.on(this.tech_, 'textdata', (e) => this.handleTechTextData_(e));
this.on(this.tech_, 'ratechange', (e) => this.handleTechRateChange_(e));
this.on(this.tech_, 'loadedmetadata', this.boundUpdateStyleEl_);
this.usingNativeControls(this.techGet_('컨트롤'));
if (이.컨트롤() && !this.usingNativeControls()) {
this.addTechControlsListeners_();
}
// 기술 요소가 이미 없는 경우 DOM에 기술 요소를 추가합니다.
// Html5를 사용하는 경우 원본 비디오 요소를 삽입하지 않도록 합니다.
if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
Dom.prependTo(this.tech_.el(), this.el());
}
// 첫 번째 기술이 로드된 후 원본 비디오 태그 참조를 제거합니다.
if(이.태그) {
this.tag.player = null;
this.태그 = null;
}
}
/**
* 현재 재생 {@link Tech}를 언로드하고 폐기합니다.
*
* @사적인
*/
unloadTech_() {
// 다음 기술에서 동일한 텍스트 트랙을 재사용할 수 있도록 현재 텍스트 트랙을 저장합니다.
TRACK_TYPES.names.forEach((이름) => {
const 소품 = TRACK_TYPES[이름];
this[props.privateName] = this[props.getterName]();
});
this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
this.isReady_ = 거짓;
this.tech_.dispose();
this.tech_ = 거짓;
if (this.isPosterFromTech_) {
this.poster_ = '';
this.trigger('posterchange');
}
this.isPosterFromTech_ = 거짓;
}
/**
* 현재 {@link Tech}에 대한 참조를 반환합니다.
* 기본적으로 기술을 직접 사용하는 위험에 대한 경고를 인쇄합니다.
* 그러나 전달된 모든 인수는 경고를 음소거합니다.
*
* @param {*} [안전]
* 경고를 침묵시키기 위해 전달된 모든 것
*
* @return {기술}
* 기술
*/
기술(안전) {
if (안전성 === 정의되지 않음) {
log.warn('기술을 직접 사용하는 것은 위험할 수 있습니다. 당신이 무엇을 하고 있는지 알고 있기를 바랍니다.\n' +
'자세한 내용은 https://github.com/videojs/video.js/issues/2617을 참조하세요.\n');
}
this.tech_를 반환합니다.
}
/**
* 재생 요소에 대한 클릭 및 터치 리스너 설정
*
* - 데스크톱: 동영상 자체를 클릭하면 재생이 전환됩니다.
* - 모바일 기기: 동영상을 클릭하면 컨트롤이 전환됩니다.
* 활성 상태와 활성 상태 사이에서 사용자 상태를 전환하여 수행됩니다.
* 비활성
* - 탭은 사용자가 활성화되었거나 비활성화되었음을 알릴 수 있습니다.
* 예를 들어 iPhone 동영상을 빠르게 탭하면 컨트롤이 표시됩니다. 또 다른
* 빠르게 탭하면 다시 숨겨야 함(사용자가 비활성 상태임을 알림)
* 보기 상태)
* - 이 외에도 다음 이후에도 사용자가 비활성 상태로 간주되기를 원합니다.
* 몇 초 동안 활동이 없습니다.
*
* > 참고: 이 설정으로 모방할 수 없는 iOS 상호 작용의 유일한 부분
* 활동으로 계산되는 동영상 요소를 길게 터치하여
* 컨트롤을 계속 표시하지만 문제가 되지 않습니다. 터치 앤 홀드
* 모든 컨트롤에서 여전히 사용자를 활성 상태로 유지합니다.
*
* @사적인
*/
addTechControlsListeners_() {
// 여러 번 호출되는 경우를 대비하여 이전 리스너를 모두 제거해야 합니다.
this.removeTechControlsListeners_();
this.on(this.tech_, '클릭', this.boundHandleTechClick_);
this.on(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
// 컨트롤이 숨겨진 경우 탭 이벤트 없이 변경되지 않도록 합니다.
// 따라서 사용자를 보고하기 전에 컨트롤이 이미 표시되었는지 확인합니다.
// 활동
this.on(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
this.on(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
this.on(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
// 탭 리스너는 터치엔드 리스너 뒤에 와야 합니다.
// 수신기는 userActive(false)를 설정할 때 보고된 사용자 활동을 취소합니다.
this.on(this.tech_, '탭', this.boundHandleTechTap_);
}
/**
* 클릭 및 탭 컨트롤에 사용되는 리스너를 제거합니다. 다음을 위해 필요합니다.
* 컨트롤이 비활성화되어 탭/터치가 아무 작업도 수행하지 않아야 합니다.
*
* @사적인
*/
removeTechControlsListeners_() {
// 단순히 `this.off()`를 사용하고 싶지는 않습니다.
// 이것을 확장하는 기술자가 추가한 리스너.
this.off(this.tech_, '탭', this.boundHandleTechTap_);
this.off(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
this.off(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
this.off(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
this.off(this.tech_, '클릭', this.boundHandleTechClick_);
this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
}
/**
* 플레이어는 기술이 준비될 때까지 기다립니다.
*
* @사적인
*/
handleTechReady_() {
this.triggerReady();
// 이전과 동일한 볼륨 유지
if (this.cache_.volume) {
this.techCall_('setVolume', this.cache_.volume);
}
// 로드하는 동안 기술자가 더 높은 해상도의 포스터를 찾았는지 확인합니다.
this.handleTechPosterChange_();
// 가능한 경우 기간을 업데이트합니다.
this.handleTechDurationChange_();
}
/**
* {@link Tech}에 의해 트리거된 `loadstart` 이벤트를 다시 트리거합니다. 이
* 함수는 첫 번째 로드 시작인 경우 {@link Player#firstplay}도 트리거합니다.
* 동영상용.
*
* @fires Player#loadstart
* @fires 플레이어#firstplay
* @listens Tech#loadstart
* @사적인
*/
handleTechLoadStart_() {
// 할 것: 대신 `emptied` 이벤트를 사용하도록 업데이트했습니다. #1277을 참조하십시오.
this.removeClass('vjs-ended');
this.removeClass('vjs-seeking');
// 오류 상태 재설정
this.오류(null);
// 기간 업데이트
this.handleTechDurationChange_();
// 이미 재생 중인 경우 지금 firstplay 이벤트를 트리거하려고 합니다.
// firstplay 이벤트는 play 및 loadstart 이벤트 모두에 의존합니다.
// 새로운 소스에 대해 임의의 순서로 발생할 수 있습니다.
if (!this.paused()) {
/**
* 사용자 에이전트가 미디어 데이터를 찾기 시작할 때 발생
*
* @event 플레이어#loadstart
* @type {이벤트대상~이벤트}
*/
this.trigger('loadstart');
this.trigger('첫 플레이');
} else {
// hasStarted 상태 재설정
this.hasStarted(거짓);
this.trigger('loadstart');
}
// 브라우저 로드가 시작된 후 자동 재생이 발생합니다.
// 그래서 우리는 그 행동을 모방합니다
this.manualAutoplay_(this.autoplay() === 참 && this.options_.normalizeAutoplay ? '재생' : this.autoplay());
}
/**
* 일반적인 부울이 아닌 자동 재생 문자열 값 처리
* 기술자가 처리해야 하는 값입니다. 아니니 참고하세요
* 사양의 일부. 유효한 값과 그 기능은 다음과 같습니다.
* Player#autoplay()의 autoplay getter에서 찾을 수 있습니다.
*/
manualAutoplay_(유형) {
if (!this.tech_ || typeof type !== '문자열') {
반품;
}
// 원래 muted() 값을 저장하고 muted를 true로 설정하고 play()를 시도합니다.
// Promise 거부 시 저장된 값에서 음소거 복원
const resolveMuted = () => {
const 이전에 음소거됨 = this.muted();
this.muted(참);
const restoreMuted = () => {
this.muted(이전에 음소거됨);
};
// 재생 종료 시 음소거 복원
this.playTerminatedQueue_.push(restoreMuted);
const mutedPromise = this.play();
if (!isPromise(mutedPromise)) {
반품;
}
mutedPromise.catch(오류 => {
복원음소거();
throw new Error(`manualAutoplay에서 거부되었습니다. 음소거된 값을 복원합니다. ${오류 ? 오류 : ''}`);
});
};
약속하자;
// 음소거된 경우 기본값은 true입니다.
// 우리가 할 수 있는 유일한 일은 play를 호출하는 것입니다.
if (유형 === '임의' && !this.muted()) {
약속 = this.play();
if (isPromise(약속)) {
약속 = 약속.catch(resolveMuted);
}
} 그렇지 않으면 (유형 === '음소거됨' && !this.muted()) {
약속 = resolveMuted();
} else {
약속 = this.play();
}
if (!isPromise(약속)) {
반품;
}
반환 약속.then(() => {
this.trigger({type: 'autoplay-success', autoplay: type});
}).캐치(() => {
this.trigger({type: 'autoplay-failure', autoplay: type});
});
}
/**
* 올바른 소스를 반환하도록 내부 소스 캐시를 업데이트합니다.
* `src()`, `currentSource()` 및 `currentSources()`.
*
* > 참고: 전달된 소스가 존재하는 경우 `currentSources`는 업데이트되지 않습니다.
* 현재 `currentSources` 캐시에 있습니다.
*
*
* @param {Tech~SourceObject} srcObj
* 캐시를 업데이트할 문자열 또는 객체 소스.
*/
updateSourceCaches_(srcObj = '') {
let src = srcObj;
let type = '';
if (src 유형 !== '문자열') {
src = srcObj.src;
유형 = srcObj.유형;
}
// 모든 캐시가 기본값으로 설정되어 있는지 확인
// null 검사를 방지하기 위해
this.cache_.source = this.cache_.source || {};
this.cache_.sources = this.cache_.sources || [];
// 전달된 src의 유형을 가져오려고 시도합니다.
만약 (src && !유형) {
type = findMimetype(this, src);
}
// `currentSource` 캐시를 항상 업데이트합니다.
this.cache_.source = mergeOptions({}, srcObj, {src, type});
const matchingSources = this.cache_.sources.filter((s) => s.src && s.src === src);
const sourceElSources = [];
const sourceEls = this.$$('소스');
const matchingSourceEls = [];
에 대한 (하자 i = 0; i < sourceEls.length; i++) {
const sourceObj = Dom.getAttributes(sourceEls[i]);
sourceElSources.push(sourceObj);
if (sourceObj.src && sourceObj.src === src) {
matchingSourceEls.push(sourceObj.src);
}
}
// 일치하는 소스 엘이 있지만 일치하는 소스가 없는 경우
// 현재 소스 캐시가 최신이 아닙니다.
if (matchingSourceEls.length && !matchingSources.length) {
this.cache_.sources = sourceElSources;
// 일치하는 소스 또는 소스 els가 없는 경우
// `currentSource` 캐시에 대한 소스 캐시
} 그렇지 않으면 (!matchingSources.length) {
this.cache_.sources = [이.cache_.소스];
}
// 기술 `src` 캐시 업데이트
this.cache_.src = src;
}
/**
* *실험적* 소스가 {@link Tech}에서 설정되거나 변경될 때 실행됩니다.
* 미디어 요소가 다시 로드되도록 합니다.
*
* 초기 소스와 각 후속 소스에 대해 실행됩니다.
* 이 이벤트는 Video.js의 맞춤 이벤트이며 {@link Tech}에 의해 트리거됩니다.
*
* 이 이벤트의 이벤트 개체에는 소스를 포함할 `src` 속성이 포함되어 있습니다.
* 이벤트가 발동되었을 때 사용 가능했던 것입니다. 일반적으로 Video.js인 경우에만 필요합니다.
* 소스가 변경되는 동안 기술을 전환하고 있습니다.
*
* 플레이어(또는 미디어 요소)에서 `load`가 호출될 때도 실행됩니다.
* {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|사양 `로드`} 때문입니다.
* 자원 선택 알고리즘을 중단하고 다시 시작해야 한다고 말합니다.
* 이 경우 `src` 속성이
* 소스가 무엇인지 알 수 없지만 빈 문자열 `""`
* 변하고 있다는 것.
*
* *이 이벤트는 현재 아직 실험 중이며 마이너 릴리스에서 변경될 수 있습니다.*
* __사용하려면 `enableSourceset` 옵션을 플레이어에게 전달하세요.__
*
* @event 플레이어#sourceset
* @type {이벤트대상~이벤트}
* @prop {문자열} src
* `sourceset`이 트리거되었을 때 사용할 수 있는 소스 URL입니다.
* 소스가 무엇인지 알 수 없으면 빈 문자열이 됩니다.
* 하지만 출처는 변경될 것임을 알아두세요.
*/
/**
* {@link Tech}에 의해 트리거된 `sourceset` 이벤트를 다시 트리거합니다.
*
* @fires Player#sourceset
* @listens Tech#sourceset
* @사적인
*/
handleTechSourceset_(이벤트) {
// 소스 캐시를 업데이트할 때만 소스 캐시를 업데이트합니다.
// 플레이어 API를 사용하여 업데이트되지 않았습니다.
if (!this.changesrc_) {
let updateSourceCaches = (src) => this.updateSourceCaches_(src);
const playerSrc = this.currentSource().src;
const eventSrc = event.src;
// blob이 아닌 playerSrc와 blob인 tech src가 있는 경우
if (playerSrc && !(/^blob:/).test(playerSrc) && (/^blob:/).test(eventSrc)) {
// 기술 소스와 플레이어 소스가 모두 업데이트된 경우 가정합니다.
// @videojs/http-streaming과 같은 것이 소스를 설정하고 소스 캐시 업데이트를 건너뜁니다.
if (!this.lastSource_ || (this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc)) {
updateSourceCaches = () => {};
}
}
// 소스를 초기 소스로 바로 업데이트
// 경우에 따라 빈 문자열이 됩니다.
updateSourceCaches(eventSrc);
// `sourceset` `src`가 빈 문자열인 경우
// `loadstart`가 캐시를 `currentSrc`로 업데이트하기를 기다립니다.
// `loadstart` 이전에 sourceset이 발생하면 상태를 재설정합니다.
if (!event.src) {
this.tech_.any(['sourceset', 'loadstart'], (e) => {
// 소스셋이 `loadstart` 이전에 발생한 경우
// 이 `handleTechSourceset_`으로 할 일이 없습니다.
// 다시 호출될 것이고 이것은 그곳에서 처리될 것입니다.
if (e.type === '소스셋') {
반품;
}
const techSrc = this.techGet('currentSrc');
this.lastSource_.tech = techSrc;
this.updateSourceCaches_(techSrc);
});
}
}
this.lastSource_ = {플레이어: this.currentSource().src, 기술: event.src};
this.trigger({
src: 이벤트.src,
유형: '소스셋'
});
}
/**
* vjs-has-started 클래스 추가/제거
*
* @fires 플레이어#firstplay
*
* @param {부울} 요청
* - true: 클래스를 추가합니다.
* - false: 클래스 제거
*
* @return {부울}
* hasStarted_의 부울 값
*/
hasStarted(요청) {
if (요청 === 정의되지 않음) {
// 변경 요청이 없으면 getter로 작동
this.hasStarted_를 반환합니다.
}
if (요청 === this.hasStarted_) {
반품;
}
this.hasStarted_ = 요청;
if (this.hasStarted_) {
this.addClass('vjs-has-started');
this.trigger('첫 플레이');
} else {
this.removeClass('vjs-has-started');
}
}
/**
* 미디어가 재생을 시작하거나 재개할 때마다 발생
*
* @see [사양]{@링크 https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
* @fires 플레이어#플레이
* @listens Tech#play
* @사적인
*/
handleTechPlay_() {
this.removeClass('vjs-ended');
this.removeClass('vjs-paused');
this.addClass('vjs-playing');
// 사용자가 재생을 누르면 포스터를 숨깁니다.
this.hasStarted(참);
/**
* {@link Tech#play} 이벤트가 발생할 때마다 트리거됩니다. 나타냅니다
* 재생이 시작되었거나 재개되었습니다.
*
* @event 플레이어#play
* @type {이벤트대상~이벤트}
*/
this.trigger('재생');
}
/**
* {@link Tech}에 의해 트리거된 `ratechange` 이벤트를 다시 트리거합니다.
*
* 재생 속도가 0일 때 대기 중인 이벤트가 있으면 실행
* 지금 그 사건들.
*
* @사적인
* @method Player#handleTechRateChange_
* @fires Player#ratechange
* @listens Tech#ratechange
*/
handleTechRateChange_() {
if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
this.queuedCallbacks_.forEach((대기) => queued.callback(queued.event));
this.queuedCallbacks_ = [];
}
this.cache_.lastPlaybackRate = this.tech_.playbackRate();
/**
* 오디오/비디오 재생 속도 변경 시 발생
*
* @event 플레이어#ratechange
* @type {이벤트}
*/
this.trigger('ratechange');
}
/**
* {@link Tech}에 의해 트리거된 `waiting` 이벤트를 다시 트리거합니다.
*
* @fires Player#waiting
* @listens Tech#waiting
* @사적인
*/
handleTechWaiting_() {
this.addClass('vjs-waiting');
/**
* DOM 요소의 readyState 변경으로 인해 재생이 중지되었습니다.
*
* @event 플레이어#waiting
* @type {이벤트대상~이벤트}
*/
this.trigger('기다림');
// 브라우저는 대기 이벤트 후에 timeupdate 이벤트를 내보낼 수 있습니다. 방지하기 위해
// 대기 중인 클래스를 조기에 제거하고 시간이 변경될 때까지 기다립니다.
const timeWhenWaiting = this.currentTime();
const timeUpdateListener = () => {
if (timeWhenWaiting !== this.currentTime()) {
this.removeClass('vjs-waiting');
this.off('timeupdate', timeUpdateListener);
}
};
this.on('timeupdate', timeUpdateListener);
}
/**
* {@link Tech}에 의해 트리거된 `canplay` 이벤트를 다시 트리거합니다.
* > 메모: 이는 브라우저 간에 일관성이 없습니다. #1351 참조
*
* @fires 플레이어#canplay
* @listens Tech#canplay
* @사적인
*/
handleTechCanPlay_() {
this.removeClass('vjs-waiting');
/**
* 미디어에 HAVE_FUTURE_DATA 이상의 readyState가 있습니다.
*
* @event 플레이어#canplay
* @type {이벤트대상~이벤트}
*/
this.trigger('canplay');
}
/**
* {@link Tech}에 의해 트리거된 `canplaythrough` 이벤트를 다시 트리거합니다.
*
* @fires Player#canplaythrough
* @listens Tech#canplaythrough
* @사적인
*/
handleTechCanPlayThrough_() {
this.removeClass('vjs-waiting');
/**
* 미디어에 HAVE_ENOUGH_DATA 이상의 readyState가 있습니다. 이것은
* 전체 미디어 파일을 버퍼링 없이 재생할 수 있습니다.
*
* @event Player#canplaythrough
* @type {이벤트대상~이벤트}
*/
this.trigger('canplaythrough');
}
/**
* {@link Tech}에 의해 트리거된 `playing` 이벤트를 다시 트리거합니다.
*
* @fires 플레이어#playing
* @listens Tech#playing
* @사적인
*/
handleTechPlaying_() {
this.removeClass('vjs-waiting');
/**
* 미디어 재생이 더 이상 차단되지 않고 재생이 시작되었습니다.
*
* @event 플레이어#playing
* @type {이벤트대상~이벤트}
*/
this.trigger('재생 중');
}
/**
* {@link Tech}에 의해 트리거된 `seeking` 이벤트를 다시 트리거합니다.
*
* @fires Player#seeking
* @listens Tech#seeking
* @사적인
*/
handleTechSeeking_() {
this.addClass('vjs-seeking');
/**
* 플레이어가 새로운 시간으로 점프할 때마다 발생
*
* @event 플레이어#seeking
* @type {이벤트대상~이벤트}
*/
this.trigger('검색');
}
/**
* {@link Tech}에 의해 트리거된 `seeked` 이벤트를 다시 트리거합니다.
*
* @fires Player#seeked
* @listens Tech#seeked
* @사적인
*/
handleTechSeeked_() {
this.removeClass('vjs-seeking');
this.removeClass('vjs-ended');
/**
* 플레이어가 새로운 시간으로 점프를 마쳤을 때 발생
*
* @event Player#seeked
* @type {이벤트대상~이벤트}
*/
this.trigger('검색');
}
/**
* {@link Tech}에 의해 트리거된 `firstplay` 이벤트를 다시 트리거합니다.
*
* @fires 플레이어#firstplay
* @listens Tech#firstplay
* @deprecated 6.0부터 firstplay 이벤트는 더 이상 사용되지 않습니다.
* 6.0부터 플레이어에게 `starttime` 옵션을 전달하고 firstplay 이벤트는 더 이상 사용되지 않습니다.
* @사적인
*/
handleTechFirstPlay_() {
// 첫 번째 starttime 속성이 지정된 경우
// 그런 다음 주어진 오프셋에서 초 단위로 시작합니다.
if (this.options_.starttime) {
log.warn('플레이어에게 `starttime` 옵션을 전달하는 것은 6.0에서 더 이상 사용되지 않습니다.');
this.currentTime(this.options_.starttime);
}
this.addClass('vjs-has-started');
/**
* 동영상이 처음 재생될 때 실행됩니다. HLS 사양의 일부가 아니며 이는
* 아마도 아직 최고의 구현은 아니므로 아껴서 사용하십시오. 당신이 가지고 있지 않은 경우
* 재생을 금지하는 이유는 `myPlayer.one('play');`을 대신 사용하십시오.
*
* @event 플레이어#firstplay
* @deprecated 6.0부터 firstplay 이벤트는 더 이상 사용되지 않습니다.
* @type {이벤트대상~이벤트}
*/
this.trigger('첫 플레이');
}
/**
* {@link Tech}에 의해 트리거된 `pause` 이벤트를 다시 트리거합니다.
*
* @fires Player#pause
* @listens Tech#pause
* @사적인
*/
handleTechPause_() {
this.removeClass('vjs-playing');
this.addClass('vjs-paused');
/**
* 미디어가 일시 중지될 때마다 실행됨
*
* @event 플레이어#pause
* @type {이벤트대상~이벤트}
*/
this.trigger('일시정지');
}
/**
* {@link Tech}에 의해 트리거된 `ended` 이벤트를 다시 트리거합니다.
*
* @fires Player#ended
* @listens Tech#ended
* @사적인
*/
handleTechEnded_() {
this.addClass('vjs-ended');
this.removeClass('vjs-waiting');
if (this.options_.loop) {
this.currentTime(0);
this.play();
} 그렇지 않으면 (!this.paused()) {
this.pause();
}
/**
* 미디어 리소스의 끝에 도달하면 시작됨(currentTime == duration)
*
* @event 플레이어#ended
* @type {이벤트대상~이벤트}
*/
this.trigger('종료');
}
/**
* 미디어 리소스의 재생 시간이 처음 알려지거나 변경될 때 발생
*
* @listens Tech#durationchange
* @사적인
*/
handleTechDurationChange_() {
this.duration(this.techGet_('기간'));
}
/**
* 미디어 요소를 클릭하여 재생/일시 정지 처리
*
* @param {EventTarget~Event} 이벤트
* 이 기능을 트리거한 이벤트
*
* @listens Tech#click
* @사적인
*/
handleTechClick_(이벤트) {
// 컨트롤이 비활성화되면 클릭으로 재생이 전환되지 않아야 합니다.
// 클릭은 컨트롤로 간주됩니다.
if (!this.controls_) {
반품;
}
만약에 (
this.options_ === 정의되지 않음 ||
this.options_.userActions === 정의되지 않음 ||
this.options_.userActions.click === 정의되지 않음 ||
this.options_.userActions.click !== 거짓
) {
만약에 (
this.options_ !== 정의되지 않음 &&
this.options_.userActions !== 정의되지 않음 &&
typeof this.options_.userActions.click === '함수'
) {
this.options_.userActions.click.call(this, event);
} 그렇지 않으면 (this.paused()) {
침묵 약속(this.play());
} else {
this.pause();
}
}
}
/**
* 미디어 요소를 두 번 클릭하여 전체 화면을 시작/종료합니다.
*
* @param {EventTarget~Event} 이벤트
* 이 기능을 트리거한 이벤트
*
* @listens Tech#dblclick
* @사적인
*/
handleTechDoubleClick_(이벤트) {
if (!this.controls_) {
반품;
}
// 전체 화면 상태를 전환하고 싶지 않습니다.
// 컨트롤 바 또는 모달 내부를 더블 클릭했을 때
const inAllowedEls = Array.prototype.some.call(
this.$$('.vjs-control-bar, .vjs-modal-dialog'),
엘 => el.contains(이벤트.대상)
);
if (!inAllowedEls) {
/*
* options.userActions.doubleClick
*
* `undefined` 또는 `true`인 경우 컨트롤이 있는 경우 두 번 클릭하면 전체 화면이 전환됩니다.
* 더블 클릭 처리를 비활성화하려면 `false`로 설정하십시오.
* 외부 더블 클릭 핸들러를 대체하는 기능으로 설정
*/
만약에 (
this.options_ === 정의되지 않음 ||
this.options_.userActions === 정의되지 않음 ||
this.options_.userActions.doubleClick === 정의되지 않음 ||
this.options_.userActions.doubleClick !== 거짓
) {
만약에 (
this.options_ !== 정의되지 않음 &&
this.options_.userActions !== 정의되지 않음 &&
typeof this.options_.userActions.doubleClick === '함수'
) {
this.options_.userActions.doubleClick.call(이, 이벤트);
} 그렇지 않으면 (this.isFullscreen()) {
this.exitFullscreen();
} else {
this.requestFullscreen();
}
}
}
}
/**
* 미디어 요소의 탭을 처리합니다. 그것은 사용자를 토글합니다
* 컨트롤을 숨기고 표시하는 활동 상태.
*
* @listens Tech#tap
* @사적인
*/
handleTechTap_() {
this.userActive(!this.userActive());
}
/**
* 시작하려면 터치를 처리하십시오.
*
* @listens Tech#touchstart
* @사적인
*/
handleTechTouchStart_() {
this.userWasActive = this.userActive();
}
/**
* 핸들 터치로 이동
*
* @listens Tech#touchmove
* @사적인
*/
handleTechTouchMove_() {
if (이.userWasActive) {
this.reportUserActivity();
}
}
/**
* 터치를 끝까지 처리
*
* @param {EventTarget~Event} 이벤트
* 트리거된 touchend 이벤트
* 이 기능
*
* @listens Tech#touchend
* @사적인
*/
handleTechTouchEnd_(이벤트) {
// 마우스 이벤트도 발생하지 않도록 중지
경우 (event.cancelable) {
event.preventDefault();
}
}
/**
* SWF의 기본 클릭 이벤트는 IE11, Win8.1RT에서 트리거되지 않음
* 대신 SWF 내부에서 트리거된 stageclick 이벤트 사용
*
* @사적인
* @listens stageclick
*/
handleStageClick_() {
this.reportUserActivity();
}
/**
* @사적인
*/
toggleFullscreenClass_() {
if (this.isFullscreen()) {
this.addClass('vjs-fullscreen');
} else {
this.removeClass('vjs-fullscreen');
}
}
/**
* 문서 fschange 이벤트가 트리거되면 이를 호출합니다.
*/
documentFullscreenChange_(e) {
const targetPlayer = e.target.player;
// 다른 플레이어가 전체 화면인 경우
// 구형 firefox는 문서를 e.target으로 지정하므로 targetPlayer에 대해 null 검사를 수행합니다.
if (targetPlayer && targetPlayer !== 이) {
반품;
}
const el = this.el();
let isFs = document[this.fsApi_.fullscreenElement] === 엘;
if (!isFs && el.matches) {
isFs = el.matches(':' + this.fsApi_.fullscreen);
} 그렇지 않으면 (!isFs && el.msMatchesSelector) {
isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen);
}
this.isFullscreen(isFs);
}
/**
* 기술 전체 화면 변경 처리
*
* @param {EventTarget~Event} 이벤트
* 이 함수를 트리거한 fullscreenchange 이벤트
*
* @param {객체} 데이터
* 이벤트와 함께 전송된 데이터
*
* @사적인
* @listens Tech#fullscreenchange
* @fires Player#fullscreenchange
*/
handleTechFullscreenChange_(이벤트, 데이터) {
만약 (데이터) {
if (data.nativeIOSFullscreen) {
this.addClass('vjs-ios-native-fs');
this.tech_.one('webkitendfullscreen', () => {
this.removeClass('vjs-ios-native-fs');
});
}
this.isFullscreen(data.isFullscreen);
}
}
handleTechFullscreenError_(이벤트, 오류) {
this.trigger('전체 화면 오류', 오류);
}
/**
* @사적인
*/
togglePictureInPictureClass_() {
if (this.isInPictureInPicture()) {
this.addClass('vjs-picture-in-picture');
} else {
this.removeClass('vjs-picture-in-picture');
}
}
/**
* 핸들 기술 Picture-in-Picture를 입력하십시오.
*
* @param {EventTarget~Event} 이벤트
* 이 함수를 트리거한 enterpictureinpicture 이벤트
*
* @사적인
* @listens Tech#enterpictureinpicture
*/
handleTechEnterPictureInPicture_(이벤트) {
this.isInPictureInPicture(true);
}
/**
* 처리 기술은 Picture-in-Picture를 남깁니다.
*
* @param {EventTarget~Event} 이벤트
* 이 기능을 트리거한 leavepictureinpicture 이벤트
*
* @사적인
* @listens Tech#leavepictureinpicture
*/
handleTechLeavePictureInPicture_(이벤트) {
this.isInPictureInPicture(거짓);
}
/**
* 오디오/비디오를 로드하는 동안 오류가 발생하면 발생합니다.
*
* @사적인
* @listens Tech#error
*/
handleTechError_() {
const error = this.tech_.error();
this.오류(오류);
}
/**
* {@link Tech}에 의해 트리거된 `textdata` 이벤트를 다시 트리거합니다.
*
* @fires Player#textdata
* @listens Tech#textdata
* @사적인
*/
handleTechTextData_() {
let 데이터 = null;
if (인수.길이 > 1) {
데이터 = 인수[1];
}
/**
* 기술팀에서 textdata 이벤트를 받으면 발생합니다.
*
* @event 플레이어#textdata
* @type {이벤트대상~이벤트}
*/
this.trigger('텍스트데이터', 데이터);
}
/**
* 캐시된 값에 대한 개체를 가져옵니다.
*
* @return {객체}
* 현재 객체 캐시 가져오기
*/
get캐시() {
this.cache_를 반환합니다.
}
/**
* 내부 캐시 개체를 재설정합니다.
*
* 플레이어 생성자 또는 재설정 메서드 외부에서 이 함수를 사용하면
* 의도하지 않은 부작용이 있습니다.
*
* @사적인
*/
resetCache_() {
this.cache_ = {
// 현재 currentTime은 항상 _really_ 캐시되지 않습니다.
// 기술에서 검색됨(참조: currentTime). 그러나 완전성을 위해,
// 여기서는 0으로 설정하여 실제로 캐싱을 시작하는지 확인합니다.
// 다른 모든 것과 함께 재설정합니다.
현재 시간: 0,
초기화 시간: 0,
inactivityTimeout: this.options_.inactivityTimeout,
지속: 난,
마지막 볼륨: 1,
lastPlaybackRate: this.defaultPlaybackRate(),
미디어: null,
소스: '',
원천: {},
출처: [],
재생 속도: [],
용량: 1
};
}
/**
* 재생 기술에 값 전달
*
* @param {문자열} [방법]
* 호출 방법
*
* @param {객체} 인수
* 전달 인수
*
* @사적인
*/
techCall_(방법, 인수) {
// 아직 준비되지 않았다면 준비되었을 때 메소드를 호출합니다.
this.ready(함수() {
if (middleware.allowedSetters의 메소드) {
return middleware.set(this.middleware_, this.tech_, 메소드, arg);
} else if (middleware.allowedMediators의 메서드) {
return middleware.mediate(this.middleware_, this.tech_, 메소드, arg);
}
{
if (this.tech_) {
this.tech_[방법](인수);
}
} 잡기 (e) {
log(e);
전자를 던져;
}
}, 진실);
}
/**
* 전화 받기는 기술을 기다릴 수 없으며 때로는 필요하지 않습니다.
*
* @param {문자열} 메소드
* 기술 방법
*
* @return {함수|정의되지 않음}
* 메서드 또는 정의되지 않음
*
* @사적인
*/
techGet_(방법) {
if (!this.tech_ || !this.tech_.isReady_) {
반품;
}
if (middleware.allowedGetters의 메서드) {
return middleware.get(this.middleware_, this.tech_, 메소드);
} else if (middleware.allowedMediators의 메서드) {
return middleware.mediate(this.middleware_, this.tech_, 메소드);
}
// Flash는 숨기거나 위치를 변경할 때 죽고 다시 로드되는 것을 좋아합니다.
// 이 경우 개체 메서드가 사라지고 오류가 발생합니다.
// 할 것: Flash 이외의 기술에도 필요합니까?
// 그런 일이 발생하면 오류를 포착하고 기술 담당자에게 더 이상 준비가 되지 않았음을 알립니다.
{
return this.tech_[방법]();
} 잡기 (e) {
// 추가 기술 라이브러리를 빌드할 때 예상되는 메서드가 아직 정의되지 않았을 수 있습니다.
if (this.tech_[method] === 정의되지 않음) {
log(`Video.js: ${this.techName_} 재생 기술에 대해 ${method} 메서드가 정의되지 않았습니다.`, e);
전자를 던져;
}
// 객체에서 메서드를 사용할 수 없으면 TypeError가 발생합니다.
if (e.name === '유형 오류') {
log(`Video.js: ${this.techName_} 재생 기술 요소에서 ${method}를 사용할 수 없습니다.`, e);
this.tech_.isReady_ = 거짓;
전자를 던져;
}
// 알 수 없는 오류인 경우 그냥 기록하고 던집니다.
log(e);
전자를 던져;
}
}
/**
* 첫 번째 기회에 재생을 시작하려고 시도합니다.
*
* @return {약속|정의되지 않음}
* 브라우저가 약속(또는 하나의
* 옵션으로 전달되었습니다). 이 약속은
* play의 반환값. 이것이 정의되지 않은 경우 다음을 충족합니다.
* Promise Chain 그렇지 않으면 다음과 같은 경우 Promise Chain이 이행됩니다.
* 놀이의 약속이 이루어집니다.
*/
놀다() {
const PromiseClass = this.options_.Promise || 창. 약속;
경우 (PromiseClass) {
새로운 PromiseClass를 반환((해결) => {
this.play_(해결);
});
}
return this.play_();
}
/**
* 재생을 위한 실제 로직은
* 플레이의 반환 값. 이를 통해 플레이 약속을 확인할 수 있습니다.
*는 최신 브라우저에 있는 것입니다.
*
* @사적인
* @param {함수} [콜백]
* 테크니션 플레이 시 호출해야 하는 콜백은 실제로 호출했을 때
*/
play_(콜백 = 침묵 약속) {
this.playCallbacks_.push(콜백);
const isSrcReady = 부울(!이. && (this.src() || this.currentSrc()));
// play_에 대한 호출을 `one` 이벤트 함수처럼 처리합니다.
if (this.waitToPlay_) {
this.off(['ready', 'loadstart'], this.waitToPlay_);
this.waitToPlay_ = null;
}
// 플레이어/기술자가 준비되지 않았거나 src 자체가 준비되지 않은 경우
// `ready` 또는 `loadstart`에서 재생할 호출을 대기열에 넣습니다.
if (!this.isReady_ || !isSrcReady) {
this.waitToPlay_ = (e) => {
this.play_();
};
this.one(['ready', 'loadstart'], this.waitToPlay_);
// Safari에 있는 경우 제스처 기간 후에 loadstart가 트리거될 가능성이 높습니다.
// 이 경우 로드를 호출하여 비디오 요소를 프라이밍해야 제 시간에 준비됩니다.
if (!isSrcReady && (브라우저.IS_ANY_SAFARI || 브라우저.IS_IOS)) {
this.load();
}
반품;
}
// 플레이어/기술이 준비되고 소스가 있으면 재생을 시도할 수 있습니다.
const val = this.techGet_('플레이');
// 리턴 값이 null이면 재생 종료
경우 (값 === null) {
this.runPlayTerminatedQueue_();
} else {
this.runPlayCallbacks_(val);
}
}
/**
* 플레이가 종료되면 이 기능들이 실행됩니다. 놀면
* runPlayCallbacks_가 실행되면 이 함수는 실행되지 않습니다. 이를 통해 우리는
* 종료된 플레이와 실제 콜 투 플레이를 구별하기 위해.
*/
runPlayTerminatedQueue_() {
const queue = this.playTerminatedQueue_.slice(0);
this.playTerminatedQueue_ = [];
queue.forEach(함수(q) {
큐();
});
}
/**
* 재생 콜백이 지연되면 다음을 실행해야 합니다.
* 기술에서 플레이가 실제로 호출될 때 콜백. 이 기능
* 지연된 콜백을 실행하고 반환 값을 수락합니다.
* 기술에서.
*
* @param {정의되지 않은|약속} 값
* 기술에서 반환 값입니다.
*/
runPlayCallbacks_(val) {
const 콜백 = this.playCallbacks_.slice(0);
this.playCallbacks_ = [];
// 실제 플레이가 끝났기 때문에 플레이 종료 대기열을 지웁니다.
this.playTerminatedQueue_ = [];
callbacks.forEach(함수(cb) {
cb(값);
});
}
/**
* 비디오 재생 일시 중지
*
* @return {플레이어}
* 이 함수가 호출된 플레이어 개체에 대한 참조
*/
정지시키다() {
this.techCall_('일시정지');
}
/**
* 플레이어가 일시 중지되었거나 아직 재생되지 않았는지 확인
*
* @return {부울}
* - false: 미디어가 현재 재생 중인 경우
* - true: 미디어가 현재 재생되지 않는 경우
*/
일시 중지() {
// paused의 초기 상태는 true여야 합니다(Safari에서는 실제로 false임).
return (this.techGet_('paused') === false) ? 거짓 : 참;
}
/**
* 사용자가 현재 시간 범위를 나타내는 TimeRange 개체를 가져옵니다.
* 연주했습니다.
*
* @return {시간 범위}
* 모든 시간 증분을 나타내는 시간 범위 객체
* 연주되었습니다.
*/
플레이() {
return this.techGet_('재생됨') || createTimeRange(0, 0);
}
/**
* 사용자가 "스크러빙"하는지 여부를 반환합니다. 스크럽은
* 사용자가 진행률 표시줄 핸들을 클릭하고
* 진행률 표시줄을 따라 드래그합니다.
*
* @param {부울} [isScrubbing]
* 사용자가 스크러빙을 하는지 여부
*
* @return {부울}
* 얻을 때 스크럽 값
*/
스크러빙(isScrubbing) {
if (isScrubbing 유형 === '정의되지 않음') {
return this.scrubbing_;
}
this.scrubbing_ = !!isScrubbing;
this.techCall_('setScrubbing', this.scrubbing_);
if (isScrubbing) {
this.addClass('vjs-scrubbing');
} else {
this.removeClass('vjs-scrubbing');
}
}
/**
* 현재 시간을 가져오거나 설정합니다(초).
*
* @param {숫자|문자열} [초]
* 찾는 시간(초)
*
* @return {숫자}
* - 가져올 때의 현재 시간(초)
*/
현재 시간(초) {
if (초 유형 !== '정의되지 않음') {
만약 (초 < 0) {
초 = 0;
}
if (!this.isReady_ || this.changesrc_ || !this.tech_ || !this.tech_.isReady_) {
this.cache_.initTime = 초;
this.off('canplay', this.boundApplyInitTime_);
this.one('canplay', this.boundApplyInitTime_);
반품;
}
this.techCall_('setCurrentTime', 초);
this.cache_.initTime = 0;
반품;
}
// 마지막 currentTime을 캐시하고 반환합니다. 기본값은 0초
//
// currentTime 캐싱은 기술자의 엄청난 양의 읽기를 방지하기 위한 것입니다.
// 스크러빙할 때 currentTime이지만 결과적으로 많은 성능 이점을 제공하지 않을 수 있습니다.
// 테스트해야 합니다. 또한 무언가는 실제 현재 시간을 읽어야 합니다. 그렇지 않으면 캐시가
// 절대 업데이트되지 않습니다.
this.cache_.currentTime = (this.techGet_('currentTime') || 0);
this.cache_.currentTime을 반환합니다.
}
/**
* 캐시에 저장된 initTime의 값을 currentTime으로 적용합니다.
*
* @사적인
*/
applyInitTime_() {
this.currentTime(this.cache_.initTime);
}
/**
* 일반적으로 비디오의 시간 길이를 초 단위로 가져옵니다.
* 가장 드문 사용 사례를 제외한 모든 경우에서 인수가 메서드에 전달되지 않습니다.
*
* > **메모**: 재생 시간이 끝나기 전에 동영상이 로드되기 시작했어야 합니다.
* 알려져 있으며 사전 로드 동작에 따라 비디오가 시작될 때까지 알려지지 않을 수 있습니다.
* 놀이.
*
* @fires Player#durationchange
*
* @param {숫자} [초]
* 동영상 재생 시간을 초 단위로 설정
*
* @return {숫자}
* - 얻을 때 비디오의 길이는 초 단위입니다.
*/
기간(초) {
if (초 === 정의되지 않음) {
// 기간을 알 수 없는 경우 NaN을 반환합니다.
return this.cache_.duration !== 정의되지 않음 ? this.cache_.duration : NaN;
}
초 = parseFloat(초);
// 라이브 신호 비디오를 위해 Infinity로 표준화
만약 (초 < 0) {
초 = 무한대;
}
if (초 !== this.cache_.duration) {
// 최적화된 스크러빙을 위해 마지막으로 설정한 값을 캐시합니다(특히. 플래시)
// 할 것: Flash 이외의 기술자에게 필요합니까?
this.cache_.duration = 초;
if (초 === 무한대) {
this.addClass('vjs-live');
} else {
this.removeClass('vjs-live');
}
if (!isNaN(초)) {
// 기간 값을 알 수 없으면 기간 변경을 실행하지 않습니다.
// @[사양] 참조{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
/**
* @event 플레이어#durationchange
* @type {이벤트대상~이벤트}
*/
this.trigger('durationchange');
}
}
}
/**
* 비디오에 남은 시간을 계산합니다. 일부가 아님
* 네이티브 비디오 API의.
*
* @return {숫자}
* 남은 시간(초)
*/
남은 시간() {
return this.duration() - this.currentTime();
}
/**
* 다음과 같은 경우에 사용하도록 의도된 남은 시간 기능
* 시간은 사용자에게 직접 표시됩니다.
*
* @return {숫자}
* 남은 반올림 시간(초)
*/
남은시간표시() {
return Math.floor(this.duration()) - Math.floor(this.currentTime());
}
//
// 다운로드된 비디오 부분의 배열과 비슷합니다.
/**
* 비디오 시간의 배열로 TimeRange 객체를 가져옵니다.
* 다운받은 것. 퍼센트만 알고 싶다면
* 다운로드된 비디오는 bufferedPercent를 사용합니다.
*
* @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
*
* @return {시간 범위}
* 모의 TimeRange 객체(HTML 사양을 따름)
*/
버퍼링() {
let buffered = this.techGet_('버퍼링됨');
if (!버퍼 || !버퍼.길이) {
buffered = createTimeRange(0, 0);
}
반환 버퍼;
}
/**
* 다운로드된 비디오의 백분율(소수점)을 가져옵니다.
* 이 메소드는 네이티브 HTML 비디오 API의 일부가 아닙니다.
*
* @return {숫자}
* 백분율을 나타내는 0과 1 사이의 소수점
* 버퍼링됨 0은 0%이고 1은 100%임
*/
bufferedPercent() {
return bufferedPercent(this.buffered(), this.duration());
}
/**
* 마지막 버퍼링된 시간 범위의 종료 시간 가져오기
* 진행률 표시줄에서 모든 시간 범위를 캡슐화하는 데 사용됩니다.
*
* @return {숫자}
* 마지막 버퍼링된 시간 범위의 끝
*/
bufferedEnd() {
const buffered = this.buffered();
지속 시간 = this.duration();
let end = buffered.end(buffered.length - 1);
만약 (종료 > 지속) {
끝 = 기간;
}
반환 종료;
}
/**
* 미디어의 현재 볼륨 가져오기 또는 설정
*
* @param {숫자} [percentAsDecimal]
* 소수점 퍼센트로 표시된 새 볼륨:
* - 0은 음소거됨/0%/꺼짐
* - 1.0은 100%/전체
* - 0.5는 부피의 절반 또는 50%입니다.
*
* @return {숫자}
* 얻을 때 백분율로 현재 볼륨
*/
볼륨(percentAsDecimal) {
권하자;
if (percentAsDecimal !== 정의되지 않음) {
// 값을 0과 1 사이로 강제 설정
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
this.cache_.volume = 볼륨;
this.techCall_('setVolume', vol);
만약 (권 > 0) {
this.lastVolume_(vol);
}
반품;
}
// 현재 볼륨을 반환할 때 기본값은 1입니다.
vol = parseFloat(this.techGet_('볼륨'));
반환 (isNaN(vol)) ? 1: 권;
}
/**
* 현재 음소거 상태를 가져오거나 음소거를 켜거나 끕니다.
*
* @param {부울} [음소거됨]
* - 음소거
* - 음소거 해제하려면 false
*
* @return {부울}
* - 음소거가 켜져 있는 경우 true
* - 음소거가 해제되어 있으면 false
*/
음소거(음소거) {
if (음소거!== 정의되지 않음) {
this.techCall_('setMute', 음소거);
반품;
}
return this.techGet_('음소거됨') || 거짓;
}
/**
* 현재 defaultMute 상태를 가져오거나 defaultMuted를 켜거나 끕니다. 기본음소거됨
*는 초기 재생 시 음소거 상태를 나타냅니다.
*
* ```js
* var myPlayer = videojs('some-player-id');
*
* myPlayer.src("http://www.example.com/path/to/video.mp4");
*
* // get, false여야 합니다.
* console.log(myPlayer.defaultMute());
* // 참으로 설정
* myPlayer.defaultMute(true);
* // get이 참이어야 합니다.
* console.log(myPlayer.defaultMute());
* ```
*
* @param {부울} [defaultMuted]
* - 음소거
* - 음소거 해제하려면 false
*
* @return {부울|플레이어}
* - defaultMute가 켜져 있고 가져오는 경우 true
* - defaultMute가 꺼져 있고 가져오는 경우 false
* - 설정할 때 현재 플레이어에 대한 참조
*/
defaultMute(기본 음소거) {
if (defaultMute!== 정의되지 않음) {
return this.techCall_('setDefaultMute', defaultMuted);
}
return this.techGet_('defaultMuted') || 거짓;
}
/**
* 마지막 볼륨을 가져오거나 설정
*
* @param {숫자} [percentAsDecimal]
* 새로운 마지막 볼륨(소수점 퍼센트):
* - 0은 음소거됨/0%/꺼짐
* - 1.0은 100%/전체
* - 0.5는 부피의 절반 또는 50%입니다.
*
* @return {숫자}
* 가져올 때 lastVolume의 현재 값(퍼센트)
*
* @사적인
*/
lastVolume_(percentAsDecimal) {
if (percentAsDecimal !== 정의되지 않음 && 퍼센트 AsDecimal !== 0) {
this.cache_.lastVolume = percentAsDecimal;
반품;
}
this.cache_.lastVolume을 반환합니다.
}
/**
* 현재 기술이 기본 전체 화면을 지원할 수 있는지 확인
* (예: iOS와 같은 내장 컨트롤 포함)
*
* @return {부울}
* 네이티브 전체화면이 지원되는 경우
*/
supportFullScreen() {
return this.techGet_('supportsFullScreen') || 거짓;
}
/**
* 플레이어가 전체 화면 모드인지 확인하거나 플레이어에게 알립니다.
*가 전체 화면 모드인지 아닌지.
*
* > 메모: 최신 HTML5 사양부터 isFullscreen은 더 이상 공식이 아닙니다.
* 속성 대신 document.fullscreenElement가 사용됩니다. 그러나 isFullscreen은
* 여전히 내부 플레이어 작업을 위한 귀중한 자산입니다.
*
* @param {부울} [isFS]
* 플레이어의 현재 전체 화면 상태 설정
*
* @return {부울}
* - 전체 화면이 켜져 있는 경우 true
* - 전체 화면이 꺼져 있으면 false
*/
isFullscreen(isFS) {
if (isFS !== 정의되지 않음) {
const oldValue = this.isFullscreen_;
this.isFullscreen_ = 부울(isFS);
// 전체 화면 상태를 변경하고 접두사 모드에 있는 경우 전체 화면 변경을 트리거합니다.
// 이전 브라우저에 대해 fullscreenchange 이벤트를 트리거하는 유일한 위치입니다.
// fullWindow 모드는 접두사 이벤트로 처리되며 fullscreenchange 이벤트도 받습니다.
if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) {
/**
* @event Player#fullscreenchange
* @type {이벤트대상~이벤트}
*/
this.trigger('전체 화면 변경');
}
this.toggleFullscreenClass_();
반품;
}
this.isFullscreen_을 반환합니다.
}
/**
* 동영상 크기를 전체 화면으로 확대
* 일부 브라우저에서는 기본적으로 전체화면이 지원되지 않아 진입합니다.
* 비디오가 브라우저 창을 채우는 "전체 창 모드".
* 네이티브 전체 화면을 지원하는 브라우저 및 기기에서는 간혹
* Video.js 사용자 정의 스킨이 아닌 브라우저의 기본 컨트롤이 표시됩니다.
* 여기에는 대부분의 모바일 장치(iOS, Android) 및 이전 버전의
* 사파리.
*
* @param {객체} [fullscreenOptions]
* 플레이어 전체 화면 옵션 재정의
*
* @fires Player#fullscreenchange
*/
요청전체화면(전체화면옵션) {
const PromiseClass = this.options_.Promise || 창. 약속;
경우 (PromiseClass) {
const 자기 = this;
새 PromiseClass((해결, 거부) 반환 => {
함수 오프핸들러() {
self.off('전체 화면 오류', errorHandler);
self.off('전체 화면 변경', changeHandler);
}
함수 changeHandler() {
오프핸들러();
해결하다();
}
함수 errorHandler(e, 오류) {
오프핸들러();
거부(오류);
}
self.one('전체화면변경', changeHandler);
self.one('전체 화면 오류', errorHandler);
const 약속 = self.requestFullscreenHelper_(fullscreenOptions);
if (약속) {
promise.then(offHandler, offHandler);
promise.then(해결, 거부);
}
});
}
return this.requestFullscreenHelper_();
}
requestFullscreenHelper_(fullscreenOptions) {
let fsOptions;
// 사양을 준수하는 브라우저에서는 전체 화면 옵션만 requestFullscreen에 전달합니다.
// 이 메서드에 직접 전달되지 않는 한 기본값 또는 플레이어 구성 옵션을 사용합니다.
if (!this.fsApi_.prefixed) {
fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {};
if (fullscreenOptions !== 정의되지 않음) {
fsOptions = 전체화면옵션;
}
}
// 이 메서드는 다음과 같이 작동합니다.
// 1. 전체 화면 api를 사용할 수 있는 경우 사용
// 1. 가능한 옵션을 사용하여 requestFullscreen을 호출합니다.
// 2. 위에서 약속을 받은 경우 이를 사용하여 isFullscreen()을 업데이트합니다.
// 2. 그렇지 않으면 기술이 전체 화면을 지원하는 경우 `enterFullScreen`을 호출합니다.
// 이것은 특히 iPhone, 구형 iPad 및 iOS의 사파리가 아닌 브라우저에 사용됩니다.
// 3. 그렇지 않으면 "fullWindow" 모드를 사용합니다.
if (this.fsApi_.requestFullscreen) {
const 약속 = this.el_[this.fsApi_.requestFullscreen](fsOptions);
if (약속) {
약속.다음(() => this.isFullscreen(참), () => this.isFullscreen(false));
}
반환 약속;
} 그렇지 않으면 (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === 참) {
// video.js 컨트롤을 전체 화면으로 표시할 수는 없지만 전체 화면으로 표시할 수는 있습니다.
// 기본 컨트롤 사용
this.techCall_('enterFullScreen');
} else {
// 전체 화면은 지원되지 않으므로 비디오 요소를
// 뷰포트 채우기
this.enterFullWindow();
}
}
/**
* 전체 화면 모드로 전환한 후 동영상을 원래 크기로 되돌립니다.
*
* @fires Player#fullscreenchange
*/
exitFullscreen() {
const PromiseClass = this.options_.Promise || 창. 약속;
경우 (PromiseClass) {
const 자기 = this;
새 PromiseClass((해결, 거부) 반환 => {
함수 오프핸들러() {
self.off('전체 화면 오류', errorHandler);
self.off('전체 화면 변경', changeHandler);
}
함수 changeHandler() {
오프핸들러();
해결하다();
}
함수 errorHandler(e, 오류) {
오프핸들러();
거부(오류);
}
self.one('전체화면변경', changeHandler);
self.one('전체 화면 오류', errorHandler);
const 약속 = self.exitFullscreenHelper_();
if (약속) {
promise.then(offHandler, offHandler);
// resolve/reject 메서드에 약속을 매핑합니다.
promise.then(해결, 거부);
}
});
}
return this.exitFullscreenHelper_();
}
exitFullscreenHelper_() {
if (this.fsApi_.requestFullscreen) {
const 약속 = 문서[this.fsApi_.exitFullscreen]();
if (약속) {
// 여기에서 Promise를 분할하므로
// 이 체인에 처리되지 않은 오류가 없도록 잠재적인 오류
침묵 약속(promise.then(() => this.isFullscreen(false)));
}
반환 약속;
} 그렇지 않으면 (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === 참) {
this.techCall_('exitFullScreen');
} else {
this.exitFullWindow();
}
}
/**
* 전체 화면이 지원되지 않는 경우 확장할 수 있습니다.
* 비디오 컨테이너는 브라우저가 허용하는 만큼 넓습니다.
*
* @fires Player#enterFullWindow
*/
enterFullWindow() {
this.isFullscreen(참);
this.isFullWindow = 참;
// 전체 화면이 꺼져 있을 때 반환할 원본 문서 오버플로 값 저장
this.docOrigOverflow = document.documentElement.style.overflow;
// Esc 키에 대한 리스너를 추가하여 전체 화면을 종료합니다.
Events.on(document, 'keydown', this.boundFullWindowOnEscKey_);
// 스크롤 막대 숨기기
document.documentElement.style.overflow = '숨김';
// 전체화면 스타일 적용
Dom.addClass(document.body, 'vjs-full-window');
/**
* @event Player#enterFullWindow
* @type {이벤트대상~이벤트}
*/
this.trigger('enterFullWindow');
}
/**
* 전체 창을 종료하거나
* ESC 키의 전체 화면
*
* @param {문자열} 이벤트
* 키 누름을 확인하는 이벤트
*/
fullWindowOnEscKey(이벤트) {
if (keycode.isEventKey(event, 'Esc')) {
if (this.isFullscreen() === 참) {
if (!this.isFullWindow) {
this.exitFullscreen();
} else {
this.exitFullWindow();
}
}
}
}
/**
* 전체 창 종료
*
* @fires Player#exitFullWindow
*/
exitFullWindow() {
this.isFullscreen(거짓);
this.isFullWindow = 거짓;
Events.off(문서, '키다운', this.boundFullWindowOnEscKey_);
// 스크롤 막대를 숨김 해제합니다.
document.documentElement.style.overflow = this.docOrigOverflow;
// 전체화면 스타일 제거
Dom.removeClass(document.body, 'vjs-full-window');
// 상자, 컨트롤러, 포스터를 원래 크기로 조정
// this.positionAll();
/**
* @event Player#exitFullWindow
* @type {이벤트대상~이벤트}
*/
this.trigger('exitFullWindow');
}
/**
* Picture-in-Picture 모드를 비활성화합니다.
*
* @param {부울} 값
* - true는 Picture-in-Picture 모드를 비활성화합니다.
* - false는 Picture-in-Picture 모드를 활성화합니다.
*/
disablePictureInPicture(값) {
if (값 === 정의되지 않음) {
return this.techGet_('disablePictureInPicture');
}
this.techCall_('setDisablePictureInPicture', 값);
this.options_.disablePictureInPicture = 값;
this.trigger('disablepictureinpicturechanged');
}
/**
* 플레이어가 Picture-in-Picture 모드인지 확인하거나 플레이어에게 알립니다.
* Picture-in-Picture 모드에 있거나 그렇지 않습니다.
*
* @param {부울} [isPiP]
* 플레이어의 현재 Picture-in-Picture 상태 설정
*
* @return {부울}
* - Picture-in-Picture가 켜져 있고 가져오는 경우 true
* - Picture-in-Picture가 꺼져 있으면 false
*/
isInPictureInPicture(isPiP) {
if (isPiP !== 정의되지 않음) {
this.isInPictureInPicture_ = !!isPiP;
this.togglePictureInPictureClass_();
반품;
}
반환 !!this.isInPictureInPicture_;
}
/**
* 플로팅 비디오 창을 항상 다른 창 위에 생성하여 사용자가
* 다른 콘텐츠 사이트와 상호 작용하는 동안 미디어를 계속 소비하거나
* 기기의 애플리케이션.
*
* @see [사양]{@link https://wicg.github.io/picture-in-picture}
*
* @fires Player#enterpictureinpicture
*
* @return {약속}
* Picture-in-Picture 창과의 약속.
*/
requestPictureInPicture() {
if (문서의 'pictureInPictureEnabled' && this.disablePictureInPicture() === false) {
/**
* 이 이벤트는 플레이어가 PIP 모드로 들어갈 때 발생합니다.
*
* @event 플레이어#enterpictureinpicture
* @type {이벤트대상~이벤트}
*/
return this.techGet_('requestPictureInPicture');
}
}
/**
* Picture-in-Picture 모드를 종료합니다.
*
* @see [사양]{@link https://wicg.github.io/picture-in-picture}
*
* @fires Player#leavepictureinpicture
*
* @return {약속}
* 약속.
*/
exitPictureInPicture() {
if (문서의 'pictureInPictureEnabled') {
/**
* 이 이벤트는 플레이어가 Picture in Picture 모드에서 나갈 때 발생합니다.
*
* @event Player#leavepictureinpicture
* @type {이벤트대상~이벤트}
*/
return document.exitPictureInPicture();
}
}
/**
* 이 플레이어에 포커스가 있고 키가 눌리거나
* 이 플레이어의 모든 구성 요소는 처리하지 않는 키 누름을 받습니다.
* 이렇게 하면 플레이어 전체 핫키(아래에 정의된 대로 또는 선택적으로
* 외부 함수에 의해).
*
* @param {EventTarget~Event} 이벤트
* 이 함수를 호출한 `keydown` 이벤트.
*
* @listens 키다운
*/
handleKeyDown(이벤트) {
const {userActions} = this.options_;
// 핫키가 구성되지 않은 경우 구제합니다.
if (!userActions || !userActions.hotkeys) {
반품;
}
// 요소를 제외할지 여부를 결정하는 함수
// 핫키 처리.
const excludeElement = (엘) => {
const tagName = el.tagName.toLowerCase();
// 첫 번째이자 가장 쉬운 테스트는 `contenteditable` 요소에 대한 것입니다.
if (el.isContentEditable) {
true를 반환합니다.
}
// 이 유형과 일치하는 입력은 여전히 다음과 같이 핫키 처리를 트리거합니다.
// 텍스트 입력이 아닙니다.
const allowedInputTypes = [
'단추',
'체크박스',
'숨겨진',
'라디오',
'초기화',
'제출하다'
];
if (태그 이름 === '입력') {
반환 allowedInputTypes.indexOf(el.type) === -1;
}
// 최종 테스트는 태그 이름으로 합니다. 이러한 태그는 완전히 제외됩니다.
const excludeTags = ['텍스트 영역'];
제외 태그를 반환합니다.indexOf(태그 이름) !== -1;
};
// 사용자가 대화형 양식 요소에 포커스가 있는 경우 구제합니다.
if (excludeElement(this.el_.ownerDocument.activeElement)) {
반품;
}
if (typeof userActions.hotkeys === '함수') {
userActions.hotkeys.call(this, event);
} else {
this.handleHotkeys(event);
}
}
/**
* 이 플레이어가 핫키 키다운 이벤트를 수신할 때 호출됩니다.
* 지원되는 플레이어 전체 핫키는 다음과 같습니다.
*
* f - 전체 화면 전환
* m - 음소거 전환
* k 또는 Space - 재생/일시정지 전환
*
* @param {EventTarget~Event} 이벤트
* 이 함수를 호출한 `keydown` 이벤트.
*/
handleHotkeys(이벤트) {
const hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {};
// `hotkeys`에서 fullscreenKey, muteKey, playPauseKey 설정, 설정되지 않은 경우 기본값 사용
{
fullscreenKey = keydownEvent => keycode.isEventKey(keydownEvent, 'f'),
음소거키 = keydownEvent => keycode.isEventKey(keydownEvent, 'm'),
playPauseKey = keydownEvent => (keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, '공간'))
} = 핫키;
if (fullscreenKey.call(이, 이벤트)) {
event.preventDefault();
event.stopPropagation();
const FSToggle = Component.getComponent('FullscreenToggle');
if (문서[this.fsApi_.fullscreenEnabled] !== false) {
FSToggle.prototype.handleClick.call(this, event);
}
} 그렇지 않으면 (muteKey.call(this, event)) {
event.preventDefault();
event.stopPropagation();
const MuteToggle = Component.getComponent('MuteToggle');
MuteToggle.prototype.handleClick.call(this, event);
} 그렇지 않으면 (playPauseKey.call(this, event)) {
event.preventDefault();
event.stopPropagation();
const PlayToggle = Component.getComponent('PlayToggle');
PlayToggle.prototype.handleClick.call(this, event);
}
}
/**
* 플레이어가 주어진 MIME 유형을 재생할 수 있는지 확인
*
* @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
*
* @param {문자열} 유형
* 확인할 mimetype
*
* @return {문자열}
* 'probably', 'maybe' 또는 '' (빈 문자열)
*/
canPlayType(유형) {
할 수 있습니다;
// 옵션 순서로 각 재생 기술을 반복합니다.
for (let i = 0, j = this.options_.techOrder; i < j.길이; i++) {
const techName = j[i];
let tech = Tech.getTech(techName);
// 구성 요소로 등록되는 기술자의 이전 동작을 지원합니다.
// 더 이상 사용되지 않는 동작이 제거되면 제거합니다.
if (!기술) {
기술 = Component.getComponent(techName);
}
// 계속하기 전에 현재 기술이 정의되어 있는지 확인
if (!기술) {
log.error(`"${techName}" 기술이 정의되지 않았습니다. 해당 기술에 대한 브라우저 지원 확인을 건너뛰었습니다.`);
계속하다;
}
// 브라우저가 이 기술을 지원하는지 확인
if (tech.isSupported()) {
수 = tech.canPlayType(유형);
만약 (할 수) {
반환할 수 있습니다.
}
}
}
반품 '';
}
/**
* 기술 주문 또는 소스 주문을 기반으로 소스 선택
* `options.sourceOrder`가 참이면 소스 순서 선택을 사용합니다. 그렇지 않으면,
* 기본적으로 기술 주문 선택
*
* @param {배열} 소스
* 미디어 자산의 소스
*
* @return {객체|부울}
* 소스 및 기술 주문의 대상 또는 허위
*/
selectSource(소스) {
// 존재하고 지원되는 `techOrder`에 지정된 기술만 가져옵니다.
// 현재 플랫폼
const 기술자 =
this.options_.techOrder
.map((기술 이름) => {
return [기술명, Tech.getTech(기술명)];
})
.filter(([기술 이름, 기술]) => {
// 계속하기 전에 현재 기술이 정의되어 있는지 확인
경우 (기술) {
// 브라우저가 이 기술을 지원하는지 확인
return tech.isSupported();
}
log.error(`"${techName}" 기술이 정의되지 않았습니다. 해당 기술에 대한 브라우저 지원 확인을 건너뛰었습니다.`);
거짓을 반환합니다.
});
// `outerArray` 요소마다 한 번씩 각 `innerArray` 요소를 반복하고 실행합니다.
// 둘 다 사용하는 `tester`. `tester`가 거짓이 아닌 값을 반환하면 일찍 종료하고 반환
// 그 값.
const findFirstPassingTechSourcePair = function(outerArray, innerArray, tester) {
찾자;
outerArray.some((outerChoice) => {
return innerArray.some((innerChoice) => {
발견 = 테스터(outerChoice, innerChoice);
경우 (발견) {
true를 반환합니다.
}
});
});
반품을 찾았습니다.
};
let foundSourceAndTech;
const 뒤집기 = (fn) => (a, b) => fn(b,a);
const finder = ([techName, tech], 소스) => {
if (tech.canPlaySource(소스, this.options_[techName.toLowerCase()])) {
return {출처, 기술: techName};
}
};
// `options.sourceOrder`의 진실성에 따라 기술자와 출처의 순서를 바꿉니다.
// 우선 순위에 따라 선택합니다.
if (this.options_.sourceOrder) {
// 소스 우선 정렬
foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
} else {
// 기술 우선 주문
foundSourceAndTech = findFirstPassingTechSourcePair(기술자, 소스, 파인더);
}
foundSourceAndTech 반환 || 거짓;
}
/**
* 소스 설정 및 로직 가져오기 실행
*
* @param {Tech~SourceObject|Tech~SourceObject[]|string} [소스]
* SourceObject, SourceObject 배열 또는 문자열 참조
* 미디어 소스에 대한 URL. _적극 권장_합니다.
* 또는 개체 배열이 여기에서 사용되므로 소스 선택
* 알고리즘은 `유형`을 고려할 수 있습니다.
*
* 제공되지 않으면 이 메서드는 getter 역할을 합니다.
* @param {부울} isRetry
* 재시도 결과 내부적으로 호출되는지 여부를 나타냅니다.
*
* @return {문자열|정의되지 않음}
* `source` 인수가 없으면 현재 소스를 반환합니다.
* URL. 그렇지 않으면 아무것도 반환하지 않거나 정의되지 않습니다.
*/
handleSrc_(소스, isRetry) {
// getter 사용법
if (소스 유형 === '정의되지 않음') {
this.cache_.src를 반환 || '';
}
// 새 소스에 대한 재시도 동작 재설정
if (this.resetRetryOnError_) {
this.resetRetryOnError_();
}
// 유효하지 않은 소스를 필터링하고 소스를
// 소스 객체의 배열
const sources = filterSource(source);
// 소스가 전달된 경우 유효하지 않습니다.
// 길이가 0인 배열로 필터링되었습니다. 그래서 우리는
// 오류 표시
if (!소스.길이) {
this.setTimeout(함수() {
this.error({ 코드: 4, 메시지: this.options_.notSupportedMessage });
}, 0);
반품;
}
// 초기 소스
this.changesrc_ = 참;
// 오류 발생 후 새 소스를 재시도하지 않는 경우에만 캐시된 소스 목록을 업데이트합니다.
// 이 경우 실패한 소스를 캐시에 포함하고 싶기 때문입니다.
if (!isRetry) {
this.cache_.sources = 소스;
}
this.updateSourceCaches_(소스[0]);
// middlewareSource는 미들웨어에 의해 변경된 후의 소스입니다.
미들웨어.setSource(이, 소스[0], (middlewareSource, mws) => {
this.middleware_ = mws;
// sourceSet이 비동기이기 때문에 소스를 선택한 후 캐시를 다시 업데이트해야 합니다.
// 선택된 소스는 이 콜백 위의 캐시 업데이트에서 순서가 맞지 않을 수 있습니다.
if (!isRetry) {
this.cache_.sources = 소스;
}
this.updateSourceCaches_(middlewareSource);
const err = this.src_(middlewareSource);
경우 (오류) {
if (소스.길이 > 1) {
return this.handleSrc_(sources.slice(1));
}
this.changesrc_ = 거짓;
// 사람들에게 오류 이벤트 처리기를 추가할 기회를 주기 위해 이것을 타임아웃으로 래핑해야 합니다.
this.setTimeout(함수() {
this.error({ 코드: 4, 메시지: this.options_.notSupportedMessage });
}, 0);
// 적절한 기술을 찾을 수 없지만 대리인에게 이 기술을 알립니다.
// 이것이 필요한 이유에 대한 더 나은 설명이 필요합니다.
this.triggerReady();
반품;
}
middleware.setTech(mws, this.tech_);
});
// 재생 전에 이 소스가 실패하면 다른 사용 가능한 소스를 시도합니다.
if (this.options_.retryOnError && 출처.길이 > 1) {
const 재시도 = () => {
// 오류 모달 제거
this.오류(null);
this.handleSrc_(sources.slice(1), true);
};
const stopListeningForErrors = () => {
this.off('오류', 재시도);
};
this.one('오류', 재시도);
this.one('재생 중', stopListeningForErrors);
this.resetRetryOnError_ = () => {
this.off('오류', 재시도);
this.off('재생 중', stopListeningForErrors);
};
}
}
/**
* 비디오 소스를 가져오거나 설정합니다.
*
* @param {Tech~SourceObject|Tech~SourceObject[]|string} [소스]
* SourceObject, SourceObject 배열 또는 문자열 참조
* 미디어 소스에 대한 URL. _적극 권장_합니다.
* 또는 개체 배열이 여기에서 사용되므로 소스 선택
* 알고리즘은 `유형`을 고려할 수 있습니다.
*
* 제공되지 않으면 이 메서드는 getter 역할을 합니다.
*
* @return {문자열|정의되지 않음}
* `source` 인수가 없으면 현재 소스를 반환합니다.
* URL. 그렇지 않으면 아무것도 반환하지 않거나 정의되지 않습니다.
*/
src(소스) {
return this.handleSrc_(소스, 거짓);
}
/**
* 기술에 소스 개체를 설정하고 여부를 나타내는 부울을 반환합니다.
* 음원 재생이 가능한 테크가 있습니다.
*
* @param {Tech~SourceObject} 소스
* Tech에 설정할 소스 객체
*
* @return {부울}
* - 이 소스를 재생할 기술이 없는 경우 참
* - 그렇지 않으면 거짓
*
* @사적인
*/
src_(소스) {
const sourceTech = this.selectSource([소스]);
if (!sourceTech) {
true를 반환합니다.
}
if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
this.changesrc_ = 참;
// 선택한 소스로 이 기술을 로드합니다.
this.loadTech_(sourceTech.tech, sourceTech.source);
this.tech_.ready(() => {
this.changesrc_ = 거짓;
});
거짓을 반환합니다.
}
// 기술자가 소스를 설정할 준비가 될 때까지 기다립니다.
// 가능한 경우 동기적으로 설정합니다(#2326).
this.ready(함수() {
// 소스 핸들러와 함께 setSource 기술 메서드가 추가되었습니다.
// 따라서 이전 기술은 지원하지 않습니다.
// 하위 클래스가 있는 경우 직접 프로토타입을 확인해야 합니다.
// 기술은 소스 핸들러를 지원하지 않습니다.
if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
this.techCall_('setSource', 소스);
} else {
this.techCall_('src', source.src);
}
this.changesrc_ = 거짓;
}, 진실);
거짓을 반환합니다.
}
/**
* src 데이터 로드를 시작합니다.
*/
짐() {
this.techCall_('로드');
}
/**
* 플레이어를 재설정합니다. techOrder에 첫 번째 기술을 로드합니다.
* 기존 `tech`의 모든 텍스트 트랙을 제거합니다.
* 그리고 `tech`에서 `reset`을 호출합니다.
*/
초기화() {
const PromiseClass = this.options_.Promise || 창. 약속;
if (this.paused() || !PromiseClass) {
this.doReset_();
} else {
const playPromise = this.play();
침묵 약속(playPromise.then(() => this.doReset_()));
}
}
doReset_() {
if (this.tech_) {
this.tech_.clearTracks('텍스트');
}
this.resetCache_();
this.poster('');
this.loadTech_(this.options_.techOrder[0], null);
this.techCall_('초기화');
this.resetControlBarUI_();
if (isEvented(이)) {
this.trigger('playerreset');
}
}
/**
* 재설정하는 하위 메서드를 호출하여 Control Bar의 UI 재설정
* Control Bar의 모든 구성 요소
*/
resetControlBarUI_() {
this.resetProgressBar_();
this.resetPlaybackRate_();
this.resetVolumeBar_();
}
/**
* UI에서 진행률 표시줄이 재설정되도록 기술의 진행률을 재설정합니다.
*/
resetProgressBar_() {
this.currentTime(0);
const { 기간 표시, 남은 시간 표시 } = this.controlBar || {};
if (기간 표시) {
durationDisplay.updateContent();
}
if (remainingTimeDisplay) {
남은시간디스플레이.업데이트컨텐트();
}
}
/**
* 재생 비율 초기화
*/
resetPlaybackRate_() {
this.playbackRate(this.defaultPlaybackRate());
this.handleTechRateChange_();
}
/**
* 볼륨 바 재설정
*/
resetVolumeBar_() {
this.볼륨(1.0);
this.trigger('볼륨 변경');
}
/**
* 현재 소스 개체를 모두 반환합니다.
*
* @return {Tech~SourceObject[]}
* 현재 소스 개체
*/
전류 소스() {
const 소스 = this.currentSource();
const 소스 = [];
// `{}` 또는 `{ src }`로 가정
if (Object.keys(source).length !== 0) {
sources.push(소스);
}
this.cache_.sources를 반환 || 출처;
}
/**
* 현재 소스 개체를 반환합니다.
*
* @return {기술~소스 객체}
* 현재 소스 객체
*/
전류 소스() {
this.cache_.source를 반환 || {};
}
/**
* 현재 소스 값의 정규화된 URL(예: http://mysite.com/video.mp4)을 반환합니다.
* 현재 소스 개체를 다시 빌드하는 데 도움이 되도록 `currentType`과 함께 사용할 수 있습니다.
*
* @return {문자열}
* 현재 소스
*/
currentSrc() {
this.currentSource()를 반환합니다. && this.currentSource().src || '';
}
/**
* 현재 소스 유형 가져오기(예: video/mp4)
* 이렇게 하면 현재 소스 개체를 다시 빌드하여 동일한 소스 개체를 로드할 수 있습니다.
* 나중에 소스 및 기술
*
* @return {문자열}
* 소스 MIME 유형
*/
전류 유형() {
this.currentSource()를 반환합니다. && this.currentSource().type || '';
}
/**
* 사전 로드 속성을 가져오거나 설정합니다.
*
* @param {부울} [값]
* - true는 미리 로드해야 함을 의미합니다.
* - false는 미리 로드하지 않아야 함을 의미합니다.
*
* @return {문자열}
* 가져올 때 preload 속성 값
*/
예압(값) {
if (값 !== 정의되지 않음) {
this.techCall_('setPreload', 값);
this.options_.preload = 값;
반품;
}
return this.techGet_('프리로드');
}
/**
* 자동 재생 옵션을 가져오거나 설정합니다. 이것이 부울이면
* 기술의 속성을 수정합니다. 문자열일 때 속성
* 기술이 제거되고 `Player`가 로드 시작 시 자동 재생을 처리합니다.
*
* @param {부울|문자열} [값]
* - true: 브라우저 동작을 사용하여 자동 재생
* - false: 자동 재생하지 않음
* - '재생': 로드 시작 시마다 play() 호출
* - 'muted': loadstart마다 muted()를 호출한 다음 play()를 호출합니다.
* - 'any': 로드가 시작될 때마다 play()를 호출합니다. 그것이 실패하면 muted()를 호출한 다음 play()를 호출합니다.
* - *: 여기에 나열된 값 이외의 값은 `autoplay`를 true로 설정합니다.
*
* @return {부울|문자열}
* 얻을 때 자동 재생의 현재 값
*/
자동재생(값) {
// getter 사용법
if (값 === 정의되지 않음) {
this.options_.autoplay를 반환 || 거짓;
}
let techAutoplay;
// 값이 유효한 문자열인 경우 이를 설정하거나 필요에 따라 'true'를 'play'로 정규화합니다.
if (값 유형 === '문자열' && (/(any|play|muted)/).test(value) || 값 === 참 && this.options_.normalizeAutoplay) {
this.options_.autoplay = 값;
this.manualAutoplay_(typeof value === 'string' ? value : 'play');
techAutoplay = 거짓;
// 잘못된 값은 브라우저에서 autoplay를 false로 설정합니다.
// 똑같이 해보자
} 그렇지 않으면 (!값) {
this.options_.autoplay = 거짓;
// 다른 값(예: truthy)은 자동 재생을 true로 설정합니다.
} else {
this.options_.autoplay = 참;
}
techAutoplay = typeof techAutoplay === '정의되지 않음' ? this.options_.autoplay : techAutoplay;
// 기술이 없으면 대기하지 않습니다.
// 기술 준비에 대한 setAutoplay 호출. 우리는 이것을 하기 때문에
// 자동 재생 옵션이 생성자에 전달되고 우리는
// 두 번 설정할 필요가 없습니다.
if (this.tech_) {
this.techCall_('setAutoplay', techAutoplay);
}
}
/**
* playsinline 속성을 설정하거나 해제합니다.
* Playsinline은 전체 화면이 아닌 재생이 선호됨을 브라우저에 알립니다.
*
* @param {부울} [값]
* - true는 기본적으로 인라인 재생을 시도해야 함을 의미합니다.
* - false는 브라우저의 기본 재생 모드를 사용해야 함을 의미합니다.
* 대부분의 경우 인라인입니다. iOS Safari는 주목할만한 예외입니다.
* 기본적으로 전체 화면으로 재생됩니다.
*
* @return {문자열|플레이어}
* - playinline의 현재 값
* - 설정시 플레이어
*
* @see [사양]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
*/
재생인라인(값) {
if (값 !== 정의되지 않음) {
this.techCall_('setPlaysinline', value);
this.options_.playsinline = 값;
이것을 반환하십시오.
}
return this.techGet_('playsinline');
}
/**
* 동영상 요소의 루프 속성을 가져오거나 설정합니다.
*
* @param {부울} [값]
* - true는 비디오를 반복해야 함을 의미합니다.
* - false는 동영상을 반복하지 않아야 함을 의미합니다.
*
* @return {부울}
* 가져올 때 루프의 현재 값
*/
루프(값) {
if (값 !== 정의되지 않음) {
this.techCall_('setLoop', 값);
this.options_.loop = 값;
반품;
}
return this.techGet_('루프');
}
/**
* 포스터 이미지 소스 URL 가져오기 또는 설정
*
* @fires Player#posterchange
*
* @param {문자열} [소스]
* 포스터 이미지 출처 URL
*
* @return {문자열}
* 획득 시 포스터의 현재 가치
*/
포스터(src) {
if (src === 정의되지 않음) {
this.poster_를 반환합니다.
}
// 포스터를 제거하는 올바른 방법은 빈 문자열로 설정하는 것입니다.
// 다른 falsey 값은 오류를 발생시킵니다.
if (!src) {
소스 = '';
}
if (src === this.poster_) {
반품;
}
// 내부 포스터 변수 업데이트
this.poster_ = src;
// 기술자의 포스터를 업데이트합니다.
this.techCall_('setPoster', src);
this.isPosterFromTech_ = 거짓;
// 포스터가 설정되었음을 알리는 구성 요소
/**
* 이 이벤트는 플레이어에서 포스터 이미지가 변경될 때 발생합니다.
*
* @event Player#posterchange
* @type {이벤트대상~이벤트}
*/
this.trigger('posterchange');
}
/**
* 일부 기술자(예: YouTube)는 포스터 소스를 제공할 수 있습니다.
* 비동기 방식. 우리는 포스터 구성 요소가 이것을 사용하기를 원합니다.
* 기술의 제어를 가릴 수 있도록 포스터 소스.
* (YouTube의 재생 버튼). 그러나 우리는 이것을 사용하고 싶습니다
* 플레이어 사용자가 포스터를 설정하지 않은 경우 출처
* 일반 API.
*
* @fires Player#posterchange
* @listens Tech#posterchange
* @사적인
*/
handleTechPosterChange_() {
if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
const newPoster = this.tech_.poster() || '';
if (newPoster !== this.poster_) {
this.poster_ = newPoster;
this.isPosterFromTech_ = 참;
// 구성 요소에 포스터가 변경되었음을 알립니다.
this.trigger('posterchange');
}
}
}
/**
* 컨트롤이 표시되는지 여부를 가져오거나 설정합니다.
*
* @fires Player#controlsenabled
*
* @param {부울} [부울]
* - 컨트롤을 켜려면 true
* - 컨트롤을 끄려면 false
*
* @return {부울}
* 가져올 때 컨트롤의 현재 값
*/
컨트롤(부울) {
if (부울 === 정의되지 않음) {
반환 !!this.controls_;
}
부울 = !!부울;
// 실제로 변경되지 않는 한 변경 이벤트를 트리거하지 않습니다.
if (this.controls_ === 부울) {
반품;
}
this.controls_ = 부울;
if (this.usingNativeControls()) {
this.techCall_('setControls', bool);
}
if (this.controls_) {
this.removeClass('vjs-controls-disabled');
this.addClass('vjs-controls-enabled');
/**
* @event Player#controlsenabled
* @type {이벤트대상~이벤트}
*/
this.trigger('controlsenabled');
if (!this.usingNativeControls()) {
this.addTechControlsListeners_();
}
} else {
this.removeClass('vjs-controls-enabled');
this.addClass('vjs-controls-disabled');
/**
* @event Player#controlsdisabled
* @type {이벤트대상~이벤트}
*/
this.trigger('controlsdisabled');
if (!this.usingNativeControls()) {
this.removeTechControlsListeners_();
}
}
}
/**
* 네이티브 컨트롤을 켜거나 끕니다. 네이티브 컨트롤은 내장된 컨트롤입니다.
* 장치(예: 기본 iPhone 컨트롤) 또는 기타 기술
* (예: Vimeo 컨트롤)
* **기술자만 알고 있기 때문에 현재 기술자만 설정해야 합니다.
* 네이티브 컨트롤을 지원할 수 있는 경우**
*
* @fires Player#usingnativecontrols
* @fires Player#usingcustomcontrols
*
* @param {부울} [부울]
* - 네이티브 컨트롤을 켜려면 true
* - 네이티브 컨트롤을 끄려면 false
*
* @return {부울}
* 얻을 때 네이티브 컨트롤의 현재 값
*/
usingNativeControls(부울) {
if (부울 === 정의되지 않음) {
반환 !!this.usingNativeControls_;
}
부울 = !!부울;
// 실제로 변경되지 않는 한 변경 이벤트를 트리거하지 않습니다.
if (this.usingNativeControls_ === 부울) {
반품;
}
this.usingNativeControls_ = 부울;
if (this.usingNativeControls_) {
this.addClass('vjs-using-native-controls');
/**
* 플레이어가 기본 장치 컨트롤을 사용하고 있습니다.
*
* @event Player#usingnativecontrols
* @type {이벤트대상~이벤트}
*/
this.trigger('usingnativecontrols');
} else {
this.removeClass('vjs-using-native-controls');
/**
* 플레이어가 맞춤 HTML 컨트롤을 사용하고 있습니다.
*
* @event Player#usingcustomcontrols
* @type {이벤트대상~이벤트}
*/
this.trigger('usingcustomcontrols');
}
}
/**
* 현재 MediaError 설정 또는 가져오기
*
* @fires 플레이어#오류
*
* @param {MediaError|문자열|숫자} [오류]
* MediaError 또는 전환할 문자열/숫자
* MediaError로
*
* @return {MediaError|null}
* 가져올 때의 현재 MediaError(또는 null)
*/
오류(오류) {
if (오류 === 정의되지 않음) {
반환 this.error_ || 없는;
}
// 후크가 오류 객체를 수정하도록 허용
hooks('beforeerror').forEach((hookFunction) => {
const newErr = hookFunction(this, err);
만약에 (!(
(isObject(newErr) && !Array.isArray(newErr)) ||
typeof newErr === '문자열' ||
typeof newErr === '숫자' ||
newErr === null
)) {
this.log.error('MediaError가 beforeerror 후크에서 예상하는 값을 반환하세요.');
반품;
}
오류 = newErr;
});
// 호환 가능한 소스가 없는 경우 첫 번째 오류 메시지를 억제합니다.
// 사용자 상호 작용
if (this.options_.suppressNotSupportedError &&
잘못 && 오류 코드 === 4
) {
const triggerSuppressedError = 함수() {
this.오류(err);
};
this.options_.suppressNotSupportedError = 거짓;
this.any(['클릭', '터치스타트'], triggerSuppressedError);
this.one('loadstart', function() {
this.off(['클릭', '터치스타트'], triggerSuppressedError);
});
반품;
}
// 기본값으로 복원
경우 (오류 === null) {
this.error_ = 오류;
this.removeClass('vjs-오류');
if (이.오류 표시) {
this.errorDisplay.close();
}
반품;
}
this.error_ = new MediaError(err);
// 플레이어에 vjs-error 클래스 이름을 추가합니다.
this.addClass('vjs-오류');
// 오류 유형의 이름과 메시지를 기록합니다.
// IE11은 "[개체 개체]"를 기록하고 오류 개체를 보려면 메시지를 확장해야 합니다.
log.error(`(CODE:${this.error_.code} ${MediaError.errorTypes[this.error_.code]})`, this.error_.message, this.error_);
/**
* @event 플레이어#오류
* @type {이벤트대상~이벤트}
*/
this.trigger('오류');
// 플레이어별 오류를 후크에 알립니다.
hooks('오류').forEach((hookFunction) => hookFunction(this, this.error_));
반품;
}
/**
* 사용자 활동 보고
*
* @param {객체} 이벤트
* 이벤트 객체
*/
reportUserActivity(이벤트) {
this.userActivity_ = 참;
}
/**
* 사용자가 활성 상태인지 가져오기/설정
*
* @fires 플레이어#useractive
* @fires 플레이어#userinactive
*
* @param {부울} [부울]
* - 사용자가 활성화된 경우 true
* - 사용자가 비활성 상태이면 false
*
* @return {부울}
* 가져올 때 userActive의 현재 값
*/
userActive(부울) {
if (부울 === 정의되지 않음) {
this.userActive_를 반환합니다.
}
부울 = !!부울;
if (부울 === this.userActive_) {
반품;
}
this.userActive_ = 부울;
if (이.userActive_) {
this.userActivity_ = 참;
this.removeClass('vjs-user-inactive');
this.addClass('vjs-user-active');
/**
* @event 플레이어#useractive
* @type {이벤트대상~이벤트}
*/
this.trigger('useractive');
반품;
}
// Chrome/Safari/IE에는 커서를 변경할 때
// mousemove 이벤트를 트리거합니다. 숨길 때 문제가 발생합니다.
// 사용자가 비활성 상태이고 mousemove가 사용자에게 신호를 보낼 때 커서
// 활동. 비활성 모드로 전환하는 것을 불가능하게 만듭니다. 구체적으로
// 이것은 커서를 정말로 숨길 필요가 있을 때 전체 화면에서 발생합니다.
//
// 모든 브라우저에서 해결되면 제거할 수 있습니다.
// https://code.google.com/p/chromium/issues/detail?id=103041
if (this.tech_) {
this.tech_.one('마우스무브', function(e) {
e.stopPropagation();
e.preventDefault();
});
}
this.userActivity_ = 거짓;
this.removeClass('vjs-user-active');
this.addClass('vjs-user-inactive');
/**
* @event 플레이어#userinactive
* @type {이벤트대상~이벤트}
*/
this.trigger('userinactive');
}
/**
* 시간 초과 값을 기준으로 사용자 활동 수신
*
* @사적인
*/
listenForUserActivity_() {
let mouseInProgress;
lastMoveX하자;
lastMoveY를 보자;
const handleActivity = Fn.bind(this, this.reportUserActivity);
const handleMouseMove = 함수(e) {
// #1068 - mousemove 스팸 방지
// 크롬 버그: https://code.google.com/p/chromium/issues/detail?id=366970
if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
lastMoveX = e.screenX;
lastMoveY = e.screenY;
handleActivity();
}
};
const handleMouseDown = 함수() {
handleActivity();
// 그들이 장치를 만지거나 마우스를 누르고 있는 동안,
// 손가락이나 마우스를 움직이지 않아도 활성 상태로 간주합니다.
// 그래서 우리는 그들이 활성 상태임을 계속 업데이트하고 싶습니다.
this.clearInterval(mouseInProgress);
// 지금 userActivity=true로 설정하고 간격을 같은 시간으로 설정
// activityCheck 간격(250)은 우리가
// 다음 활동 확인
mouseInProgress = this.setInterval(handleActivity, 250);
};
const handleMouseUpAndMouseLeave = 함수(이벤트) {
handleActivity();
// 마우스/터치가 눌린 경우 활동을 유지하는 간격을 중지합니다.
this.clearInterval(mouseInProgress);
};
// 모든 마우스 움직임은 사용자 활동으로 간주됩니다.
this.on('mousedown', handleMouseDown);
this.on('mousemove', handleMouseMove);
this.on('mouseup', handleMouseUpAndMouseLeave);
this.on('mouseleave', handleMouseUpAndMouseLeave);
const controlBar = this.getChild('controlBar');
// Android의 버그 수정 & iOS 어디에서 progressBar를 탭할 때(컨트롤 바가 표시될 때)
// controlBar는 기본 시간 초과로 더 이상 숨겨지지 않습니다.
if (컨트롤바 && !브라우저.IS_IOS && !브라우저.IS_ANDROID) {
controlBar.on('마우스엔터', 함수(이벤트) {
if (this.player().options_.inactivityTimeout !== 0) {
this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
}
this.player().options_.inactivityTimeout = 0;
});
controlBar.on('마우스리브', function(event) {
this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
});
}
// 키보드 탐색 듣기
// 키 반복 때문에 inProgress 간격을 사용할 필요가 없습니다.
this.on('keydown', handleActivity);
this.on('keyup', handleActivity);
// 모든 것을 채우는 대신 250밀리초마다 간격을 실행합니다.
// 성능 저하를 방지하기 위해 mousemove/touchmove 함수 자체.
// `this.reportUserActivity`는 단순히 this.userActivity_를 true로 설정합니다.
// 그런 다음 이 루프에 의해 선택됩니다.
// http://ejohn.org/blog/learning-from-twitter/
inactivityTimeout하자;
this.setInterval(함수() {
// 마우스/터치 활동이 발생했는지 확인
if (!this.userActivity_) {
반품;
}
// 액티비티 트래커 재설정
this.userActivity_ = 거짓;
// 사용자 상태가 비활성인 경우 상태를 활성으로 설정합니다.
this.userActive(참);
// 타이머를 다시 시작하기 위해 기존 비활성 제한 시간을 지웁니다.
this.clearTimeout(inactivityTimeout);
const timeout = this.options_.inactivityTimeout;
만약 (타임아웃 < = 0) {
반품;
}
// 안에 < 타임아웃> 더 이상 활동이 발생하지 않으면 밀리초
// 사용자는 비활성 상태로 간주됩니다.
inactivityTimeout = this.setTimeout(function() {
// inactivityTimeout이 트리거할 수 있는 경우로부터 보호
// 다음 사용자 활동이 활동 확인 루프에 의해 선택되기 전에
// 깜박임 발생
if (!this.userActivity_) {
this.userActive(거짓);
}
}, 타임아웃);
}, 250);
}
/**
* 현재 재생 속도를 가져오거나 설정합니다. 재생 속도
* 1.0은 정상 속도를 나타내고 0.5는 절반 속도를 나타냅니다.
* 예를 들어 재생.
*
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
*
* @param {숫자} [비율]
* 새로운 재생 속도를 설정할 수 있습니다.
*
* @return {숫자}
* 현재 재생 속도를 얻거나 1.0일 때
*/
재생 속도(속도) {
if (속도 !== 정의되지 않음) {
// 참고: this.cache_.lastPlaybackRate는 기술 처리기에서 설정됩니다.
// 위에 등록된
this.techCall_('setPlaybackRate', rate);
반품;
}
만약 (이것.기술_ && this.tech_.featuresPlaybackRate) {
this.cache_.lastPlaybackRate를 반환 || this.techGet_('재생 속도');
}
1.0을 반환합니다.
}
/**
* 현재 기본 재생 속도를 가져오거나 설정합니다. 기본 재생 속도
* 예를 들어 1.0은 정상 속도를 나타내고 0.5는 절반 속도 재생을 나타냅니다.
* defaultPlaybackRate는 비디오의 초기 playbackRate가 아닌 것만 나타냅니다.
* 현재 playbackRate가 아닙니다.
*
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
*
* @param {숫자} [비율]
* 새로운 기본 재생 속도를 설정할 수 있습니다.
*
* @return {번호|플레이어}
* - 받을 때 기본 재생 속도 또는 1.0
* - 설정시 플레이어
*/
defaultPlaybackRate(속도) {
if (속도 !== 정의되지 않음) {
return this.techCall_('setDefaultPlaybackRate', rate);
}
만약 (이것.기술_ && this.tech_.featuresPlaybackRate) {
return this.techGet_('defaultPlaybackRate');
}
1.0을 반환합니다.
}
/**
* 오디오 플래그를 가져오거나 설정합니다.
*
* @param {부울} 부울
* - true는 이것이 오디오 플레이어임을 나타냅니다.
* - 오디오 플레이어가 아니라는 잘못된 신호
*
* @return {부울}
* 가져올 때 isAudio의 현재 값
*/
isAudio(부울) {
if (부울 !== 정의되지 않음) {
this.isAudio_ = !!bool;
반품;
}
반환 !!this.isAudio_;
}
enableAudioOnlyUI_() {
// 높이를 얻을 수 있도록 컨트롤 막대를 표시하도록 스타일을 즉시 업데이트합니다.
this.addClass('vjs-audio-only-mode');
const playerChildren = this.children();
const controlBar = this.getChild('ControlBar');
const controlBarHeight = 컨트롤바 && controlBar.currentHeight();
// 컨트롤 바를 제외한 모든 플레이어 구성 요소를 숨깁니다. 컨트롤 막대 구성요소
// 비디오에만 필요한 CSS로 숨겨짐
playerChildren.forEach(자식 => {
if (자식 === controlBar) {
반품;
}
만약 (자식.el_ && !child.hasClass('vjs-hidden')) {
child.hide();
this.audioOnlyCache_.hiddenChildren.push(자식);
}
});
this.audioOnlyCache_.playerHeight = this.currentHeight();
// 플레이어 높이를 컨트롤 바와 동일하게 설정
this.height(controlBarHeight);
this.trigger('audioonlymodechange');
}
disableAudioOnlyUI_() {
this.removeClass('vjs-audio-only-mode');
// 이전에 숨겨진 플레이어 구성요소 표시
this.audioOnlyCache_.hiddenChildren.forEach(자식 => child.show());
// 플레이어 높이 재설정
this.height(this.audioOnlyCache_.playerHeight);
this.trigger('audioonlymodechange');
}
/**
* 현재 audioOnlyMode 상태를 가져오거나 audioOnlyMode를 true 또는 false로 설정합니다.
*
* 이것을 'true'로 설정하면 컨트롤 바를 제외한 모든 플레이어 구성 요소가 숨겨집니다.
* 비디오에만 필요한 컨트롤 막대 구성 요소도 있습니다.
*
* @param {부울} [값]
* audioOnlyMode를 설정할 값.
*
* @return {약속|부울}
* 상태를 설정할 때 Promise가 반환되고 가져올 때 부울이 반환됩니다.
* 현재 상태
*/
audioOnlyMode(값) {
if (typeof value !== 'boolean' || value === this.audioOnlyMode_) {
this.audioOnlyMode_를 반환합니다.
}
this.audioOnlyMode_ = 값;
const PromiseClass = this.options_.Promise || 창. 약속;
경우 (PromiseClass) {
// 오디오 전용 모드 활성화
경우 (값) {
const exitPromises = [];
// 전체 화면 및 PiP는 audioOnlyMode에서 지원되지 않으므로 필요하면 종료합니다.
if (this.isInPictureInPicture()) {
exitPromises.push(this.exitPictureInPicture());
}
if (this.isFullscreen()) {
exitPromises.push(this.exitFullscreen());
}
if (this.audioPosterMode()) {
exitPromises.push(this.audioPosterMode(false));
}
PromiseClass.all(exitPromises).then(() = 반환> this.enableAudioOnlyUI_());
}
// 오디오 전용 모드 비활성화
반환 PromiseClass.resolve().then(() => this.disableAudioOnlyUI_());
}
경우 (값) {
if (this.isInPictureInPicture()) {
this.exitPictureInPicture();
}
if (this.isFullscreen()) {
this.exitFullscreen();
}
this.enableAudioOnlyUI_();
} else {
this.disableAudioOnlyUI_();
}
}
enablePosterModeUI_() {
// video 요소를 숨기고 포스터 이미지를 표시하여 posterModeUI를 활성화합니다.
const 기술 = this.tech_ && this.tech_;
tech.hide();
this.addClass('vjs-audio-poster-mode');
this.trigger('오디오 포스터 모드 변경');
}
disablePosterModeUI_() {
// 비디오 요소를 표시하고 포스터 이미지를 숨겨 posterModeUI를 비활성화합니다.
const 기술 = this.tech_ && this.tech_;
tech.show();
this.removeClass('vjs-audio-poster-mode');
this.trigger('오디오 포스터 모드 변경');
}
/**
* 현재 audioPosterMode 상태를 가져오거나 audioPosterMode를 true 또는 false로 설정
*
* @param {부울} [값]
* audioPosterMode를 설정할 값.
*
* @return {약속|부울}
* 상태를 설정할 때 Promise를 반환하고 가져오면 부울을 반환합니다.
* 현재 상태
*/
audioPosterMode(값) {
if (typeof value !== 'boolean' || value === this.audioPosterMode_) {
this.audioPosterMode_를 반환합니다.
}
this.audioPosterMode_ = 값;
const PromiseClass = this.options_.Promise || 창. 약속;
경우 (PromiseClass) {
경우 (값) {
if (this.audioOnlyMode()) {
const audioOnlyModePromise = this.audioOnlyMode(false);
return audioOnlyModePromise.then(() => {
// 오디오 전용 모드가 비활성화된 후 오디오 포스터 모드를 활성화합니다.
this.enablePosterModeUI_();
});
}
반환 PromiseClass.resolve().then(() => {
// 오디오 포스터 모드 활성화
this.enablePosterModeUI_();
});
}
반환 PromiseClass.resolve().then(() => {
// 오디오 포스터 모드 비활성화
this.disablePosterModeUI_();
});
}
경우 (값) {
if (this.audioOnlyMode()) {
this.audioOnlyMode(false);
}
this.enablePosterModeUI_();
반품;
}
this.disablePosterModeUI_();
}
/**
* {@link TextTrack}을 우리의
* {@link TextTrackList}.
*
* W3C 설정 외에도 옵션을 통해 추가 정보를 추가할 수 있습니다.
*
* @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
*
* @param {문자열} [종류]
* 추가하려는 TextTrack의 종류
*
* @param {문자열} [레이블]
* TextTrack 레이블을 제공하는 레이블
*
* @param {문자열} [언어]
* TextTrack에 설정할 언어
*
* @return {TextTrack|정의되지 않음}
* 추가되었거나 정의되지 않은 TextTrack
* 기술이 없을 경우
*/
addTextTrack(종류, 라벨, 언어) {
if (this.tech_) {
return this.tech_.addTextTrack(종류, 라벨, 언어);
}
}
/**
* 원격 {@link TextTrack} 및 {@link HTMLTrackElement}를 생성합니다.
* manualCleanup이 false로 설정되면 트랙이 자동으로 제거됩니다.
* 소스 변경 시.
*
* @param {객체} 옵션
* 생성 중에 {@link HTMLTrackElement}로 전달하는 옵션. 보다
* 사용해야 하는 개체 속성에 대한 {@link HTMLTrackElement}.
*
* @param {boolean} [manualCleanup=true] false로 설정하면 TextTrack은
* 소스 변경 시 제거됨
*
* @return {HtmlTrackElement}
* 생성 및 추가된 HTMLTrackElement
* HtmlTrackElementList 및 원격
* TextTrackList
*
* @deprecated "manualCleanup" 매개변수의 기본값은 기본값이 됩니다.
* 향후 버전의 Video.js에서 "false"로 변경
*/
addRemoteTextTrack(옵션, manualCleanup) {
if (this.tech_) {
return this.tech_.addRemoteTextTrack(옵션, manualCleanup);
}
}
/**
* 각각의 원격 {@link TextTrack} 제거
* {@link TextTrackList} 및 {@link HtmlTrackElementList}.
*
* @param {객체} 트랙
* 제거할 원격 {@link TextTrack}
*
* @return {정의되지 않음}
* 아무것도 반환하지 않습니다
*/
removeRemoteTextTrack(obj = {}) {
let {트랙} = obj;
if (!트랙) {
트랙 = obj;
}
// 트랙 인수를 사용하여 입력을 객체로 분해합니다. 기본값은 arguments[0]입니다.
// 아무 것도 전달되지 않은 경우 전체 인수를 빈 객체로 기본 설정
if (this.tech_) {
return this.tech_.removeRemoteTextTrack(트랙);
}
}
/**
* W3C의 미디어에서 지정한 대로 사용 가능한 미디어 재생 품질 메트릭을 가져옵니다.
* 재생 품질 API.
*
* @see [사양]{@link https://wicg.github.io/media-playback-quality}
*
* @return {객체|정의되지 않음}
* 지원되는 미디어 재생 품질 메트릭이 있는 개체 또는 정의되지 않은 경우
*는 기술이 없거나 기술이 지원하지 않습니다.
*/
getVideoPlaybackQuality() {
return this.techGet_('getVideoPlaybackQuality');
}
/**
* 비디오 너비 얻기
*
* @return {숫자}
* 현재 비디오 너비
*/
비디오 너비() {
반환합니다.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
}
/**
* 비디오 높이 얻기
*
* @return {숫자}
* 현재 비디오 높이
*/
비디오 높이() {
반환합니다.tech_ && this.tech_.video높이 && this.tech_.videoHeight() || 0;
}
/**
* 플레이어의 언어 코드.
*
* 언어를 변경하면 트리거됩니다.
* [언어변경]{@link Player#event:languagechange}
* 제어 텍스트를 업데이트하는 데 사용할 수 있는 구성 요소.
* ClickableComponent는 기본적으로 제어 텍스트를 업데이트합니다.
* [언어변경]{@link Player#event:언어변경}.
*
* @fires Player#languagechange
*
* @param {문자열} [코드]
* 플레이어를 설정할 언어 코드
*
* @return {문자열}
* 받을 때 현재 언어 코드
*/
언어(코드) {
if (코드 === 정의되지 않음) {
this.language_를 반환합니다.
}
if (this.language_ !== String(code).toLowerCase()) {
this.language_ = String(code).toLowerCase();
// 처음 초기화하는 동안 이벤트가 발생하지 않을 가능성이 있습니다.
if (isEvented(이)) {
/**
* 플레이어 언어가 변경되면 발생
*
* @event 플레이어#언어변경
* @type {이벤트대상~이벤트}
*/
this.trigger('언어 변경');
}
}
}
/**
* 플레이어의 언어 사전 가져오기
* 새로 추가된 플러그인이 언제든지 videojs.addLanguage()를 호출할 수 있으므로 매번 병합합니다.
* 플레이어 옵션에서 직접 지정한 언어가 우선합니다.
*
* @return {배열}
* 지원되는 언어 배열
*/
언어() {
return mergeOptions(Player.prototype.options_.languages, this.languages_);
}
/**
* 현재 트랙을 나타내는 JavaScript 객체를 반환합니다.
* 정보. **JSON으로 반환하지 않습니다**
*
* @return {객체}
* 트랙 정보의 현재를 나타내는 객체
*/
toJSON() {
const 옵션 = mergeOptions(this.options_);
const 트랙 = options.tracks;
options.tracks = [];
에 대한 (하자 i = 0; i < 트랙.길이; i++) {
let track = 트랙[i];
// 트랙을 완전히 병합하고 플레이어를 무효화하므로 순환 참조가 없습니다.
트랙 = mergeOptions(트랙);
track.player = 정의되지 않음;
options.tracks[i] = 트랙;
}
반환 옵션;
}
/**
* 간단한 모달 대화 상자({@link ModalDialog}
* 구성 요소) 플레이어를 임의로 즉시 오버레이합니다.
* 콘텐츠가 닫히면 자체적으로 제거됩니다.
*
* @param {문자열|함수|요소|배열|null} 내용
* 같은 이름의 {@link ModalDialog#content}의 매개변수와 동일합니다.
* 가장 직관적인 사용법은 문자열이나 DOM을 제공하는 것입니다.
* 요소.
*
* @param {객체} [옵션]
* {@link ModalDialog}에 전달될 추가 옵션.
*
* @return {모달대화상자}
* 생성된 {@link ModalDialog}
*/
createModal(콘텐츠, 옵션) {
옵션 = 옵션 || {};
options.content = 내용 || '';
const modal = new ModalDialog(this, options);
this.addChild(모달);
modal.on('처리', () => {
this.removeChild(모달);
});
modal.open();
반환 모달;
}
/**
* 플레이어 크기를 조정할 때 중단점 클래스를 변경합니다.
*
* @사적인
*/
updateCurrentBreakpoint_() {
if (!this.responsive()) {
반품;
}
const currentBreakpoint = this.currentBreakpoint();
const currentWidth = this.currentWidth();
에 대한 (하자 i = 0; i < BREAKPOINT_ORDER.length; i++) {
const candidateBreakpoint = BREAKPOINT_ORDER[i];
const maxWidth = this.breakpoints_[candidateBreakpoint];
if (전류폭 < = 최대 너비) {
// 현재 중단점은 변경되지 않았으며 할 일이 없습니다.
if (현재 중단점 === 후보 중단점) {
반품;
}
// 현재 중단점이 있는 경우에만 클래스를 제거합니다.
if (currentBreakpoint) {
this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
}
this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
this.breakpoint_ = 후보 중단점;
부서지다;
}
}
}
/**
* 현재 중단점을 제거합니다.
*
* @사적인
*/
removeCurrentBreakpoint_() {
const className = this.currentBreakpointClass();
this.breakpoint_ = '';
if (클래스 이름) {
this.removeClass(className);
}
}
/**
* 플레이어에서 중단점을 가져오거나 설정합니다.
*
* 개체 또는 `true`와 함께 이 메서드를 호출하면 이전의 모든 항목이 제거됩니다.
* 사용자 지정 중단점 및 기본값에서 다시 시작합니다.
*
* @param {Object|boolean} [중단점]
* 객체가 주어지면 사용자 정의를 제공하는 데 사용할 수 있습니다.
* 중단점. `true`가 주어지면 기본 중단점을 설정합니다.
* 이 인수가 제공되지 않으면 단순히 현재 값을 반환합니다.
* 중단점.
*
* @param {숫자} [중단점.작은]
* "vjs-layout-tiny" 클래스의 최대 너비.
*
* @param {숫자} [중단점.xsmall]
* "vjs-layout-x-small" 클래스의 최대 너비.
*
* @param {숫자} [중단점.작음]
* "vjs-layout-small" 클래스의 최대 너비.
*
* @param {번호} [중단점.중간]
* "vjs-layout-medium" 클래스의 최대 너비.
*
* @param {숫자} [중단점.대형]
* "vjs-layout-large" 클래스의 최대 너비.
*
* @param {숫자} [중단점.xlarge]
* "vjs-layout-x-large" 클래스의 최대 너비.
*
* @param {숫자} [중단점.거대한]
* "vjs-layout-huge" 클래스의 최대 너비.
*
* @return {객체}
* 개체 매핑 중단점 이름을 최대 너비 값으로 지정합니다.
*/
중단점(중단점) {
// 게터로 사용됩니다.
if (중단점 === 정의되지 않음) {
return assign(this.breakpoints_);
}
this.breakpoint_ = '';
this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, 중단점);
// 중단점 정의가 변경되면 현재를 업데이트해야 합니다.
// 선택된 중단점.
this.updateCurrentBreakpoint_();
// 반환하기 전에 중단점을 복제합니다.
return assign(this.breakpoints_);
}
/**
* 이 플레이어가 조정해야 하는지 여부를 나타내는 플래그를 가져오거나 설정합니다.
* 크기에 따른 UI.
*
* @param {부울} 값
* 플레이어가 자신의 UI를 기반으로 UI를 조정해야 하는 경우 'true'여야 합니다.
* 치수; 그렇지 않으면 `거짓`이어야 합니다.
*
* @return {부울}
* 이 플레이어가 자신의 UI를 기반으로 UI를 조정해야 하는 경우 'true'가 됩니다.
* 치수; 그렇지 않으면 '거짓'이 됩니다.
*/
반응형(가치) {
// 게터로 사용됩니다.
if (값 === 정의되지 않음) {
this.responsive_를 반환합니다.
}
값 = 부울(값);
const 현재 = this.responsive_;
// 아무것도 바뀌지 않았다.
if (값 === 현재) {
반품;
}
// 실제로 변경된 값, 설정합니다.
this.responsive_ = 값;
// 중단점 수신을 시작하고 다음과 같은 경우 초기 중단점을 설정합니다.
// 이제 플레이어가 반응합니다.
경우 (값) {
this.on('playerresize', this.boundUpdateCurrentBreakpoint_);
this.updateCurrentBreakpoint_();
// 플레이어가 더 이상 응답하지 않으면 중단점 수신을 중지합니다.
} else {
this.off('playerresize', this.boundUpdateCurrentBreakpoint_);
this.removeCurrentBreakpoint_();
}
반환 값;
}
/**
* 현재 중단점 이름을 가져옵니다(있는 경우).
*
* @return {문자열}
* 현재 중단점이 설정되어 있으면 중단점에서 키를 반환합니다.
* 일치하는 중단점 객체. 그렇지 않으면 빈 문자열을 반환합니다.
*/
currentBreakpoint() {
this.breakpoint_를 반환합니다.
}
/**
* 현재 중단점 클래스 이름을 가져옵니다.
*
* @return {문자열}
* 일치하는 클래스 이름(예: `"vjs-layout-tiny"` 또는
* `"vjs-layout-large"`) 현재 중단점. 다음과 같은 경우 빈 문자열
* 현재 중단점이 없습니다.
*/
currentBreakpointClass() {
return BREAKPOINT_CLASSES[this.breakpoint_] || '';
}
/**
* 단일 미디어를 설명하는 개체입니다.
*
* 이 유형 설명의 일부가 아닌 속성은 유지됩니다. 그래서,
* 이것은 일반적인 메타 데이터 저장 메커니즘으로도 볼 수 있습니다.
*
* @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
* @typedef {객체} Player~MediaObject
*
* @property {문자열} [앨범]
* 이 개체가 `MediaSession`에 전달되는 경우를 제외하고 사용되지 않습니다.
* API.
*
* @property {문자열} [아티스트]
* 이 개체가 `MediaSession`에 전달되는 경우를 제외하고 사용되지 않습니다.
* API.
*
* @property {객체[]} [아트워크]
* 이 개체가 `MediaSession`에 전달되는 경우를 제외하고 사용되지 않습니다.
* API. 지정하지 않으면 `포스터`를 통해 채워집니다.
* 사용 가능.
*
* @property {문자열} [포스터]
* 재생 전에 표시될 이미지의 URL.
*
* @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
* 단일 소스 객체, 소스 객체의 배열 또는 문자열
* 미디어 소스에 대한 URL 참조. _강추합니다_
* 객체 또는 객체 배열이 여기에서 사용되므로 소스
* 선택 알고리즘은 `유형`을 고려할 수 있습니다.
*
* @property {문자열} [제목]
* 이 개체가 `MediaSession`에 전달되는 경우를 제외하고 사용되지 않습니다.
* API.
*
* @property {객체[]} [텍스트 트랙]
* 다음과 같이 텍스트 트랙을 만드는 데 사용할 개체 배열
* {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|네이티브 트랙 요소 형식}.
* 쉽게 제거할 수 있도록 "원격" 텍스트로 생성됩니다.
* 소스 변경 사항을 추적하고 자동으로 정리하도록 설정합니다.
*
* 이러한 개체에는 `src`, `kind`, `label`,
* 및 `언어`는 {@link Tech#createRemoteTextTrack}을 참조하세요.
*/
/**
* {@link Player~MediaObject|MediaObject}를 사용하여 플레이어를 채웁니다.
*
* @param {Player~MediaObject} 미디어
* 미디어 개체입니다.
*
* @param {함수} 준비됨
* 플레이어가 준비되면 호출되는 콜백입니다.
*/
loadMedia(미디어, 준비) {
if (!미디어 || 미디어 유형 !== '개체') {
반품;
}
this.reset();
// 외부에서 변경할 수 없도록 미디어 개체를 복제합니다.
this.cache_.media = mergeOptions(미디어);
const {작품, 포스터, src, textTracks} = this.cache_.media;
// `artwork`가 제공되지 않으면 `poster`를 사용하여 만듭니다.
if (!artwork && 포스터) {
this.cache_.media.artwork = [{
src: 포스터,
유형: getMimetype(포스터)
}];
}
경우 (소스) {
this.src(src);
}
만약 (포스터) {
this.poster(포스터);
}
if (Array.isArray(textTracks)) {
textTracks.forEach(tt => this.addRemoteTextTrack(tt, false));
}
this.ready(준비);
}
/**
* 이 플레이어에 대한 현재 {@link Player~MediaObject}의 클론을 가져옵니다.
*
* `loadMedia` 메서드를 사용하지 않은 경우 반환을 시도합니다.
* {@link Player~MediaObject}는 플레이어의 현재 상태를 기반으로 합니다.
*
* @return {플레이어~미디어오브젝트}
*/
getMedia() {
if (!this.cache_.media) {
const 포스터 = this.poster();
const src = this.currentSources();
const textTracks = Array.prototype.map.call(this.remoteTextTracks(), (tt) => ({
종류: tt.종류,
라벨: tt.label,
언어: tt.언어,
소스: tt.src
}));
const media = {src, textTracks};
만약 (포스터) {
media.poster = 포스터;
미디어.아트워크 = [{
src: media.poster,
유형: getMimetype(미디어.포스터)
}];
}
반환 매체;
}
mergeOptions(this.cache_.media)를 반환합니다.
}
/**
* 태그 설정 가져오기
*
* @param {요소} 태그
* 플레이어 태그
*
* @return {객체}
* 모든 설정을 포함하는 객체
* 플레이어 태그용
*/
정적 getTagSettings(태그) {
const baseOptions = {
출처: [],
트랙: []
};
const tagOptions = Dom.getAttributes(태그);
const dataSetup = tagOptions['데이터 설정'];
if (Dom.hasClass(tag, 'vjs-fill')) {
tagOptions.fill = 참;
}
if (Dom.hasClass(tag, 'vjs-fluid')) {
tagOptions.fluid = 참;
}
// data-setup attr이 존재하는지 확인합니다.
if (dataSetup !== null) {
// 파싱 옵션 JSON
// 빈 문자열이면 구문 분석 가능한 json 객체로 만듭니다.
const [err, data] = safeParseTuple(dataSetup || '{}');
경우 (오류) {
log.error(err);
}
assign(tagOptions, 데이터);
}
assign(baseOptions, tagOptions);
// 태그 하위 설정 가져오기
if (tag.hasChildNodes()) {
const children = tag.childNodes;
for (let i = 0, j = children.length; i < 제이; i++) {
const 자식 = 자식[i];
// 대소문자 변경 필요: http://ejohn.org/blog/nodename-case-sensitivity/
const childName = child.nodeName.toLowerCase();
if (아이 이름 === '출처') {
baseOptions.sources.push(Dom.getAttributes(child));
} 그렇지 않으면 (childName === '추적') {
baseOptions.tracks.push(Dom.getAttributes(child));
}
}
}
baseOptions 반환;
}
/**
* flexbox 지원 여부 결정
*
* @return {부울}
* - flexbox가 지원되는 경우 true
* - flexbox가 지원되지 않는 경우 false
*/
flexNotSupported_() {
const elem = document.createElement('i');
// 메모: 우리는 실제로 flexBasis(또는 flexOrder)를 사용하지 않지만 더 많은 것 중 하나입니다.
// 플렉스 지원을 확인할 때 신뢰할 수 있는 일반적인 플렉스 기능입니다.
return !( elem.style의 'flexBasis' ||
elem.style의 'webkitFlexBasis' ||
elem.style의 'mozFlexBasis' ||
elem.style의 'msFlexBasis' ||
// IE10 전용(2012 플렉스 사양), 완전성을 위해 사용 가능
elem.style의 'msFlexOrder');
}
/**
* 정보 수준에서 로그를 활성화/비활성화하도록 디버그 모드를 설정합니다.
*
* @param {부울} 활성화됨
* @fires Player#debugon
* @fires Player#debugoff
*/
디버그(활성화) {
if (활성화 === 정의되지 않음) {
this.debugEnabled_를 반환합니다.
}
경우 (활성화) {
this.trigger('debugon');
this.previousLogLevel_ = this.log.level;
this.log.level('디버그');
this.debugEnabled_ = 참;
} else {
this.trigger('debugoff');
this.log.level(this.previousLogLevel_);
this.previousLogLevel_ = 정의되지 않음;
this.debugEnabled_ = 거짓;
}
}
/**
* 현재 재생 속도를 설정하거나 가져옵니다.
* 배열을 가져와 새 항목으로 재생 속도 메뉴를 업데이트합니다.
* 메뉴를 숨기려면 빈 배열을 전달하십시오.
* 배열 이외의 값은 무시됩니다.
*
* @fires Player#playbackrateschange
* @param {번호[]} newRates
* 재생 속도 메뉴가 업데이트되어야 하는 새로운 속도.
* 빈 배열은 메뉴를 숨길 것입니다.
* @return {number[]} getter로 사용하면 현재 재생 속도를 반환합니다.
*/
playRates(newRates) {
if (newRates === 정의되지 않음) {
this.cache_.playbackRates 반환;
}
// 배열이 아닌 값은 무시
if (!Array.isArray(newRates)) {
반품;
}
// 숫자만 포함하지 않는 배열은 무시합니다.
if (!newRates.every((비율) => typeof rate === '숫자')) {
반품;
}
this.cache_.playbackRates = newRates;
/**
* 플레이어의 재생 속도가 변경될 때 발생
*
* @event Player#playbackrateschange
* @type {이벤트대상~이벤트}
*/
this.trigger('재생 속도 변경');
}
}
/**
* {@link VideoTrackList} 받기
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
*
* @return {VideoTrackList}
* 현재 비디오 트랙 목록
*
* @method Player.prototype.videoTracks
*/
/**
* {@link AudioTrackList} 받기
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
*
* @return {오디오트랙리스트}
* 현재 오디오 트랙 목록
*
* @method Player.prototype.audioTracks
*/
/**
* {@link TextTrackList} 받기
*
* @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
*
* @return {TextTrackList}
* 현재 텍스트 트랙 목록
*
* @method Player.prototype.textTracks
*/
/**
* 원격 {@link TextTrackList} 가져오기
*
* @return {TextTrackList}
* 현재 원격 텍스트 트랙 목록
*
* @method Player.prototype.remoteTextTracks
*/
/**
* 원격 {@link HtmlTrackElementList} 트랙을 가져옵니다.
*
* @return {HtmlTrackElementList}
* 현재 원격 텍스트 트랙 요소 목록
*
* @method Player.prototype.remoteTextTrackEls
*/
TRACK_TYPES.names.forEach(기능(이름) {
const 소품 = TRACK_TYPES[이름];
Player.prototype[props.getterName] = function() {
if (this.tech_) {
return this.tech_[props.getterName]();
}
// 아직 loadTech_가 없으면 {video,audio,text}Tracks_를 만듭니다.
// 로딩하는 동안 기술자에게 전달됩니다.
this[props.privateName] = this[props.privateName] || 새로운 props.ListClass();
return this[props.privateName];
};
});
/**
* `Player`의 crossorigin 옵션을 가져오거나 설정합니다. HTML5 플레이어의 경우 이
* `에 `crossOrigin` 속성을 설정합니다.< 동영상> ` CORS를 제어하는 태그
* 행동.
*
* @see [동영상 요소 속성]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
*
* @param {문자열} [값]
* `Player`의 크로스오리진을 설정할 값. 인수가
* 주어진 'anonymous' 또는 'use-credentials' 중 하나여야 합니다.
*
* @return {문자열|정의되지 않음}
* - 가져올 때 `Player`의 현재 crossorigin 값입니다.
* - 설정 시 정의되지 않음
*/
Player.prototype.crossorigin = Player.prototype.crossOrigin;
/**
* 플레이어의 글로벌 열거.
*
* 키는 플레이어 ID이고 값은 {@link Player}
* 폐기된 플레이어에 대한 인스턴스 또는 `null`.
*
* @type {객체}
*/
Player.players = {};
const 네비게이터 = window.navigator;
/*
* 옵션을 사용하여 표시되는 플레이어 인스턴스 옵션
* 옵션 = Player.prototype.options_
* 여기가 아닌 옵션에서 변경하십시오.
*
* @type {객체}
* @사적인
*/
Player.prototype.options_ = {
// 폴백 기술의 기본 순서
기술 주문: Tech.defaultTechOrder_,
HTML5: {},
// 기본 비활성 타임아웃
비활성 시간 초과: 2000 년,
// 기본 재생 속도
재생 속도: [],
// 속도를 추가하여 재생 속도 선택 추가
// 'playbackRates': [0.5, 1, 1.5, 2],
liveui: 거짓,
// 포함된 컨트롤 세트
어린이들: [
'미디어로더',
'포스터 이미지',
'textTrackDisplay',
'로딩스피너',
'빅플레이버튼',
'라이브트래커',
'컨트롤바',
'오류 표시',
'textTrackSettings',
'크기 조정 관리자'
],
언어: 네비게이터 && (네비게이터.언어 && navigator.languages[0] || navigator.userLanguage || navigator.언어) || '엔',
// 로케일 및 해당 언어 번역
언어: {},
// 동영상을 재생할 수 없을 때 표시할 기본 메시지입니다.
지원되지 않음메시지: '이 미디어에 대해 호환 가능한 소스를 찾을 수 없습니다.',
normalizeAutoplay: 거짓,
전체화면: {
옵션: {
navigationUI: '숨기기'
}
},
중단점: {},
반응형: 거짓,
audioOnlyMode: 거짓,
오디오 포스터 모드: 거짓
};
[
/**
* 플레이어가 "종료" 상태인지 여부를 반환합니다.
*
* @return {Boolean} 플레이어가 종료 상태이면 참, 아니면 거짓.
* @method Player#ended
*/
'종료',
/**
* 플레이어가 "탐색" 상태에 있는지 여부를 반환합니다.
*
* @return {Boolean} 플레이어가 탐색 상태에 있으면 True이고 그렇지 않으면 false입니다.
* @method Player#seeking
*/
'찾다',
/**
* 현재 사용 가능한 미디어의 TimeRanges를 반환합니다.
* 추구하기 위해.
*
* @return {TimeRanges} 미디어 타임라인의 탐색 가능한 간격
* @method Player#seekable
*/
'찾을 수 있는',
/**
*에서 요소에 대한 네트워크 활동의 현재 상태를 반환합니다.
* 아래 목록의 코드.
* - NETWORK_EMPTY(숫자 값 0)
* 요소가 아직 초기화되지 않았습니다. 모든 속성은
* 그들의 초기 상태.
* - NETWORK_IDLE(숫자 값 1)
* 요소의 자원 선택 알고리즘이 활성화되어 있고
* 리소스를 선택했지만 실제로는 네트워크를 사용하지 않습니다.
* 이 시간.
* - NETWORK_LOADING(숫자 값 2)
* 사용자 에이전트가 적극적으로 데이터 다운로드를 시도하고 있습니다.
* - NETWORK_NO_SOURCE(숫자 값 3)
* 요소의 자원 선택 알고리즘이 활성화되어 있지만
* 아직 사용할 리소스를 찾지 못했습니다.
*
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
* @return {숫자} 현재 네트워크 활동 상태
* @method Player#networkState
*/
'네트워크 상태',
/**
* 요소의 현재 상태를 나타내는 값을 반환합니다.
* 현재 재생 위치를 렌더링하는 것과 관련하여
* 아래 목록의 코드.
* - HAVE_NOTHING(숫자 값 0)
* 미디어 리소스에 대한 정보가 없습니다.
* - HAVE_METADATA(숫자 값 1)
* 해당 기간만큼 자원을 충분히 확보했습니다.
* 리소스를 사용할 수 있습니다.
* - HAVE_CURRENT_DATA(숫자 값 2)
* 바로 현재 재생 위치에 대한 데이터를 사용할 수 있습니다.
* - HAVE_FUTURE_DATA(숫자 값 3)
* 바로 현재 재생 위치에 대한 데이터는 다음과 같이 사용할 수 있습니다.
* 사용자 에이전트가 현재 작업을 진행하기에 충분한 데이터
* 재생 방향의 재생 위치.
* - HAVE_ENOUGH_DATA(숫자 값 4)
* 사용자 에이전트는 충분한 데이터를 사용할 수 있다고 추정합니다.
* 재생이 중단 없이 진행됩니다.
*
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
* @return {숫자} 현재 재생 렌더링 상태
* @method Player#readyState
*/
'준비 상태'
].forEach(함수(fn) {
Player.prototype[fn] = 함수() {
return this.techGet_(fn);
};
});
TECH_EVENTS_RETRIGGER.forEach(기능(이벤트) {
Player.prototype[`handleTech${toTitleCase(event)}_`] = function() {
return this.trigger(event);
};
});
/**
* 플레이어에 초기 지속 시간 및 차원 정보가 있을 때 실행됩니다.
*
* @event Player#loadedmetadata
* @type {이벤트대상~이벤트}
*/
/**
* 플레이어가 현재 재생 위치에서 데이터를 다운로드했을 때 발생
*
* @event 플레이어#loadeddata
* @type {이벤트대상~이벤트}
*/
/**
* 현재 재생 위치가 변경되었을 때 발생 *
* 재생 중에는 15-250밀리초마다 실행됩니다.
* 사용 중인 재생 기술.
*
* @event 플레이어#timeupdate
* @type {이벤트대상~이벤트}
*/
/**
* 볼륨이 변경될 때 발생
*
* @event 플레이어#volumechange
* @type {이벤트대상~이벤트}
*/
/**
* 플레이어가 플러그인을 사용할 수 있는지 여부를 보고합니다.
*
* 이것은 플러그인이 초기화되었는지 여부를 보고하지 않습니다.
* 이 플레이어에서. 이를 위해 [usingPlugin]{@link Player#usingPlugin}.
*
* @method Player#hasPlugin
* @param {문자열} 이름
* 플러그인의 이름입니다.
*
* @return {부울}
* 이 플레이어가 요청한 플러그인을 사용할 수 있는지 여부.
*/
/**
* 플레이어가 이름으로 플러그인을 사용하는지 여부를 보고합니다.
*
* 기본 플러그인의 경우 플러그인이 _ever_ 있었는지 여부만 보고합니다.
* 이 플레이어에서 초기화되었습니다.
*
* @method Player#usingPlugin
* @param {문자열} 이름
* 플러그인의 이름입니다.
*
* @return {부울}
* 이 플레이어가 요청된 플러그인을 사용하고 있는지 여부.
*/
Component.registerComponent('플레이어', 플레이어);
기본 플레이어 내보내기;