/**
* @file text-track-display.js
*/
從 '../component' 導入組件;
從 '../utils/fn.js' 導入 * 作為 Fn;
import * as Dom from '../utils/dom.js';
從“全局/窗口”導入窗口;
const darkGray = '#222';
const lightGray = '#ccc';
常量 fontMap = {
等寬:'等寬',
sansSerif: 'sans-serif',
襯線:'襯線',
monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
monospaceSerif: '"Courier New", monospace',
proportionalSansSerif: 'sans-serif',
proportionalSerif: '襯線',
casual: '"Comic Sans MS", Impact, fantasy',
script: '"Monotype Corsiva", 草書',
smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
};
/**
* 從給定的十六進制顏色代碼構建 rgba 顏色。
*
* @param {number} 顏色
* 顏色的十六進制數,如#f0e 或#f604e2。
*
* @param {number} 不透明度
* 不透明度值,0.0 - 1.0。
*
* @return {字符串}
* 創建的 rgba 顏色,如 'rgba(255, 0, 0, 0.3)'。
*/
導出函數構造顏色(顏色,不透明度){
讓十六進制;
如果(顏色。長度=== 4){
// 顏色看起來像“#f0e”
十六進制=顏色[1] + 顏色[1] + 顏色[2] + 顏色[2] + 顏色[3] + 顏色[3];
} else if (color.length === 7) {
// 顏色看起來像“#f604e2”
hex = color.slice(1);
}其他{
throw new Error('提供的顏色代碼無效,' + 顏色 + ';必須格式化為例如 #f0e 或 #f604e2。');
}
返回'rgba('+
parseInt(hex.slice(0, 2), 16) + ',' +
parseInt(hex.slice(2, 4), 16) + ',' +
parseInt(hex.slice(4, 6), 16) + ',' +
不透明度 + ')';
}
/**
* 嘗試更新 DOM 元素的樣式。某些樣式更改會引發錯誤,
* 特別是在 IE8 中。那些應該是空話。
*
* @param {元素} el
* 要設置樣式的 DOM 元素。
*
* @param {string} 樣式
* 應設置樣式的元素的 CSS 屬性。
*
* @param {string} 規則
* 應用於屬性的樣式規則。
*
* @私人的
*/
函數 tryUpdateStyle(el, 樣式, 規則) {
嘗試{
el.style[樣式] = 規則;
} 抓住 (e) {
// 滿足 linter。
返回;
}
}
/**
* 顯示文本軌道提示的組件。
*
* @extends 組件
*/
類 TextTrackDisplay 擴展組件 {
/**
* 創建此類的一個實例。
*
* @param {Player} 播放器
* 此類應附加到的 `Player`。
*
* @param {對象} [選項]
* 播放器選項的鍵/值存儲。
*
* @param {Component~ReadyCallback} [就緒]
* `TextTrackDisplay` 就緒時調用的函數。
*/
構造函數(播放器,選項,準備就緒){
超級(播放器,選項,準備就緒);
const updateDisplayHandler = (e) => this.updateDisplay(e);
player.on('loadstart', (e) => this.toggleDisplay(e));
player.on('texttrackchange', updateDisplayHandler);
player.on('loadedmetadata', (e) => this.preselectTrack(e));
// 這曾經在播放器初始化期間調用,但會導致錯誤
// 如果軌道應該默認顯示並且顯示尚未加載。
// 當我們支持時,可能應該移動到外部軌道加載器
// 不需要顯示的軌道。
player.ready(Fn.bind(this, function() {
如果(player.tech_ && player.tech_.featuresNativeTextTracks){
這個。隱藏();
返回;
}
player.on('fullscreenchange', updateDisplayHandler);
player.on('playerresize', updateDisplayHandler);
window.addEventListener('orientationchange', updateDisplayHandler);
player.on('dispose', () => window.removeEventListener('orientationchange', updateDisplayHandler));
const tracks = this.options_.playerOptions.tracks || [];
對於(讓我 = 0; 我 < 跟踪。長度; 我 ++){
this.player_.addRemoteTextTrack(tracks[i], true);
}
這個.preselectTrack();
}));
}
/**
* 按照此優先順序預選曲目:
* - 匹配先前選擇的 {@link TextTrack} 的語言和種類
* - 僅匹配先前選擇的 {@link TextTrack} 的語言
* - 是第一個默認字幕軌道
* - 是第一個默認描述軌道
*
* @listens Player#loadstart
*/
預選軌道(){
const 模式 = {字幕:1、字幕:1};
const trackList = this.player_.textTracks();
const userPref = this.player_.cache_.selectedLanguage;
讓 firstDesc;
讓第一個字幕;
讓首選軌道;
對於(讓我 = 0; 我 < 跟踪列表長度; 我 ++){
const track = trackList[i];
如果 (
userPref && userPref.enabled &&
userPref.language && userPref.language === track.language &&
track.kind 模式
) {
// 始終選擇匹配語言和種類的曲目
如果(track.kind === userPref.kind){
首選軌道=軌道;
// 或選擇匹配語言的第一首曲目
} 否則,如果(!首選跟踪){
首選軌道=軌道;
}
// 如果單擊了 offTextTrackMenuItem,則清除所有內容
} 否則如果(用戶偏好 &&!使用者先決條件。啟用) {
首選軌道=空;
firstDesc = null;
firstCaptions = null;
} else if (track.default) {
如果(跟踪一種 === '描述' &&!第一描述) {
firstDesc = 跟踪;
} 否則如果(跟踪模式 &&!第一字幕) {
firstCaptions = 跟踪;
}
}
}
// preferredTrack 匹配用戶偏好並採用
// 優先於所有其他軌道。
// 因此,在第一個默認曲目之前顯示 preferredTrack
// 和描述軌道之前的字幕軌道
如果(首選軌道){
preferredTrack.mode = '顯示';
} else if (firstCaptions) {
firstCaptions.mode = '顯示';
} else if (firstDesc) {
firstDesc.mode = '顯示';
}
}
/**
* 將 {@link TextTrack} 的顯示從當前狀態轉換為其他狀態。
* 只有兩種狀態:
* - '顯示'
* - '隱藏'
*
* @listens Player#loadstart
*/
切換顯示(){
如果(this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks){
這個。隱藏();
}其他{
這個。顯示();
}
}
/**
* 創建 {@link Component} 的 DOM 元素。
*
* @return {元素}
* 創建的元素。
*/
創建El() {
返回 super.createEl('div', {
className: 'vjs-text-track-display'
},{
'翻譯':'是',
'aria-live':'關閉',
'aria-atomic':'真'
});
}
/**
* 清除所有顯示的 {@link TextTrack}。
*/
清除顯示(){
如果 (typeof window.WebVTT === '函數') {
window.WebVTT.processCues(窗口,[],this.el_);
}
}
/**
* 當 {@link Player#texttrackchange} 或
* {@link Player#fullscreenchange} 被觸發。
*
* @listens Player#texttrackchange
* @listens Player#fullscreenchange
*/
更新顯示(){
const tracks = this.player_.textTracks();
const allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks;
這個.clearDisplay();
如果(allowMultipleShowingTracks){
const showingTracks = [];
為 (讓我 = 0; 我 < 跟踪. 長度; ++i) {
const track = tracks[i];
如果(跟踪模式!== '顯示'){
繼續;
}
showingTracks.push(track);
}
this.updateForTrack(showingTracks);
返回;
}
// 軌道顯示優先級模型:如果多個軌道正在“顯示”,
// 顯示第一個 'subtitles' or 'captions' track which is 'showing',
// 否則顯示第一個“正在顯示”的“描述”曲目
讓 descriptionsTrack = null;
讓 captionsSubtitlesTrack = null;
設 i = tracks.length;
當我 - ) {
const track = tracks[i];
如果(track.mode ==='顯示'){
如果(track.kind ==='描述'){
descriptionsTrack =跟踪;
}其他{
字幕字幕軌道 = 軌道;
}
}
}
如果(字幕字幕跟踪){
如果(這個。獲取屬性('詠嘆調-活')!== '關閉'){
this.setAttribute('aria-live', 'off');
}
this.updateForTrack(字幕字幕跟踪);
} else if (descriptionsTrack) {
如果(這個。獲取屬性('詠嘆調-活')!== '自信'){
this.setAttribute('aria-live', 'assertive');
}
this.updateForTrack(描述跟踪);
}
}
/**
* 根據 {@Link TextTrackSettings} 設計 {@Link TextTrack} activeCues。
*
* @param {TextTrack} 軌道
* 包含樣式活動提示的文本軌道對象。
*/
更新顯示狀態(軌道){
const overrides = this.player_.textTrackSettings.getValues();
const cues = track.activeCues;
讓我= cues.length;
當我 - ) {
const cue = 提示[i];
如果(!提示){
繼續;
}
const cueDiv = cue.displayState;
如果(覆蓋顏色){
cueDiv.firstChild.style.color = overrides.color;
}
如果(覆蓋。textOpacity){
嘗試更新樣式(
cueDiv.firstChild,
'顏色',
構造顏色(
覆蓋顏色 || '#fff',
覆蓋.textOpacity
)
);
}
如果(覆蓋。背景顏色){
cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
}
如果(覆蓋。backgroundOpacity){
嘗試更新樣式(
cueDiv.firstChild,
'背景顏色',
構造顏色(
overrides.backgroundColor || '#000',
overrides.backgroundOpacity
)
);
}
如果(覆蓋。windowColor){
如果(覆蓋。windowOpacity){
嘗試更新樣式(
提示分區,
'背景顏色',
constructColor(overrides.windowColor,overrides.windowOpacity)
);
}其他{
cueDiv.style.backgroundColor = overrides.windowColor;
}
}
如果(覆蓋。edgeStyle){
如果(overrides.edgeStyle === 'dropshadow'){
文本陰影 = `2 像素 2 像素 3 像素 $ {暗灰色},2 像素 2 像素 4 像素 $ {暗灰色},2 像素 2 像素 5 像素 $ {深灰色} `;
} else if (overrides.edgeStyle === 'raised') {
文本陰影 = `1 像素 $ {暗灰色},2 像素 2 像素 $ {暗灰色},3 像素 3 像素 $ {暗灰色} `;
} else if (overrides.edgeStyle === 'depressed') {
cueDiv.firstChild.style.textShadow = `1px 1px ${lightGray}, 0 1px ${lightGray}, -1px -1px ${darkGray}, 0 -1px ${darkGray}`;
} else if (overrides.edgeStyle === 'uniform') {
cueDiv.firstChild.style.textShadow = `0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}, 0 0 4px ${darkGray}`;
}
}
如果(覆蓋 .Font 百分比 && 覆蓋。字體百分比!1) {
const fontSize = window.parseFloat(cueDiv.style.fontSize);
cueDiv.style.fontSize = (fontSize * overrides.fontPercent) + 'px';
cueDiv.style.height = '自動';
cueDiv.style.top = '自動';
}
如果(覆蓋字體家庭 && 概述。字體家庭!== '默認'){
if (overrides.fontFamily === 'small-caps') {
cueDiv.firstChild.style.fontVariant = 'small-caps';
}其他{
cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
}
}
}
}
/**
* 將 {@link TextTrack} 添加到 {@link Tech} 的 {@link TextTrackList}。
*
* @param {TextTrack|TextTrack[]} 軌道
* 要添加到列表中的文本軌道對像或文本軌道數組。
*/
updateForTrack(曲目){
如果(!Array.isArray(曲目)){
曲目 = [曲目];
}
如果(窗口的類型。== '功能' ||
tracks.every((track)=> {
返回!track.activeCues;
})) {
返回;
}
const 線索 = [];
// 推送所有活動軌道提示
為 (讓我 = 0; 我 < 跟踪. 長度; ++i) {
const track = tracks[i];
為 (讓 j = 0; < j 軌道. 活動. 長度; ++j) {
cues.push(track.activeCues[j]);
}
}
// 在處理新線索之前刪除所有線索
window.WebVTT.processCues(窗口,提示,this.el_);
// 為每種語言文本軌道添加獨特的類,並在必要時添加設置樣式
為 (讓我 = 0; 我 < 跟踪. 長度; ++i) {
const track = tracks[i];
為 (讓 j = 0; < j 軌道. 活動. 長度; ++j) {
const cueEl = track.activeCues[j].displayState;
Dom.addClass(cueEl, 'vjs-text-track-cue');
添加類 (搜索, 'VJS-文本跟踪提示-' + ((軌道. 語言)?跟踪語言:i));
如果(track.language){
Dom.setAttribute(cueEl, 'lang', track.language);
}
}
如果(this.player_.textTrackSettings){
this.updateDisplayState(track);
}
}
}
}
Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
導出默認 TextTrackDisplay;