/**
* @file tech.js
*/
從 '../component' 導入組件;
從 '../utils/merge-options.js' 導入 mergeOptions;
從 '../utils/fn.js' 導入 * 作為 Fn;
從 '../utils/log.js' 導入日誌;
從'../utils/time-ranges.js'導入{createTimeRange};
從'../utils/buffer.js'導入{bufferedPercent};
從 '../media-error.js' 導入 MediaError;
從“全局/窗口”導入窗口;
從“全局/文檔”導入文檔;
從 '../utils/obj' 導入 {isPlain};
從 '../tracks/track-types' 導入 * 作為 TRACK_TYPES;
從 '../utils/string-cases.js' 導入 {toTitleCase, toLowerCase};
從 'videojs-vtt.js' 導入 vtt;
從 '../utils/guid.js' 導入 * 作為 Guid;
/**
* 包含如下結構的對象:`{src: 'url', type: 'mimetype'}` 或字符串
* 僅包含 src url。
* * `變種源對象 = {SRC:'http://ex.com/video.mp4',類型:「視頻/MP4」};`
* `var SourceString = 'http://example.com/some-video.mp4';`
*
* @typedef {Object|string} Tech~SourceObject
*
* @property {string} 源代碼
* 源地址
*
* @property {string} 類型
* 源的 mime 類型
*/
/**
* {@link Tech} 用來創建新的 {@link TextTrack} 的函數。
*
* @私人的
*
* @param {Tech} 自我
* Tech 類的一個實例。
*
* @param {string} 種類
* `TextTrack` 類型(字幕、字幕、說明、章節或元數據)
*
* @param {字符串} [標籤]
* 用於識別文本軌道的標籤
*
* @param {字符串} [語言]
* 兩個字母的語言縮寫
*
* @param {對象} [選項={}]
* 具有附加文本軌道選項的對象
*
* @return {TextTrack}
* 創建的文本軌道。
*/
function createTrackHelper(self, kind, label, language, options = {}) {
const tracks = self.textTracks();
options.kind = 種類;
如果(標籤){
options.label = 標籤;
}
如果(語言){
options.language = 語言;
}
options.tech = self;
const track = new TRACK_TYPES.ALL.text.TrackClass(選項);
tracks.addTrack(軌道);
返回軌道;
}
/**
* 這是媒體播放技術控制器的基類,例如
* {@link HTML5}
*
* @extends 組件
*/
類技術擴展組件{
/**
* 創建該技術的一個實例。
*
* @param {對象} [選項]
* 播放器選項的鍵/值存儲。
*
* @param {Component~ReadyCallback} 準備好了
* `HTML5` 技術準備就緒時調用的回調函數。
*/
構造函數(選項={},就緒=函數(){}){
// 我們不希望技術自動報告用戶活動。
// 這是在 addControlsListeners 中手動完成的
options.reportTouchActivity = false;
超級(空,選項,準備就緒);
this.onDurationChange_ = (e) => this.onDurationChange(e);
this.trackProgress_ = (e) => this.trackProgress(e);
this.trackCurrentTime_ = (e) => this.trackCurrentTime(e);
this.stopTrackingCurrentTime_ = (e) => this.stopTrackingCurrentTime(e);
this.disposeSourceHandler_ = (e) => this.disposeSourceHandler(e);
this.queuedHanders_ = new Set();
// 跟踪當前源是否已經播放到
// 實現一個非常有限的 played()
this.hasStarted_ = false;
this.on('播放', function() {
this.hasStarted_ = true;
});
this.on('loadstart', function() {
this.hasStarted_ = false;
});
TRACK_TYPES.ALL.names.forEach((名稱) => {
const props = TRACK_TYPES.ALL[名稱];
如果(選項 && 選項[props.getterName]){
this[props.privateName] = options[props.getterName];
}
});
// 在瀏覽器/技術未報告的情況下手動跟踪進度。
如果(!this.featuresProgressEvents){
這個.manualProgressOn();
}
// 在瀏覽器/技術未報告的情況下手動跟踪時間更新。
如果(!this.featuresTimeupdateEvents){
this.manualTimeUpdatesOn();
}
['文本', '音頻', '視頻'].forEach((track) => {
如果(選項[`native${track}Tracks`] === false){
這個[`featuresNative${track}Tracks`] = false;
}
});
如果(options.nativeCaptions === false || options.nativeTextTracks === false){
this.featuresNativeTextTracks = false;
} else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
this.featuresNativeTextTracks = true;
}
如果(!this.featuresNativeTextTracks){
this.emulateTextTracks();
}
預加載文本軌道 = 選項。預加載文本軌道!== 假的;
this.autoRemoteTextTracks_ = new TRACK_TYPES.ALL.text.ListClass();
this.initTrackListeners();
// 僅在不使用原生控件時開啟組件點擊事件
如果(!options.nativeControlsForTouch){
this.emitTapEvents();
}
如果(這個。構造函數){
this.name_ = this.constructor.name || “未知技術”;
}
}
/**
* 一種特殊的觸發源設置的功能,允許播放器
* 如果玩家或技術人員尚未準備好,則重新觸發。
*
* @fires Tech#sourceset
* @param {string} src 源更改時的源字符串。
*/
triggerSourceset(源){
如果(!this.isReady_){
// 在初始準備時,我們必須觸發源集
// 準備好後 1 毫秒,以便玩家可以觀看。
this.one('ready', () => this.setTimeout(() => this.triggerSourceset(src), 1));
}
/**
* 當源設置在導致媒體元素的技術上時觸發
* 重新加載。
*
* @see {@link 播放器 # 事件:源集}
* @event Tech#sourceset
* @type {EventTarget~Event}
*/
這個。觸發({
來源,
類型:'源集'
});
}
/* 不支持的事件類型的回退
================================================ ============================== */
/**
* 為原生不支持的瀏覽器填充 `progress` 事件。
*
* @see {@link Tech#trackProgress}
*/
manualProgressOn() {
this.on('durationchange', this.onDurationChange_);
this.manualProgress = true;
// 當源開始加載時觸發進度監視
this.one('準備好了', this.trackProgress_);
}
/**
* 關閉在中創建的 `progress` 事件的 polyfill
* {@link Tech#manualProgressOn}
*/
manualProgressOff() {
this.manualProgress = false;
this.stopTrackingProgress();
this.off('durationchange', this.onDurationChange_);
}
/**
* 這用於在緩衝的百分比發生變化時觸發“進度”事件。它
* 設置一個間隔函數,每 500 毫秒調用一次以檢查是否
* 緩衝區結束百分比已更改。
*
* > 此函數由 {@link Tech#manualProgressOn} 調用
*
* @param {EventTarget~Event} 事件
* 導致此運行的“就緒”事件。
*
* @listens Tech#ready
* @fires Tech#progress
*/
跟踪進度(事件){
this.stopTrackingProgress();
this.progressInterval = this.setInterval(Fn.bind(this, function() {
// 除非緩衝量大於上次,否則不觸發
const numBufferedPercent = this.bufferedPercent();
如果(這個。自助百分比 _!== 數字自助百分比) {
/**
*參見{@link Player#progress}
*
* @event 技術#progress
* @type {EventTarget~Event}
*/
this.trigger('進度');
}
this.bufferedPercent_ = numBufferedPercent;
如果(numBufferedPercent === 1){
this.stopTrackingProgress();
}
}), 500);
}
/**
* 通過調用在 `durationchange` 事件上更新我們的內部持續時間
* {@link Tech#duration}。
*
* @param {EventTarget~Event} 事件
* 導致此運行的 `durationchange` 事件。
*
* @listens Tech#durationchange
*/
onDurationChange(事件){
this.duration_ = this.duration();
}
/**
* 獲取並創建用於緩衝的 `TimeRange` 對象。
*
* @return {TimeRange}
* 創建的時間範圍對象。
*/
緩衝的() {
返回 createTimeRange(0, 0);
}
/**
* 獲取當前緩衝的當前視頻的百分比。
*
* @return {數字}
* 一個從 0 到 1 的數字,表示小數點的百分比
* 緩衝的視頻。
*
*/
緩衝百分比(){
返回 bufferedPercent(this.buffered(), this.duration_);
}
/**
* 關閉在中創建的 `progress` 事件的 polyfill
* {@link Tech#manualProgressOn}
* 通過清除設置的間隔來停止手動跟踪進度事件
* {@link Tech#trackProgress}。
*/
stopTrackingProgress() {
this.clearInterval(this.progressInterval);
}
/**
* 為不支持它的瀏覽器填充 `timeupdate` 事件。
*
* @see {@link Tech#trackCurrentTime}
*/
manualTimeUpdatesOn() {
this.manualTimeUpdates = true;
this.on('播放', this.trackCurrentTime_);
this.on('暫停', this.stopTrackingCurrentTime_);
}
/**
* 關閉在中創建的 `timeupdate` 事件的 polyfill
* {@link Tech#manualTimeUpdatesOn}
*/
manualTimeUpdatesOff() {
this.manualTimeUpdates = false;
this.stopTrackingCurrentTime();
this.off('播放', this.trackCurrentTime_);
this.off('暫停', this.stopTrackingCurrentTime_);
}
/**
* 設置一個間隔函數來跟踪當前時間並每隔一段時間觸發 `timeupdate`
* 250 毫秒。
*
* @listens Tech#play
* @triggers Tech#timeupdate
*/
trackCurrentTime() {
如果(this.currentTimeInterval){
this.stopTrackingCurrentTime();
}
this.currentTimeInterval = this.setInterval(函數() {
/**
* 以 250 毫秒的間隔觸發,表示時間正在視頻中流逝。
*
* @event 技術#timeupdate
* @type {EventTarget~Event}
*/
this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
// 42 = 24 fps // Webkit 使用 250 // FF 使用 15
}, 250);
}
/**
* 停止在 {@link Tech#trackCurrentTime} 中創建的間隔函數,以便
* 不再觸發 `timeupdate` 事件。
*
* @listens {技術#暫停}
*/
stopTrackingCurrentTime() {
this.clearInterval(this.currentTimeInterval);
// #1002 - 如果視頻在下一次時間更新發生之前結束,
// 進度條不會一直走到最後
this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
}
/**
* 關閉所有事件 polyfill,清除 `Tech` 的 {@link AudioTrackList},
* {@link VideoTrackList} 和 {@link TextTrackList},並處理此技術。
*
* @fires 組件#dispose
*/
處置(){
// 清除所有軌道,因為我們不能在技術人員之間重複使用它們
this.clearTracks(TRACK_TYPES.NORMAL.names);
// 關閉任何手動進度或時間更新跟踪
如果(this.manualProgress){
這個.manualProgressOff();
}
如果(this.manualTimeUpdates){
this.manualTimeUpdatesOff();
}
super.dispose();
}
/**
* 清除單個 `TrackList` 或一組 `TrackLists` 給定的名稱。
*
* > 注意:沒有源處理程序的技術人員應該在“視頻”的源之間調用它
* & `audio` 曲目。你不想在曲目之間使用它們!
*
* @param {string[]|string} 類型
* 要清除的 TrackList 名稱,有效名稱為 `video`、`audio` 和
*`文本`。
*/
clearTracks(類型){
類型 = [].concat(類型);
// 清除所有軌道,因為我們不能在技術人員之間重複使用它們
types.forEach((type) => {
const list = this[`${type}Tracks`]() || [];
讓我= list.length;
當我 - ) {
const track = 列表[i];
如果(類型==='文本'){
this.removeRemoteTextTrack(track);
}
list.removeTrack(track);
}
});
}
/**
* 刪除通過 addRemoteTextTrack 添加的任何 TextTracks
* 標記為自動垃圾收集
*/
cleanupAutoTextTracks() {
const list = this.autoRemoteTextTracks_ || [];
讓我= list.length;
當我 - ) {
const track = 列表[i];
this.removeRemoteTextTrack(track);
}
}
/**
* 重置技術,這將刪除所有來源並重置內部 readyState。
*
* @抽象的
*/
重置() {}
/**
* 從技術中獲取 `crossOrigin` 的值。
*
* @抽象的
*
* @see {Html5#crossOrigin}
*/
交叉來源(){}
/**
* 在技術上設置 `crossOrigin` 的值。
*
* @抽象的
*
* @param {字符串} 交叉起源值
* @see {Html5#setCrossOrigin}
*/
setCrossOrigin() {}
/**
* 獲取或設置技術錯誤。
*
* @param {MediaError} [錯誤]
* 技術設置錯誤
*
* @return {MediaError|null}
* 技術上的當前錯誤對象,如果沒有則為 null。
*/
錯誤(錯誤){
如果(錯誤!==未定義){
this.error_ = new MediaError(err);
this.trigger('錯誤');
}
返回這個。錯誤_;
}
/**
* 返回當前源已播放的 `TimeRange`。
*
* > 注意:這個實現是不完整的。它不跟踪播放的“TimeRange”。
* 它只檢查源是否播放過。
*
* @return {TimeRange}
* - 單個時間範圍(如果該視頻已播放)
* - 如果不是,則為一組空範圍。
*/
播放(){
如果(this.hasStarted_){
返回 createTimeRange(0, 0);
}
返回創建時間範圍();
}
/**
* 開始播放
*
* @抽象的
*
* @see {Html5#play}
*/
玩() {}
/**
* 設置我們是否正在擦洗
*
* @抽象的
*
* @see {Html5#setScrubbing}
*/
setScrubbing() {}
/**
* 獲取我們是否正在擦洗
*
* @抽象的
*
* @see {Html5#scrubbing}
*/
擦洗(){}
/**
*如果{@link Tech#manualTimeUpdatesOn}是,則會導致手動時間更新
* 之前調用過。
*
* @fires Tech#timeupdate
*/
設置當前時間(){
// 提高手動時間更新的準確性
如果(this.manualTimeUpdates){
/**
* 手動 `timeupdate` 事件。
*
* @event 技術#timeupdate
* @type {EventTarget~Event}
*/
this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
}
}
/**
* 打開 {@link VideoTrackList}、{@link {AudioTrackList} 和
* {@link TextTrackList} 事件。
*
* 這為 `addtrack` 和 `removetrack` 添加了 {@link EventTarget~EventListeners}。
*
* @fires Tech#audiotrackchange
* @fires Tech#videotrackchange
* @fires Tech#texttrackchange
*/
initTrackListeners() {
/**
* 在 Tech {@link AudioTrackList} 上添加或刪除曲目時觸發
*
* @event Tech#audiotrackchange
* @type {EventTarget~Event}
*/
/**
* 在 Tech {@link VideoTrackList} 上添加或刪除曲目時觸發
*
* @event Tech#videotrackchange
* @type {EventTarget~Event}
*/
/**
* 在 Tech {@link TextTrackList} 上添加或刪除曲目時觸發
*
* @event Tech#texttrackchange
* @type {EventTarget~Event}
*/
TRACK_TYPES.NORMAL.names.forEach((名稱) => {
const props = TRACK_TYPES.NORMAL[名稱];
const trackListChanges = () => {
this.trigger(`${name}trackchange`);
};
const tracks = this[props.getterName]();
tracks.addEventListener('removetrack', trackListChanges);
tracks.addEventListener('addtrack', trackListChanges);
this.on('處置', () => {
tracks.removeEventListener('removetrack', trackListChanges);
tracks.removeEventListener('addtrack', trackListChanges);
});
});
}
/**
* 如有必要,使用 vtt.js 模擬 TextTracks
*
* @fires Tech#vttjsloaded
* @fires Tech#vttjserror
*/
addWebVttScript_() {
如果(窗口。WebVTT){
返回;
}
// 最初,Tech.el_ 是 dummy-div 的子級,等待組件系統
// 表示 Tech 已準備就緒,此時 Tech.el_ 是 DOM 的一部分
// 在插入 WebVTT 腳本之前
如果 (document.body.contains(this.el())) {
// 如果可用且未傳入 vtt.js 腳本位置,則通過 require 加載
// 作為一個選項。 novtt 構建會將上面的 require 調用變成一個空對象
// 這將導致這個 if 檢查總是失敗。
如果(!這個。選項 _ ['vtt.js'] && 艾斯普林(VTT)和對象。鍵(VTT)。
this.trigger('vttjsloaded');
返回;
}
// 通過腳本位置選項加載 vtt.js 或沒有位置的 cdn 是
//傳入
const script = document.createElement('腳本');
script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
script.onload = () => {
/**
* 加載 vtt.js 時觸發。
*
* @event Tech#vttjsloaded
* @type {EventTarget~Event}
*/
this.trigger('vttjsloaded');
};
script.onerror = () => {
/**
* 由於錯誤而未加載 vtt.js 時觸發
*
* @event Tech#vttjsloaded
* @type {EventTarget~Event}
*/
this.trigger('vttjserror');
};
this.on('處置', () => {
腳本.onload = null;
script.onerror = null;
});
// 但是還沒有加載,我們在註入之前將它設置為 true,這樣
// 如果它立即加載,我們不會覆蓋注入的 window.WebVTT
window.WebVTT = true;
this.el().parentNode.appendChild(腳本);
}其他{
this.ready(this.addWebVttScript_);
}
}
/**
* 模擬文本軌道
*
*/
emulateTextTracks() {
const tracks = this.textTracks();
const remoteTracks = this.remoteTextTracks();
const handleAddTrack = (e) => tracks.addTrack(e.track);
const handleRemoveTrack = (e) => tracks.removeTrack(e.track);
remoteTracks.on('addtrack', handleAddTrack);
remoteTracks.on('removetrack', handleRemoveTrack);
this.addWebVttScript_();
const updateDisplay = () => this.trigger('texttrackchange');
const textTracksChanges = () => {
更新顯示();
對於(讓我 = 0; 我 < 跟踪。長度; 我 ++){
const track = tracks[i];
track.removeEventListener('cuechange', updateDisplay);
如果(track.mode ==='顯示'){
track.addEventListener('cuechange', updateDisplay);
}
}
};
textTracksChanges();
tracks.addEventListener('change', textTracksChanges);
tracks.addEventListener('addtrack', textTracksChanges);
tracks.addEventListener('removetrack', textTracksChanges);
this.on('處置', function() {
remoteTracks.off('addtrack', handleAddTrack);
remoteTracks.off('removetrack', handleRemoveTrack);
tracks.removeEventListener('change', textTracksChanges);
tracks.removeEventListener('addtrack', textTracksChanges);
tracks.removeEventListener('removetrack', textTracksChanges);
對於(讓我 = 0; 我 < 跟踪。長度; 我 ++){
const track = tracks[i];
track.removeEventListener('cuechange', updateDisplay);
}
});
}
/**
* 創建並返回一個遠程 {@link TextTrack} 對象。
*
* @param {string} 種類
* `TextTrack` 類型(字幕、字幕、說明、章節或元數據)
*
* @param {字符串} [標籤]
* 用於識別文本軌道的標籤
*
* @param {字符串} [語言]
* 兩個字母的語言縮寫
*
* @return {TextTrack}
* 創建的 TextTrack。
*/
addTextTrack(種類,標籤,語言){
如果(!種類){
throw new Error('需要 TextTrack 類型但未提供');
}
返回 createTrackHelper(this, kind, label, language);
}
/**
* 創建一個模擬的 TextTrack 供 addRemoteTextTrack 使用
*
* 這旨在被繼承自的類覆蓋
* 技術以創建本機或自定義 TextTrack。
*
* @param {Object} 選項
* 該對象應包含用於初始化 TextTrack 的選項。
*
* @param {string} [options.kind]
* `TextTrack` 類型(字幕、字幕、說明、章節或元數據)。
*
* @param {string} [選項.標籤]。
* 用於識別文本軌道的標籤
*
* @param {string} [選項.語言]
* 兩個字母的語言縮寫。
*
* @return {HTMLTrackElement}
* 創建的軌道元素。
*/
createRemoteTextTrack(選項){
const track = mergeOptions(選項, {
技術:這個
});
返回新的 TRACK_TYPES.REMOTE.remoteTextEl.TrackClass(track);
}
/**
* 創建一個遠程文本軌道對象並返回一個 html 軌道元素。
*
* > 注意:這可以是模擬的 {@link HTMLTrackElement} 或本機的。
*
* @param {Object} 選項
* 有關更多詳細屬性,請參閱 {@link Tech#createRemoteTextTrack}。
*
* @param {boolean} [manualCleanup=true]
* - 如果為 false:TextTrack 將自動從視頻中刪除
* 每當源更改時的元素
* - 當為真時:必須手動清理 TextTrack
*
* @return {HTMLTrackElement}
* 一個 Html 軌道元素。
*
* @deprecated 這個函數的默認功能是等價的
* 將來改為“manualCleanup=false”。手動清除參數將
* 也被刪除。
*/
addRemoteTextTrack(選項 = {},manualCleanup){
const htmlTrackElement = this.createRemoteTextTrack(選項);
如果(手動清理!== 真正的 && 手動清理!== 假){
//棄用警告
log.warn('在未將“manualCleanup”參數顯式設置為“true”的情況下調用 addRemoteTextTrack 已被棄用,在未來版本的 video.js 中默認為“false”');
手動清理 = 真;
}
// 將 HTMLTrackElement 和 TextTrack 存儲到遠程列表
this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
this.remoteTextTracks().addTrack(htmlTrackElement.track);
如果(手動清理!==真){
// 如果 TextTrackList 不存在則創建它
this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track));
}
返回 htmlTrackElement;
}
/**
* 從遠程 `TextTrackList` 中刪除遠程文本軌道。
*
* @param {TextTrack} 軌道
* `TextTrack` 從 `TextTrackList` 中移除
*/
removeRemoteTextTrack(軌道){
const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
// 從遠程列表中移除 HTMLTrackElement 和 TextTrack
this.remoteTextTrackEls().removeTrackElement_(trackElement);
this.remoteTextTracks().removeTrack(track);
this.autoRemoteTextTracks_.removeTrack(track);
}
/**
* 獲取 W3C 媒體指定的可用媒體播放質量指標
* 播放質量 API。
*
* @see [規範]{@link https://wicg.github.io/media-playback-quality}
*
* @return {對象}
* 具有支持的媒體播放質量指標的對象
*
* @抽象的
*/
getVideoPlaybackQuality() {
返回 {};
}
/**
* 嘗試創建一個始終位於其他窗口之上的浮動視頻窗口
* 以便用戶在與其他人互動時可以繼續消費媒體
* 內容網站或他們設備上的應用程序。
*
* @see [規範]{@link https://wicg.github.io/picture-in-picture}
*
* @return {承諾|未定義}
* 如果瀏覽器支持,承諾帶有畫中畫窗口
* 承諾(或一個作為選項傳入)。它返回未定義
* 否則。
*
* @抽象的
*/
請求畫中畫(){
const PromiseClass = this.options_.Promise || window.Promise;
如果(承諾類){
返回 PromiseClass.reject();
}
}
/**
* 一種檢查“disablePictureInPicture”<video> 屬性值的方法。
* 默認為 true,因為如果技術不支持 pip,則應將其視為已禁用
*
* @抽象的
*/
禁用畫中畫(){
返回真;
}
/**
* 設置或取消設置 'disablePictureInPicture' <video> 屬性的方法。
*
* @抽象的
*/
setDisablePictureInPicture() {}
/**
* 使用 requestAnimationFrame 的 requestVideoFrameCallback 回退實現
*
* @param {函數} cb
* @return {number} 請求ID
*/
requestVideoFrameCallback(cb) {
const id = Guid.newGUID();
如果(!this.isReady_ || this.paused()){
this.queuedHanders_.add(id);
this.one('播放', () => {
如果(this.queuedHanders_.has(id)){
this.queuedHanders_.delete(id);
cb();
}
});
}其他{
this.requestNamedAnimationFrame(id, cb);
}
返回ID;
}
/**
* cancelVideoFrameCallback 的回退實現
*
* @param {number} id 要取消的回調id
*/
cancelVideoFrameCallback(id) {
如果(this.queuedHanders_.has(id)){
this.queuedHanders_.delete(id);
}其他{
this.cancelNamedAnimationFrame(id);
}
}
/**
* 一種從 `Tech` 設置海報的方法。
*
* @抽象的
*/
設置海報(){}
/**
* 一種檢查 'playsinline' <video> 屬性是否存在的方法。
*
* @抽象的
*/
playsinline() {}
/**
* 設置或取消設置 'playsinline' <video> 屬性的方法。
*
* @抽象的
*/
setPlaysinline() {}
/**
* 嘗試強制覆蓋本機音軌。
*
* @param {boolean} override - 如果設置為 true 原生音頻將被覆蓋,
*否則可能會使用本機音頻。
*
* @抽象的
*/
overrideNativeAudioTracks() {}
/**
* 嘗試強制覆蓋本機視頻軌道。
*
* @param {boolean} override - 如果設置為 true 原生視頻將被覆蓋,
*否則可能會使用原生視頻。
*
* @抽象的
*/
overrideNativeVideoTracks() {}
/*
* 檢查技術是否可以支持給定的 mime 類型。
*
* 基礎技術不支持任何類型,但源處理程序可能
* 覆蓋這個。
*
* @param {string} 類型
* 用於檢查支持的 mimetype
*
* @return {字符串}
* 'probably', 'maybe' 或空字符串
*
* @see [規範]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
*
* @抽象的
*/
canPlayType() {
返回 '';
}
/**
* 檢查該技術是否支持該類型。
*
* 基礎技術不支持任何類型,但源處理程序可能
* 覆蓋這個。
*
* @param {string} 類型
* 要檢查的媒體類型
* @return {string} 返回原生視頻元素的響應
*/
靜態 canPlayType() {
返回 '';
}
/**
* 檢查技術是否可以支持給定的來源
*
* @param {對象} srcObj
* 源對象
* @param {Object} 選項
* 傳遞給技術的選項
* @return {string} 'probably', 'maybe', or '' (空字符串)
*/
靜態 canPlaySource(srcObj,選項){
返回 Tech.canPlayType(srcObj.type);
}
/*
* 返回參數是否為 Tech。
* 可以傳遞類似 `Html5` 的類或類似 `player.tech_` 的實例
*
* @param {Object} 組件
* 要檢查的項目
*
* @return {布爾值}
* 是否是技術
* - 如果是技術則為真
* - 如果不是則為假
*/
靜態技術(組件){
返回 component.prototype instanceof Tech ||
技術組件實例 ||
組件 === 技術;
}
/**
* 將 `Tech` 註冊到 videojs 的共享列表中。
*
* @param {string} 名稱
* 要註冊的 `Tech` 的名稱。
*
* @param {Object} 技術
* 要註冊的 `Tech` 類。
*/
靜態註冊技術(名稱,技術){
如果(!Tech.techs_){
Tech.techs_ = {};
}
如果(!Tech.isTech(技術)){
throw new Error(`Tech ${name} 必須是 Tech`);
}
如果(!Tech.canPlayType){
throw new Error('Techs must have a static canPlayType method on them');
}
如果(!Tech.canPlaySource){
throw new Error('Techs must have a static canPlaySource method on them');
}
名稱 = toTitleCase(名稱);
Tech.techs_[名稱] = 技術;
Tech.techs_[toLowerCase(name)] = tech;
如果(名稱!=='技術'){
// 駝峰式大小寫在 techOrder 中使用的 techName
Tech.defaultTechOrder_.push(name);
}
返回技術;
}
/**
* 按名稱從共享列表中獲取 `Tech`。
*
* @param {string} 名稱
* 要獲取的 Tech 的 `camelCase` 或 `TitleCase` 名稱
*
* @return {技術|未定義}
* 如果沒有具有請求名稱的技術,則為 `Tech` 或未定義。
*/
靜態getTech(名稱){
如果(!名稱){
返回;
}
if (Tech.techs_ && Tech.techs_[name]) {
返回 Tech.techs_[名稱];
}
名稱 = toTitleCase(名稱);
如果(窗口 && window.videojs && window.videojs [名稱]){
log.warn(`${name} 技術在應該使用 videojs.registerTech(name, tech) 註冊時被添加到 videojs 對像中`);
返回 window.videojs[名稱];
}
}
}
/**
* 獲取 {@link VideoTrackList}
*
* @returns {VideoTrackList}
* @method Tech.prototype.videoTracks
*/
/**
*獲取{@link AudioTrackList}
*
* @returns {AudioTrackList}
* @method Tech.prototype.audioTracks
*/
/**
*獲取{@link TextTrackList}
*
* @returns {TextTrackList}
* @method Tech.prototype.textTracks
*/
/**
* 獲取遠程元素 {@link TextTrackList}
*
* @returns {TextTrackList}
* @method Tech.prototype.remoteTextTracks
*/
/**
* 獲取遠程元素 {@link HtmlTrackElementList}
*
* @returns {HtmlTrackElementList}
* @method Tech.prototype.remoteTextTrackEls
*/
TRACK_TYPES.ALL.names.forEach(函數(名稱){
const props = TRACK_TYPES.ALL[名稱];
Tech.prototype[props.getterName] = function() {
這個[props.privateName] = 這個[props.privateName] ||新道具.ListClass();
返回這個[props.privateName];
};
});
/**
*相關文本軌道列表
*
* @type {TextTrackList}
* @私人的
* @property Tech#textTracks_
*/
/**
* 相關音軌列表。
*
* @type {AudioTrackList}
* @私人的
* @property Tech#audioTracks_
*/
/**
* 相關視頻軌道列表。
*
* @type {VideoTrackList}
* @私人的
* @property Tech#videoTracks_
*/
/**
* 布爾值表示 `Tech` 是否支持音量控制。
*
* @type {布爾}
* @默認
*/
Tech.prototype.featuresVolumeControl = true;
/**
* 表示 `Tech` 是否支持靜音音量的布爾值。
*
* @type {布爾值}
* @默認
*/
Tech.prototype.featuresMuteControl = true;
/**
* 布爾值,指示 `Tech` 是否支持全屏調整大小控制。
* 使用請求全屏調整插件大小會重新加載插件
*
* @type {布爾}
* @默認
*/
Tech.prototype.featuresFullscreenResize = false;
/**
* 表示 `Tech` 是否支持改變視頻播放速度的布爾值
* 播放。範例:
* - 設置播放器以 2 倍(兩倍)的速度播放
* - 設置播放器播放速度為 0.5 倍(一半)
*
* @type {布爾}
* @默認
*/
Tech.prototype.featuresPlaybackRate = false;
/**
* 布爾值表示 `Tech` 是否支持 `progress` 事件。這是目前
* 不是由 video-js-swf 觸發的。這將用於確定是否
* {@link Tech#manualProgressOn} 應該被調用。
*
* @type {布爾}
* @默認
*/
Tech.prototype.featuresProgressEvents = false;
/**
* 表示 `Tech` 是否支持 `sourceset` 事件的布爾值。
*
* 技術人員應將其設置為“true”,然後使用 {@link Tech#triggerSourceset}
* 在獲取後最早時間觸發 {@link 技術 # 事件:源集}
* 一個新的來源。
*
* @type {布爾}
* @默認
*/
Tech.prototype.featuresSourceset = false;
/**
* 表示 `Tech` 是否支持 `timeupdate` 事件的布爾值。這是目前
* 不是由 video-js-swf 觸發的。這將用於確定是否
* {@link Tech#manualTimeUpdates} 應該被調用。
*
* @type {布爾}
* @默認
*/
Tech.prototype.featuresTimeupdateEvents = false;
/**
* 布爾值,表示 `Tech` 是否支持原生的 `TextTrack`。
* 如果瀏覽器支持,這將幫助我們與原生 `TextTrack` 集成。
*
* @type {布爾}
* @默認
*/
Tech.prototype.featuresNativeTextTracks = false;
/**
* 表示 `Tech` 是否支持 `requestVideoFrameCallback` 的布爾值。
*
* @type {布爾}
* @默認
*/
Tech.prototype.featuresVideoFrameCallback = false;
/**
* 為想要使用源處理程序模式的技術人員提供的功能性混合。
* 源處理程序是用於處理特定格式的腳本。
* 源處理程序模式用於自適應格式(HLS、DASH)
* 手動加載視頻數據並將其送入源緩衝區(媒體源擴展)
* 示例:帶有來源處理程序的技術。呼叫(MyTech); `
*
* @param {技術} _技術
* 添加源處理函數的技術。
*
* @mixes Tech~SourceHandlerAdditions
*/
Tech.withSourceHandlers = function(_Tech) {
/**
* 註冊一個源處理器
*
* @param {函數}處理程序
* 源處理器類
*
* @param {number} [索引]
* 在以下索引處註冊
*/
_Tech.registerSourceHandler = 函數(處理程序,索引){
讓處理程序 = _Tech.sourceHandlers;
如果(!處理程序){
handlers = _Tech.sourceHandlers = [];
}
如果(索引===未定義){
// 添加到列表的末尾
index = handlers.length;
}
handlers.splice(index, 0, handler);
};
/**
* 檢查技術是否可以支持給定的類型。還檢查
* Techs sourceHandlers。
*
* @param {string} 類型
* 要檢查的 mimetype。
*
* @return {字符串}
*“可能”、“也許”或“”(空字符串)
*/
_Tech.canPlayType = 函數(類型){
常量處理程序 = _Tech.sourceHandlers || [];
讓可以;
對於(讓我 = 0; 我 < 處理器。長度; 我 ++){
can = handlers[i].canPlayType(類型);
如果能) {
退貨罐;
}
}
返回 '';
};
/**
* 返回支持該源的第一個源處理程序。
*
* 去做:回答問題:“可能”應該優先於“也許”
*
* @param {Tech~SourceObject} 來源
* 源對象
*
* @param {Object} 選項
* 傳遞給技術的選項
*
* @return {SourceHandler|null}
* 支持源的第一個源處理程序,如果為 null
* 沒有 SourceHandler 支持源
*/
_Tech.selectSourceHandler = 函數(源,選項){
常量處理程序 = _Tech.sourceHandlers || [];
讓可以;
對於(讓我 = 0; 我 < 處理器。長度; 我 ++){
can = handlers[i].canHandleSource(source, options);
如果能) {
返回處理程序[i];
}
}
返回空值;
};
/**
* 檢查技術是否可以支持給定的來源。
*
* @param {Tech~SourceObject} srcObj
* 源對象
*
* @param {Object} 選項
* 傳遞給技術的選項
*
* @return {字符串}
*“可能”、“也許”或“”(空字符串)
*/
_Tech.canPlaySource = 函數(srcObj,選項){
const sh = _Tech.selectSourceHandler(srcObj, options);
如果(噓){
返回 sh.canHandleSource(srcObj,選項);
}
返回 '';
};
/**
* 使用源處理程序時,更喜歡它的實現
* 通常由技術人員提供的任何功能。
*/
const 可延遲 = [
'可尋',
'尋求',
'期間'
];
/**
* 圍繞 {@link Tech#seekable} 的包裝器,它將調用 `SourceHandler` 的 seekable
* 函數(如果存在),回退到 Techs 可搜索函數。
*
* @method _Tech.seekable
*/
/**
* 圍繞 {@link Tech#duration} 的包裝器,它將調用 `SourceHandler` 的持續時間
* 函數(如果存在),否則它將回退到技術持續時間函數。
*
* @method _Tech.duration
*/
deferrable.forEach(函數(fnName){
const originalFn = this[fnName];
如果(原始類型 FN!== '功能') {
返回;
}
這個 [fnName] = 函數 () {
如果(this.sourceHandler_ && this.sourceHandler_[fnName]){
返回 this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
}
返回 originalFn.apply(this, arguments);
};
}, _Tech.prototype);
/**
* 創建一個使用源對象設置源的函數
* 和源處理程序。
* 除非找到源處理程序,否則永遠不應調用。
*
* @param {Tech~SourceObject} 來源
* 帶有 src 和 type 鍵的源對象
*/
_Tech.prototype.setSource = 函數(來源){
讓 sh = _Tech.selectSourceHandler(source, this.options_);
如果(!sh){
// 當不支持的源出現時回退到本地源處理程序
//故意設置
如果(_Tech.nativeSourceHandler){
sh = _Tech.nativeSourceHandler;
}其他{
log.error('沒有找到當前源的源處理程序。');
}
}
// 處置任何現有的源處理程序
this.disposeSourceHandler();
this.off('dispose', this.disposeSourceHandler_);
如果(SH!== _ 技術。本地化處理程序){
this.currentSource_ = 來源;
}
this.sourceHandler_ = sh.handleSource(source, this, this.options_);
this.one('dispose', this.disposeSourceHandler_);
};
/**
* 在處理 Tech 時清理所有現有的 SourceHandlers 和偵聽器。
*
* @listens Tech#dispose
*/
_Tech.prototype.disposeSourceHandler = function() {
// 如果我們有一個源並得到另一個
// 然後我們加載新的東西
// 而不是清除我們當前的所有曲目
如果(this.currentSource_){
this.clearTracks(['音頻', '視頻']);
this.currentSource_ = null;
}
// 總是清理自動文本軌道
this.cleanupAutoTextTracks();
如果(this.sourceHandler_){
如果(this.sourceHandler_.dispose){
this.sourceHandler_.dispose();
}
this.sourceHandler_ = null;
}
};
};
// 基礎 Tech 類需要註冊為組件。這是唯一的
// 可以註冊為組件的技術。
Component.registerComponent('技術', 技術);
Tech.registerTech('技術', 技術);
/**
* 應添加到玩家 techOrder 的技術列表
*
* @私人的
*/
Tech.defaultTechOrder_ = [];
導出默認技術;