/**
* @file seek-bar.js
*/
從'../../slider/slider.js'導入滑塊;
從 '../../component.js' 導入組件;
從 '../../utils/browser.js' 導入 {IS_IOS, IS_ANDROID};
import * as Dom from '../../utils/dom.js';
從 '../../utils/fn.js' 導入 * 作為 Fn;
從 '../../utils/format-time.js' 導入格式時間;
從'../../utils/promise'導入{silencePromise};
從“鍵碼”導入鍵碼;
從“全局/文檔”導入文檔;
導入'./load-progress-bar.js';
導入'./play-progress-bar.js';
導入 './mouse-time-display.js';
// `step*` 函數移動時間軸的秒數。
const STEP_SECONDS = 5;
// PgUp/PgDown 移動時間線的 STEP_SECONDS 倍數。
const PAGE_KEY_MULTIPLIER = 12;
/**
* 為進度條尋找 bar 和 container。使用 {@link PlayProgressBar}
* 作為它的“欄”。
*
* @extends 滑塊
*/
類 SeekBar 擴展滑塊 {
/**
* 創建此類的一個實例。
*
* @param {Player} 播放器
* 此類應附加到的 `Player`。
*
* @param {對象} [選項]
* 播放器選項的鍵/值存儲。
*/
構造函數(播放器,選項){
超級(播放器,選項);
this.setEventHandlers_();
}
/**
* 設置事件處理程序
*
* @私人的
*/
setEventHandlers_() {
this.update_ = Fn.bind(this, this.update);
this.update = Fn.throttle(this.update_, Fn.UPDATE_REFRESH_INTERVAL);
this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
如果(這個.player_.liveTracker){
this.on(this.player_.liveTracker, 'liveedgechange', this.update);
}
// 播放時,讓我們確保順利更新播放進度條
// 通過間隔
this.updateInterval = null;
this.enableIntervalHandler_ = (e) => this.enableInterval_(e);
this.disableIntervalHandler_ = (e) => this.disableInterval_(e);
this.on(this.player_, ['playing'], this.enableIntervalHandler_);
this.on(this.player_, ['結束', '暫停', '等待'], this.disableIntervalHandler_);
// 如果文檔被隱藏,我們不需要更新播放進度,
// 同樣,這會導致 CPU 峰值並最終導致 IE11 上的頁面崩潰。
如果(文檔中的'隱藏'&&文檔中的'visibilityState'){
this.on(document, 'visibilitychange', this.toggleVisibility_);
}
}
toggleVisibility_(e) {
如果(document.visibilityState ==='隱藏'){
this.cancelNamedAnimationFrame('SeekBar#update');
this.cancelNamedAnimationFrame('Slider#update');
this.disableInterval_(e);
}其他{
如果 (!this.player_.ended() && !this.player_.paused()) {
this.enableInterval_();
}
// 我們剛剛切換回頁面,可能有人在看,所以,盡快更新
這個.更新();
}
}
enableInterval_() {
如果 (this.updateInterval) {
返回;
}
this.updateInterval = this.setInterval(this.update, Fn.UPDATE_REFRESH_INTERVAL);
}
disableInterval_(e) {
if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== '結束') {
返回;
}
如果(!this.updateInterval){
返回;
}
this.clearInterval(this.updateInterval);
this.updateInterval = null;
}
/**
* 創建 `Component` 的 DOM 元素
*
* @return {元素}
* 創建的元素。
*/
創建El() {
返回 super.createEl('div', {
類名:'vjs-progress-holder'
},{
'aria-label': this.localize('進度條')
});
}
/**
* 此函數更新了播放進度條和輔助功能
* 傳入的任何內容的屬性。
*
* @param {EventTarget~Event} [事件]
* 導致此運行的 `timeupdate` 或 `ended` 事件。
*
* @listens Player#timeupdate
*
* @return {數字}
* 當前百分比為 0-1 之間的數字
*/
更新(事件){
// 選項卡隱藏時忽略更新
如果(document.visibilityState ==='隱藏'){
返回;
}
const percent = super.update();
this.requestNamedAnimationFrame('SeekBar#update', () => {
const currentTime = this.player_.ended() ?
this.player_.duration() : this.getCurrentTime_();
const liveTracker = this.player_.liveTracker;
讓持續時間 = this.player_.duration();
如果(liveTracker && liveTracker.isLive()){
duration = this.player_.liveTracker.liveCurrentTime();
}
如果(this.percent_!==百分比){
// 進度條的機器可讀值(完成百分比)
this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
this.percent_ = 百分比;
}
if (this.currentTime_ !== currentTime || this.duration_ !== duration) {
// 進度條的人類可讀值(時間完成)
這個.el_.setAttribute(
'詠嘆調值文本',
這個。本地化(
'進度條計時:currentTime={1} duration={2}',
[格式時間(當前時間,持續時間),
格式時間(持續時間,持續時間)],
“{1},共 {2}”
)
);
this.currentTime_ = currentTime;
this.duration_ = 持續時間;
}
// 使用當前時間更新進度條時間工具提示
如果(這個。酒吧){
this.bar.update(Dom.getBoundingClientRect(this.el()), this.getProgress());
}
});
回報率;
}
/**
* 防止 liveThreshold 導致 seeks 看起來像他們
* 從用戶的角度來看並沒有發生。
*
* @param {number} ct
* 當前時間尋求
*/
userSeek_(ct) {
如果 (this.player_.liveTracker && this.player_.liveTracker.isLive()) {
this.player_.liveTracker.nextSeekedFromUser();
}
這個.player_.currentTime(ct);
}
/**
* 獲取當前時間的值,但允許平滑擦洗,
* 當玩家跟不上時。
*
* @return {數字}
* 要顯示的當前時間值
*
* @私人的
*/
getCurrentTime_() {
返回(this.player_.scrubbing())?
this.player_.getCache().currentTime :
this.player_.currentTime();
}
/**
* 獲取到目前為止播放的媒體百分比。
*
* @return {數字}
* 目前播放的媒體百分比(0 到 1)。
*/
getPercent() {
const currentTime = this.getCurrentTime_();
讓百分比;
const liveTracker = this.player_.liveTracker;
如果(liveTracker && liveTracker.isLive()){
百分比 = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow();
// 防止百分比在實時邊緣發生變化
如果(liveTracker.atLiveEdge()){
百分比 = 1;
}
}其他{
percent = currentTime / this.player_.duration();
}
回報率;
}
/**
*在搜索欄上處理鼠標
*
* @param {EventTarget~Event} 事件
* 導致它運行的 `mousedown` 事件。
*
* @listens mousedown
*/
handleMouseDown(事件){
如果(!Dom.isSingleLeftClick(事件)){
返回;
}
// 停止事件傳播以防止 progress-control.js 中的雙重觸發
事件.stopPropagation();
this.videoWasPlaying = !this.player_.paused();
這個.player_.pause();
super.handleMouseDown(事件);
}
/**
* 處理鼠標在搜索欄上的移動
*
* @param {EventTarget~Event} 事件
* 導致它運行的 `mousemove` 事件。
* @param {boolean} mouseDown 這是一個標誌,如果直接調用 `handleMouseMove` 應該設置為 true。它允許我們跳過那些在按下鼠標時不應該發生但在常規鼠標移動處理程序中應該發生的事情。默認為假
*
* @listens 鼠標移動
*/
handleMouseMove(事件,mouseDown = false){
如果(!Dom.isSingleLeftClick(事件)){
返回;
}
如果 (!mouseDown && !this.player_.scrubbing()) {
this.player_.scrubbing(true);
}
讓新的時間;
const distance = this.calculateDistance(事件);
const liveTracker = this.player_.liveTracker;
如果(!liveTracker || !liveTracker.isLive()){
newTime = distance * this.player_.duration();
// 不要讓視頻在擦洗時結束。
如果(newTime === this.player_.duration()){
新時間 = 新時間 - 0.1;
}
}其他{
如果(距離 >= 0.99){
liveTracker.seekToLiveEdge();
返回;
}
const seekableStart = liveTracker.seekableStart();
const seekableEnd = liveTracker.liveCurrentTime();
newTime = seekableStart + (distance * liveTracker.liveWindow());
// 不要讓視頻在擦洗時結束。
如果(新時間 >= seekableEnd){
newTime = seekableEnd;
}
// 補償精度差異,使 currentTime 不小於
// 比可搜索的開始
如果(newTime <= seekableStart){
newTime = seekableStart + 0.1;
}
// 在 android 上,seekableEnd 有時可以是 Infinity,
// 這將導致 newTime 為 Infinity,即
// 不是有效的當前時間。
如果(新時間===無限){
返回;
}
}
// 設置新時間(告訴玩家尋找新時間)
this.userSeek_(newTime);
}
使能夠() {
超級啟用();
const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
如果(!mouseTimeDisplay){
返回;
}
mouseTimeDisplay.show();
}
禁用(){
超級禁用();
const mouseTimeDisplay = this.getChild('mouseTimeDisplay');
如果(!mouseTimeDisplay){
返回;
}
mouseTimeDisplay.hide();
}
/**
* 在搜索欄上處理鼠標
*
* @param {EventTarget~Event} 事件
* 導致它運行的 `mouseup` 事件。
*
* @listens mouseup
*/
handleMouseUp(事件){
super.handleMouseUp(事件);
// 停止事件傳播以防止 progress-control.js 中的雙重觸發
如果(事件){
事件.stopPropagation();
}
this.player_.scrubbing(false);
/**
* 觸發 timeupdate 因為我們已經完成搜索並且時間已經改變。
* 這對於播放器暫停以計時時間顯示特別有用。
*
* @event 技術#timeupdate
* @type {EventTarget~Event}
*/
this.player_.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
如果(this.videoWasPlaying){
silencePromise(this.player_.play());
}其他{
// 我們已經完成搜索,時間已經改變。
// 如果播放器暫停,請確保我們在搜索欄上顯示正確的時間。
這個.update_();
}
}
/**
* 僅鍵盤用戶可以更快地快進
*/
向前一步() {
this.userSeek_(this.player_.currentTime() + STEP_SECONDS);
}
/**
* 為僅使用鍵盤的用戶更快倒帶
*/
退後() {
this.userSeek_(this.player_.currentTime() - STEP_SECONDS);
}
/**
* 切換播放器的播放狀態
* 當在搜索欄上使用 enter 或 space 時調用
*
* @param {EventTarget~Event} 事件
* 導致調用此函數的 `keydown` 事件
*
*/
handleAction(事件){
如果 (this.player_.paused()) {
這個.player_.play();
}其他{
這個.player_.pause();
}
}
/**
* 當此 SeekBar 具有焦點並且按下某個鍵時調用。
* 支持以下按鍵:
*
* Space 或 Enter 鍵觸發點擊事件
* 主頁鍵移動到時間軸的開始
* 結束鍵移動到時間線的末尾
* 數字“0”到“9”鍵移動到 0%、10% ...80%、90%的時間線
* PageDown 鍵比 ArrowDown 向後移動了更大的一步
* PageUp鍵向前移動一大步
*
* @param {EventTarget~Event} 事件
* 導致調用此函數的 `keydown` 事件。
*
* @listens 按鍵
*/
handleKeyDown(事件){
const liveTracker = this.player_.liveTracker;
if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) {
事件.preventDefault();
事件.stopPropagation();
這個.handleAction(事件);
} else if (keycode.isEventKey(event, 'Home')) {
事件.preventDefault();
事件.stopPropagation();
這個.userSeek_(0);
} else if (keycode.isEventKey(event, 'End')) {
事件.preventDefault();
事件.stopPropagation();
如果(liveTracker && liveTracker.isLive()){
this.userSeek_(liveTracker.liveCurrentTime());
}其他{
this.userSeek_(this.player_.duration());
}
} else if (/^[0-9]$/.test(keycode(event))) {
事件.preventDefault();
事件.stopPropagation();
const gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0;
如果(liveTracker && liveTracker.isLive()){
this.userSeek_(liveTracker.seekableStart() + (liveTracker.liveWindow() * gotoFraction));
}其他{
this.userSeek_(this.player_.duration() * gotoFraction);
}
} else if (keycode.isEventKey(event, 'PgDn')) {
事件.preventDefault();
事件.stopPropagation();
this.userSeek_(this.player_.currentTime() - (STEP_SECONDS * PAGE_KEY_MULTIPLIER));
} else if (keycode.isEventKey(event, 'PgUp')) {
事件.preventDefault();
事件.stopPropagation();
this.userSeek_(this.player_.currentTime() + (STEP_SECONDS * PAGE_KEY_MULTIPLIER));
}其他{
// 為不支持的鍵傳遞 keydown 處理
super.handleKeyDown(事件);
}
}
處置(){
this.disableInterval_();
this.off(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update);
如果(這個.player_.liveTracker){
this.off(this.player_.liveTracker, 'liveedgechange', this.update);
}
this.off(this.player_, ['playing'], this.enableIntervalHandler_);
this.off(this.player_, ['結束', '暫停', '等待'], this.disableIntervalHandler_);
// 如果文檔被隱藏,我們不需要更新播放進度,
// 同樣,這會導致 CPU 峰值並最終導致 IE11 上的頁面崩潰。
如果(文檔中的'隱藏'&&文檔中的'visibilityState'){
this.off(文檔,'visibilitychange',this.toggleVisibility_);
}
super.dispose();
}
}
/**
* `SeekBar` 的默認選項
*
* @type {對象}
* @私人的
*/
SeekBar.prototype.options_ = {
孩子們: [
'加載進度條',
'播放進度條'
]、
barName: 'playProgressBar'
};
// MouseTimeDisplay 工具提示不應添加到移動設備上的播放器
如果(!IS_IOS &&!IS_ANDROID){
SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
}
Component.registerComponent('SeekBar', SeekBar);
導出默認 SeekBar;