/**
* @file tech.js
*/
import Component from '../component' ;
import mergeOptions from '../utils/merge-options.js' ;
import * as Fn from '../utils/fn.js' ;
import log from '../utils/log.js' ;
import { createTimeRange } from '../utils/time-ranges.js' ;
import { bufferedPercent } from '../utils/buffer.js' ;
import MediaError de '../media-error.js' ;
import window from 'global/window' ;
import document from 'global/document' ;
import {isPlain} from '../utils/obj' ;
import * as TRACK_TYPES from '../tracks/track-types' ;
import {toTitleCase, toLowerCase} de '../utils/string-cases.js' ;
import vtt from 'videojs-vtt.js' ;
import * as Guid from '../utils/guid.js' ;
/**
* Un objet contenant une structure comme : `{src : 'url', type : 'mimetype'}` ou une chaîne de caractères
* qui ne contient que l'url src.
* * `var SourceObject = {src : 'http://ex.com/video.mp4', type : 'video/mp4'};``
* `var SourceString = 'http://example.com/some-video.mp4';`
*
* @typedef {Objet|chaîne} Tech~SourceObject
*
* @property {string} src
* L'url de la source
*
* @property {string} type
* Le type de mime de la source
*/
/**
* Une fonction utilisée par {@link Tech} pour créer un nouveau {@link TextTrack}.
*
* @private
*
* @param {Tech} self
* Une instance de la classe Tech.
*
* @param {string} kind
* type de `TextTrack` (sous-titres, légendes, descriptions, chapitres ou métadonnées)
*
* @param {string} [label]
* Étiquette pour identifier la piste de texte
*
* @param {string} [langue]
* Abréviation linguistique à deux lettres
*
* @param {Objet} [options={}]
* Un objet avec des options supplémentaires de suivi de texte
*
* @return {TextTrack}
* La piste de texte qui a été créée.
*/
function createTrackHelper(self, kind, label, language, options = {}) {
const tracks = self.textTracks() ;
options.kind = kind ;
if (label) {
options.label = label ;
}
if (language) {
options.language = language ;
}
options.tech = self ;
const track = new TRACK_TYPES.ALL.text.TrackClass(options) ;
tracks.addTrack(track) ;
piste de retour ;
}
/**
* Il s'agit de la classe de base pour les contrôleurs de la technologie de lecture des médias, tels que
* {@link HTML5}
*
* @extends Component
*/
class Tech extends Component {
/**
* Créer une instance de ce Tech.
*
* @param {Objet} [options]
* La mémoire clé/valeur des options du lecteur.
*
* @param {Component~ReadyCallback} ready
* Fonction de rappel à appeler lorsque le Tech `HTML5` est prêt.
*/
constructor(options = {}, ready = function() {}) {
// nous ne voulons pas que la technologie signale automatiquement l'activité de l'utilisateur.
// Ceci est fait manuellement dans addControlsListeners
options.reportTouchActivity = false ;
super(null, options, ready) ;
this.onDurationChange_ = (e) => this.onDurationChange(e) ;
this.trackProgress_ = (e) => this.trackProgress(e) ;
this.trackCurrentTime_ = (e) => this.trackCurrentTime(e) ;
this.stopTrackingCurrentTime_ = (e) => this.stopTrackingCurrentTime(e) ;
this.disposeSourceHandler_ = (e) => this.disposeSourceHandler(e) ;
this.queuedHanders_ = new Set() ;
// vérifier si la source actuelle a été jouée ou non pour
// mettre en œuvre une fonction jouée() très limitée
this.hasStarted_ = false ;
this.on('playing', function() {
this.hasStarted_ = true ;
}) ;
this.on('loadstart', function() {
this.hasStarted_ = false ;
}) ;
TRACK_TYPES.ALL.names.forEach((name) => {
const props = TRACK_TYPES.ALL[name] ;
if (options && options[props.getterName]) {
this[props.privateName] = options[props.getterName] ;
}
}) ;
// Suivre manuellement la progression dans les cas où le navigateur/la technologie ne la signale pas.
if (!this.featuresProgressEvents) {
this.manualProgressOn() ;
}
// Suivre manuellement les mises à jour temporelles dans les cas où le navigateur/la technologie ne les signale pas.
if (!this.featuresTimeupdateEvents) {
this.manualTimeUpdatesOn() ;
}
['Text', 'Audio', 'Video'].forEach((track) => {
if (options[`native${track}Tracks`] === false) {
this[`featuresNative${track}Tracks`] = false ;
}
}) ;
if (options.nativeCaptions === false || options.nativeTextTracks === false) {
this.featuresNativeTextTracks = false ;
} else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
this.featuresNativeTextTracks = true ;
}
if (!this.featuresNativeTextTracks) {
this.emulateTextTracks() ;
}
this.preloadTextTracks = options.preloadTextTracks !== false ;
this.autoRemoteTextTracks_ = new TRACK_TYPES.ALL.text.ListClass() ;
this.initTrackListeners() ;
// Activation des événements de tapotement des composants uniquement si l'on n'utilise pas de contrôles natifs
if (!options.nativeControlsForTouch) {
this.emitTapEvents() ;
}
if (this.constructor) {
this.name_ = this.constructor.name || 'Unknown Tech' ;
}
}
/**
* Une fonction spéciale pour déclencher le jeu de sources d'une manière qui permette au lecteur d'avoir accès à des informations sur les activités de l'entreprise
* pour se redéclencher si le joueur ou la technologie ne sont pas encore prêts.
*
* @fires Tech#sourceset
* @param {string} src La chaîne source au moment de la modification de la source.
*/
triggerSourceset(src) {
if (!this.isReady_) {
// lors de la préparation initiale, nous devons déclencher l'ensemble des sources
// 1ms après ready pour que le joueur puisse l'attendre.
this.one('ready', () => this.setTimeout(() => this.triggerSourceset(src), 1)) ;
}
/**
* Déclenché lorsque la source est définie sur le tech à l'origine de l'élément de média
* pour recharger.
*
* @see {@link Player#event:sourceset}
* @event Tech#sourceset
* @type {EventTarget~Event}
*/
this.trigger({
src,
type : "sourceset" (jeu de sources)
}) ;
}
/* Retours pour les types d'événements non pris en charge
================================================================================ */
/**
* Polyfill l'événement `progress` pour les navigateurs qui ne le supportent pas nativement.
*
* @see {@link Tech#trackProgress}
*/
manualProgressOn() {
this.on('durationchange', this.onDurationChange_) ;
this.manualProgress = true ;
// Déclencher l'observation de la progression lorsqu'une source commence à se charger
this.one('ready', this.trackProgress_) ;
}
/**
* Désactiver le polyfill pour les événements `progress` qui a été créé dans
* {@link Tech#manualProgressOn}
*/
manualProgressOff() {
this.manualProgress = false ;
this.stopTrackingProgress() ;
this.off('durationchange', this.onDurationChange_) ;
}
/**
* Il est utilisé pour déclencher un événement `progress` lorsque le pourcentage mis en mémoire tampon change. Il
* définit une fonction d'intervalle qui sera appelée toutes les 500 millisecondes pour vérifier si la fonction
* le pourcentage final du tampon a changé.
*
* > Cette fonction est appelée par {@link Tech#manualProgressOn}
*
* @param {EventTarget~Event} event
* L'événement `ready` qui a provoqué l'exécution de cette opération.
*
* @listens Tech#ready
* @fires Tech#progress
*/
trackProgress(event) {
this.stopTrackingProgress() ;
this.progressInterval = this.setInterval(Fn.bind(this, function() {
// Ne se déclenche pas si la quantité mise en mémoire tampon est supérieure à la dernière fois
const numBufferedPercent = this.bufferedPercent() ;
if (this.bufferedPercent_ !== numBufferedPercent) {
/**
* Voir {@link Player#progress}
*
* @event Tech#progress
* @type {EventTarget~Event}
*/
this.trigger('progress') ;
}
this.bufferedPercent_ = numBufferedPercent ;
if (numBufferedPercent === 1) {
this.stopTrackingProgress() ;
}
}), 500) ;
}
/**
* Mettre à jour notre durée interne sur un événement `durationchange` en appelant
* {@link Tech#duration}.
*
* @param {EventTarget~Event} event
* L'événement `durationchange` qui a provoqué l'exécution de cette opération.
*
* @listens Tech#durationchange
*/
onDurationChange(event) {
this.duration_ = this.duration() ;
}
/**
* Récupère et crée un objet `TimeRange` pour la mise en mémoire tampon.
*
* @return {TimeRange}
* L'objet de plage horaire qui a été créé.
*/
buffered() {
return createTimeRange(0, 0) ;
}
/**
* Obtenir le pourcentage de la vidéo en cours qui est actuellement mis en mémoire tampon.
*
* @return {number}
* Un nombre compris entre 0 et 1 qui représente le pourcentage décimal de la
* vidéo qui est mise en mémoire tampon.
*
*/
bufferedPercent() {
return bufferedPercent(this.buffered(), this.duration_) ;
}
/**
* Désactiver le polyfill pour les événements `progress` qui a été créé dans
* {@link Tech#manualProgressOn}
* Arrêtez de suivre manuellement les événements de progression en supprimant l'intervalle défini dans la section
* {@link Tech#trackProgress}.
*/
stopTrackingProgress() {
this.clearInterval(this.progressInterval) ;
}
/**
* Polyfill l'événement `timeupdate` pour les navigateurs qui ne le supportent pas.
*
* @see {@link Tech#trackCurrentTime}
*/
manualTimeUpdatesOn() {
this.manualTimeUpdates = true ;
this.on('play', this.trackCurrentTime_) ;
this.on('pause', this.stopTrackingCurrentTime_) ;
}
/**
* Désactiver le polyfill pour les événements `timeupdate` qui a été créé en
* {@link Tech#manualTimeUpdatesOn}
*/
manualTimeUpdatesOff() {
this.manualTimeUpdates = false ;
this.stopTrackingCurrentTime() ;
this.off('play', this.trackCurrentTime_) ;
this.off('pause', this.stopTrackingCurrentTime_) ;
}
/**
* Met en place une fonction d'intervalle pour suivre l'heure actuelle et déclencher `timeupdate` chaque fois que
* 250 millisecondes.
*
* @listens Tech#play
* @triggers Tech#timeupdate
*/
trackCurrentTime() {
if (this.currentTimeInterval) {
this.stopTrackingCurrentTime() ;
}
this.currentTimeInterval = this.setInterval(function() {
/**
* Déclenché à un intervalle de 250 ms pour indiquer que le temps passe dans la vidéo.
*
* @event Tech#timeupdate
* @type {EventTarget~Event}
*/
this.trigger({ type : 'timeupdate', target : this, manuallyTriggered : true }) ;
// 42 = 24 fps // 250 est ce que Webkit utilise // FF utilise 15
}, 250) ;
}
/**
* Arrêter la fonction d'intervalle créée dans {@link Tech#trackCurrentTime} pour que la fonction
* l'événement `timeupdate` n'est plus déclenché.
*
* @listens {Tech#pause}
*/
stopTrackingCurrentTime() {
this.clearInterval(this.currentTimeInterval) ;
// #1002 - si la vidéo se termine juste avant la prochaine mise à jour temporelle,
// la barre de progression ne se rendra pas jusqu'à la fin
this.trigger({ type : 'timeupdate', target : this, manuallyTriggered : true }) ;
}
/**
* Désactive tous les polyfills d'événements, efface les `Tech`s {@link AudioTrackList},
* {@link VideoTrackList}, et {@link TextTrackList}, et de disposer de cette Tech.
*
* @fires Composant#dispose
*/
dispose() {
// nettoyer toutes les pistes car nous ne pouvons pas les réutiliser entre les techniciens
this.clearTracks(TRACK_TYPES.NORMAL.names) ;
// Désactiver tout suivi manuel de la progression ou de la mise à jour de l'heure
if (this.manualProgress) {
this.manualProgressOff() ;
}
if (this.manualTimeUpdates) {
this.manualTimeUpdatesOff() ;
}
super.dispose() ;
}
/**
* Efface une seule `TrackList` ou un tableau de `TrackLists` avec leurs noms.
*
* > Remarque : Les techniciens qui n'ont pas de gestionnaire de sources devraient appeler cette fonction entre les sources pour `video`
* & pistes `audio`. Vous ne voulez pas les utiliser entre les pistes !
*
* @param {string[]|string} types
* Noms des listes de pistes à effacer, les noms valides sont `video`, `audio`, et
* `text`.
*/
clearTracks(types) {
types = [].concat(types) ;
// nettoyer toutes les pistes car nous ne pouvons pas les réutiliser entre les techniciens
types.forEach((type) => {
const list = this[`${type}Tracks`]() || [] ;
let i = list.length ;
while (i--) {
const track = list[i] ;
if (type === 'text') {
this.removeRemoteTextTrack(track) ;
}
list.removeTrack(track) ;
}
}) ;
}
/**
* Supprimer tous les TextTracks ajoutés via addRemoteTextTrack qui sont
* marqués pour le ramassage automatique des ordures
*/
cleanupAutoTextTracks() {
const list = this.autoRemoteTextTracks_ || [] ;
let i = list.length ;
while (i--) {
const track = list[i] ;
this.removeRemoteTextTrack(track) ;
}
}
/**
* Réinitialiser le tech, ce qui supprime toutes les sources et réinitialise l'état de préparation interne.
*
* @abstract
*/
reset() {}
/**
* Obtenir la valeur de `crossOrigin` du tech.
*
* @abstract
*
* @see {Html5#crossOrigin}
*/
crossOrigin() {}
/**
* Fixe la valeur de `crossOrigin` sur le tech.
*
* @abstract
*
* @param {string} crossOrigin la valeur de crossOrigin
* @see {Html5#setCrossOrigin}
*/
setCrossOrigin() {}
/**
* Obtenir ou définir une erreur sur le Tech.
*
* @param {MediaError} [err]
* Erreur à régler sur le Tech
*
* @return {MediaError|null}
* L'objet d'erreur actuel sur le tech, ou null s'il n'y en a pas.
*/
error(err) {
if (err !== undefined) {
this.error_ = new MediaError(err) ;
this.trigger('error') ;
}
return this.error_ ;
}
/**
* Renvoie les `TimeRange`s qui ont été joués pour la source actuelle.
*
* > NOTE : Cette mise en œuvre est incomplète. Il ne suit pas la `TimeRange` jouée.
* Il vérifie uniquement si la source a été jouée ou non.
*
* @return {TimeRange}
* - Une plage de temps unique si la vidéo a été lue
* - Un ensemble vide de plages si ce n'est pas le cas.
*/
played() {
if (this.hasStarted_) {
return createTimeRange(0, 0) ;
}
return createTimeRange() ;
}
/**
* Démarrer la lecture
*
* @abstract
*
* @voir {Html5#play}
*/
play() {}
/**
* Définir si nous nettoyons ou non
*
* @abstract
*
* @see {Html5#setScrubbing}
*/
setScrubbing() {}
/**
* Obtenir si l'on frotte ou non
*
* @abstract
*
* @see {Html5#scrubbing}
*/
scrubbing() {}
/**
* Provoque une mise à jour manuelle de l'heure si {@link Tech#manualTimeUpdatesOn} était
* appelé précédemment.
*
* @fires Tech#timeupdate
*/
setCurrentTime() {
// améliorer la précision des mises à jour manuelles des heures de travail
if (this.manualTimeUpdates) {
/**
* Un événement manuel `timeupdate`.
*
* @event Tech#timeupdate
* @type {EventTarget~Event}
*/
this.trigger({ type : 'timeupdate', target : this, manuallyTriggered : true }) ;
}
}
/**
* Activer les récepteurs pour {@link VideoTrackList}, {@link {AudioTrackList}, et
* {@link TextTrackList} events.
*
* Cela ajoute {@link EventTarget~EventListeners} pour `addtrack`, et `removetrack`.
*
* @fires Tech#audiotrackchange
* @fires Tech#videotrackchange
* @fires Tech#texttrackchange
*/
initTrackListeners() {
/**
* Déclenché lorsque des pistes sont ajoutées ou supprimées sur la Tech {@link AudioTrackList}
*
* @event Tech#audiotrackchange
* @type {EventTarget~Event}
*/
/**
* Déclenché lorsque des pistes sont ajoutées ou supprimées dans la Tech {@link VideoTrackList}
*
* @event Tech#videotrackchange
* @type {EventTarget~Event}
*/
/**
* Déclenché lorsque des pistes sont ajoutées ou supprimées dans la Tech {@link TextTrackList}
*
* @event Tech#texttrackchange
* @type {EventTarget~Event}
*/
TRACK_TYPES.NORMAL.names.forEach((name) => {
const props = TRACK_TYPES.NORMAL[name] ;
const trackListChanges = () => {
this.trigger(`${nom}changement de piste`) ;
};
const tracks = this[props.getterName]() ;
tracks.addEventListener('removetrack', trackListChanges) ;
tracks.addEventListener('addtrack', trackListChanges) ;
this.on('dispose', () => {
tracks.removeEventListener('removetrack', trackListChanges) ;
tracks.removeEventListener('addtrack', trackListChanges) ;
}) ;
}) ;
}
/**
* Emuler les TextTracks en utilisant vtt.js si nécessaire
*
* @fires Tech#vttjsloaded
* @fires Tech#vttjserror
*/
addWebVttScript_() {
if (window.WebVTT) {
retour ;
}
// Initialement, Tech.el_ est un enfant d'un dummy-div attendre jusqu'à ce que le système Component
// signale que la technologie est prête et que Tech.el_ fait partie du DOM
// avant d'insérer le script WebVTT
if (document.body.contains(this.el()))) {
// chargement via require si disponible et si l'emplacement du script vtt.js n'a pas été transmis
// Les constructions novtt transformeront l'appel à require ci-dessus en un objet vide
// ce qui fera que cette vérification échouera toujours.
if (!this.options_['vtt.js'] && isPlain(vtt) && Object.keys(vtt).length > 0) {
this.trigger('vttjsloaded') ;
retour ;
}
// charger vtt.js via l'option d'emplacement du script ou le cdn si aucun emplacement n'a été défini
// transmis
const script = document.createElement('script') ;
script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js' ;
script.onload = () => {
/**
* Déclenché lorsque le fichier vtt.js est chargé.
*
* @event Tech#vttjsloaded
* @type {EventTarget~Event}
*/
this.trigger('vttjsloaded') ;
};
script.onerror = () => {
/**
* Déclenché lorsque vtt.js n'a pas été chargé en raison d'une erreur
*
* @event Tech#vttjsloaded
* @type {EventTarget~Event}
*/
this.trigger('vttjserror') ;
};
this.on('dispose', () => {
script.onload = null ;
script.onerror = null ;
}) ;
// mais n'ont pas encore été chargées et nous les fixons à true avant l'injection de sorte que
// nous n'écrasons pas le window.WebVTT injecté s'il se charge immédiatement
window.WebVTT = true ;
this.el().parentNode.appendChild(script) ;
} else {
this.ready(this.addWebVttScript_) ;
}
}
/**
* Emuler des pistes de texte
*
*/
emulateTextTracks() {
const tracks = this.textTracks() ;
const remoteTracks = this.remoteTextTracks() ;
const handleAddTrack = (e) => tracks.addTrack(e.track) ;
const handleRemoveTrack = (e) => tracks.removeTrack(e.track) ;
remoteTracks.on('addtrack', handleAddTrack) ;
remoteTracks.on('removetrack', handleRemoveTrack) ;
this.addWebVttScript_() ;
const updateDisplay = () => this.trigger('texttrackchange') ;
const textTracksChanges = () => {
updateDisplay() ;
for (let i = 0 ; i < tracks.length ; i++) {
const track = tracks[i] ;
track.removeEventListener('cuechange', updateDisplay) ;
if (track.mode === 'showing') {
track.addEventListener('cuechange', updateDisplay) ;
}
}
};
textTracksChanges() ;
tracks.addEventListener('change', textTracksChanges) ;
tracks.addEventListener('addtrack', textTracksChanges) ;
tracks.addEventListener('removetrack', textTracksChanges) ;
this.on('dispose', function() {
remoteTracks.off('addtrack', handleAddTrack) ;
remoteTracks.off('removetrack', handleRemoveTrack) ;
tracks.removeEventListener('change', textTracksChanges) ;
tracks.removeEventListener('addtrack', textTracksChanges) ;
tracks.removeEventListener('removetrack', textTracksChanges) ;
for (let i = 0 ; i < tracks.length ; i++) {
const track = tracks[i] ;
track.removeEventListener('cuechange', updateDisplay) ;
}
}) ;
}
/**
* Crée et renvoie un objet {@link TextTrack} distant.
*
* @param {string} kind
* type de `TextTrack` (sous-titres, légendes, descriptions, chapitres ou métadonnées)
*
* @param {string} [label]
* Étiquette pour identifier la piste de texte
*
* @param {string} [langue]
* Abréviation linguistique à deux lettres
*
* @return {TextTrack}
* La piste de texte qui est créée.
*/
addTextTrack(kind, label, language) {
if (!kind) {
lancer une nouvelle erreur ('TextTrack kind is required but was not provided') ;
}
return createTrackHelper(this, kind, label, language) ;
}
/**
* Créer un TextTrack émulé à utiliser par addRemoteTextTrack
*
* Elle est destinée à être surchargée par les classes qui héritent de
* Tech afin de créer des TextTracks natifs ou personnalisés.
*
* @param {Objet} options
* L'objet doit contenir les options permettant d'initialiser la piste de texte.
*
* @param {string} [options.kind]
* type de `TextTrack` (sous-titres, légendes, descriptions, chapitres ou métadonnées).
*
* @param {string} [options.label].
* Étiquette pour identifier la piste de texte
*
* @param {string} [options.language]
* Abréviation linguistique de deux lettres.
*
* @return {HTMLTrackElement}
* L'élément de piste qui est créé.
*/
createRemoteTextTrack(options) {
const track = mergeOptions(options, {
tech : this
}) ;
return new TRACK_TYPES.REMOTE.remoteTextEl.TrackClass(track) ;
}
/**
* Crée un objet de piste de texte distant et renvoie un élément de piste html.
*
* > Remarque : Il peut s'agir d'un {@link HTMLTrackElement} émulé ou natif.
*
* @param {Objet} options
* Voir {@link Tech#createRemoteTextTrack} pour des propriétés plus détaillées.
*
* @param {boolean} [manualCleanup=true]
* - Si false : le TextTrack sera automatiquement supprimé de la vidéo
* chaque fois que la source change
* - Quand c'est vrai : Le TextTrack devra être nettoyé manuellement
*
* @return {HTMLTrackElement}
* Un élément de piste Html.
*
* @deprecated La fonctionnalité par défaut de cette fonction sera équivalente
* à "manualCleanup=false" à l'avenir. Le paramètre manualCleanup (nettoyage manuel)
* sont également supprimés.
*/
addRemoteTextTrack(options = {}, manualCleanup) {
const htmlTrackElement = this.createRemoteTextTrack(options) ;
if (manualCleanup !== true && manualCleanup !== false) {
// avertissement de dépréciation
log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js') ;
manualCleanup = true ;
}
// enregistrer HTMLTrackElement et TextTrack dans la liste distante
this.remoteTextTrackEls().addTrackElement_(htmlTrackElement) ;
this.remoteTextTracks().addTrack(htmlTrackElement.track) ;
if (manualCleanup !== true) {
// créer la TextTrackList si elle n'existe pas
this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track)) ;
}
return htmlTrackElement ;
}
/**
* Supprime une piste de texte distante de la `TextTrackList` distante.
*
* @param {TextTrack} track
* `TextTrack` à retirer de la `TextTrackList`
*/
removeRemoteTextTrack(track) {
const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track) ;
// supprimer HTMLTrackElement et TextTrack de la liste distante
this.remoteTextTrackEls().removeTrackElement_(trackElement) ;
this.remoteTextTracks().removeTrack(track) ;
this.autoRemoteTextTracks_.removeTrack(track) ;
}
/**
* Obtient les mesures de qualité de lecture des médias disponibles, telles que spécifiées par la norme Media
* Qualité de lecture API.
*
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
*
* @return {Object}
* Un objet contenant les mesures de qualité de lecture des médias prises en charge
*
* @abstract
*/
getVideoPlaybackQuality() {
return {} ;
}
/**
* Tentative de création d'une fenêtre vidéo flottante toujours au-dessus d'autres fenêtres
* afin que les utilisateurs puissent continuer à consommer des médias tout en interagissant avec d'autres utilisateurs
* des sites de contenu ou des applications sur leur appareil.
*
* @see [Spec]{@link https://wicg.github.io/picture-in-picture}
*
* @return {Promesse|non défini}
* Une promesse avec une fenêtre d'image dans l'image si le navigateur le permet
* Promesses (ou l'une d'entre elles a été transmise en tant qu'option). Il renvoie une valeur non définie
* autrement.
*
* @abstract
*/
requestPictureInPicture() {
const PromiseClass = this.options_.Promise || window.Promise ;
if (PromiseClass) {
return PromiseClass.reject() ;
}
}
/**
* Méthode permettant de vérifier la valeur de la propriété "disablePictureInPicture" <video> .
* La valeur par défaut est true, car elle doit être considérée comme désactivée si le technicien ne prend pas en charge les tuyaux
*
* @abstract
*/
disablePictureInPicture() {
retourner vrai ;
}
/**
* Méthode permettant de définir ou d'annuler la propriété "disablePictureInPicture" de la vidéo <> .
*
* @abstract
*/
setDisablePictureInPicture() {}
/**
* Une implémentation de secours de requestVideoFrameCallback utilisant requestAnimationFrame
*
* @param {fonction} cb
* @return {number} request id
*/
requestVideoFrameCallback(cb) {
const id = Guid.newGUID() ;
if (!this.isReady_ || this.paused()) {
this.queuedHanders_.add(id) ;
this.one('playing', () => {
if (this.queuedHanders_.has(id)) {
this.queuedHanders_.delete(id) ;
cb() ;
}
}) ;
} else {
this.requestNamedAnimationFrame(id, cb) ;
}
retourner l'id ;
}
/**
* Une implémentation de secours de cancelVideoFrameCallback
*
* @param {number} id id du rappel à annuler
*/
cancelVideoFrameCallback(id) {
if (this.queuedHanders_.has(id)) {
this.queuedHanders_.delete(id) ;
} else {
this.cancelNamedAnimationFrame(id) ;
}
}
/**
* Une méthode pour créer un poster à partir d'un `Tech`.
*
* @abstract
*/
setPoster() {}
/**
* Méthode permettant de vérifier la présence de l'attribut "playsinline" <video> .
*
* @abstract
*/
playsinline() {}
/**
* Méthode permettant d'activer ou de désactiver l'attribut "playsinline" de la vidéo <> .
*
* @abstract
*/
setPlaysinline() {}
/**
* Tentative de forcer le remplacement des pistes audio natives.
*
* @param {boolean} override - Si la valeur est fixée à true, l'audio natif sera remplacé,
* sinon le son natif sera potentiellement utilisé.
*
* @abstract
*/
overrideNativeAudioTracks() {}
/**
* Tentative de forcer le remplacement des pistes vidéo natives.
*
* @param {boolean} override - Si la valeur est fixée à true, la vidéo native sera remplacée,
* dans le cas contraire, la vidéo native sera potentiellement utilisée.
*
* @abstract
*/
overrideNativeVideoTracks() {}
/*
* Vérifie si le technicien peut prendre en charge le type de message donné.
*
* La technologie de base ne prend en charge aucun type, mais les gestionnaires de sources peuvent le faire
* l'écraser.
*
* @param {string} type
* Le type d'image à vérifier pour la prise en charge
*
* @return {string}
* "probablement", "peut-être" ou chaîne vide
*
* @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
*
* @abstract
*/
canPlayType() {
retourner '' ;
}
/**
* Vérifier si le type est pris en charge par ce tech.
*
* La technologie de base ne prend en charge aucun type, mais les gestionnaires de sources peuvent le faire
* l'écraser.
*
* @param {string} type
* Le type de support à vérifier
* @return {string} Renvoie la réponse de l'élément vidéo natif
*/
static canPlayType() {
retourner '' ;
}
/**
* Vérifier si le technicien peut prendre en charge la source donnée
*
* @param {Objet} srcObj
* L'objet source
* @param {Objet} options
* Les options transmises au tech
* @return {string} 'probablement', 'peut-être', ou '' (chaîne vide)
*/
static canPlaySource(srcObj, options) {
return Tech.canPlayType(srcObj.type) ;
}
/*
* Retourne si l'argument est un Tech ou non.
* On peut lui passer une classe comme `Html5` ou une instance comme `player.tech_`
*
* @param {Object} component
* L'élément à vérifier
*
* @return {boolean}
* Qu'il s'agisse d'une technologie ou non
* - Vrai s'il s'agit d'une technologie
* - Faux si ce n'est pas le cas
*/
static isTech(component) {
return component.prototype instanceof Tech ||
composant instanceof Tech ||
composant === Tech ;
}
/**
* Enregistre une `Technologie` dans une liste partagée pour videojs.
*
* @param {string} name
* Nom de la "technologie" à enregistrer.
*
* @param {Objet} tech
* La classe `Tech` à inscrire.
*/
static registerTech(name, tech) {
if (!Tech.techs_) {
Tech.techs_ = {} ;
}
if (!Tech.isTech(tech)) {
lancer une nouvelle erreur (`Tech ${name} doit être un Tech`) ;
}
if (!Tech.canPlayType) {
throw new Error('Les techniciens doivent avoir une méthode statique canPlayType sur eux') ;
}
if (!Tech.canPlaySource) {
lancer une nouvelle erreur ('Les techniciens doivent avoir une méthode statique canPlaySource sur eux') ;
}
nom = toTitleCase(nom) ;
Tech.techs_[name] = tech ;
Tech.techs_[toLowerCase(name)] = tech ;
if (name !== 'Tech') {
// camel case le techName à utiliser dans techOrder
Tech.defaultTechOrder_.push(name) ;
}
retour tech ;
}
/**
* Obtenir un `Technicien` de la liste partagée par son nom.
*
* @param {string} name
* `camelCase` ou `TitleCase` nom de la Tech à obtenir
*
* @return {Tech|undefined}
* Le `Tech` ou undefined s'il n'y a pas de tech avec le nom demandé.
*/
static getTech(name) {
if (!name) {
retour ;
}
if (Tech.techs_ && Tech.techs_[name]) {
return Tech.techs_[nom] ;
}
nom = toTitleCase(nom) ;
if (window && window.videojs && window.videojs[name]) {
log.warn(`La technologie ${nom} a été ajoutée à l'objet videojs alors qu'elle devrait être enregistrée à l'aide de videojs.registerTech(nom, tech)`) ;
return window.videojs[name] ;
}
}
}
/**
* Obtenir la {@link VideoTrackList}
*
* @returns {VideoTrackList}
* @method Tech.prototype.videoTracks
*/
/**
* Obtenir la {@link AudioTrackList}
*
* @returns {AudioTrackList}
* @method Tech.prototype.audioTracks
*/
/**
* Obtenir la {@link TextTrackList}
*
* @returns {TextTrackList}
* @method Tech.prototype.textTracks
*/
/**
* Obtenir l'élément distant {@link TextTrackList}
*
* @returns {TextTrackList}
* @méthode Tech.prototype.remoteTextTracks
*/
/**
* Obtenir l'élément distant {@link HtmlTrackElementList}
*
* @returns {HtmlTrackElementList}
* @method Tech.prototype.remoteTextTrackEls
*/
TRACK_TYPES.ALL.names.forEach(function(name) {
const props = TRACK_TYPES.ALL[name] ;
Tech.prototype[props.getterName] = function() {
this[props.privateName] = this[props.privateName] || new props.ListClass() ;
return this[props.privateName] ;
};
}) ;
/**
* Liste des pistes de texte associées
*
* @type {TextTrackList}
* @private
* @property Tech#textTracks_
*/
/**
* Liste des pistes audio associées.
*
* @type {AudioTrackList}
* @private
* @property Tech#audioTracks_
*/
/**
* Liste des pistes vidéo associées.
*
* @type {VideoTrackList}
* @private
* @property Tech#videoTracks_
*/
/**
* Booléen indiquant si la `Tech` prend en charge le contrôle du volume.
*
* @type {boolean}
* @défaut
*/
Tech.prototype.featuresVolumeControl = true ;
/**
* Booléen indiquant si la `Tech` supporte le volume muting.
*
* @type {bolean}
* @défaut
*/
Tech.prototype.featuresMuteControl = true ;
/**
* Booléen indiquant si le `Tech` supporte le contrôle de redimensionnement plein écran.
* Le redimensionnement des plugins à l'aide de la requête fullscreen recharge le plugin
*
* @type {boolean}
* @défaut
*/
Tech.prototype.featuresFullscreenResize = false ;
/**
* Booléen indiquant si la `Tech` prend en charge la modification de la vitesse à laquelle la vidéo est diffusée
* joue. Exemples :
* - Régler le lecteur pour qu'il joue 2x (deux fois) plus vite
* - Régler le lecteur pour qu'il joue 0,5x (moitié) plus vite
*
* @type {boolean}
* @défaut
*/
Tech.prototype.featuresPlaybackRate = false ;
/**
* Booléen indiquant si la `Tech` supporte l'événement `progress`. Il s'agit actuellement
* non déclenché par video-js-swf. Cela permettra de déterminer si
* {@link Tech#manualProgressOn} doit être appelé.
*
* @type {boolean}
* @défaut
*/
Tech.prototype.featuresProgressEvents = false ;
/**
* Booléen indiquant si la `Tech` supporte l'événement `sourceset`.
*
* Un technicien doit mettre cette valeur à `true` et utiliser {@link Tech#triggerSourceset}
* pour déclencher un {@link Tech#event:sourceset} au plus tôt après avoir obtenu
* une nouvelle source.
*
* @type {boolean}
* @défaut
*/
Tech.prototype.featuresSourceset = false ;
/**
* Booléen indiquant si la `Tech` supporte l'événement `timeupdate`. Il s'agit actuellement
* non déclenché par video-js-swf. Cela permettra de déterminer si
* {@link Tech#manualTimeUpdates} doit être appelé.
*
* @type {boolean}
* @défaut
*/
Tech.prototype.featuresTimeupdateEvents = false ;
/**
* Booléen indiquant si la `Tech` supporte les `TextTrack`s natifs.
* Cela nous aidera à intégrer les `TextTrack`s natifs si le navigateur les supporte.
*
* @type {boolean}
* @défaut
*/
Tech.prototype.featuresNativeTextTracks = false ;
/**
* Booléen indiquant si la `Tech` supporte `requestVideoFrameCallback`.
*
* @type {boolean}
* @défaut
*/
Tech.prototype.featuresVideoFrameCallback = false ;
/**
* Un mixin fonctionnel pour les techniciens qui veulent utiliser le modèle Source Handler.
* Les gestionnaires de sources sont des scripts permettant de gérer des formats spécifiques.
* Le modèle de gestionnaire de source est utilisé pour les formats adaptatifs (HLS, DASH) qui
* charger manuellement des données vidéo et les introduire dans un tampon source (Media Source Extensions)
* Exemple : `Tech.withSourceHandlers.call(MyTech);`
*
* @param {Tech} _Tech
* La technologie à laquelle ajouter des fonctions de gestion des sources.
*
* @mixes Tech~SourceHandlerAdditions
*/
Tech.withSourceHandlers = function(_Tech) {
/**
* Enregistrer un gestionnaire de sources
*
* @param {Fonction} handler
* La classe du gestionnaire de source
*
* @param {number} [index]
* Enregistrez-le à l'adresse suivante
*/
_Tech.registerSourceHandler = function(handler, index) {
let handlers = _Tech.sourceHandlers ;
if (!handlers) {
handlers = _Tech.sourceHandlers = [] ;
}
if (index === undefined) {
// ajouter à la fin de la liste
index = handlers.length ;
}
handlers.splice(index, 0, handler) ;
};
/**
* Vérifie si le technicien peut prendre en charge le type donné. Vérifie également le
* Techs sourceHandlers.
*
* @param {string} type
* Le type d'image à vérifier.
*
* @return {string}
* "probablement", "peut-être" ou '' (chaîne vide)
*/
_Tech.canPlayType = function(type) {
const handlers = _Tech.sourceHandlers || [] ;
peuvent le faire ;
for (let i = 0 ; i < handlers.length ; i++) {
can = handlers[i].canPlayType(type) ;
if (can) {
peuvent être retournés ;
}
}
retourner '' ;
};
/**
* Renvoie le premier gestionnaire de source qui prend en charge la source.
*
* TODO : Réponse à la question : faut-il donner la priorité à "probablement" plutôt qu'à "peut-être" ?
*
* @param {Tech~SourceObject} source
* L'objet source
*
* @param {Objet} options
* Les options transmises au tech
*
* @return {SourceHandler|null}
* Le premier gestionnaire de source qui prend en charge la source ou null si
* aucun SourceHandler ne prend en charge la source
*/
_Tech.selectSourceHandler = function(source, options) {
const handlers = _Tech.sourceHandlers || [] ;
peuvent le faire ;
for (let i = 0 ; i < handlers.length ; i++) {
can = handlers[i].canHandleSource(source, options) ;
if (can) {
return handlers[i] ;
}
}
retourner null ;
};
/**
* Vérifier si le technicien peut prendre en charge la source donnée.
*
* @param {Tech~SourceObject} srcObj
* L'objet source
*
* @param {Objet} options
* Les options transmises au tech
*
* @return {string}
* "probablement", "peut-être" ou '' (chaîne vide)
*/
_Tech.canPlaySource = function(srcObj, options) {
const sh = _Tech.selectSourceHandler(srcObj, options) ;
if (sh) {
return sh.canHandleSource(srcObj, options) ;
}
retourner '' ;
};
/**
* Lors de l'utilisation d'un gestionnaire de source, préférez son implémentation de
* toute fonction normalement assurée par le technicien.
*/
const deferrable = [
'cherchable',
recherche",
durée
] ;
/**
* Une enveloppe autour de {@link Tech#seekable} qui appellera un `SourceHandler`s seekable
* si elle existe, avec un retour à la fonction "Techs seekable".
*
* @méthode _Tech.seekable
*/
/**
* Un wrapper autour de {@link Tech#duration} qui appellera un `SourceHandler`s duration
* si elle existe, sinon il se rabattra sur la fonction de durée des techniciens.
*
* @method _Tech.duration
*/
deferrable.forEach(function(fnName) {
const originalFn = this[fnName] ;
if (typeof originalFn !== 'function') {
retour ;
}
this[fnName] = function() {
if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments) ;
}
return originalFn.apply(this, arguments) ;
};
}, _Tech.prototype) ;
/**
* Créer une fonction pour définir la source à l'aide d'un objet source
* et les gestionnaires de sources.
* Ne devrait jamais être appelé à moins qu'un gestionnaire de source n'ait été trouvé.
*
* @param {Tech~SourceObject} source
* Un objet source avec les clés src et type
*/
_Tech.prototype.setSource = function(source) {
let sh = _Tech.selectSourceHandler(source, this.options_) ;
if (!sh) {
// Revenir à un gestionnaire de source natif lorsque des sources non prises en charge sont
// délibérément fixé
if (_Tech.nativeSourceHandler) {
sh = _Tech.nativeSourceHandler ;
} else {
log.error('Aucun gestionnaire de source n'a été trouvé pour la source actuelle.') ;
}
}
// Se débarrasser de tout gestionnaire de source existant
this.disposeSourceHandler() ;
this.off('dispose', this.disposeSourceHandler_) ;
if (sh !== _Tech.nativeSourceHandler) {
this.currentSource_ = source ;
}
this.sourceHandler_ = sh.handleSource(source, this, this.options_) ;
this.one('dispose', this.disposeSourceHandler_) ;
};
/**
* Nettoyer tous les SourceHandlers et listeners existants lorsque le Tech est éliminé.
*
* @listens Tech#dispose
*/
_Tech.prototype.disposeSourceHandler = function() {
// si nous disposons d'une source et en obtenons une autre
// nous chargeons alors quelque chose de nouveau
// que d'effacer toutes nos traces actuelles
if (this.currentSource_) {
this.clearTracks(['audio', 'video']) ;
this.currentSource_ = null ;
}
// toujours nettoyer les pistes de texte automatique
this.cleanupAutoTextTracks() ;
if (this.sourceHandler_) {
if (this.sourceHandler_.dispose) {
this.sourceHandler_.dispose() ;
}
this.sourceHandler_ = null ;
}
};
};
// La classe Tech de base doit être enregistrée en tant que composant. C'est le seul
// Tech qui peut être enregistré en tant que composant.
Component.registerComponent('Tech', Tech) ;
Tech.registerTech('Tech', Tech) ;
/**
* Une liste de technologies qui devraient être ajoutées à techOrder sur les joueurs
*
* @private
*/
Tech.defaultTechOrder_ = [] ;
export default Tech ;