/**
* @file text-track-display.js
*/
import Component from '../component' ;
import * as Fn from '../utils/fn.js' ;
import * as Dom from '../utils/dom.js' ;
import window from 'global/window' ;
const darkGray = '#222' ;
const lightGray = '#ccc' ;
const fontMap = {
monospace : "monospace",
sansSerif : "sans-serif",
serif : "serif",
monospaceSansSerif : '"Andale Mono", "Lucida Console", monospace",
monospaceSerif : '"Courier New", monospace",
proportionalSansSerif : "sans-serif",
proportionalSerif : "serif",
casual : '"Comic Sans MS", Impact, fantasy",
script : '"Monotype Corsiva", cursive",
smallcaps : '"Andale Mono", "Lucida Console", monospace, sans-serif
};
/**
* Construit une couleur rgba à partir d'un code couleur hexadécimal donné.
*
* @param {number} color
* Numéro hexadécimal de la couleur, comme #f0e ou #f604e2.
*
* @param {number} opacity
* Valeur de l'opacité, 0,0 - 1,0.
*
* @return {string}
* La couleur rgba qui a été créée, comme 'rgba(255, 0, 0, 0.3)'.
*/
export function constructColor(color, opacity) {
laissez hex ;
if (color.length === 4) {
// la couleur ressemble à "#f0e"
hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3] ;
} else if (color.length === 7) {
// la couleur ressemble à "#f604e2"
hex = color.slice(1) ;
} else {
throw new Error('Code de couleur invalide fourni, ' + color + ' ; doit être formaté comme par exemple #f0e ou #f604e2.') ;
}
return 'rgba(' +
parseInt(hex.slice(0, 2), 16) + ',' +
parseInt(hex.slice(2, 4), 16) + ',' +
parseInt(hex.slice(4, 6), 16) + ',' +
opacity + ')' ;
}
/**
* Essayer de mettre à jour le style d'un élément DOM. Certains changements de style entraîneront une erreur,
* en particulier dans IE8. Ce devrait être des noops.
*
* @param {Element} el
* L'élément DOM à styliser.
*
* @param {string} style
* La propriété CSS de l'élément qui doit être stylisée.
*
* @param {string} rule
* Règle de style à appliquer à la propriété.
*
* @private
*/
function tryUpdateStyle(el, style, rule) {
essayez {
el.style[style] = rule ;
} catch (e) {
// Satisfait le linter.
retour ;
}
}
/**
* Composant permettant d'afficher les repères de la piste de texte.
*
* @extends Component
*/
class TextTrackDisplay extends Component {
/**
* Crée une instance de cette classe.
*
* @param {Player} player
* Le `Player` auquel cette classe doit être attachée.
*
* @param {Objet} [options]
* La mémoire clé/valeur des options du lecteur.
*
* @param {Component~ReadyCallback} [ready]
* La fonction à appeler lorsque `TextTrackDisplay` est prêt.
*/
constructor(player, options, ready) {
super(player, options, ready) ;
const updateDisplayHandler = (e) => this.updateDisplay(e) ;
player.on('loadstart', (e) => this.toggleDisplay(e)) ;
player.on('texttrackchange', updateDisplayHandler) ;
player.on('loadedmetadata', (e) => this.preselectTrack(e)) ;
// Cette fonction était appelée lors de l'initialisation du joueur, mais provoquait une erreur
// si une piste doit être affichée par défaut et que l'affichage n'a pas encore été chargé.
// Devrait probablement être déplacé vers un chargeur de piste externe lorsque nous prendrons en charge
// les pistes qui n'ont pas besoin d'être affichées.
player.ready(Fn.bind(this, function() {
if (player.tech_ && player.tech_.featuresNativeTextTracks) {
this.hide() ;
retour ;
}
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 || [] ;
for (let i = 0 ; i < tracks.length ; i++) {
this.player_.addRemoteTextTrack(tracks[i], true) ;
}
this.preselectTrack() ;
})) ;
}
/**
* Présélectionner une piste en respectant cette priorité :
* - correspond à la langue et au type du {@link TextTrack} précédemment sélectionné
* - correspond uniquement à la langue du {@link TextTrack} précédemment sélectionné
* - est la première piste de légendes par défaut
* - est la première piste de description par défaut
*
* @listens Player#loadstart
*/
preselectTrack() {
const modes = {captions : 1, sous-titres : 1} ;
const trackList = this.player_.textTracks() ;
const userPref = this.player_.cache_.selectedLanguage ;
let firstDesc ;
let firstCaptions ;
let preferredTrack ;
for (let i = 0 ; i < trackList.length ; i++) {
const track = trackList[i] ;
si (
userPref && userPref.enabled &&
userPref.language && userPref.language === track.language &&
type de voie dans les modes
) {
// Choisissez toujours la piste qui correspond à la langue et au genre
if (track.kind === userPref.kind) {
preferredTrack = track ;
// ou choisir la première piste qui correspond à la langue
} else if (!preferredTrack) {
preferredTrack = track ;
}
// tout effacer si l'élément de menu offTextTrack a été cliqué
} else if (userPref && !userPref.enabled) {
piste préférée = null ;
firstDesc = null ;
firstCaptions = null ;
} else if (track.default) {
if (track.kind === 'descriptions' && !firstDesc) {
firstDesc = track ;
} else if (track.kind in modes && !firstCaptions) {
firstCaptions = track ;
}
}
}
// La piste préférée correspond à la préférence de l'utilisateur et prend le nom de "piste préférée"
// priorité sur toutes les autres pistes.
// Ainsi, afficher la piste préférée avant la première piste par défaut
// et la piste des sous-titres/captions avant la piste des descriptions
if (preferredTrack) {
preferredTrack.mode = 'showing' ;
} else if (firstCaptions) {
firstCaptions.mode = "showing" ;
} else if (firstDesc) {
firstDesc.mode = 'showing' ;
}
}
/**
* Transforme l'affichage des {@link TextTrack} de l'état actuel en l'autre état.
* Il n'y a que deux États :
* - montré
* - caché
*
* @listens Player#loadstart
*/
toggleDisplay() {
if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
this.hide() ;
} else {
this.show() ;
}
}
/**
* Crée l'élément DOM du {@link Component}.
*
* @return {Element}
* L'élément qui a été créé.
*/
createEl() {
return super.createEl('div', {
className : 'vjs-text-track-display'
}, {
traduire" : "oui",
aria-live" : "off",
'aria-atomic' : 'true'
}) ;
}
/**
* Efface tous les {@link TextTrack} affichés.
*/
clearDisplay() {
if (typeof window.WebVTT === 'function') {
window.WebVTT.processCues(window, [], this.el_) ;
}
}
/**
* Mettre à jour la piste de texte affichée lorsqu'un {@link Player#texttrackchange} ou un {@link Player#texttrack change} est affiché
* un {@link Player#fullscreenchange} est déclenché.
*
* @listens Player#texttrackchange
* @listens Player#fullscreenchange
*/
updateDisplay() {
const tracks = this.player_.textTracks() ;
const allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks ;
this.clearDisplay() ;
if (allowMultipleShowingTracks) {
const showingTracks = [] ;
for (let i = 0 ; i < tracks.length ; ++i) {
const track = tracks[i] ;
if (track.mode !== 'showing') {
continuer ;
}
showingTracks.push(track) ;
}
this.updateForTrack(showingTracks) ;
retour ;
}
// Modèle de priorité d'affichage des pistes : si plusieurs pistes sont "affichées",
// afficher la première piste de "sous-titres" ou de "sous-titres" qui est "montrée",
// sinon, afficher la première piste "descriptions" qui est "montrée"
let descriptionsTrack = null ;
let captionsSubtitlesTrack = null ;
let i = tracks.length ;
while (i--) {
const track = tracks[i] ;
if (track.mode === 'showing') {
if (track.kind === 'descriptions') {
descriptionsTrack = track ;
} else {
captionsSubtitlesTrack = track ;
}
}
}
if (captionsSubtitlesTrack) {
if (this.getAttribute('aria-live') !== 'off') {
this.setAttribute('aria-live', 'off') ;
}
this.updateForTrack(captionsSubtitlesTrack) ;
} else if (descriptionsTrack) {
if (this.getAttribute('aria-live') !== 'assertive') {
this.setAttribute('aria-live', 'assertive') ;
}
this.updateForTrack(descriptionsTrack) ;
}
}
/**
* Style des repères actifs {@Link TextTrack} en fonction des {@Link TextTrackSettings}.
*
* @param {TextTrack} track
* Objet de piste de texte contenant des repères actifs à styliser.
*/
updateDisplayState(track) {
const overrides = this.player_.textTrackSettings.getValues() ;
const cues = track.activeCues ;
let i = cues.length ;
while (i--) {
const cue = cues[i] ;
if (!cue) {
continuer ;
}
const cueDiv = cue.displayState ;
if (overrides.color) {
cueDiv.firstChild.style.color = overrides.color ;
}
if (overrides.textOpacity) {
tryUpdateStyle(
cueDiv.firstChild,
couleur",
constructColor(
overrides.color || '#fff',
overrides.textOpacity
)
) ;
}
if (overrides.backgroundColor) {
cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor ;
}
if (overrides.backgroundOpacity) {
tryUpdateStyle(
cueDiv.firstChild,
couleur d'arrière-plan",
constructColor(
overrides.backgroundColor || '#000',
overrides.backgroundOpacity
)
) ;
}
if (overrides.windowColor) {
if (overrides.windowOpacity) {
tryUpdateStyle(
cueDiv,
couleur d'arrière-plan",
constructColor(overrides.windowColor, overrides.windowOpacity)
) ;
} else {
cueDiv.style.backgroundColor = overrides.windowColor ;
}
}
if (overrides.edgeStyle) {
if (overrides.edgeStyle === 'dropshadow') {
cueDiv.firstChild.style.textShadow = `2px 2px 3px ${darkGray}, 2px 2px 4px ${darkGray}, 2px 2px 5px ${darkGray}` ;
} else if (overrides.edgeStyle === 'raised') {
cueDiv.firstChild.style.textShadow = `1px 1px ${darkGray}, 2px 2px ${darkGray}, 3px 3px ${darkGray}` ;
} 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}, 0 0 4px ${darkGray}` ;
}
}
if (overrides.fontPercent && overrides.fontPercent !== 1) {
const fontSize = window.parseFloat(cueDiv.style.fontSize) ;
cueDiv.style.fontSize = (fontSize * overrides.fontPercent) + 'px' ;
cueDiv.style.height = 'auto' ;
cueDiv.style.top = 'auto' ;
}
if (overrides.fontFamily && overrides.fontFamily !== 'default') {
if (overrides.fontFamily === 'small-caps') {
cueDiv.firstChild.style.fontVariant = 'small-caps' ;
} else {
cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily] ;
}
}
}
}
/**
* Ajouter un {@link TextTrack} à la {@link Tech}s {@link TextTrackList}.
*
* @param {TextTrack|TextTrack[]} tracks
* Objet ou tableau de pistes de texte à ajouter à la liste.
*/
updateForTrack(tracks) {
if (!Array.isArray(tracks)) {
pistes = [pistes] ;
}
if (typeof window.WebVTT !== 'function' ||
tracks.every((track)=> {
return !track.activeCues ;
})) {
retour ;
}
const cues = [] ;
// pousser tous les signaux actifs de la piste
for (let i = 0 ; i < tracks.length ; ++i) {
const track = tracks[i] ;
for (let j = 0 ; j < track.activeCues.length ; ++j) {
cues.push(track.activeCues[j]) ;
}
}
// supprime tous les signaux avant d'en traiter de nouveaux
window.WebVTT.processCues(window, cues, this.el_) ;
// ajouter une classe unique à chaque langue & ajouter des paramètres de style si nécessaire
for (let i = 0 ; i < tracks.length ; ++i) {
const track = tracks[i] ;
for (let j = 0 ; j < track.activeCues.length ; ++j) {
const cueEl = track.activeCues[j].displayState ;
Dom.addClass(cueEl, 'vjs-text-track-cue') ;
Dom.addClass(cueEl, 'vjs-text-track-cue-' + ((track.language) ? track.language : i)) ;
if (track.language) {
Dom.setAttribute(cueEl, 'lang', track.language) ;
}
}
if (this.player_.textTrackSettings) {
this.updateDisplayState(track) ;
}
}
}
}
Component.registerComponent('TextTrackDisplay', TextTrackDisplay) ;
exporter l'affichage par défaut de TextTrackDisplay ;