從 './component.js' 導入組件;
從 './utils/merge-options.js' 導入 mergeOptions;
從“全局/文檔”導入文檔;
import * as browser from './utils/browser.js';
從“全局/窗口”導入窗口;
從 './utils/fn.js' 導入 * 作為 Fn;
常量默認值 = {
跟踪閾值:20,
生活公差:+15
};
/*
當我們處於實時邊緣時進行跟踪,以及用於實時播放的其他助手 */
/**
* 用於檢查實時時間並確定播放器何時播放的類
* 位於活邊或活邊後面。
*/
類 LiveTracker 擴展組件 {
/**
* 創建此類的一個實例。
*
* @param {Player} 播放器
* 此類應附加到的 `Player`。
*
* @param {對象} [選項]
* 播放器選項的鍵/值存儲。
*
* @param {number} [options.trackingThreshold=20]
* 活動窗口的秒數(seekableEnd - seekableStart)
* 在 liveui 顯示之前需要有媒體。
*
* @param {number} [options.liveTolerance=15]
* 我們必須落後於直播的秒數
* 之前我們將被視為非直播。請注意,這只會
* 在現場比賽時使用。這允許大的可搜索端
* 更改不會影響我們是否活著。
*/
構造函數(播放器,選項){
// LiveTracker 不需要元素
const options_ = mergeOptions(defaults, options, {createEl: false});
超級(玩家,選項_);
this.handleVisibilityChange_ = (e) => this.handleVisibilityChange(e);
this.trackLiveHandler_ = () => this.trackLive_();
this.handlePlay_ = (e) => this.handlePlay(e);
this.handleFirstTimeupdate_ = (e) => this.handleFirstTimeupdate(e);
this.handleSeeked_ = (e) => this.handleSeeked(e);
this.seekToLiveEdge_ = (e) => this.seekToLiveEdge(e);
這個.reset_();
this.on(this.player_, 'durationchange', (e) => this.handleDurationchange(e));
// 我們應該嘗試將 canplay 上的跟踪切換為原生播放引擎,例如 Safari
// 在那之前可能沒有諸如 seekableEnd 之類的正確值
this.on(this.player_, 'canplay', () => this.toggleTracking());
// 如果文檔被隱藏,我們不需要跟踪實時回放,
// 同樣,跟踪文檔何時被隱藏可以
// 導致 CPU 峰值並最終導致 IE11 上的頁面崩潰。
if (browser.IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
this.on(document, 'visibilitychange', this.handleVisibilityChange_);
}
}
/**
* 根據文檔可見性切換跟踪
*/
handleVisibilityChange() {
如果(this.player_.duration()!==無限){
返回;
}
如果(文件。隱藏){
this.stopTracking();
}其他{
這個.startTracking();
}
}
/**
* 尋找結束變化時的所有跟踪功能
* 並追踪我們應該到達終點的距離
*/
trackLive_() {
const seekable = this.player_.seekable();
// 跳過未定義的可尋址
如果(!seekable ||!seekable.length){
返回;
}
const newTime = Number(window.performance.now().toFixed(4));
const deltaTime = this.lastTime_ === -1 ?0 : (newTime - this.lastTime_) / 1000;
this.lastTime_ = newTime;
this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime;
const liveCurrentTime = this.liveCurrentTime();
const currentTime = this.player_.currentTime();
// 如果有任何真值,我們就落後於 live
// 1. 播放器暫停
// 2. 用戶尋找到距離現場 2 秒的位置
// 3. live 和 current 時間差比較大
// liveTolerance 默認為 15s
讓 isBehind = this.player_.paused() || this.seekedBehindLive_ ||
Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance;
// 我們不能落後 if
// 1. 直到我們還沒有看到時間更新
// 2. liveCurrentTime 為 Infinity,這發生在 Android 和 Native Safari 上
如果(!this.timeupdateSeen_ || liveCurrentTime === Infinity){
落後=假;
}
如果(isBehind !== this.behindLiveEdge_){
this.behindLiveEdge_ = isBehind;
this.trigger('liveedgechange');
}
}
/**
* 處理播放器上的 durationchange 事件
* 並相應地開始/停止跟踪。
*/
handleDurationchange() {
this.toggleTracking();
}
/**
* 開始/停止跟踪
*/
切換跟踪(){
if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) {
如果(這個.player_.options_.liveui){
this.player_.addClass('vjs-liveui');
}
這個.startTracking();
}其他{
this.player_.removeClass('vjs-liveui');
this.stopTracking();
}
}
/**
* 開始追踪直播回放
*/
開始跟踪(){
如果(this.isTracking()){
返回;
}
// 如果我們還沒有看到時間更新,我們需要檢查是否播放
// 在此組件開始跟踪之前開始。這很常見
// 當使用自動播放時。
如果(!this.timeupdateSeen_){
this.timeupdateSeen_ = this.player_.hasStarted();
}
this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, Fn.UPDATE_REFRESH_INTERVAL);
這個.trackLive_();
this.on(this.player_, ['播放', '暫停'], this.trackLiveHandler_);
如果(!this.timeupdateSeen_){
this.one(this.player_, 'play', this.handlePlay_);
this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
}其他{
this.on(this.player_, 'seeked', this.handleSeeked_);
}
}
/**
* 如果播放器尚未播放,則處理播放器的第一次更新
* 實時跟踪器開始跟踪時。
*/
handleFirstTimeupdate() {
this.timeupdateSeen_ = true;
this.on(this.player_, 'seeked', this.handleSeeked_);
}
/**
*跟踪搜索開始的時間,並聽取搜索
* 找到搜索結束的地方。
*/
handleSeeked() {
const timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime());
this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2;
this.nextSeekedFromUser_ = false;
這個.trackLive_();
}
/**
* 處理播放器的第一次播放,並確保我們尋找
* 就在現場邊緣。
*/
handlePlay() {
this.one(this.player_, 'timeupdate', this.seekToLiveEdge_);
}
/**
* 停止跟踪,並將所有內部變量設置為
* 它們的初始值。
*/
重置_() {
this.lastTime_ = -1;
this.pastSeekEnd_ = 0;
this.lastSeekEnd_ = -1;
this.behindLiveEdge_ = true;
this.timeupdateSeen_ = false;
this.seekedBehindLive_ = false;
this.nextSeekedFromUser_ = false;
this.clearInterval(this.trackingInterval_);
this.trackingInterval_ = null;
this.off(this.player_, ['播放', '暫停'], this.trackLiveHandler_);
this.off(this.player_, 'seeked', this.handleSeeked_);
this.off(this.player_, 'play', this.handlePlay_);
this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate_);
this.off(this.player_, 'timeupdate', this.seekToLiveEdge_);
}
/**
* 下一個搜索事件來自用戶。這意味著任何尋求
* > 直播落後 2 秒將被視為真正落後直播
* liveTolerance 將被忽略。
*/
nextSeekedFromUser() {
this.nextSeekedFromUser_ = true;
}
/**
* 停止跟踪直播回放
*/
停止跟踪(){
如果(!this.isTracking()){
返回;
}
這個.reset_();
this.trigger('liveedgechange');
}
/**
* 幫助玩家獲得可搜索的結局
* 這樣我們就不必到處進行空檢查
*
* @return {數字}
* 最遠可尋的終點或無窮大。
*/
seekableEnd() {
const seekable = this.player_.seekable();
const seekableEnds = [];
讓我=可尋找的?可搜索的長度:0;
當我 - ) {
seekableEnds.push(seekable.end(i));
}
// 獲取排序後最遠的可搜索端,或者如果沒有
// 默認為無窮大
返回 seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] :無窮;
}
/**
* 幫助玩家尋找起點
* 這樣我們就不必到處進行空檢查
*
* @return {數字}
* 最早的可搜索開始或 0。
*/
seekableStart() {
const seekable = this.player_.seekable();
const seekableStarts = [];
讓我=可尋找的?可搜索的長度:0;
當我 - ) {
seekableStarts.push(seekable.start(i));
}
// 獲取排序後的第一個可搜索的開始,或者如果沒有
// 默認為 0
返回 seekableStarts.length ? seekableStarts.sort()[0] :0;
}
/**
* 獲取實時窗口又名
* seekable 開始和之間的時間量
* 直播當前時間。
*
* @return {數字}
* 可搜索的秒數
*現場視頻。
*/
活窗(){
const liveCurrentTime = this.liveCurrentTime();
// 如果 liveCurrenTime 是 Infinity 那麼我們根本就沒有 liveWindow
如果(liveCurrentTime === Infinity){
返回 0;
}
返回 liveCurrentTime - this.seekableStart();
}
/**
* 判斷播放器是否在線,只檢查這個組件是否
* 是否跟踪實時回放
*
* @return {布爾值}
* liveTracker是否正在跟踪
*/
isLive() {
返回 this.isTracking();
}
/**
* 判斷currentTime是否處於實時邊緣,不會落後
* 在每個 seekableendchange 上
*
* @return {布爾值}
* 播放是否處於現場邊緣
*/
在LiveEdge(){
返回 !this.behindLiveEdge();
}
/**
* 得到我們期望的實時時間
*
* @return {數字}
* 預計直播當前時間
*/
liveCurrentTime() {
返回 this.pastSeekEnd() + this.seekableEnd();
}
/**
* seekable 結束後經過的秒數
*改變了。一旦可搜索端發生變化,這將重置為 0。
*
* @return {數字}
* 超過當前可搜索結束的秒數
*/
過去搜索結束(){
const seekableEnd = this.seekableEnd();
if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
this.pastSeekEnd_ = 0;
}
this.lastSeekEnd_ = seekableEnd;
返回 this.pastSeekEnd_;
}
/**
* 如果我們目前落後於實時邊緣,又名 currentTime 將是
* 落後於 seekableendchange
*
* @return {布爾值}
* 如果我們落後於實時邊緣
*/
behindLiveEdge() {
返回 this.behindLiveEdge_;
}
/**
* 實時跟踪器當前是否正在跟踪。
*/
是跟踪(){
返回類型 this.trackingInterval_ === 'number';
}
/**
* 如果我們落後於實時邊緣,則尋求實時邊緣
*/
seekToLiveEdge() {
this.seekedBehindLive_ = false;
如果(this.atLiveEdge()){
返回;
}
this.nextSeekedFromUser_ = false;
this.player_.currentTime(this.liveCurrentTime());
}
/**
* 處理 liveTracker
*/
處置(){
this.off(文檔, 'visibilitychange', this.handleVisibilityChange_);
this.stopTracking();
super.dispose();
}
}
Component.registerComponent('LiveTracker', LiveTracker);
導出默認的 LiveTracker;