/**
* @file player.js
*/
// 子類組件
從 './component.js' 導入組件;
從'../../package.json'導入{version};
從“全局/文檔”導入文檔;
從“全局/窗口”導入窗口;
從'./mixins/evented'導入事件;
從 './mixins/evented' 導入 {isEvented, addEventedCallback};
import * as Events from './utils/events.js';
import * as Dom from './utils/dom.js';
從 './utils/fn.js' 導入 * 作為 Fn;
從 './utils/guid.js' 導入 * 作為 Guid;
import * as browser from './utils/browser.js';
從 './utils/browser.js' 導入 {IE_VERSION, IS_CHROME, IS_WINDOWS};
從'./utils/log.js'導入日誌,{createLogger};
從 './utils/string-cases.js' 導入 {toTitleCase, titleCaseEquals};
從'./utils/time-ranges.js'導入{createTimeRange};
從'./utils/buffer.js'導入{bufferedPercent};
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 as TRACK_TYPES};
從 './utils/filter-source' 導入 filterSource;
從 './utils/mimetypes' 導入 {getMimetype, findMimetype};
從 './utils/hooks' 導入 {hooks};
從 './utils/obj' 導入 {isObject};
從“鍵碼”導入鍵碼;
// 下面的imports只是用來保證對應的模塊
// 始終包含在 video.js 包中。導入模塊將
// 執行它們,它們將自己註冊到 video.js。
導入'./tech/loader.js';
導入“./poster-image.js”;
導入“./tracks/text-track-display.js”;
導入'./loading-spinner.js';
導入'./big-play-button.js';
導入'./close-button.js';
導入'./control-bar/control-bar.js';
導入'./error-display.js';
導入“./tracks/text-track-settings.js”;
導入'./resize-manager.js';
導入'./live-tracker.js';
// 引入 Html5 技術,至少是為了處理原始視頻標籤。
導入'./tech/html5.js';
// 以下技術事件只是重新觸發
// 當它們發生時在播放器上
const TECH_EVENTS_RETRIGGER = [
/**
* 在用戶代理下載媒體數據時觸發。
*
* @event Player#progress
* @type {EventTarget~Event}
*/
/**
* 重新觸發由 {@link Tech} 觸發的 `progress` 事件。
*
* @私人的
* @method Player#handleTechProgress_
* @fires Player#progress
* @listens Tech#progress
*/
'進步',
/**
* 當音頻/視頻加載中止時觸發。
*
* @event Player#中止
* @type {EventTarget~Event}
*/
/**
* 重新觸發由 {@link Tech} 觸發的 `abort` 事件。
*
* @私人的
* @method Player#handleTechAbort_
* @fires Player#中止
* @listens Tech#abort
*/
'中止',
/**
* 當瀏覽器有意不獲取媒體數據時觸發。
*
* @event Player#暫停
* @type {EventTarget~Event}
*/
/**
* 重新觸發由 {@link Tech} 觸發的 `suspend` 事件。
*
* @私人的
* @method Player#handleTechSuspend_
* @fires Player#暫停
* @listens 技術#suspend
*/
'暫停',
/**
* 當前播放列表為空時觸發。
*
* @event Player#emptied
* @type {EventTarget~Event}
*/
/**
* 重新觸發由 {@link Tech} 觸發的 `emptied` 事件。
*
* @私人的
* @method Player#handleTechEmptied_
* @fires Player#emptied
* @listens Tech#emptied
*/
'清空',
/**
* 當瀏覽器試圖獲取媒體數據但數據不可用時觸發。
*
* @event Player#stalled
* @type {EventTarget~Event}
*/
/**
* 重新觸發由 {@link Tech} 觸發的 `stalled` 事件。
*
* @私人的
* @method Player#handleTechStalled_
* @fires Player#stalled
* @listens Tech#stalled
*/
'停滯不前',
/**
* 當瀏覽器加載了音頻/視頻的元數據時觸發。
*
* @event Player#loadedmetadata
* @type {EventTarget~Event}
*/
/**
* 重新觸發由 {@link Tech} 觸發的 `loadedmetadata` 事件。
*
* @私人的
* @method Player#handleTechLoadedmetadata_
* @fires Player#loadedmetadata
* @listens Tech#loadedmetadata
*/
'加載元數據',
/**
* 當瀏覽器加載了音頻/視頻的當前幀時觸發。
*
* @event Player#loadeddata
* @type {事件}
*/
/**
* 重新觸發由 {@link Tech} 觸發的 `loadeddata` 事件。
*
* @私人的
* @method Player#handleTechLoaddeddata_
* @fires Player#loadeddata
* @listens Tech#loadeddata
*/
'加載數據',
/**
* 當前播放位置改變時觸發。
*
* @event Player#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#調整大小
* @listens Tech#resize
*/
'調整大小',
/**
* 音量改變時觸發
*
* @event Player#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
*/
'texttrackchange'
];
// 當播放速率為零時要排隊的事件
// 這是一個哈希,其唯一目的是映射非駝峰事件名稱
// 駝峰式函數名
const TECH_EVENTS_QUEUE = {
可以玩:'可以玩',
可以玩:'可以播放',
播放:'玩',
尋求:'尋求'
};
const BREAKPOINT_ORDER = [
'微小的',
'xsmall',
'小的',
'中等的',
'大的',
'超大',
'巨大的'
];
const BREAKPOINT_CLASSES = {};
// grep: vjs-layout-tiny
// grep: vjs-layout-x-small
// grep: vjs-layout-small
// grep: vjs-layout-medium
// grep: vjs-layout-large
// grep: vjs-layout-x-large
// grep: vjs-layout-huge
BREAKPOINT_ORDER.forEach(k => {
常量 v = K 字符(0)=== 'X'?`x $ {K. 子字符串(1)} `:k;
BREAKPOINT_CLASSES[k] = `vjs-layout-${v}`;
});
const DEFAULT_BREAKPOINTS = {
微小的:210,
x小:320,
小的:425、
中等的:768,
大的:1440,
超大:2560,
巨大的:無窮
};
/**
* 當任何 Video.js 設置方法時,都會創建 `Player` 類的實例
* 用於初始化視頻。
*
* 創建實例後,可以通過兩種方式全局訪問它:
* 1。通過調用 '視頻 JS(' 例如視頻 1 '); `
* 2。通過使用它直接通過視頻播放器示例 _ 視頻 1; `
*
* @extends 組件
*/
類播放器擴展組件{
/**
* 創建此類的一個實例。
*
* @param {元素} 標籤
* 用於配置選項的原始視頻 DOM 元素。
*
* @param {對象} [選項]
* 選項名稱和值的對象。
*
* @param {Component~ReadyCallback} [就緒]
* 準備回調函數。
*/
構造函數(標籤,選項,準備就緒){
// 確保標籤 ID 存在
tag.id = tag.id ||選項.id || `vjs_video_${Guid.newGUID()}`;
// 設置選項
// 選項參數覆蓋視頻標籤中設置的選項
// 覆蓋全局設置的選項。
// 後半部分與加載順序一致
//(標籤必須存在於播放器之前)
options = assign(Player.getTagSettings(tag), options);
// 延遲孩子的初始化,因為我們需要設置
// 播放器屬性優先,不能在 `super()` 之前使用 `this`
options.initChildren = false;
// 與創建元素相同
options.createEl = false;
// 不要自動混合事件混合
options.evented = false;
// 我們不希望播放器報告自己的觸摸活動
// 查看組件中的 enableTouchActivity
options.reportTouchActivity = false;
// 如果未設置語言,則獲取最接近的 lang 屬性
如果(!options.language){
如果(typeof tag.closest ==='函數'){
const closest = tag.closest('[lang]');
如果(最接近的 && closest.getAttribute){
options.language = closest.getAttribute('lang');
}
}其他{
讓元素=標籤;
while (element && element.nodeType === 1) {
如果 (Dom.getAttributes(element).hasOwnProperty('lang')) {
options.language = element.getAttribute('lang');
休息;
}
element = 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);
// 默認 isFullscreen_ 為 false
this.isFullscreen_ = false;
// 創建記錄器
this.log = createLogger(this.id_);
// 保留我們自己對全屏 api 的引用,以便可以在測試中對其進行模擬
this.fsApi_ = FullscreenApi;
// 跟踪技術何時更改海報
this.isPosterFromTech_ = false;
// 保存播放速率為零時排隊的回調信息
// 並且正在尋找
this.queuedCallbacks_ = [];
// 關閉 API 訪問,因為我們正在加載一項可能異步加載的新技術
this.isReady_ = false;
// 初始化狀態 hasStarted_
this.hasStarted_ = false;
// 初始化狀態 userActive_
this.userActive_ = false;
// 初始化 debugEnabled_
this.debugEnabled_ = false;
// 初始化狀態audioOnlyMode_
this.audioOnlyMode_ = false;
// 初始化狀態audioPosterMode_
this.audioPosterMode_ = false;
// 初始化狀態audioOnlyCache_
this.audioOnlyCache_ = {
播放器高度:空,
隱藏的孩子:[]
};
// 如果全局選項對像被不小心吹走了
// 某人,由於信息性錯誤而提早退出
如果(!this.options_ ||
!這個。選項 _ 技術訂單 ||
!這個選項 _. 技術訂單. 長度) {
throw new Error('沒有指定 techOrder。你覆蓋了'+
'videojs.options 而不是僅僅改變 ' +
'你想覆蓋的屬性?');
}
// 存儲用於設置選項的原始標籤
this.tag = 標籤;
// 存儲用於恢復html5元素的標籤屬性
this.tagAttributes = tag && Dom.getAttributes(tag);
// 更新當前語言
this.language(this.options_.language);
// 更新支持的語言
如果(選項。語言){
// 將播放器選項語言規範化為小寫
const languagesToLower = {};
Object.getOwnPropertyNames(options.languages).forEach(函數(名稱){
languagesToLower[name.toLowerCase()] = options.languages[name];
});
this.languages_ = languagesToLower;
}其他{
this.languages_ = Player.prototype.options_.languages;
}
這個.resetCache_();
// 設置海報
this.poster_ = options.poster || '';
// 設置控件
這個。控制 _ =!!選項。控件;
// 選項中存儲的原始標籤設置
// 現在立即刪除,這樣本機控件就不會閃爍。
// 如果 nativeControlsForTouch 為真,HTML5 技術可能會重新啟用
tag.controls = false;
tag.removeAttribute('控件');
this.changingSrc_ = false;
this.playCallbacks_ = [];
this.playTerminatedQueue_ = [];
// 屬性覆蓋選項
如果(tag.hasAttribute('自動播放')){
這個。自動播放(真);
}其他{
// 否則使用 setter 來驗證和
// 設置正確的值。
this.autoplay(this.options_.autoplay);
}
// 檢查插件
如果(選項。插件){
Object.keys(options.plugins).forEach((name) => {
如果(鍵入此 [名稱]!== '功能') {
throw new Error(`插件“${name}”不存在`);
}
});
}
/*
* 存儲擦洗的內部狀態
*
* @私人的
* @return {Boolean} 如果用戶正在擦洗則為真
*/
this.scrubbing_ = false;
this.el_ = this.createEl();
// 將其設為事件對象並使用 `el_` 作為其事件總線。
事件(這,{eventBusKey:'el_'});
// 監聽 document 和 player fullscreenchange 處理程序以便我們接收這些事件
// 在用戶可以接收它們之前,我們可以適當地更新 isFullscreen。
// 確保我們先監聽 fullscreenchange 事件,以確保
// 我們的 isFullscreen 方法針對內部組件和外部組件進行了正確更新。
如果(this.fsApi_.requestFullscreen){
Events.on(文檔, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
this.on(this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
}
如果(this.fluid_){
this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
}
// 我們還想將原始播放器選項傳遞給每個組件和插件
// 也這樣他們以後就不需要再回到播放器中獲取選項了。
// 我們還需要復制 this.options_ 所以我們不會以
// 無限循環。
const playerOptionsCopy = mergeOptions(this.options_);
//加載插件
如果(選項。插件){
Object.keys(options.plugins).forEach((name) => {
這個[名稱](選項。插件[名稱]);
});
}
// 啟用調試模式以觸發所有插件的 debugon 事件。
如果(選項。調試){
這個。調試(真);
}
this.options_.playerOptions = playerOptionsCopy;
this.middleware_ = [];
this.playbackRates(options.playbackRates);
這個.initChildren();
// 根據是否使用音頻標籤設置 isAudio
this.isAudio(tag.nodeName.toLowerCase() === '音頻');
// 更新控件類名。最初控制時無法執行此操作
// 設置是因為該元素尚不存在。
如果(this.controls()){
this.addClass('vjs-controls-enabled');
}其他{
this.addClass('vjs-controls-disabled');
}
// 根據播放器類型設置 ARIA 標籤和區域角色
this.el_.setAttribute('角色', '區域');
如果(this.isAudio()){
this.el_.setAttribute('aria-label', this.localize('Audio Player'));
}其他{
this.el_.setAttribute('aria-label', this.localize('Video Player'));
}
如果(this.isAudio()){
this.addClass('vjs-audio');
}
如果(this.flexNotSupported_()){
this.addClass('vjs-no-flex');
}
// 去做:讓這更聰明。在觸摸/鼠標之間切換用戶狀態
// 使用事件,因為設備可以同時具有觸摸和鼠標事件。
// 去做:當窗口在監視器之間切換時再次執行此檢查
//(參見 https://github.com/videojs/video.js/issues/5683)
如果(瀏覽器.TOUCH_ENABLED){
this.addClass('vjs-touch-enabled');
}
// iOS Safari 中斷了懸停處理
如果(!瀏覽器。IS_IOS){
this.addClass('vjs-workinghover');
}
// 讓玩家可以通過 ID 輕鬆找到
Player.players[this.id_] = this;
// 添加一個主要版本類來幫助插件中的 css
const majorVersion = version.split('.')[0];
this.addClass(`vjs-v${majorVersion}`);
// player第一次初始化時,觸發activity so組件
// 如果需要,就像控制欄顯示自己一樣
這個。userActive(真);
這個.reportUserActivity();
this.one('play', (e) => this.listenForUserActivity_(e));
this.on('stageclick', (e) => this.handleStageClick_(e));
this.on('keydown', (e) => this.handleKeyDown(e));
this.on('languagechange', (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 Player#dispose
* @type {EventTarget~Event}
*/
this.trigger('處置');
// 防止 dispose 被調用兩次
this.off('處置');
// 確保所有播放器特定的文檔偵聽器都是未綁定的。這是
Events.off(文檔,this.fsApi_.fullscreenchange,this.boundDocumentFullscreenChange_);
Events.off(文檔, 'keydown', this.boundFullWindowOnEscKey_);
如果(this.styleEl_ && this.styleEl_.parentNode){
this.styleEl_.parentNode.removeChild(this.styleEl_);
this.styleEl_ = null;
}
// 殺死對這個玩家的引用
Player.players[this.id_] = null;
如果(this.tag && this.tag.player){
this.tag.player = null;
}
如果(this.el_ && this.el_.player){
this.el_.player = null;
}
如果(this.tech_){
這個.tech_.dispose();
this.isPosterFromTech_ = false;
this.poster_ = '';
}
如果(this.playerElIngest_){
this.playerElIngest_ = null;
}
如果(這個。標籤){
this.tag = null;
}
middleware.clearCacheForPlayer(this);
// 刪除軌道列表的所有事件處理程序
// 所有軌道和軌道偵聽器都被移除
// 技術處置
TRACK_TYPES.names.forEach((名稱) => {
const props = TRACK_TYPES[名稱];
const list = this[props.getterName]();
// 如果它不是原生列表
// 我們必須手動移除事件監聽器
如果(列表&& list.off){
列表.off();
}
});
//實際的 .el_ 在這裡被刪除,或者如果更換
super.dispose({restoreEl: this.options_.restoreEl});
}
/**
* 創建 `Player` 的 DOM 元素。
*
* @return {元素}
* 創建的 DOM 元素。
*/
創建El() {
讓 tag = this.tag;
讓埃爾;
讓 playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
const divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
如果(playerElIngest){
el = this.el_ = tag.parentNode;
} 否則如果(!divEmbed){
el = this.el_ = super.createEl('div');
}
// 複製標籤的所有屬性,包括 ID 和類
// ID 現在將引用播放器框,而不是視頻標籤
const attrs = Dom.getAttributes(標籤);
如果(divEmbed){
el = this.el_ = 標籤;
tag = this.tag = document.createElement('video');
而(el.children.length){
tag.appendChild(el.firstChild);
}
如果(!DOM.Hasclass (埃爾, '視頻 JS')) {
Dom.addClass(el, 'video-js');
}
el.appendChild(標籤);
playerElIngest = this.playerElIngest_ = el;
// 將屬性從我們自定義的 `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 的行為
如果 (IE_VERSION || (IS_CHROME && IS_WINDOWS)) {
tag.setAttribute('角色', '應用程序');
attrs.role = '應用程序';
}
// 從標籤中移除寬度/高度屬性,這樣 CSS 就可以使它成為 100% 寬度/高度
tag.removeAttribute('寬度');
tag.removeAttribute('高度');
如果(屬性中的“寬度”){
刪除 attrs.width;
}
如果(屬性中的“高度”){
刪除 attrs.height;
}
Object.getOwnPropertyNames(屬性).forEach(函數(屬性){
// 當我們在 div 嵌入中時,不要將 class 屬性複製到播放器元素
// 該類已經在 divEmbed 案例中正確設置
// 我們要確保 `video-js` 類不會丟失
如果(!(潛入 && 屬性 === '類')) {
埃爾。設置屬性(屬性,屬性 [ATTR]);
}
如果(divEmbed){
tag.setAttribute(attr, attr[attr]);
}
});
// 更新標籤 id/class 以用作 HTML5 播放技術
//可能認為我們應該在嵌入容器之後這樣做,以便 .vjs-tech 類
//不會閃爍 100% 的寬度/高度,但類只適用於 .視頻/js 父
tag.playerId = tag.id;
tag.id += '_html5_api';
tag.className = 'vjs-tech';
// 讓玩家在元素上可被找到
tag.player = el.player = this;
// 視頻的默認狀態是暫停
this.addClass('vjs-paused');
// 在我們將用於設置寬度/高度的播放器中添加一個樣式元素
// 播放器的方式仍然可以被 CSS 覆蓋,就像
// 視頻元素
如果(窗口。視頻 _ 動態風格!真){
this.styleEl_ = stylesheet.createStyleElement('vjs-styles-dimensions');
const defaultsStyleEl = Dom.$('.vjs-styles-defaults');
const head = Dom.$('head');
頭。插入之前(這個。樣式 _,默認樣式?默認樣式。下一個兄弟姐妹:頭。第一個孩子);
}
this.fill_ = false;
this.fluid_ = false;
// 傳入 width/height/aspectRatio 選項,這將更新樣式 el
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 links = tag.getElementsByTagName('a');
對於(讓我 = 0; 我 < 鏈接. 長度; 我 ++){
const linkEl = links.item(i);
Dom.addClass(linkEl, 'vjs-hidden');
linkEl.setAttribute('隱藏', '隱藏');
}
// insertElFirst 似乎導致 networkState 從 3 閃爍到 2,所以
// 跟踪原始文件以備後用,以便我們知道源文件最初是否失敗
tag.initNetworkState_ = tag.networkState;
// 在 div (el/box) 容器中包裝視頻標籤
如果(標記. 父節點 &&!最重要的) {
tag.parentNode.insertBefore(el, tag);
}
// 插入標籤作為播放器元素的第一個子元素
// 然後手動將它添加到 children 數組中,這樣 this.addChild
// 將對其他組件正常工作
//
// 破壞 iPhone,已在 HTML5 設置中修復。
Dom.prependTo(tag, el);
this.children_.unshift(標籤);
//在播放器上設置郎屬性,以確保 CSS:Lang()與玩家保持一致
// 如果它被設置為與文檔不同的東西
this.el_.setAttribute('lang', this.language_);
this.el_.setAttribute('翻譯', '否');
this.el_ = el;
返回 el;
}
/**
* 獲取或設置 `Player` 的 crossOrigin 選項。對於 HTML5 播放器,這
* 在 `<video>` 標籤上設置 `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 值。
* - 設置時未定義
*/
交叉來源(價值){
如果(!值){
返回 this.techGet_('crossOrigin');
}
如果(值!== '匿名' && 值!== '使用憑據') {
log.warn(`crossOrigin 必須是“匿名”或“使用憑據”,給定“${value}”`);
返回;
}
this.techCall_('setCrossOrigin', value);
返回;
}
/**
* `Player` 寬度的 getter/setter。返回玩家的配置值。
* 要獲取當前寬度,請使用 `currentWidth()`。
*
* @param {number} [值]
* 將 `Player` 的寬度設置為的值。
*
* @return {數字}
* 獲取時`Player`的當前寬度。
*/
寬度(值){
返回 this.dimension('width', value);
}
/**
* `Player` 高度的 getter/setter。返回玩家的配置值。
* 要獲取當前高度,請使用 currentheight() 。
*
* @param {number} [值]
* 將 `Player` 的高度設置為的值。
*
* @return {數字}
* 獲取時`Player`的當前高度。
*/
高度(值){
返回 this.dimension('height', value);
}
/**
* `Player` 的寬度和高度的 getter/setter。
*
* @param {string} 維度
* 這個字符串可以是:
* - '寬度'
* - '高度'
*
* @param {number} [值]
* 第一個參數中指定的維度值。
*
* @return {數字}
* 獲取(寬度/高度)時的尺寸參數值。
*/
維度(維度,價值){
const privDimension = 維度 + '_';
如果(值===未定義){
返回這個[privDimension] || 0;
}
如果(值===''||值==='自動'){
// 如果給出空字符串,則將維度重置為自動
這個 [privDimension] = 未定義;
this.updateStyleEl_();
返回;
}
const parsedVal = parseFloat(值);
如果 (isNaN(parsedVal)) {
log.error(`為 ${dimension} 提供的不正確值“${value}”`);
返回;
}
這個 [privDimension] = parsedVal;
this.updateStyleEl_();
}
/**
* `Player` 上 vjs-fluid `className` 的 getter/setter/toggler。
*
* 打開此項將關閉填充模式。
*
* @param {布爾} [布爾]
* - 值為 true 添加類。
* - 值為 false 會刪除該類。
* - 沒有任何價值會成為吸氣劑。
*
* @return {布爾值|未定義}
* - 獲得時的流體值。
* - 設置時為`undefined`。
*/
流體(布爾){
如果(布爾===未定義){
返回!!這個. 流體 _;
}
這個. 流體 _ =!!布爾;
如果(isEvented(這個)){
this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_);
}
如果(布爾){
this.addClass('vjs-fluid');
這個.填充(假);
添加事件回調(這個,()=> {
this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
});
}其他{
this.removeClass('vjs-fluid');
}
this.updateStyleEl_();
}
/**
* `Player` 上 vjs-fill `className` 的 getter/setter/toggler。
*
* 打開此功能將關閉流體模式。
*
* @param {布爾} [布爾]
* - 值為 true 添加類。
* - 值為 false 會刪除該類。
* - 沒有任何價值會成為吸氣劑。
*
* @return {布爾值|未定義}
* - 獲得時的流體值。
* - 設置時為`undefined`。
*/
填充(布爾){
如果(布爾===未定義){
返回!!這個。填充 _;
}
這個填充 _ =!!布爾;
如果(布爾){
this.addClass('vjs-fill');
這個。流體(假);
}其他{
this.removeClass('vjs-fill');
}
}
/**
* 獲取/設置縱橫比
*
* @param {string} [比率]
*播放器的縱橫比
*
* @return {字符串|未定義}
* 獲取時返回當前縱橫比
*/
/**
* `Player` 縱橫比的 getter/setter。
*
* @param {string} [比率]
* 將 `Player` 的縱橫比設置為的值。
*
* @return {字符串|未定義}
* - 獲取時 `Player` 的當前縱橫比。
* - 設置時未定義
*/
aspectRatio(比率){
如果(比率===未定義){
返回this.aspectRatio_;
}
// 檢查寬度:高度格式
如果(!(/^\ d+\:\ D+$/). 測試 (比率)) {
throw new Error('為寬高比提供的值不正確。格式應該是寬度:高度,例如 16:9。');
}
this.aspectRatio_ = 比率;
// 我們假設如果您設置縱橫比,您需要流體模式,
// 因為在固定模式下你可以自己計算寬度和高度。
這個。流體(真);
this.updateStyleEl_();
}
/**
* 更新 `Player` 元素的樣式(高度、寬度和縱橫比)。
*
* @私人的
* @listens Tech#loadedmetadata
*/
updateStyleEl_() {
如果(window.VIDEOJS_NO_DYNAMIC_STYLE === true){
常量寬度 = 這個。寬度 _ === '數字' 的類型?這個寬度 _:這個。選項 _ 寬度;
常量高度 = 此類型。高度 _ === '數字'?這個。高度 _:這個。選項 _ 高度;
const techEl = this.tech_ && this.tech_.el();
如果(技術){
如果(寬度 >= 0){
techEl.width = 寬度;
}
如果(高度 >= 0){
techEl.height = 高度;
}
}
返回;
}
讓寬度;
讓身高;
讓縱橫比;
讓 idClass;
// 縱橫比要么直接使用,要么計算寬高。
如果(這個。方面的 _!== 未定義 && 這個。方面的 _!== '自動'){
// 使用任何專門設置的 aspectRatio
aspectRatio = this.aspectRatio_;
} else if (this.videoWidth() > 0) {
// 否則嘗試從視頻元數據中獲取縱橫比
aspectRatio = this.videoWidth() + ':' + this.videoHeight();
}其他{
// 或者使用默認值。視頻元素的是 2:1,但 16:9 更常見。
相關交易 = '16:9';
}
// 獲取我們可以用來計算尺寸的小數比率
const ratioParts = aspectRatio.split(':');
const ratioMultiplier = ratioParts[1] / ratioParts[0];
如果(this.width_!==未定義){
// 使用專門設置的任何寬度
width = this.width_;
} 否則,如果(這個。高度 _!== 未定義){
// 或者,如果已設置高度,則根據縱橫比計算寬度
width = this.height_ / ratioMultiplier;
}其他{
// 或者使用視頻的元數據,或者使用視頻el默認的300
寬度 = this.videoWidth() || 300;
}
如果(this.height_!==未定義){
// 使用任何專門設置的高度
height = this.height_;
}其他{
// 否則根據比率和寬度計算高度
高度 = 寬度 * 比率乘數;
}
// 通過以字母字符開頭來確保 CSS 類有效
如果 ((/^[^a-zA-Z]/).test(this.id())) {
idClass = '維度-' + this.id();
}其他{
idClass = this.id() + '-尺寸';
}
// 確保樣式元素的正確類仍在播放器上
this.addClass(idClass);
stylesheet.setTextContent(this.styleEl_, `
.${idClass} {
寬度:${width}px;
高度:${height}px;
}
。$ {ID類}. VJ-流體:不 (. VJS-音頻僅模式) {
填充頂部:${ratioMultiplier * 100}%;
}
`);
}
/**
*加載/創建播放實例{@link Tech}包括元素
* 和 API 方法。然後將 Tech 元素附加到 Player 中作為子元素。
*
* @param {string} 技術名稱
* 播放技術名稱
*
* @param {string} 來源
* 視頻源
*
* @私人的
*/
loadTech_(技術名稱,來源){
// 暫停並移除當前播放技術
如果(this.tech_){
這個.unloadTech_();
}
const titleTechName = toTitleCase(techName);
const camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);
// 一旦我們使用另一種技術,就擺脫 HTML5 視頻標籤
如果(標題技術名稱!== 'HTML5' && 這個。標籤){
Tech.getTech('Html5').disposeMediaElement(this.tag);
this.tag.player = null;
this.tag = null;
}
this.techName_ = titleTechName;
// 關閉 API 訪問,因為我們正在加載一項可能異步加載的新技術
this.isReady_ = false;
讓 autoplay = this.autoplay();
// 如果自動播放是一個字符串(或帶有 normalizeAutoplay: true 的 `true`),我們將 false 傳遞給技術人員
// 因為播放器將在 `loadstart` 上處理自動播放
if (typeof this.autoplay() === 'string' || this.autoplay() === true && 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.language(),
'playerElIngest': this.playerElIngest_ ||錯誤的,
'vtt.js': this.options_['vtt.js'],
「罐頭儲罐器」:!!這個。選項 _。技術海報,
'enableSourceset': this.options_.enableSourceset,
'承諾':this.options_.Promise
};
TRACK_TYPES.names.forEach((名稱) => {
const props = TRACK_TYPES[名稱];
techOptions[props.getterName] = this[props.privateName];
});
分配(techOptions,this.options_[titleTechName]);
分配(techOptions,this.options_[camelTechName]);
分配(techOptions,this.options_[techName.toLowerCase()]);
如果(這個。標籤){
techOptions.tag = this.tag;
}
if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
techOptions.startTime = this.cache_.currentTime;
}
// 初始化技術實例
const TechClass = Tech.getTech(技術名稱);
如果(!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_, event, (e) => this[`handleTech${toTitleCase(event)}_`](e));
});
Object.keys(TECH_EVENTS_QUEUE).forEach((event) => {
this.on(this.tech_, event, (eventObj) => {
如果 (this.tech_.playbackRate() === 0 && this.tech_.seeking()) {
this.queuedCallbacks_.push({
回調:this[`handleTech${TECH_EVENTS_QUEUE[event]}_`].bind(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_, 'waiting', (e) => this.handleTechWaiting_(e));
this.on(this.tech_, 'ended', (e) => this.handleTechEnded_(e));
this.on(this.tech_, 'seeking', (e) => this.handleTechSeeking_(e));
this.on(this.tech_, 'play', (e) => this.handleTechPlay_(e));
this.on(this.tech_, 'firstplay', (e) => this.handleTechFirstPlay_(e));
this.on(this.tech_, 'pause', (e) => this.handleTechPause_(e));
this.on(this.tech_, 'durationchange', (e) => this.handleTechDurationChange_(e));
this.on(this.tech_, 'fullscreenchange', (e, data) => this.handleTechFullscreenChange_(e, data));
this.on(this.tech_, 'fullscreenerror', (e, err) => 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_, 'error', (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_('controls'));
如果(這個。控件()&&!這個。使用本地控制()){
this.addTechControlsListeners_();
}
// 如果 tech 元素不存在,則在 DOM 中添加它
// 如果使用 Html5,請確保不要插入原始視頻元素
如果(這個 .tech_el()。父節點!== 這個 .el () && (標題技術名稱!「HTML5」||!這個。標籤)){
Dom.prependTo(this.tech_.el(), this.el());
}
// 在加載第一個技術後去掉原始視頻標籤引用
如果(這個。標籤){
this.tag.player = null;
this.tag = null;
}
}
/**
* 卸載並處理當前播放 {@link Tech}。
*
* @私人的
*/
unloadTech_() {
// 保存當前的文本軌道,以便我們可以在下一個技術中重複使用相同的文本軌道
TRACK_TYPES.names.forEach((名稱) => {
const props = TRACK_TYPES[名稱];
this[props.privateName] = this[props.getterName]();
});
this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
this.isReady_ = false;
這個.tech_.dispose();
this.tech_ = false;
如果(this.isPosterFromTech_){
this.poster_ = '';
this.trigger('posterchange');
}
this.isPosterFromTech_ = false;
}
/**
* 返回對當前 {@link Tech} 的引用。
* 默認情況下,它會打印一條關於直接使用該技術的危險的警告
* 但是傳入的任何參數都會使警告靜音。
*
* @param {*} [安全]
* 傳入的任何內容都會使警告靜音
*
* @return {技術}
*技術
*/
技術(安全){
如果(安全===未定義){
log.warn('直接使用技術可能很危險。我希望你知道你在做什麼。\\n' +
'有關詳細信息,請參閱 https://github.com/videojs/video.js/issues/2617。\\n');
}
返回this.tech_;
}
/**
* 為播放元素設置點擊和触摸監聽器
*
* - 在台式機上:點擊視頻本身將切換播放
* - 在移動設備上:點擊視頻切換控件
* 這是通過在活動和之間切換用戶狀態來完成的
* 無效
* - 點擊可以表示用戶已激活或已變為非活動狀態
* 例如,快速點擊一部 iPhone 電影應該會顯示控件。其他
* 快速點擊應該再次隱藏它們(表示用戶處於非活動狀態
* 查看狀態)
* - 除此之外,我們仍然希望用戶在之後被視為不活躍
* 幾秒鐘的不活動。
*
* > 注意:iOS 交互的唯一部分我們無法通過此設置模仿
* 觸摸並按住算作活動的視頻元素,以便
* 保持控件顯示,但這應該不是問題。觸摸並按住
* 在任何控件上仍然會讓用戶保持活動狀態
*
* @私人的
*/
addTechControlsListeners_() {
// 確保移除所有之前的監聽器,以防我們被多次調用。
this.removeTechControlsListeners_();
this.on(this.tech_, 'click', 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_);
// tap 監聽器需要在 touchend 監聽器之後,因為 tap
// 偵聽器在設置 userActive(false) 時取消任何 reportedUserActivity
this.on(this.tech_, 'tap', this.boundHandleTechTap_);
}
/**
* 刪除用於點擊和點擊控件的偵聽器。這是需要的
* 切換到禁用的控件,點擊/觸摸應該什麼都不做。
*
* @私人的
*/
removeTechControlsListeners_() {
// 我們不想只使用 `this.off()` 因為可能還有其他需要
// 由擴展它的技術人員添加的偵聽器。
this.off(this.tech_, 'tap', 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_, 'click', this.boundHandleTechClick_);
this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
}
/**
* 玩家等待技術準備就緒
*
* @私人的
*/
handleTechReady_() {
this.triggerReady();
// 保持和之前一樣的音量
如果(this.cache_.volume){
this.techCall_('setVolume', this.cache_.volume);
}
// 查看技術人員在加載時是否找到更高分辨率的海報
this.handleTechPosterChange_();
// 如果可用,更新持續時間
this.handleTechDurationChange_();
}
/**
* 重新觸發由 {@link Tech} 觸發的 `loadstart` 事件。這個
* 如果它是第一個加載開始,函數也會觸發 {@link Player#firstplay}
* 對於視頻。
*
* @fires Player#loadstart
* @fires Player#firstplay
* @listens Tech#loadstart
* @私人的
*/
handleTechLoadStart_() {
// 去做:更新為使用“emptied”事件。參見#1277。
this.removeClass('vjs-ended');
this.removeClass('vjs-seeking');
// 重置錯誤狀態
這個錯誤(空);
// 更新時長
this.handleTechDurationChange_();
// 如果它已經在播放,我們現在想觸發一個 firstplay 事件。
// firstplay 事件同時依賴於 play 和 loadstart 事件
// 對於新源,這可以以任何順序發生
如果(!this.paused()){
/**
* 當用戶代理開始尋找媒體數據時觸發
*
* @event Player#loadstart
* @type {EventTarget~Event}
*/
this.trigger('loadstart');
this.trigger('firstplay');
}其他{
// 重置 hasStarted 狀態
這個.hasStarted(false);
this.trigger('loadstart');
}
// 自動播放發生在瀏覽器加載開始之後,
// 所以我們模仿這種行為
手動自動播放 _(這個自動播放()=== 真 && 這個。選項 _。正常自動播放?'播放':這個。自動播放());
}
/**
* 處理自動播放字符串值,而不是典型的布爾值
* 應該由技術處理的值。請注意,這不是
* 任何規範的一部分。有效值及其作用可以是
* 在 Player#autoplay() 的自動播放吸氣器上找到
*/
手動自動播放_(類型){
如果(!這種技術 _ || 類型的類型!== '字符串'){
返回;
}
// 保存原始的 muted() 值,將 muted 設置為 true,並嘗試播放 ()。
// 在 promise 被拒絕時,從保存的值中恢復靜音
const resolveMuted = () => {
const previouslyMuted = this.muted();
這個。靜音(真);
const restoreMuted = () => {
這個。靜音(以前靜音);
};
// 在播放終止時恢復靜音
this.playTerminatedQueue_.push(restoreMuted);
const mutedPromise = this.play();
如果(!isPromise(mutedPromise)){
返回;
}
返回 mutedPromise.catch(err => {
恢復靜音();
throw new Error(`Rejection at manualAutoplay.恢復靜音值。 ${錯了嗎?錯誤:''}`);
});
};
讓承諾;
// 如果靜音默認為 true
// 我們唯一能做的就是調用 play
如果(鍵入 === '任何' &&!這個。靜音()){
promise = this.play();
如果(isPromise(承諾)){
promise = promise.catch(resolveMuted);
}
} 否則,如果(類型 === '靜音' &&!這個。靜音()){
承諾 = resolveMuted();
}其他{
promise = this.play();
}
如果(!isPromise(承諾)){
返回;
}
返回 promise.then(() => {
this.trigger({type: 'autoplay-success', autoplay: type});
}).catch(() => {
this.trigger({type: 'autoplay-failure', autoplay: type});
});
}
/**
*更新內部源緩存,以便我們從中返回正確的源
* `src()`、`currentSource()` 和 `currentSources()`。
*
* > 注意:如果傳入的源存在,則不會更新 `currentSources`
* 在當前的 `currentSources` 緩存中。
*
*
* @param {Tech~SourceObject} srcObj
* 將我們的緩存更新為的字符串或對像源。
*/
updateSourceCaches_(srcObj = '') {
讓 src = srcObj;
讓類型 = '';
如果(類型的 src!== '字符串'){
src = srcObj.src;
type = srcObj.type;
}
// 確保所有緩存都設置為默認值
// 防止空檢查
this.cache_.source = this.cache_.source || {};
this.cache_.sources = this.cache_.sources || [];
// 嘗試獲取傳入的 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 = [];
對於(讓我 = 0; 我 < 來源。長度; 我 ++){
const sourceObj = Dom.getAttributes(sourceEls[i]);
sourceElSources.push(sourceObj);
如果(sourceObj.src && sourceObj.src === src){
matchingSourceEls.push(sourceObj.src);
}
}
// 如果我們有匹配的源 el 但沒有匹配的源
// 當前源緩存不是最新的
如果(匹配源代碼。長度 &&!匹配源. 長度) {
this.cache_.sources = sourceElSources;
// 如果我們沒有匹配的源或源 els 設置
// 源緩存到 `currentSource` 緩存
} 否則,如果(!匹配源. 長度) {
this.cache_.sources = [this.cache_.source];
}
// 更新技術 `src` 緩存
this.cache_.src = src;
}
/**
* *EXPERIMENTAL* 在 {@link Tech} 上設置或更改源時觸發
* 導致媒體元素重新加載。
*
* 它將為初始源和每個後續源觸發。
* 此事件是來自 Video.js 的自定義事件,由 {@link Tech} 觸發。
*
* 此事件的事件對象包含一個 `src` 屬性,該屬性將包含源
* 事件觸發時可用。這通常只有在 Video.js 時才有必要
* 在更改源時正在切換技術。
*
* 當在播放器(或媒體元素)上調用 `load` 時它也會被觸發
* 因為 {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|`load` 的規範
* 表示資源選擇算法需要中止並重新啟動。
* 在這種情況下,很可能會將 `src` 屬性設置為
* 空字符串 `""` 表示我們不知道源是什麼但是
*它正在改變。
*
* *此事件目前仍處於試驗階段,可能會在次要版本中發生變化。*
* __要使用它,請將 `enableSourceset` 選項傳遞給播放器.__
*
* @event Player#sourceset
* @type {EventTarget~Event}
* @prop {string} 源
* 觸發 `sourceset` 時可用的源 url。
* 如果我們不知道來源是什麼,它將是一個空字符串
* 但知道來源會改變。
*/
/**
* 重新觸發由 {@link Tech} 觸發的 `sourceset` 事件。
*
* @fires Player#sourceset
* @listens Tech#sourceset
* @私人的
*/
handleTechSourceset_(事件){
// 只在有源時更新源緩存
// 未使用播放器 api 更新
如果(!this.changingSrc_){
讓 updateSourceCaches = (src) => this.updateSourceCaches_(src);
const playerSrc = this.currentSource().src;
const eventSrc = event.src;
// 如果我們有一個不是 blob 的 playerSrc 和一個是 blob 的 tech src
如果(播放器 SRC &&!測試(玩家 SRC)&&(/^ BLOB:/)。測試(事件 RC)){
// 如果技術源和播放器源都更新了,我們假設
// 像@videojs/http-streaming 這樣的東西做了源集並跳過更新源緩存。
如果(!這個最後來源 _ ||(這是最後來源 _ 技術!== 事件 && 這個。最後來源 _. 播放器!== 播放器 SRC)) {
updateSourceCaches = () => {};
}
}
// 立即將源更新為初始源
// 在某些情況下,這將是空字符串
updateSourceCaches(eventSrc);
// 如果 `sourceset` `src` 是一個空字符串
// 等待 `loadstart` 將緩存更新為 `currentSrc`。
// 如果 sourceset 發生在 `loadstart` 之前,我們重置狀態
如果(!event.src){
this.tech_.any(['sourceset', 'loadstart'], (e) => {
// 如果源集發生在 `loadstart` 之前
// 與這個 `handleTechSourceset_` 無關
// 將再次調用,這將在那里處理。
如果(e.type === 'sourceset'){
返回;
}
const techSrc = this.techGet('currentSrc');
this.lastSource_.tech = techSrc;
this.updateSourceCaches_(techSrc);
});
}
}
this.lastSource_ = {player: this.currentSource().src, tech: event.src};
這個。觸發({
來源:事件.src,
類型:'源集'
});
}
/**
* 添加/刪除 vjs-has-started 類
*
* @fires Player#firstplay
*
* @param {boolean} 請求
* - 真:添加類
* - false:刪除類
*
* @return {布爾值}
* hasStarted_ 的布爾值
*/
已開始(請求){
如果(請求===未定義){
// 充當 getter,如果我們沒有更改請求
返回 this.hasStarted_;
}
如果(請求=== this.hasStarted_){
返回;
}
this.hasStarted_ = 請求;
如果(this.hasStarted_){
this.addClass('vjs-has-started');
this.trigger('firstplay');
}其他{
this.removeClass('vjs-has-started');
}
}
/**
* 媒體開始或恢復播放時觸發
*
* @see [規範]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
* @fires Player#play
* @listens Tech#play
* @私人的
*/
handleTechPlay_() {
this.removeClass('vjs-ended');
this.removeClass('vjs-paused');
this.addClass('vjs-playing');
// 當用戶點擊播放時隱藏海報
這個.hasStarted(真);
/**
* 每當 {@link Tech#play} 事件發生時觸發。表示
* 播放已開始或恢復。
*
* @event 播放器#play
* @type {EventTarget~Event}
*/
this.trigger('播放');
}
/**
* 重新觸發由 {@link Tech} 觸發的 `ratechange` 事件。
*
* 如果播放速率為零時有任何事件排隊,則觸發
* 現在的那些事件。
*
* @私人的
* @method Player#handleTechRateChange_
* @fires Player#ratechange
* @listens Tech#ratechange
*/
handleTechRateChange_() {
如果 (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
this.queuedCallbacks_.forEach((queued) => queued.callback(queued.event));
this.queuedCallbacks_ = [];
}
this.cache_.lastPlaybackRate = this.tech_.playbackRate();
/**
* 當音頻/視頻的播放速度改變時觸發
*
* @event Player#ratechange
* @type {事件}
*/
this.trigger('ratechange');
}
/**
* 重新觸發由 {@link Tech} 觸發的“等待”事件。
*
* @fires Player#waiting
* @listens Tech#waiting
* @私人的
*/
handleTechWaiting_() {
this.addClass('vjs-waiting');
/**
* DOM 元素上的 readyState 更改導致播放停止。
*
* @event Player#waiting
* @type {EventTarget~Event}
*/
this.trigger('等待');
// 瀏覽器可能會在等待事件後發出一個 timeupdate 事件。為了阻止
// 過早移除等待類,等待時間改變。
const timeWhenWaiting = this.currentTime();
const timeUpdateListener = () => {
如果(等待時間!== 這個。當前時間()){
this.removeClass('vjs-waiting');
this.off('timeupdate', timeUpdateListener);
}
};
this.on('timeupdate', timeUpdateListener);
}
/**
* 重新觸發由 {@link Tech} 觸發的 `canplay` 事件。
* > 注意:這在瀏覽器之間不一致。見#1351
*
* @fires Player#canplay
* @listens Tech#canplay
* @私人的
*/
handleTechCanPlay_() {
this.removeClass('vjs-waiting');
/**
* 媒體的 readyState 為 HAVE_FUTURE_DATA 或更高。
*
* @event Player#canplay
* @type {EventTarget~Event}
*/
this.trigger('canplay');
}
/**
* 重新觸發由 {@link Tech} 觸發的 `canplaythrough` 事件。
*
* @fires Player#canplaythrough
* @listens Tech#canplaythrough
* @私人的
*/
handleTechCanPlayThrough_() {
this.removeClass('vjs-waiting');
/**
* 媒體的 readyState 為 HAVE_ENOUGH_DATA 或更高。這意味著
* 無需緩衝即可播放整個媒體文件。
*
* @event Player#canplaythrough
* @type {EventTarget~Event}
*/
this.trigger('canplaythrough');
}
/**
* 重新觸發由 {@link Tech} 觸發的“播放”事件。
*
* @fires Player#playing
* @listens Tech#playing
* @私人的
*/
handleTechPlaying_() {
this.removeClass('vjs-waiting');
/**
* 媒體不再被阻止播放,並開始播放。
*
* @event Player#playing
* @type {EventTarget~Event}
*/
this.trigger('播放');
}
/**
* 重新觸發由 {@link Tech} 觸發的 `seeking` 事件。
*
* @fires Player#seeking
* @listens Tech#seeking
* @私人的
*/
handleTechSeeking_() {
this.addClass('vjs-seeking');
/**
* 每當玩家跳到新時間時觸發
*
* @event Player#seeking
* @type {EventTarget~Event}
*/
this.trigger('尋求');
}
/**
* 重新觸發由 {@link Tech} 觸發的 `seeked` 事件。
*
* @fires Player#seeked
* @listens Tech#seeked
* @私人的
*/
handleTechSeeked_() {
this.removeClass('vjs-seeking');
this.removeClass('vjs-ended');
/**
* 當玩家完成跳轉到新時間時觸發
*
* @event Player#seeked
* @type {EventTarget~Event}
*/
this.trigger('seeked');
}
/**
* 重新觸發由 {@link Tech} 觸發的“firstplay”事件。
*
* @fires Player#firstplay
* @listens Tech#firstplay
* @deprecated 自 6.0 起,firstplay 事件已棄用。
* 從 6.0 開始,將 `starttime` 選項傳遞給玩家和 firstplay 事件已被棄用。
* @私人的
*/
handleTechFirstPlay_() {
// 如果指定了第一個開始時間屬性
// 然後我們將以秒為單位從給定的偏移量開始
如果(this.options_.starttime){
log.warn('將 `starttime` 選項傳遞給播放器將在 6.0 中被棄用');
this.currentTime(this.options_.starttime);
}
this.addClass('vjs-has-started');
/**
* 第一次播放視頻時觸發。不是 HLS 規範的一部分,這是
* 可能還不是最好的實現,所以請謹慎使用。如果你沒有
* 阻止播放的原因,使用「我的圖層。一('播放'); `代替。
*
* @event Player#firstplay
* @deprecated 自 6.0 起,firstplay 事件已棄用。
* @type {EventTarget~Event}
*/
this.trigger('firstplay');
}
/**
* 重新觸發由 {@link Tech} 觸發的“暫停”事件。
*
* @fires Player#暫停
* @listens Tech#pause
* @私人的
*/
handleTechPause_() {
this.removeClass('vjs-playing');
this.addClass('vjs-paused');
/**
* 媒體暫停時觸發
*
* @event Player#暫停
* @type {EventTarget~Event}
*/
this.trigger('暫停');
}
/**
* 重新觸發由 {@link Tech} 觸發的 `ended` 事件。
*
* @fires Player#ended
* @listens Tech#ended
* @私人的
*/
handleTechEnded_() {
this.addClass('vjs-ended');
this.removeClass('vjs-waiting');
如果(this.options_.loop){
這個.currentTime(0);
這個.play();
} 否則,如果(!這個。暫停()){
這個。暫停();
}
/**
* 到達媒體資源末尾時觸發(currentTime == duration)
*
* @event Player#ended
* @type {EventTarget~Event}
*/
this.trigger('結束');
}
/**
* 當媒體資源的持續時間首次已知或更改時觸發
*
* @listens Tech#durationchange
* @私人的
*/
handleTechDurationChange_() {
this.duration(this.techGet_('duration'));
}
/**
* 處理媒體元素上的點擊以播放/暫停
*
* @param {EventTarget~Event} 事件
* 導致該函數觸發的事件
*
* @listens Tech#click
* @私人的
*/
handleTechClick_(事件){
// 當控件被禁用時,點擊不應切換播放,因為
// 點擊被認為是一個控件
如果(!this.controls_){
返回;
}
如果 (
this.options_ === undefined ||
this.options_.userActions === undefined ||
this.options_.userActions.click === undefined ||
此選項 _. 使用者動作。按一下!== 假
) {
如果 (
這個。選項 _!== 未定義的 &&
這個。選項 _. 用戶操作!== 未定義的 &&
typeof this.options_.userActions.click === '函數'
) {
this.options_.userActions.click.call(this, event);
} else if (this.paused()) {
silencePromise(this.play());
}其他{
這個。暫停();
}
}
}
/**
*處理雙擊媒體元素進入/退出全屏
*
* @param {EventTarget~Event} 事件
* 導致該函數觸發的事件
*
* @listens Tech#dblclick
* @私人的
*/
handleTechDoubleClick_(事件){
如果(!this.controls_){
返回;
}
// 我們不想切換全屏狀態
// 在控制欄或模式框內雙擊時
const inAllowedEls = Array.prototype.some.call(
這個 .$$('.vJS-控制欄,.vJS 模態對話 '),
el => el.contains(event.target)
);
如果(!inAllowedEls){
/*
* options.userActions.doubleClick
*
* 如果“undefined”或“true”,如果控件存在,雙擊切換全屏
* 設置為“false”以禁用雙擊處理
* 設置為替代外部雙擊處理程序的函數
*/
如果 (
this.options_ === undefined ||
this.options_.userActions === undefined ||
this.options_.userActions.doubleClick === undefined ||
這個。選項 _. 用戶操作。雙擊!== 假
) {
如果 (
這個。選項 _!== 未定義的 &&
這個。選項 _. 用戶操作!== 未定義的 &&
typeof this.options_.userActions.doubleClick === '函數'
) {
this.options_.userActions.doubleClick.call(this, event);
} else if (this.isFullscreen()) {
this.exitFullscreen();
}其他{
this.requestFullscreen();
}
}
}
}
/**
* 處理媒體元素上的點擊。它將切換用戶
*活動狀態,隱藏和顯示控件。
*
* @listens Tech#tap
* @私人的
*/
handleTechTap_() {
這個。用戶激活(!這個。用戶激活());
}
/**
*處理觸摸開始
*
* @listens Tech#touchstart
* @私人的
*/
handleTechTouchStart_() {
this.userWasActive = this.userActive();
}
/**
*手柄觸摸移動
*
* @listens Tech#touchmove
* @私人的
*/
handleTechTouchMove_() {
如果(this.userWasActive){
這個.reportUserActivity();
}
}
/**
* 處理觸摸結束
*
* @param {EventTarget~Event} 事件
* 觸發的touchend事件
* 這個函數
*
* @listens Tech#touchend
* @私人的
*/
handleTechTouchEnd_(事件){
// 阻止鼠標事件的發生
如果(事件。可取消){
事件.preventDefault();
}
}
/**
* SWF 上的原生點擊事件不會在 IE11、Win8.1RT 上觸發
* 改為使用從 SWF 內部觸發的 stageclick 事件
*
* @私人的
* @listens stageclick
*/
handleStageClick_() {
這個.reportUserActivity();
}
/**
* @私人的
*/
toggleFullscreenClass_() {
如果(this.isFullscreen()){
this.addClass('vjs-fullscreen');
}其他{
this.removeClass('vjs-fullscreen');
}
}
/**
* 當文檔 fschange 事件觸發時調用它
*/
documentFullscreenChange_(e) {
const targetPlayer = e.target.player;
// 如果另一個玩家是全屏的
// 對 targetPlayer 進行空檢查,因為舊的 firefox 會將文檔作為 e.target
如果(目標播放器 && 目標播放器!== 這個){
返回;
}
const el = this.el();
讓 isFs = 文檔 [this.fsApi_.fullscreenElement] === el;
如果(!isFs && el.matches){
isFs = el.matches(':' + this.fsApi_.fullscreen);
} 否則,如果(!ISFS & El.ms 匹配選擇器) {
isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen);
}
this.isFullscreen(isFs);
}
/**
*處理技術全屏變化
*
* @param {EventTarget~Event} 事件
* 觸發此功能的全屏更改事件
*
* @param {Object} 數據
* 隨事件發送的數據
*
* @私人的
* @listens Tech#fullscreenchange
* @fires Player#fullscreenchange
*/
handleTechFullscreenChange_(事件,數據){
如果(數據){
如果(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('fullscreenerror', err);
}
/**
* @私人的
*/
togglePictureInPictureClass_() {
如果 (this.isInPictureInPicture()) {
this.addClass('vjs-畫中畫');
}其他{
this.removeClass('vjs-畫中畫');
}
}
/**
* Handle Tech 輸入畫中畫。
*
* @param {EventTarget~Event} 事件
* 觸發該函數的enterpictureinpicture事件
*
* @私人的
* @listens Tech#enterpictureinpicture
*/
handleTechEnterPictureInPicture_(事件){
this.isInPictureInPicture(true);
}
/**
* 處理技術休假畫中畫。
*
* @param {EventTarget~Event} 事件
* 觸發該函數的畫中畫事件
*
* @私人的
* @listens Tech#leavepictureinpicture
*/
handleTechLeavePictureInPicture_(事件){
this.isInPictureInPicture(false);
}
/**
* 在加載音頻/視頻期間發生錯誤時觸發。
*
* @私人的
* @listens 技術#error
*/
handleTechError_() {
const error = this.tech_.error();
這個錯誤(錯誤);
}
/**
* 重新觸發由 {@link Tech} 觸發的 `textdata` 事件。
*
* @fires Player#textdata
* @listens Tech#textdata
* @私人的
*/
handleTechTextData_() {
讓數據=空;
如果(參數長度 > 1){
數據=參數[1];
}
/**
* 當我們從 tech 獲得文本數據事件時觸發
*
* @event Player#textdata
* @type {EventTarget~Event}
*/
this.trigger('文本數據', 數據);
}
/**
* 獲取緩存值的對象。
*
* @return {對象}
* 獲取當前對象緩存
*/
getCache() {
返回this.cache_;
}
/**
* 重置內部緩存對象。
*
* 在播放器構造函數或重置方法之外使用此函數可能
* 有意想不到的副作用。
*
* @私人的
*/
重置緩存_() {
this.cache_ = {
// 現在,currentTime 並沒有_真正_緩存,因為它總是
// 從技術中檢索(參見:currentTime)。但是,為了完整性,
// 我們在這裡將其設置為零以確保如果我們確實開始實際緩存
// 它,我們將它與其他所有內容一起重置。
當前時間:0,
初始化時間:0,
inactivityTimeout:this.options_.inactivityTimeout,
持續時間:南,
最後一卷:1、
lastPlaybackRate: this.defaultPlaybackRate(),
媒體:空,
來源:'',
來源: {},
資料來源:[],
回放率:[],
體積:1
};
}
/**
* 將值傳遞給播放技術
*
* @param {字符串} [方法]
* 調用的方法
*
* @param {對象} 參數
* 要傳遞的參數
*
* @私人的
*/
techCall_(方法,參數){
// 如果還沒有準備好,就在準備好時調用方法
這個。準備好(功能(){
如果(中間件中的方法。allowedSetters){
返回 middleware.set(this.middleware_, this.tech_, method, arg);
} else if (middleware.allowedMediators 中的方法) {
返回 middleware.mediate(this.middleware_, this.tech_, method, arg);
}
嘗試{
如果(this.tech_){
this.tech_[方法](arg);
}
} 抓住 (e) {
日誌(e);
扔 e;
}
}, 真的);
}
/**
* 接聽電話等不及技術人員,有時甚至不需要。
*
* @param {string} 方法
* 技術方法
*
* @return {函數|未定義}
* 方法或未定義
*
* @私人的
*/
techGet_(方法){
如果(!this.tech_ || !this.tech_.isReady_){
返回;
}
如果(中間件中的方法。allowedGetters){
返回 middleware.get(this.middleware_, this.tech_, method);
} else if (middleware.allowedMediators 中的方法) {
返回 middleware.mediate(this.middleware_, this.tech_, method);
}
// Flash 喜歡在您隱藏或重新定位時死掉並重新加載。
// 在這些情況下,對象方法消失,我們得到錯誤。
// 去做:這對 Flash 以外的技術人員來說是必需的嗎?
// 當這種情況發生時,我們將捕獲錯誤並通知技術人員它還沒有準備好。
嘗試{
返回 this.tech_[方法]();
} 抓住 (e) {
// 當構建額外的技術庫時,可能還沒有定義預期的方法
如果(this.tech_[方法] ===未定義){
log(`Video.js: ${method} 方法沒有為 ${this.techName_} 播放技術定義。`, e);
扔 e;
}
// 當方法在對像上不可用時,它會拋出 TypeError
如果(e.name === 'TypeError'){
log(`Video.js: ${method} 在 ${this.techName_} 播放技術元素上不可用。`, e);
this.tech_.isReady_ = false;
扔 e;
}
// 如果錯誤未知,只記錄並拋出
日誌(e);
扔 e;
}
}
/**
* 嘗試在第一時間開始播放。
*
* @return {承諾|未定義}
* 如果瀏覽器支持 Promise(或一個
* 作為選項傳入)。這個承諾將在
* 播放的返回值。如果這是未定義的,它將滿足
* promise 鏈 否則 promise 鏈將在
* 遊戲中的承諾已兌現。
*/
玩() {
const PromiseClass = this.options_.Promise || window.Promise;
如果(承諾類){
返回新的 PromiseClass((resolve) => {
this.play_(解決);
});
}
返回 this.play_();
}
/**
* 播放的實際邏輯,需要一個將在
* 播放的返回值。如果有的話,這使我們能夠解決遊戲承諾
* 是現代瀏覽器上的一個。
*
* @私人的
* @param {函數} [回調]
* 實際調用技術人員播放時應調用的回調
*/
play_(callback = silencePromise) {
this.playCallbacks_.push(回調);
常量是基本 = 布爾值 (!這個。更改 SRC_ &&(這個 .src()|| 這個 .CurrentsRC()));
// 對 play_ 的調用有點像 `one` 事件函數
如果(this.waitToPlay_){
this.off(['ready', 'loadstart'], this.waitToPlay_);
this.waitToPlay_ = null;
}
// 如果播放器/技術沒有準備好或者 src 本身沒有準備好
// 排隊調用以在 `ready` 或 `loadstart` 播放
如果(!這是。島上 _ ||!伊斯蘭迪) {
this.waitToPlay_ = (e) => {
這個.play_();
};
this.one(['ready', 'loadstart'], this.waitToPlay_);
// 如果我們在 Safari 中,loadstart 很有可能會在手勢時間段後觸發
// 在這種情況下,我們需要通過調用 load 來準備視頻元素,以便它能及時準備好
如果(!這是一個 && (瀏覽器. 是任何 Safari 瀏覽器 || 瀏覽器.
這個.load();
}
返回;
}
// 如果播放器/技術準備就緒並且我們有源,我們可以嘗試播放。
const val = this.techGet_('play');
// 如果返回值為 null,則播放終止
如果(val === null){
this.runPlayTerminatedQueue_();
}其他{
this.runPlayCallbacks_(val);
}
}
/**
* 這些功能將在播放終止時運行。如果玩
* runPlayCallbacks_ 運行這些函數將不會運行。這讓我們
* 區分終止播放和實際調用播放。
*/
runPlayTerminatedQueue_() {
const queue = this.playTerminatedQueue_.slice(0);
this.playTerminatedQueue_ = [];
queue.forEach(函數(q){
q();
});
}
/**
* 當回調播放延遲時,我們必須運行這些
* 在技術上實際調用播放時的回調。這個功能
* 運行延遲的回調並接受返回值
* 來自技術。
*
* @param {undefined|Promise} 值
* 技術的返回值。
*/
runPlayCallbacks_(val) {
const callbacks = this.playCallbacks_.slice(0);
this.playCallbacks_ = [];
// 清除播放終止隊列,因為我們完成了真正的播放
this.playTerminatedQueue_ = [];
回調.forEach(函數(cb){
cb(val);
});
}
/**
* 暫停視頻播放
*
* @return {玩家}
* 調用此函數的播放器對象的引用
*/
暫停() {
this.techCall_('暫停');
}
/**
* 檢查播放器是否暫停或尚未播放
*
* @return {布爾值}
* - false:如果媒體當前正在播放
* - 真:如果媒體當前沒有播放
*/
暫停(){
// paused 的初始狀態應該是 true(在 Safari 中它實際上是 false)
返回 (this.techGet_('paused') === false) ?假:真;
}
/**
* 獲取表示用戶當前時間範圍的 TimeRange 對象
* 有玩過。
*
* @return {TimeRange}
* 一個時間範圍對象,表示具有的所有時間增量
* 已播放。
*/
播放(){
返回 this.techGet_('played') ||創建時間範圍(0,0);
}
/**
* 返回用戶是否正在“清理”。擦洗是
* 當用戶點擊了進度條句柄並且是
* 沿著進度條拖動它。
*
* @param {boolean} [isScrubbing]
* 用戶是否正在擦洗
*
* @return {布爾值}
* 獲取時擦洗的值
*/
擦洗(isScrubbing){
如果(typeof isScrubbing === 'undefined'){
返回 this.scrubbing_;
}
這個。擦洗 _ =!!擦洗;
this.techCall_('setScrubbing', this.scrubbing_);
如果(isScrubbing){
this.addClass('vjs-scrubbing');
}其他{
this.removeClass('vjs-scrubbing');
}
}
/**
* 獲取或設置當前時間(以秒為單位)
*
* @param {number|string} [秒]
* 以秒為單位尋找的時間
*
* @return {數字}
* - 獲取時的當前時間(以秒為單位)
*/
當前時間(秒){
如果(類型秒!== '未定義') {
如果 (秒 < 0) {
秒 = 0;
}
如果(!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_){
this.cache_.initTime = seconds;
this.off('canplay', this.boundApplyInitTime_);
this.one('canplay', this.boundApplyInitTime_);
返回;
}
this.techCall_('setCurrentTime', seconds);
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 {number} [秒數]
* 以秒為單位設置的視頻時長
*
* @return {數字}
* - 獲取時視頻的持續時間(以秒為單位)
*/
持續時間(秒){
如果(秒 === 未定義){
// 如果持續時間未知,則返回 NaN
返回這個。緩存 _ 持續時間!== 未定義?這個. 緩存 _. 持續時間:南;
}
秒 = parseFloat(秒);
// 對 Infinity 進行標準化以發出視頻信號
如果 (秒 < 0) {
秒 = 無限;
}
如果(秒!== this.cache_.duration){
// 緩存優化清理的最後一個設置值(尤其是閃光)
// 去做:是否需要 Flash 以外的技術?
this.cache_.duration = 秒;
如果(秒===無限){
this.addClass('vjs-live');
}其他{
this.removeClass('vjs-live');
}
如果(!isNaN(秒)){
// 除非持續時間值已知,否則不要觸發 durationchange。
// @see [規範]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
/**
* @event Player#durationchange
* @type {EventTarget~Event}
*/
this.trigger('durationchange');
}
}
}
/**
* 計算視頻剩餘時間。不屬於
* 本機視頻 API。
*
* @return {數字}
* 剩餘時間以秒為單位
*/
剩餘時間() {
返回 this.duration() - this.currentTime();
}
/**
* 打算在以下情況下使用的剩餘時間功能
* 時間將直接顯示給用戶。
*
* @return {數字}
* 以秒為單位的剩餘時間
*/
剩餘時間顯示(){
返回 Math.floor(this.duration()) - Math.floor(this.currentTime());
}
//
// 有點像已下載視頻部分的數組。
/**
* 獲取包含視頻時間數組的 TimeRange 對象
* 已下載。如果你只想要百分比
* 已下載的視頻,使用 bufferedPercent。
*
* @see [緩衝規範]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
*
* @return {TimeRange}
* 模擬 TimeRange 對象(遵循 HTML 規範)
*/
緩衝的() {
讓 buffered = this.techGet_('buffered');
如果(!緩衝||!緩衝長度){
緩衝 = createTimeRange(0, 0);
}
返回緩衝;
}
/**
* 獲取已下載視頻的百分比(小數形式)。
* 此方法不是本機 HTML 視頻 API 的一部分。
*
* @return {數字}
* 0 到 1 之間的小數,表示百分比
* 緩衝的 0 為 0%,1 為 100%
*/
緩衝百分比(){
返回 bufferedPercent(this.buffered(), this.duration());
}
/**
* 獲取最後一次緩衝時間範圍的結束時間
* 這個是用在進度條裡面,封裝所有的時間範圍。
*
* @return {數字}
* 最後緩衝時間範圍的結束
*/
緩衝結束(){
const buffered = this.buffered();
const duration = this.duration();
let end = buffered.end(buffered.length - 1);
如果(結束>持續時間){
結束=持續時間;
}
返回端;
}
/**
* 獲取或設置媒體的當前音量
*
* @param {number} [percentAsDecimal]
* 以小數百分比表示的新體積:
* - 0 靜音/0%/關閉
* - 1.0 是 100%/滿
* - 0.5 是體積的一半或 50%
*
* @return {數字}
* 獲取時的當前音量百分比
*/
體積(percentAsDecimal){
讓卷;
如果(百分比十進制!== 未定義){
// 強制值在 0 和 1 之間
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
this.cache_.volume = vol;
this.techCall_('setVolume', vol);
如果(音量 > 0){
this.lastVolume_(vol);
}
返回;
}
// 返回當前音量時默認為 1。
vol = parseFloat(this.techGet_('volume'));
返回(isNaN(vol))?1:體積;
}
/**
* 獲取當前靜音狀態,或者開啟或關閉靜音
*
* @param {boolean} [靜音]
* - 靜音
* - false 取消靜音
*
* @return {布爾值}
* - 如果靜音開啟並獲得,則為真
* - 如果靜音關閉並獲取則為 false
*/
靜音(靜音){
如果(靜音!==未定義){
this.techCall_('setMuted', 靜音);
返回;
}
返回 this.techGet_('muted') ||錯誤的;
}
/**
* 獲取當前 defaultMuted 狀態,或打開或關閉 defaultMuted。默認靜音
* 表示初始播放時靜音狀態。
*
*```js
* var myPlayer = videojs('some-player-id');
*
* myPlayer.src("http://www.example.com/path/to/video.mp4");
*
* // 得到,應該是假的
* console.log(myPlayer.defaultMuted());
* // 設置為真
* myPlayer.defaultMuted(true);
* // 得到應該是真的
* console.log(myPlayer.defaultMuted());
*```
*
* @param {boolean} [默認靜音]
* - 靜音
* - false 取消靜音
*
* @return {布爾值|玩家}
* - 如果 defaultMuted 開啟並獲取則為真
* - 如果 defaultMuted 關閉並獲取則為 false
* - 設置時對當前播放器的引用
*/
默認靜音(默認靜音){
如果(默認靜音!==未定義){
返回 this.techCall_('setDefaultMuted', defaultMuted);
}
返回 this.techGet_('defaultMuted') ||錯誤的;
}
/**
* 獲取最後一個音量,或設置它
*
* @param {number} [percentAsDecimal]
* 新的最後一個體積為小數百分比:
* - 0 靜音/0%/關閉
* - 1.0 是 100%/滿
* - 0.5 是體積的一半或 50%
*
* @return {數字}
* 獲取時 lastVolume 的當前值作為百分比
*
* @私人的
*/
lastVolume_(percentAsDecimal) {
如果(百分比十進制!== 未定義 && 百分比十進制!== 0) {
this.cache_.lastVolume = percentAsDecimal;
返回;
}
返回 this.cache_.lastVolume;
}
/**
* 檢查當前技術是否支持原生全屏
*(例如,內置控件,如 iOS)
*
* @return {布爾值}
* 如果支持原生全屏
*/
支持全屏(){
返回 this.techGet_('supportsFullScreen') ||錯誤的;
}
/**
* 檢查播放器是否處於全屏模式或告訴播放器它
* 是否處於全屏模式。
*
* > 注意:從最新的 HTML5 規範開始,isFullscreen 不再是官方的
* 屬性,而不是使用 document.fullscreenElement。但是是全屏是
* 仍然是內部玩家工作的寶貴財產。
*
* @param {boolean} [isFS]
* 設置播放器當前全屏狀態
*
* @return {布爾值}
* - 如果全屏打開並獲取則為真
* - 如果全屏關閉並獲取則為 false
*/
isFullscreen(isFS) {
如果(isFS !==未定義){
const oldValue = this.isFullscreen_;
this.isFullscreen_ = Boolean(isFS);
// 如果我們改變全屏狀態並且我們處於前綴模式,觸發 fullscreenchange
// 這是我們為舊瀏覽器觸發 fullscreenchange 事件的唯一地方
// fullWindow 模式被視為一個前綴事件,也會得到一個 fullscreenchange 事件
如果(這個。全屏 _!== 舊值 && 這個 .fsapi_. 前綴) {
/**
* @event Player#fullscreenchange
* @type {EventTarget~Event}
*/
this.trigger('fullscreenchange');
}
this.toggleFullscreenClass_();
返回;
}
返回 this.isFullscreen_;
}
/**
* 將視頻的大小增加到全屏
* 部分瀏覽器原生不支持全屏,所以進入
*“全窗口模式”,視頻填滿瀏覽器窗口。
* 在支持原生全屏的瀏覽器和設備中,有時
* 將顯示瀏覽器的默認控件,而不是 Video.js 自定義皮膚。
* 這包括大多數移動設備(iOS、Android)和舊版本的
* 蘋果瀏覽器。
*
* @param {Object} [全屏選項]
*覆蓋播放器全屏選項
*
* @fires Player#fullscreenchange
*/
請求全屏(全屏選項){
const PromiseClass = this.options_.Promise || window.Promise;
如果(承諾類){
常量自我=這個;
返回新的 PromiseClass((解決, 拒絕) => {
函數 offHandler() {
self.off('fullscreenerror', errorHandler);
self.off('fullscreenchange', changeHandler);
}
函數 changeHandler() {
關閉處理程序();
解決();
}
函數 errorHandler(e, err) {
關閉處理程序();
拒絕(錯誤);
}
self.one('fullscreenchange', changeHandler);
self.one('fullscreenerror', errorHandler);
const promise = self.requestFullscreenHelper_(fullscreenOptions);
如果(承諾){
promise.then(offHandler, offHandler);
promise.then(解決,拒絕);
}
});
}
返回 this.requestFullscreenHelper_();
}
requestFullscreenHelper_(fullscreenOptions) {
讓 fsOptions;
// 僅在符合規範的瀏覽器中將全屏選項傳遞給 requestFullscreen。
// 除非直接傳遞給此方法,否則使用默認值或播放器配置選項。
如果(!this.fsApi_.prefixed){
fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {};
如果(全屏選項!==未定義){
fsOptions = 全屏選項;
}
}
// 這個方法的工作原理如下:
// 1. 如果全屏 api 可用,則使用它
// 1. 使用潛在選項調用 requestFullscreen
// 2. 如果我們從上面得到一個承諾,用它來更新 isFullscreen()
// 2. 否則,如果技術支持全屏,則在其上調用 `enterFullScreen`。
// 這特別適用於 iPhone、舊版 iPad 和 iOS 上的非 Safari 瀏覽器。
// 3. 否則,使用“fullWindow”模式
如果(this.fsApi_.requestFullscreen){
const promise = this.el_[this.fsApi_.requestFullscreen](fsOptions);
如果(承諾){
promise.then(() => this.isFullscreen(true), () => this.isFullscreen(false));
}
回報承諾;
} 否則,如果(這個。技術支持全屏()&&!首選窗口 === 真){
// 我們不能讓 video.js 控件全屏,但我們可以全屏
// 使用原生控件
this.techCall_('enterFullScreen');
}其他{
// 不支持全屏,所以我們只是將視頻元素拉伸到
// 填充視口
這個.enterFullWindow();
}
}
/**
* 在全屏模式下將視頻恢復到正常大小
*
* @fires Player#fullscreenchange
*/
退出全屏(){
const PromiseClass = this.options_.Promise || window.Promise;
如果(承諾類){
常量自我=這個;
返回新的 PromiseClass((解決, 拒絕) => {
函數 offHandler() {
self.off('fullscreenerror', errorHandler);
self.off('fullscreenchange', changeHandler);
}
函數 changeHandler() {
關閉處理程序();
解決();
}
函數 errorHandler(e, err) {
關閉處理程序();
拒絕(錯誤);
}
self.one('fullscreenchange', changeHandler);
self.one('fullscreenerror', errorHandler);
const promise = self.exitFullscreenHelper_();
如果(承諾){
promise.then(offHandler, offHandler);
// 將 promise 映射到我們的 resolve/reject 方法
promise.then(解決,拒絕);
}
});
}
返回 this.exitFullscreenHelper_();
}
exitFullscreenHelper_() {
如果(this.fsApi_.requestFullscreen){
const promise = document[this.fsApi_.exitFullscreen]();
如果(承諾){
// 我們在這里拆分承諾,所以,我們想抓住
// 潛在的錯誤,這樣這條鏈就沒有未處理的錯誤
silencePromise(promise.then(() => this.isFullscreen(false)));
}
回報承諾;
} 否則,如果(這個。技術支持全屏()&&!首選窗口 === 真){
this.techCall_('exitFullScreen');
}其他{
這個.exitFullWindow();
}
}
/**
* 當不支持全屏時,我們可以拉伸
* 視頻容器的寬度與瀏覽器允許的一樣寬。
*
* @fires Player#enterFullWindow
*/
enterFullWindow() {
這是全屏(真);
this.isFullWindow = true;
// 存儲原始文檔溢出值以在全屏關閉時返回
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 {EventTarget~Event}
*/
this.trigger('enterFullWindow');
}
/**
* 檢查呼叫退出全窗口或
* ESC 鍵全屏
*
* @param {string} 事件
* 檢查按鍵的事件
*/
fullWindowOnEscKey(事件){
如果 (keycode.isEventKey(event, 'Esc')) {
如果(this.isFullscreen()===真){
如果(!this.isFullWindow){
this.exitFullscreen();
}其他{
這個.exitFullWindow();
}
}
}
}
/**
* 退出全窗口
*
* @fires Player#exitFullWindow
*/
exitFullWindow() {
this.isFullscreen(false);
this.isFullWindow = false;
Events.off(文檔, 'keydown', this.boundFullWindowOnEscKey_);
//取消隱藏滾動條。
document.documentElement.style.overflow = this.docOrigOverflow;
// 移除全屏樣式
Dom.removeClass(document.body, 'vjs-full-window');
// 將盒子、控制器和海報調整為原始大小
// this.positionAll();
/**
* @event Player#exitFullWindow
* @type {EventTarget~Event}
*/
this.trigger('exitFullWindow');
}
/**
* 禁用畫中畫模式。
*
* @param {boolean} 值
* - true 將禁用畫中畫模式
* - false 將啟用畫中畫模式
*/
禁用畫中畫(值){
如果(值===未定義){
返回 this.techGet_('disablePictureInPicture');
}
this.techCall_('setDisablePictureInPicture', value);
this.options_.disablePictureInPicture = 值;
this.trigger('禁用畫中畫更改');
}
/**
* 檢查播放器是否處於畫中畫模式或告訴播放器它
* 是否處於畫中畫模式。
*
* @param {boolean} [isPiP]
* 設置播放器當前畫中畫狀態
*
* @return {布爾值}
* - 如果畫中畫開啟並獲取則為真
* - 如果畫中畫關閉並獲取則為 false
*/
畫中畫(畫中畫){
如果(iSPIP!== 未定義){
這是。無畫內景 _ =!!國際互動協會;
this.togglePictureInPictureClass_();
返回;
}
返回!!這是。無畫像文化 _;
}
/**
* 創建一個始終位於其他窗口之上的浮動視頻窗口,以便用戶可以
* 在與其他內容網站互動時繼續消費媒體,或
* 他們設備上的應用程序。
*
* @see [規範]{@link https://wicg.github.io/picture-in-picture}
*
* @fires Player#enterpictureinpicture
*
* @return {承諾}
* 帶有畫中畫窗口的承諾。
*/
請求畫中畫(){
if ('pictureInPictureEnabled' in document && this.disablePictureInPicture() === false) {
/**
* 當玩家進入畫中畫模式時觸發此事件
*
* @event Player#enterpictureinpicture
* @type {EventTarget~Event}
*/
返回 this.techGet_('requestPictureInPicture');
}
}
/**
* 退出畫中畫模式。
*
* @see [規範]{@link https://wicg.github.io/picture-in-picture}
*
* @fires Player#leavepictureinpicture
*
* @return {承諾}
* 一個承諾。
*/
退出畫中畫(){
如果(文檔中的“pictureInPictureEnabled”){
/**
* 當玩家離開畫中畫模式時觸發此事件
*
* @event Player#leavepictureinpicture
* @type {EventTarget~Event}
*/
返回 document.exitPictureInPicture();
}
}
/**
* 當這個 Player 有焦點並且按下一個鍵時調用,或者當
* 該播放器的任何組件都會收到它無法處理的按鍵。
* 這允許播放器範圍內的熱鍵(定義如下,或可選
* 通過外部函數)。
*
* @param {EventTarget~Event} 事件
* 導致調用此函數的 `keydown` 事件。
*
* @listens 按鍵
*/
handleKeyDown(事件){
const {userActions} = this.options_;
// 如果未配置熱鍵,則退出。
如果(!userActions ||!userActions.hotkeys){
返回;
}
// 確定是否從中排除元素的函數
// 熱鍵處理。
const excludeElement = (el) => {
const tagName = el.tagName.toLowerCase();
// 第一個也是最簡單的測試是針對 `contenteditable` 元素。
如果(el.isContentEditable){
返回真;
}
// 匹配這些類型的輸入仍然會觸發熱鍵處理
// 它們不是文本輸入。
const allowedInputTypes = [
'按鈕',
'複選框',
'隱',
'收音機',
'重置',
'提交'
];
如果(標籤名稱==='輸入'){
返回 allowedInputTypes.indexOf(el.type) === -1;
}
// 最後的測試是通過標籤名。這些標籤將被完全排除。
const excludedTags = ['textarea'];
返回排除的標籤。索引(標籤名)!== -1;
};
// 如果用戶專注於交互式表單元素,則退出。
如果(排除元素(this.el_.ownerDocument.activeElement)){
返回;
}
如果(typeof userActions.hotkeys ==='函數'){
userActions.hotkeys.call(this, event);
}其他{
這個.handleHotkeys(事件);
}
}
/**
* 當此 Player 收到熱鍵按鍵事件時調用。
* 支持的播放器範圍的熱鍵是:
*
* f - 切換全屏
* m - 切換靜音
* k 或 Space - 切換播放/暫停
*
* @param {EventTarget~Event} 事件
* 導致調用此函數的 `keydown` 事件。
*/
handleHotkeys(事件){
常量熱鍵 = 這個。選項 _.這個. 選項 _. 用戶操作. 熱鍵:{};
// 從 `hotkeys` 設置 fullscreenKey, muteKey, playPauseKey,如果沒有設置則使用默認值
常數 {
fullscreenKey = keydownEvent => keycode.isEventKey(keydownEvent, 'f'),
muteKey = keydownEvent => keycode.isEventKey(keydownEvent, 'm'),
playPauseKey = keydownEvent => (keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space'))
} = 熱鍵;
如果 (fullscreenKey.call(this, event)) {
事件.preventDefault();
事件.stopPropagation();
const FSToggle = Component.getComponent('FullscreenToggle');
如果(文檔 [這個 .FSAPI _. 全屏啟用]!== 假){
FSToggle.prototype.handleClick.call(this, event);
}
} else if (muteKey.call(this, event)) {
事件.preventDefault();
事件.stopPropagation();
const MuteToggle = Component.getComponent('MuteToggle');
MuteToggle.prototype.handleClick.call(this, event);
} else if (playPauseKey.call(this, event)) {
事件.preventDefault();
事件.stopPropagation();
const PlayToggle = Component.getComponent('PlayToggle');
PlayToggle.prototype.handleClick.call(this, event);
}
}
/**
* 檢查播放器是否可以播放給定的 mimetype
*
* @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
*
* @param {string} 類型
* 要檢查的 mimetype
*
* @return {字符串}
*“可能”、“也許”或“”(空字符串)
*/
canPlayType(類型){
讓可以;
// 循環遍歷選項順序中的每一種播放技術
為 (讓我 = 0, j = 這個. 選項 _. 技術訂單; I < J. 長度; 我 ++) {
const 技術名稱 = j[i];
讓 tech = Tech.getTech(techName);
// 支持將技術註冊為組件的舊行為。
// 一旦刪除了不推薦使用的行為,就刪除。
如果(!技術){
tech = Component.getComponent(techName);
}
// 在繼續之前檢查當前技術是否已定義
如果(!技術){
log.error(`“${techName}”技術未定義。跳過該技術的瀏覽器支持檢查。`);
繼續;
}
// 檢查瀏覽器是否支持該技術
如果(技術支持()){
can = tech.canPlayType(類型);
如果能) {
退貨罐;
}
}
}
返回 '';
}
/**
* 根據技術順序或來源順序選擇來源
* 如果 `options.sourceOrder` 為真,則使用源順序選擇。否則,
* 默認為技術訂單選擇
*
* @param {Array} 來源
* 媒體資產的來源
*
* @return {對象|布爾}
* 來源對象和技術順序或錯誤
*/
選擇來源(來源){
// 僅獲取 `techOrder` 中指定的存在且受其支持的技術
// 當前平台
const 技術人員 =
this.options_.techOrder
.map((技術名稱) => {
返回 [技術名稱, Tech.getTech(技術名稱)];
})
.filter(([技術名稱, 技術]) => {
// 在繼續之前檢查當前技術是否已定義
如果(技術){
// 檢查瀏覽器是否支持該技術
返回 tech.isSupported();
}
log.error(`“${techName}”技術未定義。跳過該技術的瀏覽器支持檢查。`);
返回假;
});
// 對每個 `outerArray` 元素迭代一次每個 `innerArray` 元素並執行
// `tester` 兩者都有。如果 `tester` 返回一個非假值,提前退出並返回
//那個值。
const findFirstPassingTechSourcePair = function(outerArray, innerArray, tester) {
讓我們發現;
outerArray.some((outerChoice) => {
返回 innerArray.some((innerChoice) => {
發現=測試者(outerChoice,innerChoice);
如果(找到){
返回真;
}
});
});
返回找到;
};
讓 foundSourceAndTech;
const flip = (fn) => (a, b) => fn(b, a);
const finder = ([技術名稱, 技術], 來源) => {
if (tech.canPlaySource(source, this.options_[techName.toLowerCase()])) {
返回 {source, tech: techName};
}
};
// 根據 `options.sourceOrder` 的真實性,我們交換技術和來源的順序
// 根據優先級從中選擇。
如果(this.options_.sourceOrder){
// 源優先順序
foundSourceAndTech = findFirstPassingTechSourcePair(資源,技術,翻轉(查找器));
}其他{
// 技術優先排序
foundSourceAndTech = findFirstPassingTechSourcePair(技術,資源,查找器);
}
返回 foundSourceAndTech ||錯誤的;
}
/**
* 執行源設置和獲取邏輯
*
* @param {Tech~SourceObject|Tech~SourceObject[]|string} [來源]
* 一個 SourceObject、一個 SourceObjects 數組或一個字符串引用
* 指向媒體源的 URL。_強烈推薦_一個對象
* 或此處使用對像數組,以便選擇源
* 算法可以考慮“類型”。
*
* 如果未提供,則此方法充當吸氣劑。
* @param {boolean} isRetry
* 指示是否由於重試而在內部調用
*
* @return {字符串|未定義}
* 如果缺少 `source` 參數,則返回當前源
* 網址。否則,不返回任何內容/未定義。
*/
handleSrc_(source, isRetry) {
// 吸氣劑用法
如果(源類型==='未定義'){
返回 this.cache_.src || '';
}
// 重置新源的重試行為
如果(this.resetRetryOnError_){
this.resetRetryOnError_();
}
// 過濾掉無效源並將我們的源轉換為
// 源對像數組
const sources = filterSource(來源);
// 如果傳入了一個源,那麼它是無效的,因為
// 它被過濾為零長度數組。所以我們必須
// 顯示錯誤
如果(!sources.length){
this.setTimeout(函數() {
這個。錯誤({代碼:4、消息:this.options_.notSupportedMessage});
}, 0);
返回;
}
// 初始資源
this.changingSrc_ = true;
// 如果我們在錯誤後不重試新源,則只更新緩存的源列表,
// 因為在那種情況下我們希望在緩存中包含失敗的源
如果(!isRetry){
this.cache_.sources = 來源;
}
this.updateSourceCaches_(sources[0]);
// middlewareSource 是經過中間件改變後的源
middleware.setSource(this, sources[0], (middlewareSource, mws) => {
this.middleware_ = mws;
// 由於 sourceSet 是異步的,我們必須在選擇源後再次更新緩存,因為
// 所選源可能與此回調上方的緩存更新順序不一致。
如果(!isRetry){
this.cache_.sources = 來源;
}
this.updateSourceCaches_(middlewareSource);
const err = this.src_(middlewareSource);
如果(錯誤){
如果(來源。長度> 1){
返回 this.handleSrc_(sources.slice(1));
}
this.changingSrc_ = false;
// 我們需要將其包裝在超時中,讓人們有機會添加錯誤事件處理程序
this.setTimeout(函數() {
這個。錯誤({代碼:4、消息:this.options_.notSupportedMessage});
}, 0);
// 我們找不到合適的技術,但我們仍然通知委託人就是這樣
// 這需要更好地說明為什麼需要這樣做
this.triggerReady();
返回;
}
middleware.setTech(mws, this.tech_);
});
// 如果這個源在播放前失敗,請嘗試另一個可用的源。
如果(this.options_.retryOnError && sources.length > 1){
const 重試 = () => {
// 刪除錯誤模態
這個錯誤(空);
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、一個 SourceObjects 數組或一個字符串引用
* 指向媒體源的 URL。_強烈推薦_一個對象
* 或此處使用對像數組,以便選擇源
* 算法可以考慮“類型”。
*
* 如果未提供,則此方法充當吸氣劑。
*
* @return {字符串|未定義}
* 如果缺少 `source` 參數,則返回當前源
* 網址。否則,不返回任何內容/未定義。
*/
來源(來源){
返回 this.handleSrc_(source, false);
}
/**
* 在技術上設置源對象,返回一個布爾值,指示是否
*有技術能不能播放源碼
*
* @param {Tech~SourceObject} 來源
* 要在 Tech 上設置的源對象
*
* @return {布爾值}
* - 如果沒有技術播放此源則為真
* - 否則為假
*
* @私人的
*/
src_(來源){
const sourceTech = this.selectSource([source]);
如果(!sourceTech){
返回真;
}
如果(!標題資訊(來源技術,這個。技術名稱 _)){
this.changingSrc_ = true;
// 使用選擇的源加載此技術
this.loadTech_(sourceTech.tech, sourceTech.source);
this.tech_.ready(() => {
this.changingSrc_ = false;
});
返回假;
}
// 等到技術人員準備好設置源
// 並儘可能同步設置它 (#2326)
這個。準備好(功能(){
// setSource 技術方法添加了源處理程序
// 所以老技術不會支持它
// 我們需要檢查子類的情況下的直接原型
// 該技術不支持源處理程序
如果 (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
this.techCall_('setSource', 來源);
}其他{
this.techCall_('src', source.src);
}
this.changingSrc_ = false;
}, 真的);
返回假;
}
/**
* 開始加載 src 數據。
*/
加載() {
this.techCall_('load');
}
/**
* 重置播放器。加載 techOrder 中的第一個技術,
* 刪除現有 `tech` 中的所有文本軌道,
* 並在 `tech` 上調用 `reset`。
*/
重置() {
const PromiseClass = this.options_.Promise || window.Promise;
如果(這個。暫停()||!責任級別) {
這個.doReset_();
}其他{
const playPromise = this.play();
silencePromise(playPromise.then(() => this.doReset_()));
}
}
doReset_() {
如果(this.tech_){
this.tech_.clearTracks('text');
}
這個.resetCache_();
這個。海報('');
this.loadTech_(this.options_.techOrder[0], null);
this.techCall_('重置');
this.resetControlBarUI_();
如果(isEvented(這個)){
this.trigger('playerreset');
}
}
/**
* 通過調用重置的子方法重置控制欄的 UI
* 控制欄的所有組件
*/
resetControlBarUI_() {
this.resetProgressBar_();
this.resetPlaybackRate_();
this.resetVolumeBar_();
}
/**
* 重置技術進度,以便在 UI 中重置進度條
*/
resetProgressBar_() {
這個.currentTime(0);
const { durationDisplay, remainingTimeDisplay } = this.controlBar || {};
如果(持續時間顯示){
durationDisplay.updateContent();
}
如果(剩餘時間顯示){
remainingTimeDisplay.updateContent();
}
}
/**
* 重置播放比例
*/
resetPlaybackRate_() {
this.playbackRate(this.defaultPlaybackRate());
this.handleTechRateChange_();
}
/**
*重置音量條
*/
resetVolumeBar_() {
這個.volume(1.0);
this.trigger('volumechange');
}
/**
* 返回所有當前源對象。
*
* @return {Tech~SourceObject[]}
* 當前源對象
*/
當前來源(){
const source = this.currentSource();
const 來源 = [];
// 假設 `{}` 或 `{ src }`
如果(對象鍵(源)。== 0) {
來源.推送(來源);
}
返回 this.cache_.sources ||來源;
}
/**
* 返回當前源對象。
*
* @return {Tech~SourceObject}
* 當前源對象
*/
當前來源(){
返回 this.cache_.source || {};
}
/**
* 返回當前源值的完全限定 URL,例如 http://mysite.com/video.mp4
* 可與 `currentType` 結合使用,以協助重建當前源對象。
*
* @return {字符串}
* 當前源
*/
當前源(){
返回 this.currentSource() && this.currentSource().src || '';
}
/**
* 獲取當前源類型,例如 video/mp4
* 這可以讓你重建當前的源對象,這樣你就可以加載相同的
* 稍後的來源和技術
*
* @return {字符串}
* 源 MIME 類型
*/
當前類型(){
返回 this.currentSource() && this.currentSource().type || '';
}
/**
* 獲取或設置預加載屬性
*
* @param {boolean} [值]
* - true 意味著我們應該預加載
* - false 意味著我們不應該預加載
*
* @return {字符串}
* 獲取時的preload屬性值
*/
預加載(值){
如果(值!==未定義){
this.techCall_('setPreload', value);
this.options_.preload = 值;
返回;
}
返回 this.techGet_('preload');
}
/**
* 獲取或設置自動播放選項。當這是一個布爾值時,它將
* 修改技術屬性。當這是一個字符串時,屬性
* 該技術將被刪除,`Player` 將在加載開始時處理自動播放。
*
* @param {boolean|string} [值]
* - true:使用瀏覽器行為自動播放
* - false:不自動播放
* - 'play': 在每次加載開始時調用 play()
* - 'muted': 在每次加載開始時調用 muted() 然後 play()
* - 'any':在每次加載開始時調用 play()。如果失敗調用 muted() 然後 play()。
* - *:此處列出的值以外的值將設置為 `autoplay` 為 true
*
* @return {布爾值|字符串}
* 獲取時自動播放的當前值
*/
自動播放(值){
// 吸氣劑用法
如果(值===未定義){
返回 this.options_.autoplay ||錯誤的;
}
讓技術自動播放;
// 如果該值是一個有效的字符串,則將其設置為該值,或者在需要時將 `true` 規範化為 'play'
if (typeof value === 'string' && (/(any|play|muted)/).test(value) || value === true && this.options_.normalizeAutoplay) {
this.options_.autoplay = 值;
這是。手動自動播放 _(值的類型 === '字符串'?價值:'玩');
技術自動播放=假;
// 任何 falsy 值都會在瀏覽器中將自動播放設置為 false,
// 讓我們做同樣的事情
} 否則如果(!價值){
this.options_.autoplay = false;
// 任何其他值(即 truthy)將自動播放設置為 true
}其他{
this.options_.autoplay = true;
}
技術自動播放 = 技術自動播放 ===「未定義」的類型?這個選項 _. 自動播放:技術自動播放;
// 如果我們沒有技術那麼我們就不會排隊
// 技術就緒時的 setAutoplay 調用。我們這樣做是因為
// 自動播放選項將在構造函數中傳遞,我們
// 不需要設置兩次
如果(this.tech_){
this.techCall_('setAutoplay', techAutoplay);
}
}
/**
* 設置或取消設置 playsinline 屬性。
* Playsinline 告訴瀏覽器首選非全屏播放。
*
* @param {boolean} [值]
* - true 意味著我們應該默認嘗試內聯播放
* - false 表示我們應該使用瀏覽器的默認播放模式,
* 在大多數情況下是內聯的。 iOS Safari 是一個明顯的例外
* 並默認全屏播放。
*
* @return {字符串|播放器}
* - playsinline 的當前值
* - 設置時的播放器
*
* @see [規範]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
*/
在線播放(值){
如果(值!==未定義){
this.techCall_('setPlaysinline', value);
this.options_.playsinline = 值;
歸還這個;
}
返回 this.techGet_('playsinline');
}
/**
* 獲取或設置視頻元素的循環屬性。
*
* @param {boolean} [值]
* - true 表示我們應該循環播放視頻
* - false 表示我們不應該循環播放視頻
*
* @return {布爾值}
* 獲取時循環的當前值
*/
循環(值){
如果(值!==未定義){
this.techCall_('setLoop', value);
this.options_.loop = 值;
返回;
}
返回 this.techGet_('loop');
}
/**
* 獲取或設置海報圖片來源url
*
* @fires Player#posterchange
*
* @param {字符串} [來源]
* 海報圖片來源網址
*
* @return {字符串}
* 獲得時海報的當前值
*/
海報(src){
如果(源===未定義){
返回this.poster_;
}
// 刪除海報的正確方法是設置為空字符串
// 其他假值會拋出錯誤
如果(!src){
來源 = '';
}
如果(src === this.poster_){
返回;
}
// 更新內部海報變量
this.poster_ = src;
// 更新技術海報
this.techCall_('setPoster', src);
this.isPosterFromTech_ = false;
// 提醒組件海報已設置
/**
* 當播放器上的海報圖像發生變化時會觸發此事件。
*
* @event Player#posterchange
* @type {EventTarget~Event}
*/
this.trigger('posterchange');
}
/**
* 一些技術人員(例如 YouTube)可以在
* 異步方式。我們希望海報組件使用它
*海報來源,以便它掩蓋技術的控制。
*(YouTube 的播放按鈕)。但是我們只想用這個
* 如果播放器用戶沒有通過設置海報
* 正常的 API。
*
* @fires Player#posterchange
* @listens Tech#posterchange
* @私人的
*/
handleTechPosterChange_() {
如果((!this.poster_ || this.options_.techCanOverridePoster)&& this.tech_ && this.tech_.poster){
const newPoster = this.tech_.poster() || '';
如果(新海報!== this.poster_){
this.poster_ = newPoster;
this.isPosterFromTech_ = true;
// 讓組件知道海報已經改變
this.trigger('posterchange');
}
}
}
/**
* 獲取或設置控件是否顯示。
*
* @fires Player#controlsenabled
*
* @param {布爾} [布爾]
* - true 打開控件
* - false 關閉控件
*
* @return {布爾值}
* 獲取時控件的當前值
*/
控制(布爾){
如果(布爾===未定義){
返回!!這個。控制 _;
}
布爾 =!!布爾;
// 不要觸發更改事件,除非它確實發生了變化
如果(this.controls_ === bool){
返回;
}
this.controls_ = bool;
如果(this.usingNativeControls()){
this.techCall_('setControls', bool);
}
如果(this.controls_){
this.removeClass('vjs-controls-disabled');
this.addClass('vjs-controls-enabled');
/**
* @event Player#controlsenabled
* @type {EventTarget~Event}
*/
this.trigger('controlsenabled');
如果(!this.usingNativeControls()){
this.addTechControlsListeners_();
}
}其他{
this.removeClass('vjs-controls-enabled');
this.addClass('vjs-controls-disabled');
/**
* @event Player#controlsdisabled
* @type {EventTarget~Event}
*/
this.trigger('controlsdisabled');
如果(!this.usingNativeControls()){
this.removeTechControlsListeners_();
}
}
}
/**
* 打開/關閉本機控件。本機控件是內置於
* 設備(例如默認的 iPhone 控件)或其他技術
*(例如 Vimeo 控件)
* **這應該只能由當前技術設置,因為只有技術知道
* 如果它可以支持原生控件**
*
* @fires Player#usingnativecontrols
* @fires Player#usingcustomcontrols
*
* @param {布爾} [布爾]
* - true 打開本機控件
* - false 關閉本機控件
*
* @return {布爾值}
* 獲取時原生控件的當前值
*/
使用本機控件(布爾){
如果(布爾===未定義){
返回!!這是。使用本地控制 _;
}
布爾 =!!布爾;
// 不要觸發更改事件,除非它確實發生了變化
如果(this.usingNativeControls_ === bool){
返回;
}
this.usingNativeControls_ = bool;
如果(this.usingNativeControls_){
this.addClass('vjs-using-native-controls');
/**
* 播放器正在使用本機設備控件
*
* @event Player#usingnativecontrols
* @type {EventTarget~Event}
*/
this.trigger('usingnativecontrols');
}其他{
this.removeClass('vjs-using-native-controls');
/**
* 播放器正在使用自定義 HTML 控件
*
* @event Player#usingcustomcontrols
* @type {EventTarget~Event}
*/
this.trigger('usingcustomcontrols');
}
}
/**
* 設置或獲取當前的MediaError
*
* @fires Player#錯誤
*
* @param {MediaError|string|number} [錯誤]
* MediaError 或要轉換的字符串/數字
* 變成一個 MediaError
*
* @return {MediaError|null}
* 獲取(或null)時的當前MediaError
*/
錯誤(錯誤){
如果(錯誤===未定義){
返回 this.error_ ||無效的;
}
// 允許鉤子修改錯誤對象
鉤子('beforeeror').forEach((hookFunction)=> {
const newErr = hookFunction(this, err);
如果 (!(
(隔離物件 (更新者) &&!陣列. 伊斯陣列(新版))||
typeof newErr === '字符串' ||
typeof newErr === '數字' ||
newErr === 空
)) {
this.log.error('請在 beforeerror 鉤子中返回 MediaError 期望的值');
返回;
}
錯誤=新錯誤;
});
// 抑制不兼容源的第一條錯誤消息,直到
// 用戶交互
如果(this.options_.suppressNotSupportedError &&
錯誤 && 錯誤代碼 === 4
) {
const triggerSuppressedError = function() {
這個錯誤(錯誤);
};
this.options_.suppressNotSupportedError = false;
this.any(['click', 'touchstart'], triggerSuppressedError);
this.one('loadstart', function() {
this.off(['click', 'touchstart'], triggerSuppressedError);
});
返回;
}
//恢復默認
如果(錯誤=== null){
this.error_ = 錯誤;
this.removeClass('vjs-error');
如果(this.errorDisplay){
這個.errorDisplay.close();
}
返回;
}
this.error_ = new MediaError(err);
// 將 vjs-error 類名添加到播放器
this.addClass('vjs-error');
// 記錄錯誤類型的名稱和任何消息
//IE11 日誌「[對象對象]」,並要求您展開消息以查看錯誤對象
日誌錯誤(`(代碼:$ {這個 .error_.code} $ {媒體錯誤。錯誤類型 [這個 .error_.code]})`,這個 .error_.message,這個 .error_);
/**
* @event Player#錯誤
* @type {EventTarget~Event}
*/
this.trigger('錯誤');
// 通知每個玩家錯誤的鉤子
hooks('error').forEach((hookFunction) => hookFunction(this, this.error_));
返回;
}
/**
* 報告用戶活動
*
* @param {Object} 事件
* 事件對象
*/
報告用戶活動(事件){
this.userActivity_ = true;
}
/**
* 獲取/設置用戶是否活躍
*
* @fires Player#useractive
* @fires Player#userinactive
*
* @param {布爾} [布爾]
* - 如果用戶處於活動狀態則為真
* - 如果用戶處於非活動狀態則為 false
*
* @return {布爾值}
* 獲取時userActive的當前值
*/
userActive(布爾){
如果(布爾===未定義){
返回這個。userActive_;
}
布爾 =!!布爾;
如果(bool === this.userActive_){
返回;
}
this.userActive_ = bool;
如果(this.userActive_){
this.userActivity_ = true;
this.removeClass('vjs-user-inactive');
this.addClass('vjs-user-active');
/**
* @event Player#useractive
* @type {EventTarget~Event}
*/
this.trigger('useractive');
返回;
}
// Chrome/Safari/IE 有錯誤,當你改變光標時它可以
// 觸發鼠標移動事件。當您隱藏時,這會導致問題
// 用戶不活動時的光標,鼠標移動信號用戶
// 活動。使其無法進入非活動模式。具體來說
// 當我們真的需要隱藏光標時,這會在全屏模式下發生。
//
// 當這在所有瀏覽器中得到解決時,它可以被刪除
// https://code.google.com/p/chromium/issues/detail?id=103041
如果(this.tech_){
this.tech_.one('mousemove', function(e) {
e.stopPropagation();
e.preventDefault();
});
}
this.userActivity_ = false;
this.removeClass('vjs-user-active');
this.addClass('vjs-user-inactive');
/**
* @event Player#userinactive
* @type {EventTarget~Event}
*/
this.trigger('userinactive');
}
/**
* 根據超時值監聽用戶活動
*
* @私人的
*/
listenForUserActivity_() {
讓 mouseInProgress;
讓 lastMoveX;
讓 lastMoveY;
const handleActivity = Fn.bind(this, this.reportUserActivity);
const handleMouseMove = function(e) {
// #1068 - 防止 mousemove 垃圾郵件
// Chrome 錯誤:https://code.google.com/p/chromium/issues/detail?id=366970
如果(。屏幕!最後一次移動== 拉斯莫維) {
lastMoveX = e.screenX;
lastMoveY = e.screenY;
處理活動();
}
};
const handleMouseDown = function() {
處理活動();
// 只要他們正在觸摸設備或按下鼠標,
// 即使他們沒有移動手指或鼠標,我們也認為他們是活躍的。
// 所以我們想繼續更新他們是活躍的
this.clearInterval(mouseInProgress);
// 現在設置 userActivity=true 並將間隔設置為同一時間
// 因為 activityCheck 間隔 (250) 應該確保我們不會錯過
// 下一個活動檢查
mouseInProgress = this.setInterval(handleActivity, 250);
};
const handleMouseUpAndMouseLeave = 函數(事件){
處理活動();
// 如果鼠標/觸摸按下,則停止保持活動的間隔
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 上點擊進度條時(顯示控制欄時)的錯誤
// controlBar 將不再被默認超時隱藏。
如果(控制欄 &&!瀏覽器 .iOS&&!瀏覽器安卓) {
controlBar.on('mouseenter', function(event) {
如果(這個。播放器()。選項 _ 不活動超時!== 0) {
this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
}
this.player().options_.inactivityTimeout = 0;
});
controlBar.on('mouseleave', 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/
讓不活動超時;
this.setInterval(函數() {
// 檢查鼠標/觸摸活動是否發生
如果(!this.userActivity_){
返回;
}
// 重置活動跟踪器
this.userActivity_ = false;
// 如果用戶狀態為非活動狀態,則將狀態設置為活動狀態
這個。userActive(真);
// 清除任何現有的不活動超時以重新啟動計時器
this.clearTimeout(inactivityTimeout);
const timeout = this.options_.inactivityTimeout;
如果(超時 <= 0){
返回;
}
// 在 <timeout> 毫秒內,如果沒有更多的活動發生
// 用戶將被視為不活躍
inactivityTimeout = this.setTimeout(函數() {
// 防止 inactivityTimeout 可以觸發的情況
// 在活動檢查循環獲取下一個用戶活動之前
// 引起閃爍
如果(!this.userActivity_){
this.userActive(false);
}
}, 暫停);
}, 250);
}
/**
* 獲取或設置當前播放速率。播放速率為
* 1.0 代表正常速度,0.5 代表半速
* 播放,例如。
*
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
*
* @param {number} [率]
* 要設置的新播放速率。
*
* @return {數字}
* 獲取或1.0時的當前播放速率
*/
回放率(速率){
如果(率!==未定義){
// 注意:this.cache_.lastPlaybackRate 是從技術處理程序中設置的
//上面註冊的
this.techCall_('setPlaybackRate', 速率);
返回;
}
如果(this.tech_ && this.tech_.featuresPlaybackRate){
返回 this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
}
返回 1.0;
}
/**
* 獲取或設置當前默認播放速率。默認播放速率
* 例如,1.0 表示正常速度,0.5 表示半速播放。
* defaultPlaybackRate 將僅代表視頻的初始播放速率,而不是
* 不是當前播放速率。
*
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
*
* @param {number} [率]
* 要設置的新默認播放速率。
*
* @return {number|玩家}
* - 獲取或1.0時的默認播放速率
* - 設置時的播放器
*/
defaultPlaybackRate(速率){
如果(率!==未定義){
返回 this.techCall_('setDefaultPlaybackRate', rate);
}
如果(this.tech_ && this.tech_.featuresPlaybackRate){
返回 this.techGet_('defaultPlaybackRate');
}
返回 1.0;
}
/**
* 獲取或設置音頻標誌
*
* @param {boolean} 布爾
* - true 表示這是一個音頻播放器
* - 錯誤信號表明這不是音頻播放器
*
* @return {布爾值}
* 獲取時isAudio的當前值
*/
isAudio(布爾){
如果(布爾!==未定義){
這是音頻 _ =!!布爾;
返回;
}
返回!!這是音頻 _;
}
enableAudioOnlyUI_() {
// 立即更新樣式以顯示控制欄,這樣我們就可以得到它的高度
this.addClass('vjs-audio-only-mode');
const playerChildren = this.children();
const controlBar = this.getChild('ControlBar');
const controlBarHeight = controlBar && controlBar.currentHeight();
// 隱藏除控制欄之外的所有播放器組件。控制欄組件
// 只有視頻需要用 CSS 隱藏
playerChildren.forEach(孩子 => {
如果(孩子=== controlBar){
返回;
}
如果(孩子的孩子 _ &&!兒童. 有類 ('VJS-隱藏')) {
孩子。隱藏();
this.audioOnlyCache_.hiddenChildren.push(child);
}
});
this.audioOnlyCache_.playerHeight = this.currentHeight();
// 設置播放器高度與控制條相同
this.height(controlBarHeight);
this.trigger('audioonlymodechange');
}
disableAudioOnlyUI_() {
this.removeClass('vjs-audio-only-mode');
// 顯示之前隱藏的播放器組件
this.audioOnlyCache_.hiddenChildren.forEach(child => child.show());
// 重置玩家高度
this.height(this.audioOnlyCache_.playerHeight);
this.trigger('audioonlymodechange');
}
/**
* 獲取當前的 audioOnlyMode 狀態或將 audioOnlyMode 設置為 true 或 false。
*
* 將此設置為 `true` 將隱藏除控制欄之外的所有播放器組件,
* 以及僅視頻所需的控制欄組件。
*
* @param {boolean} [值]
* 將 audioOnlyMode 設置為的值。
*
* @return {承諾|布爾}
* 設置狀態時返回一個Promise,獲取時返回一個布爾值
* 目前的狀態
*/
audioOnlyMode(值){
如果(類型值!== '布爾值' || 值 === 這個。音頻專用模式 _) {
返回this.audioOnlyMode_;
}
this.audioOnlyMode_ = 值;
const PromiseClass = this.options_.Promise || window.Promise;
如果(承諾類){
//啟用純音頻模式
如果(值){
const exitPromises = [];
// audioOnlyMode 不支持全屏和畫中畫,所以如果需要請退出。
如果 (this.isInPictureInPicture()) {
exitPromises.push(this.exitPictureInPicture());
}
如果 (this.isFullscreen()) {
exitPromises.push(this.exitFullscreen());
}
如果(this.audioPosterMode()){
exitPromises.push(this.audioPosterMode(false));
}
返回 PromiseClass.all(exitPromises).then(() => this.enableAudioOnlyUI_());
}
// 禁用純音頻模式
返回 PromiseClass.resolve().then(() => this.disableAudioOnlyUI_());
}
如果(值){
如果 (this.isInPictureInPicture()) {
this.exitPictureInPicture();
}
如果(this.isFullscreen()){
this.exitFullscreen();
}
this.enableAudioOnlyUI_();
}其他{
this.disableAudioOnlyUI_();
}
}
enablePosterModeUI_() {
// 隱藏視頻元素並顯示海報圖像以啟用 posterModeUI
const tech = this.tech_ && this.tech_;
技術隱藏();
this.addClass('vjs-audio-poster-mode');
this.trigger('audiopostermodechange');
}
disablePosterModeUI_() {
// 顯示視頻元素並隱藏海報圖像以禁用 posterModeUI
const tech = this.tech_ && this.tech_;
技術展示();
this.removeClass('vjs-audio-poster-mode');
this.trigger('audiopostermodechange');
}
/**
* 獲取當前audioPosterMode狀態或設置audioPosterMode為true或false
*
* @param {boolean} [值]
* 將 audioPosterMode 設置為的值。
*
* @return {承諾|布爾}
* 設置狀態時返回一個 Promise,獲取時返回一個布爾值
* 目前的狀態
*/
audioPosterMode(值){
如果(類型值!== '布爾' || 值 === 這個. 音頻海報模式 _) {
返回this.audioPosterMode_;
}
this.audioPosterMode_ = 值;
const PromiseClass = this.options_.Promise || window.Promise;
如果(承諾類){
如果(值){
如果(this.audioOnlyMode()){
const audioOnlyModePromise = this.audioOnlyMode(false);
返回 audioOnlyModePromise.then(() => {
// 在禁用純音頻模式後啟用音頻海報模式
this.enablePosterModeUI_();
});
}
返回 PromiseClass.resolve().then(() => {
//啟用音頻海報模式
this.enablePosterModeUI_();
});
}
返回 PromiseClass.resolve().then(() => {
// 禁用音頻海報模式
this.disablePosterModeUI_();
});
}
如果(值){
如果(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 {string} [種類]
* 您要添加的 TextTrack 類型
*
* @param {字符串} [標籤]
* 給TextTrack標籤的標籤
*
* @param {字符串} [語言]
* 在 TextTrack 上設置的語言
*
* @return {TextTrack|undefined}
* 添加或未定義的 TextTrack
* 如果沒有技術
*/
addTextTrack(種類,標籤,語言){
如果(this.tech_){
返回 this.tech_.addTextTrack(種類、標籤、語言);
}
}
/**
* 創建一個遠程 {@link TextTrack} 和一個 {@link HTMLTrackElement}。
* 當manualCleanup設置為false時,track會被自動清除
* 源更改。
*
* @param {Object} 選項
* 在創建過程中傳遞給 {@link HTMLTrackElement} 的選項。看
* {@link HTMLTrackElement} 用於您應該使用的對象屬性。
*
* @param {boolean} [manualCleanup=true] 如果設置為 false,TextTrack 將是
* 在源更改時刪除
*
* @return {HtmlTrackElement}
* 創建並添加的 HTMLTrackElement
* 到 HtmlTrackElementList 和遠程
* 文本軌道列表
*
* @deprecated “manualCleanup”參數的默認值將默認
* 在即將推出的 Video.js 版本中設置為“false”
*/
addRemoteTextTrack(選項,manualCleanup){
如果(this.tech_){
返回 this.tech_.addRemoteTextTrack(選項,manualCleanup);
}
}
/**
*從各自的刪除遠程{@link TextTrack}
* {@link TextTrackList} 和 {@link HtmlTrackElementList}。
*
* @param {Object} 軌道
* 遠程 {@link TextTrack} 刪除
*
* @return {未定義}
* 不返回任何東西
*/
removeRemoteTextTrack(obj = {}) {
讓 {track} = obj;
如果(!跟踪){
軌道=對象;
}
// 將輸入解構為一個帶有 track 參數的對象,默認為 arguments[0]
// 如果沒有傳入任何內容,則將整個參數默認為一個空對象
如果(this.tech_){
返回 this.tech_.removeRemoteTextTrack(track);
}
}
/**
* 獲取 W3C 媒體指定的可用媒體播放質量指標
* 播放質量 API。
*
* @see [規範]{@link https://wicg.github.io/media-playback-quality}
*
* @return {對象|未定義}
* 具有支持的媒體播放質量指標或未定義的對象(如果有)
* 沒有技術或技術不支持它。
*/
getVideoPlaybackQuality() {
返回 this.techGet_('getVideoPlaybackQuality');
}
/**
* 獲取視頻寬度
*
* @return {數字}
* 當前視頻寬度
*/
視頻寬度(){
返回 this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
}
/**
* 獲取視頻高度
*
* @return {數字}
* 當前視頻高度
*/
視頻高度(){
返回 this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
}
/**
* 玩家的語言代碼。
*
* 更改語言會觸發
* [語言更改] {@link 播放器 # 事件:語言更改}
* 哪些組件可以用來更新控製文本。
* ClickableComponent 將默認更新其控件文本
* [語言更改] {@link 播放器 # 事件:語言更改}。
*
* @fires Player#languagechange
*
* @param {字符串} [代碼]
* 設置播放器的語言代碼
*
* @return {字符串}
* 獲取時的當前語言代碼
*/
語言(代碼){
如果(代碼===未定義){
返回 this.language_;
}
如果(這個語言 _!== 字符串(代碼)。小寫()){
this.language_ = String(code).toLowerCase();
// 在第一次初始化期間,有些事情可能不會被觸發
如果(isEvented(這個)){
/**
* 當玩家語言改變時觸發
*
* @event Player#languagechange
* @type {EventTarget~Event}
*/
this.trigger('languagechange');
}
}
}
/**
* 獲取玩家的語言詞典
* 每次合併,因為新添加的插件可能隨時調用 videojs.addLanguage()
* 直接在播放器選項中指定的語言優先
*
* @return {數組}
*一系列支持的語言
*/
語言(){
返回 mergeOptions(Player.prototype.options_.languages, this.languages_);
}
/**
* 返回代表當前曲目的 JavaScript 對象
* 信息。 **不將其作為 JSON 返回**
*
* @return {對象}
* 表示當前軌道信息的對象
*/
toJSON() {
const options = mergeOptions(this.options_);
const tracks = options.tracks;
選項.tracks = [];
對於(讓我 = 0; 我 < 跟踪。長度; 我 ++){
讓 track = tracks[i];
// 深度合併音軌並清空播放器,因此沒有循環引用
track = mergeOptions(track);
track.player = undefined;
options.tracks[i] = track;
}
退貨選項;
}
/**
* 創建一個簡單的模式對話框({@link ModalDialog} 的一個實例
* component) 立即覆蓋播放器
* 內容並在關閉時自行刪除。
*
* @param {string|Function|Element|Array|null} 內容
* 與 {@link ModalDialog#content} 的同名參數相同。
* 最直接的用法是提供字符串或 DOM
* 元素。
*
* @param {對象} [選項]
* 將傳遞給 {@link ModalDialog} 的額外選項。
*
* @return {ModalDialog}
* 創建的 {@link ModalDialog}
*/
createModal(內容,選項){
選項=選項|| {};
options.content = 內容 || '';
const modal = new ModalDialog(this, options);
this.addChild(模態);
modal.on('處置', () => {
this.removeChild(模態);
});
模態.open();
返回模態;
}
/**
* 當播放器調整大小時更改斷點類。
*
* @私人的
*/
updateCurrentBreakpoint_() {
如果(!this.responsive()){
返回;
}
const currentBreakpoint = this.currentBreakpoint();
const currentWidth = this.currentWidth();
對於(讓我 = 0; 我 < 斷點 _ 順序。長度; 我 ++){
const candidateBreakpoint = BREAKPOINT_ORDER[i];
const maxWidth = this.breakpoints_[candidateBreakpoint];
如果(當前寬度 <= 最大寬度){
// 當前斷點沒有改變,不用做什麼。
if (currentBreakpoint === candidateBreakpoint) {
返回;
}
// 如果存在當前斷點,則只刪除一個類。
如果(當前斷點){
this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
}
this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
this.breakpoint_ = candidateBreakpoint;
休息;
}
}
}
/**
* 刪除當前斷點。
*
* @私人的
*/
removeCurrentBreakpoint_() {
const className = this.currentBreakpointClass();
this.breakpoint_ = '';
如果(類名){
this.removeClass(類名);
}
}
/**
* 在播放器上獲取或設置斷點。
*
* 使用對像或 `true` 調用此方法將刪除任何先前的
* 自定義斷點並再次從默認值開始。
*
* @param {Object|boolean} [斷點]
* 如果給定一個對象,它可以用來提供自定義
*斷點。如果給出 true ,將設置默認斷點。
* 如果沒有給出這個參數,將簡單地返回當前
*斷點。
*
* @param {number} [斷點.tiny]
* “vjs-layout-tiny”類的最大寬度。
*
* @param {number} [斷點.xsmall]
* “vjs-layout-x-small”類的最大寬度。
*
* @param {number} [斷點.small]
* “vjs-layout-small”類的最大寬度。
*
* @param {number} [斷點.medium]
* “vjs-layout-medium”類的最大寬度。
*
* @param {number} [breakpoints.large]
* “vjs-layout-large”類的最大寬度。
*
* @param {number} [斷點.xlarge]
* “vjs-layout-x-large”類的最大寬度。
*
* @param {number} [breakpoints.huge]
* “vjs-layout-huge”類的最大寬度。
*
* @return {對象}
* 將斷點名稱映射到最大寬度值的對象。
*/
斷點(斷點){
// 用作吸氣劑。
如果(斷點===未定義){
返回分配(this.breakpoints_);
}
this.breakpoint_ = '';
this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, 斷點);
// 當斷點定義改變時,我們需要更新當前
// 選定的斷點。
this.updateCurrentBreakpoint_();
// 返回前克隆斷點。
返回分配(this.breakpoints_);
}
/**
* 獲取或設置一個標誌,指示該玩家是否應該調整
* 其 UI 基於其尺寸。
*
* @param {boolean} 值
* 如果播放器應根據其調整其 UI,則應為 `true`
* 尺寸;否則,應該是「假」。
*
* @return {布爾值}
* 如果此播放器應根據其調整其 UI,則為“true”
* 尺寸; 否則,將是 '假'。
*/
響應(值){
// 用作吸氣劑。
如果(值===未定義){
返回this.responsive_;
}
值 = 布爾值(值);
const current = this.responsive_;
// 沒有改變。
如果(值===當前){
返回;
}
// 值實際改變了,設置它。
this.responsive_ = 值;
// 開始偵聽斷點並設置初始斷點
// 播放器現在響應。
如果(值){
this.on('playerresize', this.boundUpdateCurrentBreakpoint_);
this.updateCurrentBreakpoint_();
// 如果播放器不再響應,則停止監聽斷點。
}其他{
this.off('playerresize', this.boundUpdateCurrentBreakpoint_);
this.removeCurrentBreakpoint_();
}
返回值;
}
/**
* 獲取當前斷點名稱,如果有的話。
*
* @return {字符串}
* 如果當前設置了斷點,則返回一個來自
* 斷點對象匹配它。否則,返回一個空字符串。
*/
當前斷點(){
返回 this.breakpoint_;
}
/**
* 獲取當前斷點類名。
*
* @return {字符串}
* 匹配的類名(例如 `"vjs-layout-tiny"` 或
* `"vjs-layout-large"`) 當前斷點。空字符串如果
* 沒有當前斷點。
*/
當前斷點類(){
返回 BREAKPOINT_CLASSES[this.breakpoint_] || '';
}
/**
* 描述單個媒體的對象。
*
* 不屬於該類型描述的屬性將被保留;所以,
* 這也可以被視為一種通用的元數據存儲機制。
*
* @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
* @typedef {Object} Player~MediaObject
*
* @property {字符串} [專輯]
* 未使用,除非將此對像傳遞給“MediaSession”
* API。
*
* @property {string} [藝術家]
* 未使用,除非將此對像傳遞給“MediaSession”
* API。
*
* @property {對象[]} [藝術品]
* 未使用,除非將此對像傳遞給“MediaSession”
* API。如果未指定,將通過“海報”填充,如果
* 可用的。
*
* @property {string} [海報]
* 將在播放前顯示的圖像的 URL。
*
* @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
* 單個源對象、源對像數組或字符串
* 引用指向媒體源的 URL。這是_強烈推薦_
* 這裡使用了一個對像或對像數組,所以源
* 選擇算法可以考慮“類型”。
*
* @property {字符串} [標題]
* 未使用,除非將此對像傳遞給“MediaSession”
* API。
*
* @property {Object[]} [textTracks]
* 一組用於創建文本軌道的對象,如下
* {@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 {Function} 準備好了
* 播放器準備就緒時調用的回調。
*/
loadMedia(媒體,準備就緒){
如果(!媒體 || 類型的媒體!== '對象'){
返回;
}
這個。重置();
// 克隆媒體對象,使其不能從外部改變。
this.cache_.media = mergeOptions(媒體);
const {藝術品、海報、src、textTracks} = this.cache_.media;
// 如果未給出 `artwork`,則使用 `poster` 創建它。
如果(!藝術品和海報){
this.cache_.media.artwork = [{
來源:海報,
類型:getMimetype(海報)
}];
}
如果(源){
這個.src(src);
}
如果(海報){
這個。海報(海報);
}
如果(Array.isArray(textTracks)){
textTracks.forEach(tt => this.addRemoteTextTrack(tt, false));
}
this.ready(準備就緒);
}
/**
* 為該播放器獲取當前 {@link Player~MediaObject} 的克隆。
*
* 如果沒有使用 `loadMedia` 方法,將嘗試返回一個
* {@link Player~MediaObject} 基於播放器的當前狀態。
*
* @return {播放器~MediaObject}
*/
得到媒體(){
如果(!this.cache_.media){
const poster = this.poster();
const src = this.currentSources();
const textTracks = Array.prototype.map.call(this.remoteTextTracks(), (tt) => ({
種類:tt.kind,
標籤:tt.label,
語言:tt.language,
來源:tt.src
}));
const media = {src, textTracks};
如果(海報){
media.poster = 海報;
媒體.藝術品= [{
來源:media.poster,
類型:getMimetype(media.poster)
}];
}
返回媒體;
}
返回合併選項(this.cache_.media);
}
/**
* 獲取標籤設置
*
* @param {元素} 標籤
* 玩家標籤
*
* @return {對象}
* 包含所有設置的對象
* 對於玩家標籤
*/
靜態 getTagSettings(標籤){
const 基礎選項 = {
資料來源:[],
曲目:[]
};
const tagOptions = Dom.getAttributes(標籤);
const dataSetup = tagOptions['data-setup'];
如果 (Dom.hasClass(tag, 'vjs-fill')) {
tagOptions.fill = true;
}
如果 (Dom.hasClass(tag, 'vjs-fluid')) {
tagOptions.fluid = true;
}
// 檢查數據設置屬性是否存在。
如果(數據設置!== null){
// 解析選項 JSON
// 如果為空字符串,將其設為可解析的 json 對象。
const [err, data] = safeParseTuple(dataSetup || '{}');
如果(錯誤){
日誌錯誤(錯誤);
}
分配(標籤選項,數據);
}
分配(基礎選項,標籤選項);
// 獲取標籤子設置
如果(tag.hasChildNodes()){
const children = tag.childNodes;
對於 (讓我 = 0, j = 孩子. 長度; 我 < ; 我 ++) {
const child = children[i];
// 需要更改大小寫:http://ejohn.org/blog/nodename-case-sensitivity/
const childName = child.nodeName.toLowerCase();
如果 (childName === '來源') {
baseOptions.sources.push(Dom.getAttributes(child));
} else if (childName === 'track') {
baseOptions.tracks.push(Dom.getAttributes(child));
}
}
}
返回基本選項;
}
/**
* 判斷是否支持flexbox
*
* @return {布爾值}
* - 如果支持 flexbox 則為真
* - 如果不支持 flexbox,則為 false
*/
flexNotSupported_() {
const elem = document.createElement('i');
// 筆記:我們實際上並沒有使用 flexBasis(或 flexOrder),但它是其中之一
// 在檢查 flex 支持時我們可以依賴的常見 flex 特性。
返回!(元素風格 || 中的「柔性基礎」
elem.style 中的 'webkitFlexBasis' ||
elem.style 中的 'mozFlexBasis' ||
elem.style 中的 'msFlexBasis' ||
// 特定於 IE10(2012 flex 規範),可用於完整性
elem.style 中的“msFlexOrder”);
}
/**
* 設置調試模式以在信息級別啟用/禁用日誌。
*
* @param {boolean} 啟用
* @fires Player#debugon
* @fires Player#debugoff
*/
調試(啟用){
如果(啟用===未定義){
返回 this.debugEnabled_;
}
如果(啟用){
this.trigger('debugon');
this.previousLogLevel_ = this.log.level;
this.log.level('調試');
this.debugEnabled_ = true;
}其他{
this.trigger('debugoff');
this.log.level(this.previousLogLevel_);
this.previousLogLevel_ = undefined;
this.debugEnabled_ = false;
}
}
/**
* 設置或獲取當前播放速率。
* 採用數組並使用新項目更新播放速率菜單。
* 傳入一個空數組以隱藏菜單。
* 忽略數組以外的值。
*
* @fires Player#playbackrateschange
* @param {number[]} newRates
* 播放速率菜單應更新到的新速率。
* 空數組將隱藏菜單
* @return {number[]} 當用作 getter 時將返回當前播放速率
*/
回放率(新率){
如果(新利率===未定義){
返回 this.cache_.playbackRates;
}
// 忽略任何不是數組的值
如果(!Array.isArray(newRates)){
返回;
}
// 忽略任何不僅僅包含數字的數組
如果(!每((率)=> 類型率 ===「數字」){
返回;
}
this.cache_.playbackRates = newRates;
/**
* 當播放器中的播放速率改變時觸發
*
* @event Player#playbackrateschange
* @type {EventTarget~Event}
*/
this.trigger('playbackrateschange');
}
}
/**
* 獲取 {@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 {AudioTrackList}
* 當前音軌列表
*
* @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 props = TRACK_TYPES[名稱];
Player.prototype[props.getterName] = function() {
如果(this.tech_){
返回 this.tech_[props.getterName]();
}
//如果我們還沒有 LoadTech_,我們創建 {視頻,音頻,文本} 軌道 _
// 這些將在加載期間傳遞給技術人員
這個[props.privateName] = 這個[props.privateName] ||新道具.ListClass();
返回這個[props.privateName];
};
});
/**
* 獲取或設置 `Player` 的 crossorigin 選項。對於 HTML5 播放器,這
* 在 `<video>` 標籤上設置 `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 值。
* - 設置時未定義
*/
Player.prototype.crossorigin = Player.prototype.crossOrigin;
/**
* 玩家的全球枚舉。
*
* 鍵是玩家 ID,值是 {@link Player}
* 已處置玩家的實例或 `null`。
*
* @type {對象}
*/
Player.players = {};
const navigator = window.navigator;
/*
*播放器實例選項,使用選項浮出水面
* options = Player.prototype.options_
* 在選項中進行更改,不在此處。
*
* @type {對象}
* @私人的
*/
Player.prototype.options_ = {
// 回退技術的默認順序
技術訂單:Tech.defaultTechOrder_,
html5:{},
// 默認不活動超時
不活動超時:2000年
// 默認播放速率
回放率:[],
// 通過添加速率添加播放速率選擇
// 'playbackRates': [0.5, 1, 1.5, 2],
liveui:假的,
// 包含的控制集
孩子們: [
'媒體加載器',
'海報圖片',
'textTrackDisplay',
'加載微調器',
'大播放按鈕',
'liveTracker',
'控制欄',
'錯誤顯示',
'textTrackSettings',
'調整大小管理器'
]、
語言:導航器 && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || '恩',
// 語言環境及其語言翻譯
語言:{},
// 無法播放視頻時顯示的默認消息。
不支持消息:'找不到此媒體的兼容源。',
正常化自動播放:假,
全屏: {
選項: {
navigationUI: '隱藏'
}
},
斷點:{},
回應:假的,
audioOnlyMode:假,
audioPosterMode: false
};
[
/**
* 返回播放器是否處於“結束”狀態。
*
* @return {Boolean} 如果玩家處於結束狀態則為真,否則為假。
* @method 播放器#ended
*/
'結束',
/**
* 返回玩家是否處於“尋找”狀態。
*
* @return {Boolean} 如果玩家處於搜索狀態則為真,否則為假。
* @method Player#seeking
*/
'尋求',
/**
* 返回當前可用媒體的時間範圍
* 尋求。
*
* @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 {number} 當前網絡活動狀態
* @method 播放器#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 {number} 當前播放渲染狀態
* @method 播放器#readyState
*/
'就緒狀態'
].forEach(函數(fn) {
Player.prototype[fn] = function() {
返回 this.techGet_(fn);
};
});
TECH_EVENTS_RETRIGGER.forEach(函數(事件){
Player.prototype[`handleTech${toTitleCase(event)}_`] = function() {
返回 this.trigger(event);
};
});
/**
* 當玩家有初始持續時間和維度信息時觸發
*
* @event Player#loadedmetadata
* @type {EventTarget~Event}
*/
/**
* 當播放器在當前播放位置下載數據時觸發
*
* @event Player#loadeddata
* @type {EventTarget~Event}
*/
/**
*當前播放位置改變時觸發*
* 在播放期間,每 15-250 毫秒觸發一次,具體取決於
* 使用的回放技術。
*
* @event Player#timeupdate
* @type {EventTarget~Event}
*/
/**
* 音量變化時觸發
*
* @event Player#volumechange
* @type {EventTarget~Event}
*/
/**
* 報告玩家是否有可用的插件。
*
* 這不會報告插件是否已經初始化
* 在這個播放器上。為此,[usingPlugin]{@link Player#usingPlugin}。
*
* @method 播放器#hasPlugin
* @param {string} 名稱
* 插件名稱。
*
* @return {布爾值}
* 該播放器是否有可用的請求插件。
*/
/**
* 報告玩家是否按名稱使用插件。
*
* 對於基本插件,這只報告插件是否_曾經_被使用過
* 在此播放器上初始化。
*
* @method 播放器#usingPlugin
* @param {string} 名稱
* 插件名稱。
*
* @return {布爾值}
* 此播放器是否正在使用請求的插件。
*/
Component.registerComponent('Player', Player);
導出默認播放器;