import Component from './component.js' ;
import mergeOptions from './utils/merge-options.js' ;
import document from 'global/document' ;
import * as browser from './utils/browser.js' ;
import window from 'global/window' ;
import * as Fn from './utils/fn.js' ;
const defaults = {
seuil de suivi : 20,
liveTolerance : 15
};
/*
suivre le moment où nous sommes au bord de la scène, et d'autres aides pour la lecture en direct */
/**
* Une classe permettant de vérifier l'heure en direct et de déterminer quand le joueur
* est au niveau ou derrière le bord vivant.
*/
class LiveTracker 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 {number} [options.trackingThreshold=20]
* Nombre de secondes de la fenêtre en direct (seekableEnd - seekableStart) que
* doit avoir avant que le liveui ne soit diffusé.
*
* @param {number} [options.liveTolerance=15]
* Nombre de secondes de retard par rapport au direct que nous devons avoir
* avant d'être considéré comme non vivant. Notez que cela n'aura pour effet que de
* être utilisé pour jouer sur le bord de la scène. Cela permet d'obtenir de grandes extrémités recherchables
* les modifications n'ont pas d'incidence sur le fait que nous soyons en direct ou non.
*/
constructor(player, options) {
// LiveTracker n'a pas besoin d'élément
const options_ = mergeOptions(defaults, options, {createEl : false}) ;
super(player, options_) ;
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) ;
this.reset_() ;
this.on(this.player_, 'durationchange', (e) => this.handleDurationchange(e)) ;
// nous devrions essayer de basculer le suivi sur canplay comme les moteurs de lecture natifs, comme Safari
// peut ne pas avoir les bonnes valeurs pour des choses comme seekableEnd jusqu'à ce moment-là
this.on(this.player_, 'canplay', () => this.toggleTracking()) ;
// nous n'avons pas besoin de suivre la lecture en direct si le document est caché,
// également, le suivi de l'occultation du document peut
// provoquent un pic d'activité de l'unité centrale et finissent par faire planter la page sur IE11.
if (browser.IE_VERSION && 'hidden' in document && 'visibilityState' in document) {
this.on(document, 'visibilitychange', this.handleVisibilityChange_) ;
}
}
/**
* suivi des bascules en fonction de la visibilité du document
*/
handleVisibilityChange() {
if (this.player_.duration() !== Infinity) {
retour ;
}
if (document.hidden) {
this.stopTracking() ;
} else {
this.startTracking() ;
}
}
/**
* toutes les fonctionnalités permettant de suivre les changements de fin de recherche
* et pour savoir à quelle distance de la fin de la recherche nous devrions nous trouver
*/
trackLive_() {
const seekable = this.player_.seekable() ;
// Sauter les éléments recherchés indéfinis
if (!seekable || !seekable.length) {
retour ;
}
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() ;
// nous sommes derrière le direct si l'un d'entre eux est vrai
// 1. le lecteur est en pause
// 2. l'utilisateur a recherché un lieu situé à 2 secondes de l'endroit où il se trouve
// 3. la différence entre l'heure réelle et l'heure actuelle est plus importante
// liveTolerance dont la valeur par défaut est de 15s
let isBehind = this.player_.paused() || this.seekedBehindLive_ ||
Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance ;
// nous ne pouvons pas être en retard si
// 1. jusqu'à ce que nous n'ayons pas encore vu de timeupdate
// 2. liveCurrentTime est Infinity, ce qui se produit sur Android et Native Safari
if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) {
isBehind = false ;
}
if (isBehind !== this.behindLiveEdge_) {
this.behindLiveEdge_ = isBehind ;
this.trigger('liveedgechange') ;
}
}
/**
* gérer un événement de changement de durée sur le joueur
* et démarrer/arrêter le suivi en conséquence.
*/
handleDurationchange() {
this.toggleTracking() ;
}
/**
* démarrage/arrêt du suivi
*/
toggleTracking() {
if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) {
if (this.player_.options_.liveui) {
this.player_.addClass('vjs-liveui') ;
}
this.startTracking() ;
} else {
this.player_.removeClass('vjs-liveui') ;
this.stopTracking() ;
}
}
/**
* début du suivi lecture en direct
*/
startTracking() {
if (this.isTracking()) {
retour ;
}
// Si nous n'avons pas vu de timeupdate, nous devons vérifier si le playback
// a commencé avant que ce composant ne commence le suivi. Cela peut se produire couramment
// lors de l'utilisation de l'autoplay.
if (!this.timeupdateSeen_) {
this.timeupdateSeen_ = this.player_.hasStarted() ;
}
this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, Fn.UPDATE_REFRESH_INTERVAL) ;
this.trackLive_() ;
this.on(this.player_, ['play', 'pause'], this.trackLiveHandler_) ;
if (!this.timeupdateSeen_) {
this.one(this.player_, 'play', this.handlePlay_) ;
this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_) ;
} else {
this.on(this.player_, 'seeked', this.handleSeeked_) ;
}
}
/**
* gérer la première mise à jour temporelle du lecteur s'il n'était pas déjà en train de jouer
* date à laquelle le live tracker a commencé à suivre.
*/
handleFirstTimeupdate() {
this.timeupdateSeen_ = true ;
this.on(this.player_, 'seeked', this.handleSeeked_) ;
}
/**
* Gardez la trace de l'heure à laquelle une recherche commence, et écoutez les recherches
* pour trouver la fin d'une recherche.
*/
handleSeeked() {
const timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime()) ;
this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2 ;
this.nextSeekedFromUser_ = false ;
this.trackLive_() ;
}
/**
* gérer le premier jeu sur le joueur, et s'assurer que nous recherchons
* jusqu'au bord de l'eau.
*/
handlePlay() {
this.one(this.player_, 'timeupdate', this.seekToLiveEdge_) ;
}
/**
* Arrêter le suivi et mettre toutes les variables internes à
* leur valeur initiale.
*/
reset_() {
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_, ['play', 'pause'], 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_) ;
}
/**
* L'événement recherché suivant est celui de l'utilisateur. Cela signifie que toute recherche
* > les 2 derrière le direct seront considérés comme derrière le direct pour de vrai et les 2 derrière le direct pour de vrai
* liveTolerance sera ignorée.
*/
nextSeekedFromUser() {
this.nextSeekedFromUser_ = true ;
}
/**
* arrêter le suivi lecture en direct
*/
stopTracking() {
if (!this.isTracking()) {
retour ;
}
this.reset_() ;
this.trigger('liveedgechange') ;
}
/**
* Une aide pour obtenir la fin recherchable du joueur
* afin de ne pas avoir à vérifier les nullités partout
*
* @return {number}
* L'extrémité la plus éloignée ou l'infini.
*/
seekableEnd() {
const seekable = this.player_.seekable() ;
const seekableEnds = [] ;
let i = seekable ? seekable.length : 0 ;
while (i--) {
seekableEnds.push(seekable.end(i)) ;
}
// saisir l'extrémité cherchable la plus éloignée après le tri, ou s'il n'y en a pas
// par défaut à l'infini
return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : L'infini ;
}
/**
* Une aide pour obtenir le début de la recherche du joueur
* afin de ne pas avoir à vérifier les nullités partout
*
* @return {number}
* Le début de la recherche au plus tôt ou 0.
*/
seekableStart() {
const seekable = this.player_.seekable() ;
const seekableStarts = [] ;
let i = seekable ? seekable.length : 0 ;
while (i--) {
seekableStarts.push(seekable.start(i)) ;
}
// prend le premier départ cherchable après le tri, ou s'il n'y en a pas
// la valeur par défaut est 0
return seekableStarts.length ? seekableStarts.sort()[0] : 0 ;
}
/**
* Obtenir la fenêtre de temps en direct aka
* le temps écoulé entre le début de la recherche et la fin de la recherche
* l'heure actuelle en direct.
*
* @return {number}
* Le nombre de secondes qui peuvent être recherchées dans
* la vidéo en direct.
*/
liveWindow() {
const liveCurrentTime = this.liveCurrentTime() ;
// si liveCurrenTime est Infinity alors nous n'avons pas de liveWindow du tout
if (liveCurrentTime === Infinity) {
retour 0 ;
}
return liveCurrentTime - this.seekableStart() ;
}
/**
* Détermine si le lecteur est en direct, vérifie uniquement si ce composant
* le suivi de la lecture en direct ou non
*
* @return {boolean}
* Si liveTracker suit
*/
isLive() {
return this.isTracking() ;
}
/**
* Détermine si currentTime se trouve au bord de l'eau et ne risque pas de prendre du retard
* sur chaque changement possible
*
* @return {boolean}
* Si la lecture se fait au bord de l'eau
*/
atLiveEdge() {
return !this.behindLiveEdge() ;
}
/**
* obtenir ce que nous attendons de l'heure actuelle en direct
*
* @return {number}
* L'heure actuelle prévue en direct
*/
liveCurrentTime() {
return this.pastSeekEnd() + this.seekableEnd() ;
}
/**
* Le nombre de secondes qui se sont écoulées après la fin de la recherche
* a changé. Celui-ci sera remis à 0 une fois que la fin recherchée aura changé.
*
* @return {number}
* Secondes après la fin de la période de recherche en cours
*/
pastSeekEnd() {
const seekableEnd = this.seekableEnd() ;
if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
this.pastSeekEnd_ = 0 ;
}
this.lastSeekEnd_ = seekableEnd ;
return this.pastSeekEnd_ ;
}
/**
* Si nous sommes actuellement derrière le bord en direct, aka currentTime sera
* en retard sur un changement possible
*
* @return {boolean}
* Si nous sommes derrière le bord en direct
*/
behindLiveEdge() {
return this.behindLiveEdge_ ;
}
/**
* Si le traceur en direct est en cours de suivi ou non.
*/
isTracking() {
return typeof this.trackingInterval_ === 'number' ;
}
/**
* Chercher le bord en direct si nous sommes derrière le bord en direct
*/
seekToLiveEdge() {
this.seekedBehindLive_ = false ;
if (this.atLiveEdge()) {
retour ;
}
this.nextSeekedFromUser_ = false ;
this.player_.currentTime(this.liveCurrentTime()) ;
}
/**
* Se débarrasser de liveTracker
*/
dispose() {
this.off(document, 'visibilitychange', this.handleVisibilityChange_) ;
this.stopTracking() ;
super.dispose() ;
}
}
Component.registerComponent('LiveTracker', LiveTracker) ;
exporter le LiveTracker par défaut ;