/**
* fichier player.js
*/
// Sous-classes Composant
import Component from './component.js' ;
import {version} de '../../package.json' ;
import document from 'global/document' ;
import window from 'global/window' ;
import evented de './mixins/evented' ;
import {isEvented, addEventedCallback} from './mixins/evented' ;
import * as Events from './utils/events.js' ;
import * as Dom from './utils/dom.js' ;
import * as Fn from './utils/fn.js' ;
import * as Guid from './utils/guid.js' ;
import * as browser from './utils/browser.js' ;
import {IE_VERSION, IS_CHROME, IS_WINDOWS} from './utils/browser.js' ;
import log, { createLogger } from './utils/log.js' ;
import {toTitleCase, titleCaseEquals} from './utils/string-cases.js' ;
import { createTimeRange } from './utils/time-ranges.js' ;
import { bufferedPercent } from './utils/buffer.js' ;
import * as stylesheet from './utils/stylesheet.js' ;
import FullscreenApi de './fullscreen-api.js' ;
import MediaError de './media-error.js' ;
import safeParseTuple from 'safe-json-parse/tuple' ;
import {assign} from './utils/obj' ;
import mergeOptions from './utils/merge-options.js' ;
import {silencePromise, isPromise} de './utils/promesse' ;
import textTrackConverter from './tracks/text-track-list-converter.js' ;
import ModalDialog from './modal-dialog' ;
import Tech from './tech/tech.js' ;
import * as middleware from './tech/middleware.js' ;
import {ALL as TRACK_TYPES} from './tracks/track-types' ;
import filterSource de './utils/filter-source' ;
import {getMimetype, findMimetype} de './utils/mimetypes' ;
import {hooks} de './utils/hooks' ;
import {isObject} de './utils/obj' ;
import keycode from 'keycode' ;
// Les importations suivantes ne sont utilisées que pour garantir que les modules correspondants
// sont toujours inclus dans le paquet video.js. L'importation des modules
// exécutez-les et ils s'enregistreront avec video.js.
import './tech/loader.js' ;
import './poster-image.js' ;
import './tracks/text-track-display.js' ;
import './loading-spinner.js' ;
import './big-play-button.js' ;
import './close-button.js' ;
import './control-bar/control-bar.js' ;
import './error-display.js' ;
import './tracks/text-track-settings.js' ;
import './resize-manager.js' ;
import './live-tracker.js' ;
// Importer la technologie Html5, au moins pour éliminer la balise vidéo originale.
import './tech/html5.js' ;
// Les événements techniques suivants sont simplement redéclenchés
// sur le joueur lorsqu'ils se produisent
const TECH_EVENTS_RETRIGGER = [
/**
* Déclenché lorsque l'agent utilisateur est en train de télécharger des données multimédias.
*
* @event Player#progress
* @type {EventTarget~Event}
*/
/**
* Redéclenche l'événement `progress` qui a été déclenché par le {@link Tech}.
*
* @private
* @method Player#handleTechProgress_ (méthode du joueur)
* @fires Player#progress
* @listens Tech#progrès
*/
'progrès',
/**
* Se déclenche lorsque le chargement d'un fichier audio/vidéo est interrompu.
*
* @event Player#abort
* @type {EventTarget~Event}
*/
/**
* Redéclenche l'événement `abort` qui a été déclenché par le {@link Tech}.
*
* @private
* @method Player#handleTechAbort_ (méthode du joueur)
* @fires Player#abort
* @listens Tech#abort
*/
'abort',
/**
* Se déclenche lorsque le navigateur ne reçoit intentionnellement pas de données multimédias.
*
* @event Player#suspend
* @type {EventTarget~Event}
*/
/**
* Redéclenche l'événement `suspend` qui a été déclenché par le {@link Tech}.
*
* @private
* @method Player#handleTechSuspend_ (méthode du joueur)
* @fires Player#suspend
* @listens Tech#suspend
*/
suspendre",
/**
* Se déclenche lorsque la liste de lecture actuelle est vide.
*
* @event Player#emptied
* @type {EventTarget~Event}
*/
/**
* Redéclenche l'événement `emptied` qui a été déclenché par le {@link Tech}.
*
* @private
* @method Player#handleTechEmptied_ (méthode du joueur)
* @fires Player#emptied
* @listens Tech#emptied
*/
vide",
/**
* Se déclenche lorsque le navigateur tente d'obtenir des données multimédias, mais que celles-ci ne sont pas disponibles.
*
* @event Player#stalled
* @type {EventTarget~Event}
*/
/**
* Redéclenche l'événement `stalled` qui a été déclenché par le {@link Tech}.
*
* @private
* @method Player#handleTechStalled_ (méthode du joueur)
* @fires Player#stalled
* @listens Tech#stalled
*/
"bloqué",
/**
* Se déclenche lorsque le navigateur a chargé les métadonnées de l'audio/vidéo.
*
* @event Player#loadedmetadata
* @type {EventTarget~Event}
*/
/**
* Redéclenche l'événement `loadedmetadata` qui a été déclenché par le {@link Tech}.
*
* @private
* @method Player#handleTechLoadedmetadata_ (méthode du joueur)
* @fires Player#loadedmetadata
* @listens Tech#loadedmetadata
*/
'loadedmetadata',
/**
* Se déclenche lorsque le navigateur a chargé l'image actuelle de l'audio/vidéo.
*
* @event Player#loadeddata
* @type {event}
*/
/**
* Redéclenche l'événement `loadeddata` qui a été déclenché par le {@link Tech}.
*
* @private
* @method Player#handleTechLoaddeddata_ (méthode du joueur)
* @fires Player#loadeddata
* @listens Tech#loadeddata
*/
'loadeddata',
/**
* Se déclenche lorsque la position de lecture actuelle a changé.
*
* @event Player#timeupdate
* @type {event}
*/
/**
* Redéclenche l'événement `timeupdate` qui a été déclenché par le {@link Tech}.
*
* @private
* @method Player#handleTechTimeUpdate_ (méthode du joueur)
* @fires Player#timeupdate
* @listens Tech#timeupdate
*/
'timeupdate',
/**
* Se déclenche lorsque les dimensions intrinsèques de la vidéo changent
*
* @event Player#resize
* @type {event}
*/
/**
* Redéclenche l'événement `resize` qui a été déclenché par le {@link Tech}.
*
* @private
* @method Player#handleTechResize_ (méthode du joueur)
* @fires Player#resize
* @listens Tech#resize
*/
'redimensionner',
/**
* Se déclenche lorsque le volume a été modifié
*
* @event Player#volumechange
* @type {event}
*/
/**
* Redéclenche l'événement `volumechange` qui a été déclenché par le {@link Tech}.
*
* @private
* @method Player#handleTechVolumechange_ (méthode du joueur)
* @fires Player#volumechange
* @listens Tech#volumechange
*/
'volumechange',
/**
* Se déclenche lorsque la piste de texte a été modifiée
*
* @event Player#texttrackchange
* @type {event}
*/
/**
* Redéclenche l'événement `texttrackchange` qui a été déclenché par le {@link Tech}.
*
* @private
* @method Joueur#manipulerTexttrackchange_
* @fires Player#texttrackchange
* @listens Tech#texttrackchange
*/
'texttrackchange' (changement de texte)
] ;
// événements à mettre en file d'attente lorsque la vitesse de lecture est nulle
// il s'agit d'un hachage dans le seul but de mettre en correspondance les noms d'événements sans majuscule
// aux noms de fonctions en majuscules
const TECH_EVENTS_QUEUE = {
canplay : canPlay",
peut jouer : canPlayThrough",
jouer : jouer",
recherchée : cherché
};
const BREAKPOINT_ORDER = [
minuscule",
xsmall",
petit",
moyen",
grand",
xlarge",
énorme
] ;
const BREAKPOINT_CLASSES = {} ;
// grep : vjs-layout-tiny
// grep : vjs-layout-x-small
// grep : vjs-layout-small
// grep : vjs-layout-medium
// grep : vjs-layout-large
// grep : vjs-layout-x-large
// grep : vjs-layout-huge
BREAKPOINT_ORDER.forEach(k => {
const v = k.charAt(0) === 'x' ? `x-${k.substring(1)}` : k ;
BREAKPOINT_CLASSES[k] = `vjs-layout-${v}` ;
}) ;
const DEFAULT_BREAKPOINTS = {
minuscule : 210,
xsmall : 320,
petit : 425,
moyen : 768,
grand : 1440,
xlarge : 2560,
énorme : L'infini
};
/**
* Une instance de la classe `Player` est créée lorsque l'une des méthodes d'installation de Video.js
* sont utilisées pour initialiser une vidéo.
*
* Une fois qu'une instance a été créée, il est possible d'y accéder globalement de deux manières :
* 1. En appelant `videojs('example_video_1');`
* 2. En l'utilisant directement via `videojs.players.example_video_1;`
*
* @extends Component
*/
class Player extends Component {
/**
* Créer une instance de cette classe.
*
* @param {Element} tag
* L'élément DOM de la vidéo originale utilisé pour configurer les options.
*
* @param {Objet} [options]
* Objet contenant les noms et les valeurs des options.
*
* @param {Component~ReadyCallback} [ready]
* Fonction de rappel de l'état prêt.
*/
constructor(tag, options, ready) {
// S'assurer que l'identifiant de l'étiquette existe
tag.id = tag.id || options.id || `vjs_video_${Guid.newGUID()}` ;
// Définir les options
// L'argument options remplace les options définies dans la balise vidéo
// qui remplace les options définies globalement.
// Cette dernière partie coïncide avec l'ordre de chargement
// (la balise doit exister avant le lecteur)
options = assign(Player.getTagSettings(tag), options) ;
// Retarder l'initialisation des enfants parce que nous devons mettre en place
// les propriétés du joueur en premier, et ne peut pas utiliser `this` avant `super()`
options.initChildren = false ;
// Idem pour la création de l'élément
options.createEl = false ;
// ne pas mélanger automatiquement le mélange de l'événement
options.evented = false ;
// nous ne voulons pas que le lecteur signale une activité tactile sur lui-même
// voir enableTouchActivity dans le composant
options.reportTouchActivity = false ;
// Si la langue n'est pas définie, l'attribut lang le plus proche est obtenu
if (!options.language) {
if (typeof tag.closest === 'function') {
const closest = tag.closest('[lang]') ;
if (closest && closest.getAttribute) {
options.language = closest.getAttribute('lang') ;
}
} else {
let element = tag ;
while (element && element.nodeType === 1) {
if (Dom.getAttributes(element).hasOwnProperty('lang')) {
options.language = element.getAttribute('lang') ;
pause ;
}
element = element.parentNode ;
}
}
}
// Exécuter le composant de base en l'initialisant avec les nouvelles options
super(null, options, ready) ;
// Créer des méthodes liées pour les auditeurs de documents.
this.boundDocumentFullscreenChange_ = (e) => this.documentFullscreenChange_(e) ;
this.boundFullWindowOnEscKey_ = (e) => this.fullWindowOnEscKey(e) ;
this.boundUpdateStyleEl_ = (e) => this.updateStyleEl_(e) ;
this.boundApplyInitTime_ = (e) => this.applyInitTime_(e) ;
this.boundUpdateCurrentBreakpoint_ = (e) => this.updateCurrentBreakpoint_(e) ;
this.boundHandleTechClick_ = (e) => this.handleTechClick_(e) ;
this.boundHandleTechDoubleClick_ = (e) => this.handleTechDoubleClick_(e) ;
this.boundHandleTechTouchStart_ = (e) => this.handleTechTouchStart_(e) ;
this.boundHandleTechTouchMove_ = (e) => this.handleTechTouchMove_(e) ;
this.boundHandleTechTouchEnd_ = (e) => this.handleTechTouchEnd_(e) ;
this.boundHandleTechTap_ = (e) => this.handleTechTap_(e) ;
// la valeur par défaut de isFullscreen_ est false
this.isFullscreen_ = false ;
// créer un logger
this.log = createLogger(this.id_) ;
// Conserver notre propre référence à l'api fullscreen pour qu'elle puisse être simulée dans les tests
this.fsApi_ = FullscreenApi ;
// Trace le changement d'affiche par un technicien
this.isPosterFromTech_ = false ;
// Contient les informations de rappel qui sont mises en file d'attente lorsque la vitesse de lecture est nulle
// et une recherche est en cours
this.queuedCallbacks_ = [] ;
// Désactiver l'accès à l'API car nous chargeons une nouvelle technologie qui pourrait se charger de manière asynchrone
this.isReady_ = false ;
// État d'initialisation hasStarted_
this.hasStarted_ = false ;
// État d'initialisation userActive_
this.userActive_ = false ;
// Init debugEnabled_
this.debugEnabled_ = false ;
// Init state audioOnlyMode_ (état d'activation)
this.audioOnlyMode_ = false ;
// État d'initialisation audioPosterMode_
this.audioPosterMode_ = false ;
// État d'initialisation audioOnlyCache_
this.audioOnlyCache_ = {
playerHeight : null,
hiddenChildren : []
};
// si l'objet global de l'option a été accidentellement emporté par
// quelqu'un, se retirer plus tôt avec une erreur d'information
if (!this.options_ ||
!this.options_.techOrder ||
!this.options_.techOrder.length) {
lancer une nouvelle erreur ('No techOrder specified. Avez-vous écrasé ' +
'videojs.options au lieu de simplement changer le ' +
'properties you want to override?') ;
}
// Conserve la balise originale utilisée pour définir les options
this.tag = tag ;
// Stocke les attributs de la balise utilisés pour restaurer l'élément html5
this.tagAttributes = tag && Dom.getAttributes(tag) ;
// Mise à jour de la langue actuelle
this.language(this.options_.language) ;
// Mise à jour des langues prises en charge
if (options.languages) {
// Normaliser les langues des options du lecteur en minuscules
const languagesToLower = {} ;
Object.getOwnPropertyNames(options.languages).forEach(function(name) {
languagesToLower[name.toLowerCase()] = options.languages[name] ;
}) ;
this.languages_ = languagesToLower ;
} else {
this.languages_ = Player.prototype.options_.languages ;
}
this.resetCache_() ;
// Affiche fixe
this.poster_ = options.poster || '' ;
// Définir les contrôles
this.controls_ = !!options.controls ;
// Les paramètres originaux de la balise sont stockés dans les options
// à retirer immédiatement pour que les contrôles natifs ne clignotent pas.
// Peut être réactivé par la technologie HTML5 si nativeControlsForTouch est vrai
tag.controls = false ;
tag.removeAttribute('controls') ;
this.changingSrc_ = false ;
this.playCallbacks_ = [] ;
this.playTerminatedQueue_ = [] ;
// l'attribut remplace l'option
if (tag.hasAttribute('autoplay')) {
this.autoplay(true) ;
} else {
// sinon utiliser le setter pour valider et
// définir la valeur correcte.
this.autoplay(this.options_.autoplay) ;
}
// vérifier les plugins
if (options.plugins) {
Object.keys(options.plugins).forEach((name) => {
if (typeof this[name] !== 'function') {
throw new Error(`le plugin "${nom}" n'existe pas`) ;
}
}) ;
}
/*
* Stocker l'état interne de l'épuration
*
* @private
* @return {Boolean} True si l'utilisateur est en train d'épurer
*/
this.scrubbing_ = false ;
this.el_ = this.createEl() ;
// Faire de cet objet un objet événementiel et utiliser `el_` comme bus d'événements.
evented(this, {eventBusKey : 'el_'}) ;
// écouter les gestionnaires de changement d'écran du document et du lecteur afin de recevoir ces événements
// avant qu'un utilisateur ne les reçoive, afin que nous puissions mettre à jour isFullscreen de manière appropriée.
// s'assurer que nous écoutons les événements fullscreenchange avant tout le reste pour s'assurer que
// notre méthode isFullscreen est mise à jour correctement pour les composants internes comme pour les composants externes.
if (this.fsApi_.requestFullscreen) {
Events.on(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_) ;
this.on(this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_) ;
}
if (this.fluid_) {
this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_) ;
}
// Nous voulons également transmettre les options du lecteur d'origine à chaque composant et plugin
// afin qu'ils n'aient pas besoin de retourner dans le lecteur pour trouver des options plus tard.
// Nous devons également faire une autre copie de this.options_ pour ne pas nous retrouver avec
// une boucle infinie.
const playerOptionsCopy = mergeOptions(this.options_) ;
// Chargement des plugins
if (options.plugins) {
Object.keys(options.plugins).forEach((name) => {
this[name](options.plugins[name]) ;
}) ;
}
// Activer le mode débogage pour déclencher l'événement debugon pour tous les plugins.
if (options.debug) {
this.debug(true) ;
}
this.options_.playerOptions = playerOptionsCopy ;
this.middleware_ = [] ;
this.playbackRates(options.playbackRates) ;
this.initChildren() ;
// Définir isAudio en fonction de l'utilisation ou non d'une balise audio
this.isAudio(tag.nodeName.toLowerCase() === 'audio') ;
// Mise à jour des contrôles className. Impossible de le faire lorsque les contrôles sont initiaux
// set parce que l'élément n'existe pas encore.
if (this.controls()) {
this.addClass('vjs-controls-enabled') ;
} else {
this.addClass('vjs-controls-disabled') ;
}
// Définir l'étiquette ARIA et le rôle de la région en fonction du type de joueur
this.el_.setAttribute('role', 'region') ;
if (this.isAudio()) {
this.el_.setAttribute('aria-label', this.localize('Audio Player')) ;
} else {
this.el_.setAttribute('aria-label', this.localize('Video Player')) ;
}
if (this.isAudio()) {
this.addClass('vjs-audio') ;
}
if (this.flexNotSupported_()) {
this.addClass('vjs-no-flex') ;
}
// TODO : Rendez cela plus intelligent. Basculer l'état de l'utilisateur entre le toucher et la souris
// en utilisant des événements, puisque les appareils peuvent avoir à la fois des événements tactiles et des événements souris.
// TODO : Cette vérification doit être répétée lorsque la fenêtre passe d'un moniteur à l'autre
// (Voir https://github.com/videojs/video.js/issues/5683)
if (browser.TOUCH_ENABLED) {
this.addClass('vjs-touch-enabled') ;
}
// Safari iOS ne gère pas le survol de l'image
if (!browser.IS_IOS) {
this.addClass('vjs-workinghover') ;
}
// Rendre le joueur facilement trouvable par son identifiant
Player.players[this.id_] = this ;
// Ajouter une classe de version majeure pour aider les css dans les plugins
const majorVersion = version.split('.')[0] ;
this.addClass(`vjs-v${majorVersion}`) ;
// Lorsque le lecteur est initialisé pour la première fois, déclencher l'activité pour que les composants
// comme la barre de contrôle s'affichent si nécessaire
this.userActive(true) ;
this.reportUserActivity() ;
this.one('play', (e) => this.listenForUserActivity_(e)) ;
this.on('stageclick', (e) => this.handleStageClick_(e)) ;
this.on('keydown', (e) => this.handleKeyDown(e)) ;
this.on('languagechange', (e) => this.handleLanguagechange(e)) ;
this.breakpoints(this.options_.breakpoints) ;
this.responsive(this.options_.responsive) ;
// Appel des deux méthodes de mode audio après que le lecteur soit entièrement activé
// configuration pour pouvoir écouter les événements déclenchés par eux
this.on('ready', () => {
// Appeler d'abord la méthode audioPosterMode de façon à ce que
// l'option audioOnlyMode peut avoir la priorité lorsque les deux options sont réglées sur true
this.audioPosterMode(this.options_.audioPosterMode) ;
this.audioOnlyMode(this.options_.audioOnlyMode) ;
}) ;
}
/**
* Détruit le lecteur vidéo et effectue le nettoyage nécessaire.
*
* Ceci est particulièrement utile si vous ajoutez et supprimez des vidéos de manière dynamique
* vers/depuis le DOM.
*
* @fires Player#dispose
*/
dispose() {
/**
* Appelé lorsque le lecteur est éliminé.
*
* @event Player#dispose
* @type {EventTarget~Event}
*/
this.trigger('dispose') ;
// éviter que la fonction "disposer" ne soit appelée deux fois
this.off('dispose') ;
// S'assurer que tous les auditeurs de documents spécifiques au lecteur sont déliés. Il s'agit de
Events.off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_) ;
Events.off(document, 'keydown', this.boundFullWindowOnEscKey_) ;
if (this.styleEl_ && this.styleEl_.parentNode) {
this.styleEl_.parentNode.removeChild(this.styleEl_) ;
this.styleEl_ = null ;
}
// Tuer la référence à ce joueur
Player.players[this.id_] = null ;
if (this.tag && this.tag.player) {
this.tag.player = null ;
}
if (this.el_ && this.el_.player) {
this.el_.player = null ;
}
if (this.tech_) {
this.tech_.dispose() ;
this.isPosterFromTech_ = false ;
this.poster_ = '' ;
}
if (this.playerElIngest_) {
this.playerElIngest_ = null ;
}
if (this.tag) {
this.tag = null ;
}
middleware.clearCacheForPlayer(this) ;
// supprimer tous les gestionnaires d'événements pour les listes de pistes
// toutes les pistes et tous les auditeurs de pistes sont supprimés sur
// tech dispose
TRACK_TYPES.names.forEach((name) => {
const props = TRACK_TYPES[name] ;
const list = this[props.getterName]() ;
// s'il ne s'agit pas d'une liste native
// nous devons supprimer manuellement les récepteurs d'événements
if (list && list.off) {
list.off() ;
}
}) ;
// le .el_ actuel est supprimé ici, ou remplacé si
super.dispose({restoreEl : this.options_.restoreEl}) ;
}
/**
* Créer l'élément DOM du `Player`.
*
* @return {Element}
* L'élément DOM qui est créé.
*/
createEl() {
let tag = this.tag ;
laisser el ;
let playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player') ;
const divEmbed = this.tag.tagName.toLowerCase() === 'video-js' ;
if (playerElIngest) {
el = this.el_ = tag.parentNode ;
} else if (!divEmbed) {
el = this.el_ = super.createEl('div') ;
}
// Copie de tous les attributs de la balise, y compris l'ID et la classe
// L'ID fera désormais référence à la boîte du lecteur et non à la balise vidéo
const attrs = Dom.getAttributes(tag) ;
if (divEmbed) {
el = this.el_ = tag ;
tag = this.tag = document.createElement('video') ;
while (el.children.length) {
tag.appendChild(el.firstChild) ;
}
if (!Dom.hasClass(el, 'video-js')) {
Dom.addClass(el, 'video-js') ;
}
el.appendChild(tag) ;
playerElIngest = this.playerElIngest_ = el ;
// déplacer les propriétés de notre élément `video-js` personnalisé
// à notre nouvel élément `video`. Cela permettra de déplacer des éléments tels que
// `src` ou `controls` qui ont été définis par js avant le lecteur
// a été initialisé.
Object.keys(el).forEach((k) => {
essayez {
tag[k] = el[k] ;
} catch (e) {
// nous avons une propriété comme outerHTML que nous ne pouvons pas copier, ignorez-la
}
}) ;
}
// fixer l'indice de tabulation à -1 pour supprimer l'élément vidéo de l'ordre de mise au point
tag.setAttribute('tabindex', '-1') ;
attrs.tabindex = '-1' ;
// Contournement du problème #4583 (JAWS+IE n'annonce pas le BPB ou le bouton de lecture), et
// pour le même problème avec Chrome (sur Windows) avec JAWS.
// Voir https://github.com/FreedomScientific/VFO-standards-support/issues/78
// Notez que nous ne pouvons pas détecter si JAWS est utilisé, mais cet attribut ARIA
// ne modifie pas le comportement d'IE11 ou de Chrome si JAWS n'est pas utilisé
if (IE_VERSION || (IS_CHROME && IS_WINDOWS)) {
tag.setAttribute('role', 'application') ;
attrs.role = 'application' ;
}
// Supprimer les attrs width/height de la balise pour que CSS puisse la rendre 100% width/height
tag.removeAttribute('width') ;
tag.removeAttribute('height') ;
if ("width" in attrs) {
supprimer attrs.width ;
}
if ("height" in attrs) {
supprimer attrs.height ;
}
Object.getOwnPropertyNames(attrs).forEach(function(attr) {
// ne pas copier l'attribut class de l'élément player lorsque nous sommes dans un div embed
// la classe est déjà configurée correctement dans le cas divEmbed
// et nous voulons nous assurer que la classe `video-js` ne se perde pas
if ( !(divEmbed && attr === 'class')) {
el.setAttribute(attr, attrs[attr]) ;
}
if (divEmbed) {
tag.setAttribute(attr, attrs[attr]) ;
}
}) ;
// Mise à jour de l'id/classe de la balise pour une utilisation en tant que HTML5 playback tech
// On pourrait penser qu'il faut le faire après l'intégration dans le conteneur, donc classe .vjs-tech
// ne clignote pas à 100% en largeur/hauteur, mais la classe ne s'applique qu'avec le parent .video-js
tag.playerId = tag.id ;
tag.id += '_html5_api' ;
tag.className = 'vjs-tech' ;
// Rendre le joueur trouvable sur les éléments
tag.player = el.player = this ;
// L'état par défaut de la vidéo est la pause
this.addClass('vjs-paused') ;
// Ajouter un élément de style dans le lecteur que nous utiliserons pour définir la largeur/hauteur
// du lecteur d'une manière qui peut être surchargée par le CSS, tout comme la balise
// élément vidéo
if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
this.styleEl_ = stylesheet.createStyleElement('vjs-styles-dimensions') ;
const defaultsStyleEl = Dom.$('.vjs-styles-defaults') ;
const head = Dom.$('head') ;
head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild) ;
}
this.fill_ = false ;
this.fluid_ = false ;
// Passez les options de largeur/hauteur/aspectRatio qui mettront à jour le style el
this.width(this.options_.width) ;
this.height(this.options_.height) ;
this.fill(this.options_.fill) ;
this.fluid(this.options_.fluid) ;
this.aspectRatio(this.options_.aspectRatio) ;
// supporte à la fois crossOrigin et crossorigin pour réduire la confusion et les problèmes liés au nom
this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin) ;
// Masquer tout lien dans la balise vidéo/audio,
// car IE ne les cache pas complètement aux lecteurs d'écran.
const links = tag.getElementsByTagName('a') ;
for (let i = 0 ; i < links.length ; i++) {
const linkEl = links.item(i) ;
Dom.addClass(linkEl, 'vjs-hidden') ;
linkEl.setAttribute('hidden', 'hidden') ;
}
// insertElFirst semble faire vaciller l'état du réseau de 3 à 2, donc
// garder la trace de l'original pour plus tard afin de savoir si la source a échoué à l'origine
tag.initNetworkState_ = tag.networkState ;
// Envelopper la balise vidéo dans le conteneur div (el/box)
if (tag.parentNode && !playerElIngest) {
tag.parentNode.insertBefore(el, tag) ;
}
// insérer la balise comme premier enfant de l'élément player
// puis l'ajoute manuellement au tableau des enfants de façon à ce que this.addChild
// fonctionnera correctement pour les autres composants
//
// Casse l'iPhone, corrigé dans la configuration HTML5.
Dom.prependTo(tag, el) ;
this.children_.unshift(tag) ;
// Définir l'attr lang sur le lecteur pour s'assurer que CSS :lang() est cohérent avec le lecteur
// s'il a été réglé sur une valeur différente de celle du document
this.el_.setAttribute('lang', this.language_) ;
this.el_.setAttribute('translate', 'no') ;
this.el_ = el ;
return el ;
}
/**
* Obtient ou définit l'option crossOrigin du `Player`. Pour le lecteur HTML5, il s'agit de
* définit la propriété `crossOrigin` sur la balise `<video>` pour contrôler le CORS
* comportement.
*
* @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
*
* @param {string} [valeur]
* La valeur à donner au crossOrigin du `Player`. Si un argument est
* doit être `anonymous` ou `use-credentials`.
*
* @return {string|undefined}
* - La valeur crossOrigin actuelle du `Player` lors de l'obtention.
* - indéfinie lors de la mise en place
*/
crossOrigin(value) {
if (!value) {
return this.techGet_('crossOrigin') ;
}
if (value !== 'anonymous' && value !== 'use-credentials') {
log.warn(`crossOrigin doit être "anonymous" ou "use-credentials", donné "${valeur}"`) ;
retour ;
}
this.techCall_('setCrossOrigin', value) ;
retour ;
}
/**
* Un getter/setter pour la largeur du `Player`. Renvoie la valeur configurée du joueur.
* Pour obtenir la largeur actuelle, utilisez `currentWidth()`.
*
* @param {number} [value]
* La valeur de la largeur du `Player`.
*
* @return {number}
* La largeur actuelle du `Player` lors de l'obtention.
*/
width(value) {
return this.dimension('width', value) ;
}
/**
* Un getter/setter pour la hauteur du `Player`. Renvoie la valeur configurée du joueur.
* Pour obtenir la hauteur actuelle, utilisez `currentheight()`.
*
* @param {number} [value]
* La valeur de la taille du joueur.
*
* @return {number}
* La hauteur actuelle du `Player` lors de l'obtention.
*/
height(value) {
return this.dimension('height', value) ;
}
/**
* Obtenir et définir la largeur du joueur & height.
*
* @param {string} dimension
* Cette chaîne peut être :
* - largeur
* - hauteur
*
* @param {number} [value]
* Valeur de la dimension spécifiée dans le premier argument.
*
* @return {number}
* La valeur des arguments de dimension lors de l'obtention (largeur/hauteur).
*/
dimension(dimension, valeur) {
const privDimension = dimension + '_' ;
if (value === undefined) {
return this[privDimension] || 0 ;
}
if (value === '' || value === 'auto') {
// Si une chaîne vide est donnée, réinitialiser la dimension pour qu'elle soit automatique
this[privDimension] = undefined ;
this.updateStyleEl_() ;
retour ;
}
const parsedVal = parseFloat(value) ;
if (isNaN(parsedVal)) {
log.error(`Valeur incorrecte "${valeur}" fournie pour ${dimension}`) ;
retour ;
}
this[privDimension] = parsedVal ;
this.updateStyleEl_() ;
}
/**
* Un getter/setter/toggler pour le vjs-fluid `className` sur le `Player`.
*
* L'activation de ce paramètre désactive le mode de remplissage.
*
* @param {boolean} [bool]
* - Une valeur de true ajoute la classe.
* - La valeur false supprime la classe.
* - Aucune valeur ne sera un getter.
*
* @return {boolean|undefined}
* - La valeur du fluide lors de l'obtention.
* - `undefined` when setting.
*/
fluid(bool) {
if (bool === undefined) {
return !!this.fluid_ ;
}
this.fluid_ = !!bool ;
if (isEvented(this)) {
this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_) ;
}
if (bool) {
this.addClass('vjs-fluid') ;
this.fill(false) ;
addEventedCallback(this, () => {
this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_) ;
}) ;
} else {
this.removeClass('vjs-fluid') ;
}
this.updateStyleEl_() ;
}
/**
* Un getter/setter/toggler pour le vjs-fill `className` sur le `Player`.
*
* En activant cette option, le mode fluide est désactivé.
*
* @param {boolean} [bool]
* - Une valeur de true ajoute la classe.
* - La valeur false supprime la classe.
* - Aucune valeur ne sera un getter.
*
* @return {boolean|undefined}
* - La valeur du fluide lors de l'obtention.
* - `undefined` when setting.
*/
fill(bool) {
if (bool === undefined) {
return !!this.fill_ ;
}
this.fill_ = !!bool ;
if (bool) {
this.addClass('vjs-fill') ;
this.fluid(false) ;
} else {
this.removeClass('vjs-fill') ;
}
}
/**
* Obtention/réglage du rapport d'aspect
*
* @param {string} [ratio]
* Rapport d'aspect du lecteur
*
* @return {string|undefined}
* renvoie le rapport d'aspect actuel lors de l'obtention de
*/
/**
* Un getter/setter pour le ratio d'aspect du `Player`.
*
* @param {string} [ratio]
* La valeur du ratio d'aspect du `Player`.
*
* @return {string|undefined}
* - Le rapport d'aspect actuel du `Player` lorsqu'il est obtenu.
* - indéfinie lors de la mise en place
*/
aspectRatio(ratio) {
if (ratio === undefined) {
return this.aspectRatio_ ;
}
// Vérifier le format largeur:hauteur
if ( !(/^\d+\:\d+$/).test(ratio)) {
lancer une nouvelle erreur ('Improper value supplied for aspect ratio. Le format doit être largeur:hauteur, par exemple 16:9.') ;
}
this.aspectRatio_ = ratio ;
// Nous supposons que si vous définissez un ratio d'aspect, vous souhaitez le mode fluide,
// parce qu'en mode fixe, vous pouvez calculer vous-même la largeur et la hauteur.
this.fluid(true) ;
this.updateStyleEl_() ;
}
/**
* Mise à jour des styles de l'élément `Player` (hauteur, largeur et rapport d'aspect).
*
* @private
* @listens Tech#loadedmetadata
*/
updateStyleEl_() {
if (window.VIDEOJS_NO_DYNAMIC_STYLE === true) {
const width = typeof this.width_ === 'number' ? this.width_ : this.options_.width ;
const height = typeof this.height_ === 'number' ? this.height_ : this.options_.height ;
const techEl = this.tech_ && this.tech_.el() ;
if (techEl) {
if (width >= 0) {
techEl.width = width ;
}
if (height >= 0) {
techEl.height = height ;
}
}
retour ;
}
laisser la largeur ;
let height ;
let aspectRatio ;
let idClass ;
// Le rapport d'aspect est utilisé directement ou pour calculer la largeur et la hauteur.
if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
// Utiliser tout aspectRatio qui a été spécifiquement défini
aspectRatio = this.aspectRatio_ ;
} else if (this.videoWidth() > 0) {
// Sinon, essayer d'obtenir le rapport hauteur/largeur à partir des métadonnées de la vidéo
aspectRatio = this.videoWidth() + ':' + this.videoHeight() ;
} else {
// Ou utiliser une valeur par défaut. L'élément vidéo est 2:1, mais le 16:9 est plus courant.
aspectRatio = '16:9' ;
}
// Obtenir le ratio sous forme de décimale que l'on peut utiliser pour calculer les dimensions
const ratioParts = aspectRatio.split(':') ;
const ratioMultiplier = ratioParts[1] / ratioParts[0] ;
if (this.width_ !== undefined) {
// Utiliser toute largeur qui a été spécifiquement définie
width = this.width_ ;
} else if (this.height_ !== undefined) {
// Ou calculer la largeur à partir du rapport d'aspect si une hauteur a été définie
width = this.height_ / ratioMultiplier ;
} else {
// Ou utiliser les métadonnées de la vidéo, ou utiliser la valeur par défaut de 300 de l'el vidéo
width = this.videoWidth() || 300 ;
}
if (this.height_ !== undefined) {
// Utiliser toute hauteur qui a été spécifiquement définie
height = this.height_ ;
} else {
// Sinon, calculer la hauteur à partir du ratio et de la largeur
hauteur = largeur * ratioMultiplier ;
}
// S'assurer que la classe CSS est valide en commençant par un caractère alpha
if ((/^[^a-zA-Z]/).test(this.id())) {
idClass = 'dimensions-' + this.id() ;
} else {
idClass = this.id() + '-dimensions' ;
}
// S'assurer que la bonne classe est toujours présente dans le lecteur pour l'élément de style
this.addClass(idClass) ;
stylesheet.setTextContent(this.styleEl_, `
.${idClass} {
largeur : ${width}px ;
hauteur : ${height}px ;
}
.${idClass}.vjs-fluid:not(.vjs-audio-only-mode) {
padding-top : ${ratioMultiplier * 100}% ;
}
`) ;
}
/**
* Chargement/création d'une instance de lecture {@link Tech} incluant un élément
* et les méthodes de l'API. Puis ajoutez l'élément `Tech` dans `Player` en tant qu'enfant.
*
* @param {string} techName
* nom de la technologie de lecture
*
* @param {string} source
* source vidéo
*
* @private
*/
loadTech_(techName, source) {
// Mettre en pause et supprimer la technologie de lecture en cours
if (this.tech_) {
this.unloadTech_() ;
}
const titleTechName = toTitleCase(techName) ;
const camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1) ;
// se débarrasser de la balise vidéo HTML5 dès que l'on utilise une autre technologie
if (titleTechName !== 'Html5' && this.tag) {
Tech.getTech('Html5').disposeMediaElement(this.tag) ;
this.tag.player = null ;
this.tag = null ;
}
this.techName_ = titleTechName ;
// Désactiver l'accès à l'API car nous chargeons une nouvelle technologie qui pourrait se charger de manière asynchrone
this.isReady_ = false ;
let autoplay = this.autoplay() ;
// si autoplay est une chaîne (ou `true` avec normalizeAutoplay : true) nous passons false à la tech
// parce que le lecteur va gérer l'autoplay sur `loadstart`
if (typeof this.autoplay() === 'string' || this.autoplay() === true && this.options_.normalizeAutoplay) {
autoplay = false ;
}
// Récupère les options techniques spécifiques des options du lecteur et ajoute l'élément source et l'élément parent à utiliser.
const techOptions = {
source,
autoplay,
'nativeControlsForTouch' : this.options_.nativeControlsForTouch,
'playerId' : this.id(),
'techId' : `${this.id()}_${camelTechName}_api`,
'playsinline' : this.options_.playsinline,
'preload' : this.options_.preload,
'loop' : this.options_.loop,
'disablePictureInPicture' : this.options_.disablePictureInPicture,
'muted' : this.options_.muted,
'poster' : this.poster(),
'language' : this.language(),
'playerElIngest' : this.playerElIngest_ || false,
'vtt.js' : this.options_['vtt.js'],
'canOverridePoster' : !!this.options_.techCanOverridePoster,
'enableSourceset' : this.options_.enableSourceset,
'Promesse' : this.options_.Promise
};
TRACK_TYPES.names.forEach((name) => {
const props = TRACK_TYPES[name] ;
techOptions[props.getterName] = this[props.privateName] ;
}) ;
assign(techOptions, this.options_[titleTechName]) ;
assign(techOptions, this.options_[camelTechName]) ;
assign(techOptions, this.options_[techName.toLowerCase()]) ;
if (this.tag) {
techOptions.tag = this.tag ;
}
if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
techOptions.startTime = this.cache_.currentTime ;
}
// Initialisation de l'instance technique
const TechClass = Tech.getTech(techName) ;
if (!TechClass) {
throw new Error(`Aucune technologie nommée '${titleTechName}' n'existe ! '${titleTechName}' doit être enregistré à l'aide de videojs.registerTech()'`) ;
}
this.tech_ = new TechClass(techOptions) ;
// player.triggerReady est toujours asynchrone, il n'est donc pas nécessaire que ce soit asynchrone
this.tech_.ready(Fn.bind(this, this.handleTechReady_), true) ;
textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_) ;
// Écouter tous les événements définis par HTML5 et les déclencher sur le lecteur
TECH_EVENTS_RETRIGGER.forEach((event) => {
this.on(this.tech_, event, (e) => this[`handleTech${toTitleCase(event)}_`](e)) ;
}) ;
Object.keys(TECH_EVENTS_QUEUE).forEach((event) => {
this.on(this.tech_, event, (eventObj) => {
if (this.tech_.playbackRate() === 0 && this.tech_.seeking()) {
this.queuedCallbacks_.push({
callback : this[`handleTech${TECH_EVENTS_QUEUE[event]}_`].bind(this),
événement : eventObj
}) ;
retour ;
}
this[`handleTech${TECH_EVENTS_QUEUE[event]}_`](eventObj) ;
}) ;
}) ;
this.on(this.tech_, 'loadstart', (e) => this.handleTechLoadStart_(e)) ;
this.on(this.tech_, 'sourceset', (e) => this.handleTechSourceset_(e)) ;
this.on(this.tech_, 'waiting', (e) => this.handleTechWaiting_(e)) ;
this.on(this.tech_, 'ended', (e) => this.handleTechEnded_(e)) ;
this.on(this.tech_, 'seeking', (e) => this.handleTechSeeking_(e)) ;
this.on(this.tech_, 'play', (e) => this.handleTechPlay_(e)) ;
this.on(this.tech_, 'firstplay', (e) => this.handleTechFirstPlay_(e)) ;
this.on(this.tech_, 'pause', (e) => this.handleTechPause_(e)) ;
this.on(this.tech_, 'durationchange', (e) => this.handleTechDurationChange_(e)) ;
this.on(this.tech_, 'fullscreenchange', (e, data) => this.handleTechFullscreenChange_(e, data)) ;
this.on(this.tech_, 'fullscreenerror', (e, err) => this.handleTechFullscreenError_(e, err)) ;
this.on(this.tech_, 'enterpictureinpicture', (e) => this.handleTechEnterPictureInPicture_(e)) ;
this.on(this.tech_, 'leavepictureinpicture', (e) => this.handleTechLeavePictureInPicture_(e)) ;
this.on(this.tech_, 'error', (e) => this.handleTechError_(e)) ;
this.on(this.tech_, 'posterchange', (e) => this.handleTechPosterChange_(e)) ;
this.on(this.tech_, 'textdata', (e) => this.handleTechTextData_(e)) ;
this.on(this.tech_, 'ratechange', (e) => this.handleTechRateChange_(e)) ;
this.on(this.tech_, 'loadedmetadata', this.boundUpdateStyleEl_) ;
this.usingNativeControls(this.techGet_('controls')) ;
if (this.controls() && !this.usingNativeControls()) {
this.addTechControlsListeners_() ;
}
// Ajouter l'élément tech dans le DOM s'il n'y est pas déjà
// Veillez à ne pas insérer l'élément vidéo original si vous utilisez Html5
if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
Dom.prependTo(this.tech_.el(), this.el()) ;
}
// Se débarrasser de la référence à la balise vidéo d'origine après le chargement de la première technologie
if (this.tag) {
this.tag.player = null ;
this.tag = null ;
}
}
/**
* Décharge et élimine la lecture en cours {@link Tech}.
*
* @private
*/
unloadTech_() {
// Sauvegarde des pistes de texte actuelles afin de pouvoir réutiliser les mêmes pistes de texte avec la prochaine technologie
TRACK_TYPES.names.forEach((name) => {
const props = TRACK_TYPES[name] ;
this[props.privateName] = this[props.getterName]() ;
}) ;
this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_) ;
this.isReady_ = false ;
this.tech_.dispose() ;
this.tech_ = false ;
if (this.isPosterFromTech_) {
this.poster_ = '' ;
this.trigger('posterchange') ;
}
this.isPosterFromTech_ = false ;
}
/**
* Renvoie une référence au {@link Tech} actuel.
* Il affichera par défaut un avertissement sur le danger d'utiliser la technologie directement
* mais tout argument passé fera taire l'avertissement.
*
* @param {*} [safety]
* Tout ce qui est passé pour faire taire l'avertissement
*
* @return {Tech}
* La technologie
*/
tech(safety) {
if (safety === undefined) {
log.warn('L'utilisation directe de la technologie peut être dangereuse. J'espère que vous savez ce que vous faites
'Voir https://github.com/videojs/video.js/issues/2617 pour plus d'informations.\n') ;
}
return this.tech_ ;
}
/**
* Configurer les récepteurs de clic et de toucher pour l'élément de lecture
*
* - Sur les ordinateurs de bureau : un clic sur la vidéo elle-même permet de basculer la lecture
* - Sur les appareils mobiles : un clic sur la vidéo permet de basculer les contrôles
* ce qui se fait en faisant basculer l'état de l'utilisateur entre l'état actif et l'état actif
* inactif
* - Une tape peut signaler qu'un utilisateur est devenu actif ou inactif
* par exemple, une pression rapide sur un film iPhone devrait faire apparaître les commandes. Autre
* un appui rapide devrait les masquer à nouveau (en signalant que l'utilisateur se trouve dans une zone inactive)
* état de visionnage)
* - En outre, nous voulons toujours que l'utilisateur soit considéré comme inactif après
* quelques secondes d'inactivité.
*
* > Remarque : la seule partie de l'interaction avec iOS que nous ne pouvons pas imiter avec cette configuration
* le fait de toucher et de maintenir l'élément vidéo compte-t-il comme une activité afin de
* de garder les contrôles affichés, mais cela ne devrait pas poser de problème. Un toucher et un maintien
* sur n'importe quel contrôle permettra à l'utilisateur de rester actif
*
* @private
*/
addTechControlsListeners_() {
// Veillez à supprimer tous les listeners précédents au cas où nous serions appelés plusieurs fois.
this.removeTechControlsListeners_() ;
this.on(this.tech_, 'click', this.boundHandleTechClick_) ;
this.on(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_) ;
// Si les contrôles sont cachés, nous ne voulons pas que cela change sans qu'il y ait un événement "tap"
// afin de vérifier si les contrôles étaient déjà affichés avant le rapport de l'utilisateur
// activité
this.on(this.tech_, 'touchstart', this.boundHandleTechTouchStart_) ;
this.on(this.tech_, 'touchmove', this.boundHandleTechTouchMove_) ;
this.on(this.tech_, 'touchend', this.boundHandleTechTouchEnd_) ;
// L'écouteur de tapotement doit être placé après l'écouteur de toucher, car l'écouteur de tapotement
// l'auditeur annule toute activitéUserActivity signalée lors de la définition de userActive(false)
this.on(this.tech_, 'tap', this.boundHandleTechTap_) ;
}
/**
* Supprimez les écouteurs utilisés pour les commandes par clic et par tapotement. Cela est nécessaire pour
* le basculement vers des contrôles désactivés, alors qu'un tapotement ne devrait rien faire.
*
* @private
*/
removeTechControlsListeners_() {
// Nous ne voulons pas simplement utiliser `this.off()` parce qu'il pourrait y avoir d'autres besoins
// les auditeurs ajoutés par les techniciens qui étendent cette fonction.
this.off(this.tech_, 'tap', this.boundHandleTechTap_) ;
this.off(this.tech_, 'touchstart', this.boundHandleTechTouchStart_) ;
this.off(this.tech_, 'touchmove', this.boundHandleTechTouchMove_) ;
this.off(this.tech_, 'touchend', this.boundHandleTechTouchEnd_) ;
this.off(this.tech_, 'click', this.boundHandleTechClick_) ;
this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_) ;
}
/**
* Le joueur attend que la technologie soit prête
*
* @private
*/
handleTechReady_() {
this.triggerReady() ;
// Conserver le même volume que précédemment
if (this.cache_.volume) {
this.techCall_('setVolume', this.cache_.volume) ;
}
// Vérifier si le technicien a trouvé une affiche de plus haute résolution pendant le chargement
this.handleTechPosterChange_() ;
// Mise à jour de la durée si elle est disponible
this.handleTechDurationChange_() ;
}
/**
* Redéclenche l'événement `loadstart` qui a été déclenché par le {@link Tech}. Cette
* déclenchera également {@link Player#firstplay} s'il s'agit du premier loadstart
* pour une vidéo.
*
* @fires Player#loadstart
* @fires Player#firstplay
* @listens Tech#loadstart
* @private
*/
handleTechLoadStart_() {
// TODO : Mise à jour pour utiliser l'événement `emptied` à la place. Voir #1277.
this.removeClass('vjs-ended') ;
this.removeClass('vjs-seeking') ;
// réinitialisation de l'état d'erreur
this.error(null) ;
// Mise à jour de la durée
this.handleTechDurationChange_() ;
// S'il est déjà en cours de lecture, nous voulons déclencher un événement de première lecture maintenant.
// L'événement "firstplay" repose sur les événements "play" et "loadstart"
// ce qui peut se produire dans n'importe quel ordre pour une nouvelle source
if (!this.paused())) {
/**
* Déclenché lorsque l'agent utilisateur commence à rechercher des données multimédias
*
* @event Player#loadstart
* @type {EventTarget~Event}
*/
this.trigger('loadstart') ;
this.trigger('firstplay') ;
} else {
// réinitialisation de l'état hasStarted
this.hasStarted(false) ;
this.trigger('loadstart') ;
}
// la lecture automatique se produit après le démarrage du navigateur,
// nous imitons donc ce comportement
this.manualAutoplay_(this.autoplay() === true && this.options_.normalizeAutoplay ? 'play' : this.autoplay()) ;
}
/**
* Traite les valeurs de chaîne de caractères autoplay, plutôt que les valeurs booléennes typiques
* les valeurs qui doivent être gérées par le technicien. Notez qu'il ne s'agit pas d'une
* de toute spécification. Les valeurs valides et ce qu'elles font peuvent être
* trouvé sur le getter autoplay à Player#autoplay()
*/
manualAutoplay_(type) {
if (!this.tech_ || typeof type !== 'string') {
retour ;
}
// Sauvegarder la valeur originale de muted(), mettre muted à true, et essayer de jouer().
// En cas de rejet de la promesse, rétablir le silence à partir de la valeur sauvegardée
const resolveMuted = () => {
const previouslyMuted = this.muted() ;
this.muted(true) ;
const restoreMuted = () => {
this.muted(previouslyMuted) ;
};
// rétablissement de la sourdine à la fin du jeu
this.playTerminatedQueue_.push(restoreMuted) ;
const mutedPromise = this.play() ;
if (!isPromise(mutedPromise)) {
retour ;
}
return mutedPromise.catch(err => {
restoreMuted() ;
lancer une nouvelle Erreur(`Rejet à manualAutoplay. Restauration de la valeur muette. ${err ? err : ''}`) ;
}) ;
};
laisser promettre ;
// en cas de sourdine, la valeur par défaut est true
// la seule chose que nous puissions faire est d'appeler à jouer
if (type === 'any' && !this.muted()) {
promise = this.play() ;
if (isPromise(promise)) {
promise = promise.catch(resolveMuted) ;
}
} else if (type === 'muted' && !this.muted()) {
promise = resolveMuted() ;
} else {
promise = this.play() ;
}
if (!isPromise(promise)) {
retour ;
}
return promise.then(() => {
this.trigger({type : 'autoplay-success', autoplay : type}) ;
}).catch(() => {
this.trigger({type : 'autoplay-failure', autoplay : type}) ;
}) ;
}
/**
* Mettre à jour les caches de sources internes afin de renvoyer la bonne source à partir de
* `src()`, `currentSource()`, et `currentSources()`.
*
* > Note : `currentSources` ne sera pas mis à jour si la source fournie existe déjà
* dans le cache `currentSources` actuel.
*
*
* @param {Tech~SourceObject} srcObj
* Une chaîne ou un objet source pour mettre à jour nos caches.
*/
updateSourceCaches_(srcObj = '') {
let src = srcObj ;
let type = '' ;
if (typeof src !== 'string') {
src = srcObj.src ;
type = srcObj.type ;
}
// s'assurer que tous les caches sont réglés sur les valeurs par défaut
// pour éviter la vérification de la nullité
this.cache_.source = this.cache_.source || {} ;
this.cache_.sources = this.cache_.sources || [] ;
// essaie d'obtenir le type du src qui lui a été transmis
if (src && !type) {
type = findMimetype(this, src) ;
}
// mettre à jour le cache `currentSource` toujours
this.cache_.source = mergeOptions({}, srcObj, {src, type}) ;
const matchingSources = this.cache_.sources.filter((s) => s.src && s.src === src) ;
const sourceElSources = [] ;
const sourceEls = this.$$('source') ;
const matchingSourceEls = [] ;
for (let i = 0 ; i < sourceEls.length ; i++) {
const sourceObj = Dom.getAttributes(sourceEls[i]) ;
sourceElSources.push(sourceObj) ;
if (sourceObj.src && sourceObj.src === src) {
matchingSourceEls.push(sourceObj.src) ;
}
}
// si nous avons des listes de sources correspondantes mais pas de sources correspondantes
// le cache source actuel n'est pas à jour
if (matchingSourceEls.length && !matchingSources.length) {
this.cache_.sources = sourceElSources ;
// si nous n'avons pas de source ou d'autres sources correspondantes, fixer le
// sources cache to the `currentSource` cache
} else if (!matchingSources.length) {
this.cache_.sources = [this.cache_.source] ;
}
// mettre à jour le cache tech `src`
this.cache_.src = src ;
}
/**
* *EXPERIMENTAL* Déclenché lorsque la source est définie ou modifiée sur le {@link Tech}
* ce qui entraîne le rechargement de l'élément multimédia.
*
* Il se déclenchera pour la source initiale et pour chaque source suivante.
* Cet événement est un événement personnalisé de Video.js et est déclenché par le {@link Tech}.
*
* L'objet de cet événement contient une propriété `src` qui contiendra la source
* qui était disponible au moment où l'événement a été déclenché. Cela n'est généralement nécessaire que si Video.js
* change de technicien pendant que la source est changée.
*
* Elle est également déclenchée lorsque la fonction `load` est appelée sur le lecteur (ou l'élément multimédia)
* parce que la {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|spécification pour `load`}
* indique que l'algorithme de sélection des ressources doit être interrompu et redémarré.
* Dans ce cas, il est très probable que la propriété `src` soit fixée à la propriété
* chaîne vide `""` pour indiquer que nous ne savons pas ce que sera la source, mais que nous ne savons pas ce que sera la source
* qu'il est en train de changer.
*
* *Cet événement est actuellement encore expérimental et peut être modifié dans les versions mineures
* __Pour l'utiliser, passez l'option `enableSourceset` au lecteur.__
*
* @event Player#sourceset
* @type {EventTarget~Event}
* @prop {string} src
* L'url de la source disponible lorsque le `sourceset` a été déclenché.
* Il s'agira d'une chaîne vide si nous ne pouvons pas savoir quelle est la source
* mais sachez que la source changera.
*/
/**
* Redéclenche l'événement `sourceset` qui a été déclenché par le {@link Tech}.
*
* @fires Player#sourceset
* @listens Tech#sourceset
* @private
*/
handleTechSourceset_(event) {
// ne met à jour le cache de la source que lorsque la source
// n'a pas été mis à jour à l'aide de l'interface utilisateur du lecteur
if (!this.changingSrc_) {
let updateSourceCaches = (src) => this.updateSourceCaches_(src) ;
const playerSrc = this.currentSource().src ;
const eventSrc = event.src ;
// si nous avons un playerSrc qui n'est pas un blob, et un tech src qui est un blob
if (playerSrc && !(/^blob:/).test(playerSrc) && (/^blob:/).test(eventSrc)) {
// si la source technique et la source joueur ont été mises à jour, nous supposons que
// quelque chose comme @videojs/http-streaming a fait le jeu de sources et a ignoré la mise à jour du cache des sources.
if (!this.lastSource_ || (this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc)) {
updateSourceCaches = () => {} ;
}
}
// mise à jour immédiate de la source vers la source initiale
// dans certains cas, il s'agira d'une chaîne vide
updateSourceCaches(eventSrc) ;
// si le `sourceset` `src` est une chaîne vide
// attendre un `loadstart` pour mettre à jour le cache vers `currentSrc`.
// Si un sourceset se produit avant un `loadstart`, nous réinitialisons l'état
if (!event.src) {
this.tech_.any(['sourceset', 'loadstart'], (e) => {
// si un sourceet se produit avant un `loadstart` à cet endroit
// n'a rien à voir avec ce `handleTechSourceset_`
// sera appelé à nouveau et la question sera traitée à ce moment-là.
if (e.type === 'sourceset') {
retour ;
}
const techSrc = this.techGet('currentSrc') ;
this.lastSource_.tech = techSrc ;
this.updateSourceCaches_(techSrc) ;
}) ;
}
}
this.lastSource_ = {player : this.currentSource().src, tech : event.src} ;
this.trigger({
src : event.src,
type : "sourceset" (jeu de sources)
}) ;
}
/**
* Ajouter/supprimer la classe vjs-has-started
*
* @fires Player#firstplay
*
* @param {boolean} request
* - true : ajoute la classe
* - false : supprimer la classe
*
* @return {boolean}
* la valeur booléenne de hasStarted_
*/
hasStarted(request) {
if (request === undefined) {
// agit comme getter, si nous n'avons pas de demande à modifier
return this.hasStarted_ ;
}
if (request === this.hasStarted_) {
retour ;
}
this.hasStarted_ = request ;
if (this.hasStarted_) {
this.addClass('vjs-has-started') ;
this.trigger('firstplay') ;
} else {
this.removeClass('vjs-has-started') ;
}
}
/**
* Déclenchée chaque fois que le support commence ou reprend la lecture
*
* @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
* @fires Player#play
* @listens Tech#play
* @private
*/
handleTechPlay_() {
this.removeClass('vjs-ended') ;
this.removeClass('vjs-paused') ;
this.addClass('vjs-playing') ;
// cacher l'affiche lorsque l'utilisateur appuie sur play
this.hasStarted(true) ;
/**
* Déclenché chaque fois qu'un événement {@link Tech#play} se produit. Indique que
* la lecture a commencé ou repris.
*
* @event Player#play
* @type {EventTarget~Event}
*/
this.trigger('play') ;
}
/**
* Redéclenche l'événement `ratechange` qui a été déclenché par le {@link Tech}.
*
* Si des événements ont été mis en file d'attente alors que le taux de lecture était nul, déclencher
* ces événements maintenant.
*
* @private
* @method Player#handleTechRateChange_ (méthode du joueur)
* @fires Player#ratechange
* @listens Tech#ratechange
*/
handleTechRateChange_() {
if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
this.queuedCallbacks_.forEach((queued) => queued.callback(queued.event)) ;
this.queuedCallbacks_ = [] ;
}
this.cache_.lastPlaybackRate = this.tech_.playbackRate() ;
/**
* Se déclenche lorsque la vitesse de lecture de l'audio/vidéo est modifiée
*
* @event Player#ratechange
* @type {event}
*/
this.trigger('ratechange') ;
}
/**
* Redéclenche l'événement `waiting` qui a été déclenché par le {@link Tech}.
*
* @fires Player#waiting
* @listens Tech#attente
* @private
*/
handleTechWaiting_() {
this.addClass('vjs-waiting') ;
/**
* Un changement de readyState sur l'élément DOM a provoqué l'arrêt de la lecture.
*
* @event Player#waiting
* @type {EventTarget~Event}
*/
this.trigger('waiting') ;
// Les navigateurs peuvent émettre un événement timeupdate après un événement waiting. Afin d'éviter
// retrait prématuré de la classe d'attente, attendre le changement d'heure.
const timeWhenWaiting = this.currentTime() ;
const timeUpdateListener = () => {
if (timeWhenWaiting !== this.currentTime()) {
this.removeClass('vjs-waiting') ;
this.off('timeupdate', timeUpdateListener) ;
}
};
this.on('timeupdate', timeUpdateListener) ;
}
/**
* Redéclenche l'événement `canplay` qui a été déclenché par l'{@link Tech}.
* > Remarque : Ce n'est pas cohérent entre les navigateurs. Voir #1351
*
* @fires Player#canplay
* @listens Tech#canplay
* @private
*/
handleTechCanPlay_() {
this.removeClass('vjs-waiting') ;
/**
* Le média a un readyState égal ou supérieur à HAVE_FUTURE_DATA.
*
* @event Player#canplay
* @type {EventTarget~Event}
*/
this.trigger('canplay') ;
}
/**
* Redéclenche l'événement `canplaythrough` qui a été déclenché par le {@link Tech}.
*
* @fires Player#canplaythrough
* @listens Tech#canplaythrough
* @private
*/
handleTechCanPlayThrough_() {
this.removeClass('vjs-waiting') ;
/**
* Le média a un readyState égal ou supérieur à HAVE_ENOUGH_DATA. Cela signifie que le
* l'intégralité du fichier multimédia peut être lue sans mise en mémoire tampon.
*
* @event Player#canplaythrough
* @type {EventTarget~Event}
*/
this.trigger('canplaythrough') ;
}
/**
* Redéclenche l'événement `playing` qui a été déclenché par le {@link Tech}.
*
* @fires Player#playing
* @listens Tech#playing
* @private
*/
handleTechPlaying_() {
this.removeClass('vjs-waiting') ;
/**
* La lecture du média n'est plus bloquée et a commencé.
*
* @event Player#playing
* @type {EventTarget~Event}
*/
this.trigger('playing') ;
}
/**
* Redéclenche l'événement `seeking` qui a été déclenché par le {@link Tech}.
*
* @fires Player#seeking
* @listens Tech#seeking
* @private
*/
handleTechSeeking_() {
this.addClass('vjs-seeking') ;
/**
* Déclenché chaque fois que le joueur passe à une nouvelle heure
*
* @event Player#seeking
* @type {EventTarget~Event}
*/
this.trigger('seeking') ;
}
/**
* Redéclenche l'événement `seeked` qui a été déclenché par le {@link Tech}.
*
* @fires Player#seeked
* @listens Tech#seeked
* @private
*/
handleTechSeeked_() {
this.removeClass('vjs-seeking') ;
this.removeClass('vjs-ended') ;
/**
* Déclenché lorsque le joueur a fini de sauter à un nouveau moment
*
* @event Player#seeked
* @type {EventTarget~Event}
*/
this.trigger('seeked') ;
}
/**
* Redéclenche l'événement `firstplay` qui a été déclenché par le {@link Tech}.
*
* @fires Player#firstplay
* @listens Tech#firstplay
* @deprecated A partir de la version 6.0, l'événement firstplay est obsolète.
* A partir de la version 6.0, le passage de l'option `starttime` au joueur et l'événement firstplay sont dépréciés.
* @private
*/
handleTechFirstPlay_() {
// Si le premier attribut "starttime" est spécifié
// nous commencerons alors à l'offset donné en secondes
if (this.options_.starttime) {
log.warn('Passing the `starttime` option to the player will be deprecated in 6.0') ;
this.currentTime(this.options_.starttime) ;
}
this.addClass('vjs-has-started') ;
/**
* Lancé lors de la première lecture d'une vidéo. Ne fait pas partie de la spécification HLS, et c'est
* n'est probablement pas encore la meilleure implémentation, il faut donc l'utiliser avec parcimonie. Si vous n'avez pas de
* pour empêcher la lecture, utilisez `myPlayer.one('play');` à la place.
*
* @event Player#firstplay
* @deprecated A partir de la version 6.0, l'événement firstplay est obsolète.
* @type {EventTarget~Event}
*/
this.trigger('firstplay') ;
}
/**
* Redéclenche l'événement `pause` qui a été déclenché par le {@link Tech}.
*
* @fires Player#pause
* @listens Tech#pause
* @private
*/
handleTechPause_() {
this.removeClass('vjs-playing') ;
this.addClass('vjs-paused') ;
/**
* Déclenché lorsque le média a été mis en pause
*
* @event Player#pause
* @type {EventTarget~Event}
*/
this.trigger('pause') ;
}
/**
* Redéclenche l'événement `ended` qui a été déclenché par le {@link Tech}.
*
* @fires Player#ended
* @listens Tech#ended
* @private
*/
handleTechEnded_() {
this.addClass('vjs-ended') ;
this.removeClass('vjs-waiting') ;
if (this.options_.loop) {
this.currentTime(0) ;
this.play() ;
} else if (!this.paused()) {
this.pause() ;
}
/**
* Déclenché lorsque la fin de la ressource multimédia est atteinte (currentTime == duration)
*
* @event Player#ended
* @type {EventTarget~Event}
*/
this.trigger('ended') ;
}
/**
* Déclenché lorsque la durée de la ressource multimédia est connue ou modifiée pour la première fois
*
* @listens Tech#durationchange
* @private
*/
handleTechDurationChange_() {
this.duration(this.techGet_('duration')) ;
}
/**
* Gestion d'un clic sur l'élément multimédia pour la lecture/pause
*
* @param {EventTarget~Event} event
* l'événement qui a provoqué le déclenchement de cette fonction
*
* @listens Tech#click
* @private
*/
handleTechClick_(event) {
// Lorsque les contrôles sont désactivés, un clic ne doit pas faire basculer la lecture car
// le clic est considéré comme un contrôle
if (!this.controls_) {
retour ;
}
si (
this.options_ === undefined ||
this.options_.userActions === undefined ||
this.options_.userActions.click === undefined ||
this.options_.userActions.click !== false
) {
si (
this.options_ !== undefined &&
this.options_.userActions !== undefined &&
typeof this.options_.userActions.click === 'function'
) {
this.options_.userActions.click.call(this, event) ;
} else if (this.paused()) {
silencePromesse(this.play()) ;
} else {
this.pause() ;
}
}
}
/**
* Gestion d'un double-clic sur l'élément multimédia pour entrer/sortir du plein écran
*
* @param {EventTarget~Event} event
* l'événement qui a provoqué le déclenchement de cette fonction
*
* @listens Tech#dblclick
* @private
*/
handleTechDoubleClick_(event) {
if (!this.controls_) {
retour ;
}
// nous ne voulons pas faire basculer l'état de plein écran
// lors d'un double-clic à l'intérieur d'une barre de contrôle ou d'une fenêtre modale
const inAllowedEls = Array.prototype.some.call(
this.$$('.vjs-control-bar, .vjs-modal-dialog'),
el => el.contains(event.target)
) ;
if (!inAllowedEls) {
/*
* options.userActions.doubleClick
*
* Si `undefined` ou `true`, le double-clic fait basculer le plein écran si des contrôles sont présents
* Mettre à `false` pour désactiver la gestion du double-clic
* Définir une fonction pour remplacer un gestionnaire de double-clic externe
*/
si (
this.options_ === undefined ||
this.options_.userActions === undefined ||
this.options_.userActions.doubleClick === undefined ||
this.options_.userActions.doubleClick !== false
) {
si (
this.options_ !== undefined &&
this.options_.userActions !== undefined &&
typeof this.options_.userActions.doubleClick === 'function'
) {
this.options_.userActions.doubleClick.call(this, event) ;
} else if (this.isFullscreen()) {
this.exitFullscreen() ;
} else {
this.requestFullscreen() ;
}
}
}
}
/**
* Gérer un tapotement sur l'élément multimédia. Il fera basculer l'utilisateur
* l'état de l'activité, qui masque et affiche les contrôles.
*
* @listens Tech#tap
* @private
*/
handleTechTap_() {
this.userActive(!this.userActive()) ;
}
/**
* Toucher la poignée pour démarrer
*
* @listens Tech#touchstart
* @private
*/
handleTechTouchStart_() {
this.userWasActive = this.userActive() ;
}
/**
* Toucher de la poignée pour se déplacer
*
* @listens Tech#touchmove
* @private
*/
handleTechTouchMove_() {
if (this.userWasActive) {
this.reportUserActivity() ;
}
}
/**
* Touche de la poignée à l'extrémité
*
* @param {EventTarget~Event} event
* l'événement tactile qui a déclenché
* cette fonction
*
* @listens Tech#touchend
* @private
*/
handleTechTouchEnd_(event) {
// Empêche les événements de la souris de se produire également
if (event.cancelable) {
event.preventDefault() ;
}
}
/**
* les événements de clics natifs sur le SWF ne sont pas déclenchés sur IE11, Win8.1RT
* utiliser les événements stageclick déclenchés à l'intérieur du SWF à la place
*
* @private
* @listens stageclick
*/
handleStageClick_() {
this.reportUserActivity() ;
}
/**
* @private
*/
toggleFullscreenClass_() {
if (this.isFullscreen()) {
this.addClass('vjs-fullscreen') ;
} else {
this.removeClass('vjs-fullscreen') ;
}
}
/**
* lorsque l'événement fschange du document se déclenche, il appelle ceci
*/
documentFullscreenChange_(e) {
const targetPlayer = e.target.player ;
// si un autre joueur est en plein écran
// vérifie que le joueur cible est nul car les anciens firefox mettaient document comme e.target
if (targetPlayer && targetPlayer !== this) {
retour ;
}
const el = this.el() ;
let isFs = document[this.fsApi_.fullscreenElement] === el ;
if (!isFs && el.matches) {
isFs = el.matches(':' + this.fsApi_.fullscreen) ;
} else if (!isFs && el.msMatchesSelector) {
isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen) ;
}
this.isFullscreen(isFs) ;
}
/**
* Gérer le changement d'écran complet de la technologie
*
* @param {EventTarget~Event} event
* l'événement fullscreenchange qui a déclenché cette fonction
*
* @param {Object} data
* les données qui ont été envoyées avec l'événement
*
* @private
* @listens Tech#fullscreenchange
* @fires Player#fullscreenchange
*/
handleTechFullscreenChange_(event, data) {
if (data) {
if (data.nativeIOSFullscreen) {
this.addClass('vjs-ios-native-fs') ;
this.tech_.one('webkitendfullscreen', () => {
this.removeClass('vjs-ios-native-fs') ;
}) ;
}
this.isFullscreen(data.isFullscreen) ;
}
}
handleTechFullscreenError_(event, err) {
this.trigger('fullscreenerror', err) ;
}
/**
* @private
*/
togglePictureInPictureClass_() {
if (this.isInPictureInPicture()) {
this.addClass('vjs-picture-in-picture') ;
} else {
this.removeClass('vjs-picture-in-picture') ;
}
}
/**
* Handle Tech Enter Picture-in-Picture.
*
* @param {EventTarget~Event} event
* l'événement enterpictureinpicture qui a déclenché cette fonction
*
* @private
* @listens Tech#enterpictureinpicture
*/
handleTechEnterPictureInPicture_(event) {
this.isInPictureInPicture(true) ;
}
/**
* Handle Tech Leave Picture-in-Picture.
*
* @param {EventTarget~Event} event
* l'événement leavepictureinpicture qui a déclenché cette fonction
*
* @private
* @listens Tech#leavepictureinpicture
*/
handleTechLeavePictureInPicture_(event) {
this.isInPictureInPicture(false) ;
}
/**
* Se déclenche lorsqu'une erreur s'est produite lors du chargement d'un fichier audio/vidéo.
*
* @private
* @listens Tech#error
*/
handleTechError_() {
const error = this.tech_.error() ;
this.error(error) ;
}
/**
* Redéclenche l'événement `textdata` qui a été déclenché par le {@link Tech}.
*
* @fires Player#textdata
* @listens Tech#textdata
* @private
*/
handleTechTextData_() {
let data = null ;
if (arguments.length > 1) {
data = arguments[1] ;
}
/**
* Se déclenche lorsque nous recevons un événement textdata de tech
*
* @event Player#textdata
* @type {EventTarget~Event}
*/
this.trigger('textdata', data) ;
}
/**
* Obtention d'un objet pour les valeurs mises en cache.
*
* @return {Object}
* obtenir le cache d'objets actuel
*/
getCache() {
return this.cache_ ;
}
/**
* Réinitialise l'objet de cache interne.
*
* L'utilisation de cette fonction en dehors du constructeur du lecteur ou de la méthode de réinitialisation peut
* ont des effets secondaires inattendus.
*
* @private
*/
resetCache_() {
this.cache_ = {
// Pour l'instant, l'heure actuelle n'est pas _vraiment_ mise en cache parce qu'elle est toujours
// extrait de la technologie (voir : currentTime). Toutefois, par souci d'exhaustivité,
// nous le mettons à zéro ici pour nous assurer que si nous commençons à mettre en cache
// il, nous le réinitialisons avec tout le reste.
l'heure actuelle : 0,
initTime : 0,
inactivityTimeout : this.options_.inactivityTimeout,
durée: NaN,
dernierVolume : 1,
lastPlaybackRate : this.defaultPlaybackRate(),
media : null,
src : '',
source : {},
sources : [],
playbackRates : [],
volume : 1
};
}
/**
* Transmettre des valeurs à la technologie de lecture
*
* @param {string} [méthode]
* la méthode à appeler
*
* @param {Objet} arg
* l'argument à transmettre
*
* @private
*/
techCall_(method, arg) {
// S'il n'est pas encore prêt, appeler la méthode lorsqu'il l'est
this.ready(function() {
if (method in middleware.allowedSetters) {
return middleware.set(this.middleware_, this.tech_, method, arg) ;
else if (method in middleware.allowedMediators) {
return middleware.mediate(this.middleware_, this.tech_, method, arg) ;
}
essayez {
if (this.tech_) {
this.tech_[method](arg) ;
}
} catch (e) {
log(e) ;
lancer e ;
}
}, true) ;
}
/**
* Les appels ne peuvent pas attendre le technicien, et parfois n'ont pas besoin de l'être.
*
* @param {string} method
* Méthode technique
*
* @return {Fonction|undefined}
* la méthode ou non définie
*
* @private
*/
techGet_(méthode) {
if (!this.tech_ || !this.tech_.isReady_) {
retour ;
}
if (method in middleware.allowedGetters) {
return middleware.get(this.middleware_, this.tech_, method) ;
else if (method in middleware.allowedMediators) {
return middleware.mediate(this.middleware_, this.tech_, method) ;
}
// Flash aime mourir et se recharger lorsque vous le cachez ou le repositionnez.
// Dans ce cas, les méthodes de l'objet disparaissent et nous obtenons des erreurs.
// TODO : Est-ce nécessaire pour les techniciens autres que Flash ?
// Lorsque cela se produit, nous détectons les erreurs et informons le technicien qu'il n'est plus prêt.
essayez {
return this.tech_[method]() ;
} catch (e) {
// Lors de la construction de librairies techniques supplémentaires, il se peut qu'une méthode attendue ne soit pas encore définie
if (this.tech_[method] === undefined) {
log(`Video.js : méthode ${method} non définie pour la technologie de lecture ${this.techName_}.`, e) ;
lancer e ;
}
// Lorsqu'une méthode n'est pas disponible sur l'objet, une erreur de type (TypeError) est déclenchée
if (e.name === 'TypeError') {
log(`Video.js : ${method} unavailable on ${this.techName_} playback technology element.`, e) ;
this.tech_.isReady_ = false ;
lancer e ;
}
// Si l'erreur est inconnue, il suffit d'enregistrer et de lancer
log(e) ;
lancer e ;
}
}
/**
* Essayez de commencer la lecture à la première occasion.
*
* @return {Promesse|non défini}
* Renvoie une promesse si le navigateur prend en charge les promesses (ou l'une d'entre elles)
* a été passé en option). Cette promesse sera résolue le
* la valeur de retour du jeu. S'il n'est pas défini, il remplira la fonction
* sinon la chaîne de promesses sera remplie lorsque
* la promesse du jeu est tenue.
*/
play() {
const PromiseClass = this.options_.Promise || window.Promise ;
if (PromiseClass) {
return new PromiseClass((resolve) => {
this.play_(resolve) ;
}) ;
}
return this.play_() ;
}
/**
* La logique de jeu proprement dite utilise une fonction de rappel qui sera résolue lors de l'exécution de la fonction
* valeur de retour du jeu. Cela nous permet de résoudre la promesse de jeu s'il y a
* est un sur les navigateurs modernes.
*
* @private
* @param {Fonction} [callback]
* Le callback qui devrait être appelé lorsque le jeu des techniciens est effectivement appelé
*/
play_(callback = silencePromise) {
this.playCallbacks_.push(callback) ;
const isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())) ;
// traiter les appels à play_ un peu comme la fonction `one` event
if (this.waitToPlay_) {
this.off(['ready', 'loadstart'], this.waitToPlay_) ;
this.waitToPlay_ = null ;
}
// si le joueur/technicien n'est pas prêt ou si le serveur lui-même n'est pas prêt
// mettre en file d'attente un appel à jouer sur `ready` ou `loadstart`
if (!this.isReady_ || !isSrcReady) {
this.waitToPlay_ = (e) => {
this.play_() ;
};
this.one(['ready', 'loadstart'], this.waitToPlay_) ;
// si nous sommes dans Safari, il y a de fortes chances que loadstart se déclenche après la période de temps du geste
// dans ce cas, nous devons amorcer l'élément vidéo en appelant load pour qu'il soit prêt à temps
if (!isSrcReady && (browser.IS_ANY_SAFARI || browser.IS_IOS)) {
this.load() ;
}
retour ;
}
// Si le lecteur/la technologie est prêt(e) et que nous disposons d'une source, nous pouvons tenter la lecture.
const val = this.techGet_('play') ;
// la lecture a été interrompue si la valeur renvoyée est nulle
if (val === null) {
this.runPlayTerminatedQueue_() ;
} else {
this.runPlayCallbacks_(val) ;
}
}
/**
* Ces fonctions seront exécutées lorsque le jeu sera terminé. Si le jeu
* runPlayCallbacks_ est exécuté, ces fonctions ne seront pas exécutées. Cela nous permet
* de faire la différence entre un jeu interrompu et un appel au jeu.
*/
runPlayTerminatedQueue_() {
const queue = this.playTerminatedQueue_.slice(0) ;
this.playTerminatedQueue_ = [] ;
queue.forEach(function(q) {
q() ;
}) ;
}
/**
* Lorsqu'un appel à la lecture est retardé, nous devons exécuter les opérations suivantes
* callbacks lorsque le jeu est effectivement appelé sur le technicien. Cette fonction
* exécute les callbacks qui ont été retardés et accepte la valeur de retour
* de la technologie.
*
* @param {undefined|Promesse} val
* La valeur de retour du tech.
*/
runPlayCallbacks_(val) {
const callbacks = this.playCallbacks_.slice(0) ;
this.playCallbacks_ = [] ;
// effacer le jeu terminé en file d'attente depuis que nous avons terminé un vrai jeu
this.playTerminatedQueue_ = [] ;
callbacks.forEach(function(cb) {
cb(val) ;
}) ;
}
/**
* Pause de la lecture vidéo
*
* @return {Player}
* Une référence à l'objet joueur sur lequel cette fonction a été appelée
*/
pause() {
this.techCall_('pause') ;
}
/**
* Vérifier si le lecteur est en pause ou s'il doit encore jouer
*
* @return {boolean}
* - false : si le média est en cours de lecture
* - true : si le média n'est pas en cours de lecture
*/
paused() {
// L'état initial de paused devrait être true (dans Safari, il est en fait false)
return (this.techGet_('paused') === false) ? false : true ;
}
/**
* Obtention d'un objet TimeRange représentant les plages de temps actuelles que l'utilisateur
* a joué.
*
* @return {TimeRange}
* Un objet d'intervalle de temps qui représente tous les incréments de temps ayant
* ont été joués.
*/
played() {
return this.techGet_('played') || createTimeRange(0, 0) ;
}
/**
* Indique si l'utilisateur est en train de "frotter" ou non. Le lavage est
* lorsque l'utilisateur a cliqué sur la poignée de la barre de progression et qu'il est
* en le faisant glisser le long de la barre de progression.
*
* @param {boolean} [isScrubbing]
* si l'utilisateur est ou n'est pas en train de se laver
*
* @return {boolean}
* L'intérêt de l'épuration lors de l'obtention
*/
scrubbing(isScrubbing) {
if (typeof isScrubbing === 'undefined') {
return this.scrubbing_ ;
}
this.scrubbing_ = !!isScrubbing ;
this.techCall_('setScrubbing', this.scrubbing_) ;
if (isScrubbing) {
this.addClass('vjs-scrubbing') ;
} else {
this.removeClass('vjs-scrubbing') ;
}
}
/**
* Obtenir ou régler l'heure actuelle (en secondes)
*
* @param {nombre|chaîne} [secondes]
* Temps de recherche en secondes
*
* @return {number}
* - l'heure actuelle en secondes lorsque l'on obtient
*/
currentTime(seconds) {
if (typeof seconds !== 'undefined') {
if (seconds < 0) {
secondes = 0 ;
}
if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) {
this.cache_.initTime = seconds ;
this.off('canplay', this.boundApplyInitTime_) ;
this.one('canplay', this.boundApplyInitTime_) ;
retour ;
}
this.techCall_('setCurrentTime', seconds) ;
this.cache_.initTime = 0 ;
retour ;
}
// met en cache la dernière heure courante et la renvoie. 0 secondes par défaut
//
// La mise en cache de l'heure actuelle a pour but d'éviter une quantité massive de lectures sur la page d'accueil de la technologie
// currentTime lors de l'épuration, mais il se peut qu'il n'y ait pas beaucoup d'avantages en termes de performances après tout.
// Doit être testé. Il faut aussi que quelque chose lise l'heure actuelle, sinon la mémoire cache
// ne sont jamais mises à jour.
this.cache_.currentTime = (this.techGet_('currentTime') || 0) ;
return this.cache_.currentTime ;
}
/**
* Appliquer la valeur de initTime stockée dans le cache en tant que currentTime.
*
* @private
*/
applyInitTime_() {
this.currentTime(this.cache_.initTime) ;
}
/**
* Normalement, il s'agit de la durée de la vidéo en secondes ;
* dans tous les cas d'utilisation, sauf les plus rares, un argument ne sera PAS transmis à la méthode
*
* > **NOTE** : Le chargement de la vidéo doit avoir commencé pour que la durée puisse être calculée
* connu, et en fonction du comportement de préchargement, il peut ne pas être connu avant que la vidéo ne commence
* jouer.
*
* @fires Player#durationchange
*
* @param {number} [seconds]
* Durée de la vidéo à définir en secondes
*
* @return {number}
* - La durée de la vidéo en secondes lors de l'obtention
*/
durée(secondes) {
if (seconds === undefined) {
// renvoie NaN si la durée n'est pas connue
return this.cache_.duration !== undefined ? this.cache_.duration : NaN ;
}
seconds = parseFloat(seconds) ;
// Standardisation de l'infini pour signaler que la vidéo est en direct
if (seconds < 0) {
secondes = Infini ;
}
if (seconds !== this.cache_.duration) {
// Mettre en cache la dernière valeur définie pour optimiser l'épuration (esp. Flash)
// TODO : Requis pour les techniciens autres que Flash ?
this.cache_.duration = seconds ;
if (seconds === Infinity) {
this.addClass('vjs-live') ;
} else {
this.removeClass('vjs-live') ;
}
if (!isNaN(seconds)) {
// Ne déclenche pas le changement de durée si la valeur de la durée n'est pas connue.
// @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}
/**
* @event Player#durationchange
* @type {EventTarget~Event}
*/
this.trigger('durationchange') ;
}
}
}
/**
* Calcule le temps restant dans la vidéo. Pas de partie
* de l'API vidéo native.
*
* @return {number}
* Le temps restant en secondes
*/
remainingTime() {
return this.duration() - this.currentTime() ;
}
/**
* Une fonction de temps restant qui est destinée à être utilisée lorsque
* l'heure doit être affichée directement à l'utilisateur.
*
* @return {number}
* Le temps restant arrondi en secondes
*/
remainingTimeDisplay() {
return Math.floor(this.duration()) - Math.floor(this.currentTime()) ;
}
//
// Une sorte de tableau des parties de la vidéo qui ont été téléchargées.
/**
* Obtention d'un objet TimeRange contenant un tableau des durées de la vidéo
* qui ont été téléchargés. Si vous ne voulez que le pourcentage de la
* vidéo qui a été téléchargée, utilisez bufferedPercent.
*
* @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
*
* @return {TimeRange}
* Un objet TimeRange fictif (conformément à la spécification HTML)
*/
buffered() {
let buffered = this.techGet_('buffered') ;
if (!buffered || !buffered.length) {
buffered = createTimeRange(0, 0) ;
}
retourner buffered ;
}
/**
* Obtenir le pourcentage (en décimal) de la vidéo qui a été téléchargée.
* Cette méthode ne fait pas partie de l'API vidéo HTML native.
*
* @return {number}
* Une décimale entre 0 et 1 représentant le pourcentage
* qui est mis en mémoire tampon 0 étant 0 % et 1 étant 100 %
*/
bufferedPercent() {
return bufferedPercent(this.buffered(), this.duration()) ;
}
/**
* Obtenir l'heure de fin de la dernière plage temporelle mise en mémoire tampon
* Elle est utilisée dans la barre de progression pour encapsuler toutes les plages de temps.
*
* @return {number}
* Fin de la dernière plage de temps mise en mémoire tampon
*/
bufferedEnd() {
const buffered = this.buffered() ;
const duration = this.duration() ;
let end = buffered.end(buffered.length - 1) ;
if (end > duration) {
fin = durée ;
}
retour fin ;
}
/**
* Obtenir ou définir le volume actuel du support
*
* @param {number} [percentAsDecimal]
* Le nouveau volume en pourcentage décimal :
* - 0 est en sourdine/0%/off
* - 1.0 est 100%/plein
* - 0,5 correspond à un demi-volume ou à 50 %
*
* @return {number}
* Le volume actuel en pourcentage lors de l'obtention
*/
volume(percentAsDecimal) {
laisser vol ;
if (percentAsDecimal !== undefined) {
// Forcer la valeur entre 0 et 1
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))) ;
this.cache_.volume = vol ;
this.techCall_('setVolume', vol) ;
if (vol > 0) {
this.lastVolume_(vol) ;
}
retour ;
}
// La valeur par défaut est 1 lorsque le volume actuel est renvoyé.
vol = parseFloat(this.techGet_('volume')) ;
return (isNaN(vol)) ? 1 : vol ;
}
/**
* Obtenir l'état actuel de la sourdine, ou activer ou désactiver la sourdine
*
* @param {boolean} [muted]
* - true pour mettre en sourdine
* - faux pour rétablir le son
*
* @return {boolean}
* - vrai si la sourdine est activée et si l'on obtient
* - faux si la sourdine est désactivée et si l'on obtient
*/
muted(muted) {
if (muted !== undefined) {
this.techCall_('setMuted', muted) ;
retour ;
}
return this.techGet_('muted') || false ;
}
/**
* Obtenir l'état actuel de defaultMuted, ou activer ou désactiver defaultMuted. defaultMuted
* indique l'état de sourdine lors de la lecture initiale.
*
* ``js
* var myPlayer = videojs('some-player-id') ;
*
* myPlayer.src("http://www.example.com/path/to/video.mp4") ;
*
* // obtenir, devrait être faux
* console.log(myPlayer.defaultMuted()) ;
* // mis à true (vrai)
* myPlayer.defaultMuted(true) ;
* // get devrait être vrai
* console.log(myPlayer.defaultMuted()) ;
* ```
*
* @param {boolean} [defaultMuted]
* - true pour mettre en sourdine
* - faux pour rétablir le son
*
* @return {boolean|Player}
* - true si defaultMuted est activé et obtient
* - false si defaultMuted est désactivé et obtient
* - Une référence au lecteur actuel lors du réglage
*/
defaultMuted(defaultMuted) {
if (defaultMuted !== undefined) {
return this.techCall_('setDefaultMuted', defaultMuted) ;
}
return this.techGet_('defaultMuted') || false ;
}
/**
* Obtenir le dernier volume ou le définir
*
* @param {number} [percentAsDecimal]
* Le nouveau dernier volume en pourcentage décimal :
* - 0 est en sourdine/0%/off
* - 1.0 est 100%/plein
* - 0,5 correspond à un demi-volume ou à 50 %
*
* @return {number}
* la valeur actuelle de lastVolume en pourcentage lors de l'obtention de
*
* @private
*/
lastVolume_(percentAsDecimal) {
if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
this.cache_.lastVolume = percentAsDecimal ;
retour ;
}
return this.cache_.lastVolume ;
}
/**
* Vérifier si la technologie actuelle peut supporter le plein écran natif
* (par exemple, avec des contrôles intégrés comme iOS)
*
* @return {boolean}
* si le plein écran natif est pris en charge
*/
supportsFullScreen() {
return this.techGet_('supportsFullScreen') || false ;
}
/**
* Vérifier si le lecteur est en mode plein écran ou indiquer au lecteur qu'il est en mode plein écran
* est ou n'est pas en mode plein écran.
*
* > NOTE : Depuis la dernière version de la spécification HTML5, isFullscreen n'est plus une norme officielle
* et le document.fullscreenElement est utilisé à la place. Mais isFullscreen est
* reste une propriété précieuse pour le fonctionnement interne des joueurs.
*
* @param {boolean} [isFS]
* Définir l'état actuel de plein écran du joueur
*
* @return {boolean}
* - true si le fullscreen est activé et obtient
* - false si le fullscreen est désactivé et obtient
*/
isFullscreen(isFS) {
if (isFS !== undefined) {
const oldValue = this.isFullscreen_ ;
this.isFullscreen_ = Boolean(isFS) ;
// si nous avons changé l'état du fullscreen et que nous sommes en mode préfixé, déclencher le fullscreenchange
// c'est le seul endroit où nous déclenchons des événements de changement d'écran pour les navigateurs plus anciens
// le mode fullWindow est traité comme un événement préfixé et recevra également un événement fullscreenchange
if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) {
/**
* @event Player#fullscreenchange
* @type {EventTarget~Event}
*/
this.trigger('fullscreenchange') ;
}
this.toggleFullscreenClass_() ;
retour ;
}
return this.isFullscreen_ ;
}
/**
* Augmenter la taille de la vidéo en plein écran
* Dans certains navigateurs, le plein écran n'est pas pris en charge en mode natif
* le mode "pleine fenêtre", dans lequel la vidéo remplit la fenêtre du navigateur.
* Dans les navigateurs et les appareils qui prennent en charge le plein écran en mode natif, il arrive que l'option
* les contrôles par défaut du navigateur seront affichés, et non l'habillage personnalisé Video.js.
* Cela inclut la plupart des appareils mobiles (iOS, Android) et les anciennes versions de
* Safari.
*
* @param {Object} [fullscreenOptions]
* Remplacer les options de plein écran du lecteur
*
* @fires Player#fullscreenchange
*/
requestFullscreen(fullscreenOptions) {
const PromiseClass = this.options_.Promise || window.Promise ;
if (PromiseClass) {
const self = this ;
return new PromiseClass((resolve, reject) => {
function offHandler() {
self.off('fullscreenerror', errorHandler) ;
self.off('fullscreenchange', changeHandler) ;
}
function changeHandler() {
offHandler() ;
resolve() ;
}
function errorHandler(e, err) {
offHandler() ;
reject(err) ;
}
self.one('fullscreenchange', changeHandler) ;
self.one('fullscreenerror', errorHandler) ;
const promise = self.requestFullscreenHelper_(fullscreenOptions) ;
if (promise) {
promise.then(offHandler, offHandler) ;
promise.then(resolve, reject) ;
}
}) ;
}
return this.requestFullscreenHelper_() ;
}
requestFullscreenHelper_(fullscreenOptions) {
let fsOptions ;
// Ne transmettre les options de plein écran à requestFullscreen que dans les navigateurs conformes à la spécification.
// Utiliser les valeurs par défaut ou l'option configurée par le lecteur, à moins qu'elle ne soit transmise directement à cette méthode.
if (!this.fsApi_.prefixed) {
fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {} ;
if (fullscreenOptions !== undefined) {
fsOptions = fullscreenOptions ;
}
}
// Cette méthode fonctionne comme suit :
// 1. si une interface utilisateur pour le plein écran est disponible, l'utiliser
// 1. appeler requestFullscreen avec des options potentielles
// 2. si nous avons obtenu une promesse, nous l'utilisons pour mettre à jour isFullscreen()
// 2. sinon, si le tech supporte le fullscreen, appeler `enterFullScreen` sur lui.
// Ceci est particulièrement utilisé pour l'iPhone, les anciens iPads, et les navigateurs non-Safari sur iOS.
// 3. sinon, utiliser le mode "fullWindow
if (this.fsApi_.requestFullscreen) {
const promise = this.el_[this.fsApi_.requestFullscreen](fsOptions) ;
if (promise) {
promise.then(() => this.isFullscreen(true), () => this.isFullscreen(false)) ;
}
retourner la promesse ;
} else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
// nous ne pouvons pas mettre les contrôles video.js en plein écran mais nous pouvons passer en plein écran
// avec des contrôles natifs
this.techCall_('enterFullScreen') ;
} else {
// le plein écran n'est pas pris en charge, nous allons donc étirer l'élément vidéo jusqu'à
// remplir la fenêtre de visualisation
this.enterFullWindow() ;
}
}
/**
* Ramener la vidéo à sa taille normale après avoir été en mode plein écran
*
* @fires Player#fullscreenchange
*/
exitFullscreen() {
const PromiseClass = this.options_.Promise || window.Promise ;
if (PromiseClass) {
const self = this ;
return new PromiseClass((resolve, reject) => {
function offHandler() {
self.off('fullscreenerror', errorHandler) ;
self.off('fullscreenchange', changeHandler) ;
}
function changeHandler() {
offHandler() ;
resolve() ;
}
function errorHandler(e, err) {
offHandler() ;
reject(err) ;
}
self.one('fullscreenchange', changeHandler) ;
self.one('fullscreenerror', errorHandler) ;
const promise = self.exitFullscreenHelper_() ;
if (promise) {
promise.then(offHandler, offHandler) ;
// mappage de la promesse à nos méthodes de résolution/refus
promise.then(resolve, reject) ;
}
}) ;
}
return this.exitFullscreenHelper_() ;
}
exitFullscreenHelper_() {
if (this.fsApi_.requestFullscreen) {
const promise = document[this.fsApi_.exitFullscreen]() ;
if (promise) {
// nous séparons la promesse ici, donc nous voulons attraper la promesse
// erreur potentielle afin que cette chaîne n'ait pas d'erreurs non gérées
silencePromise(promise.then(() => this.isFullscreen(false))) ;
}
retourner la promesse ;
} else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
this.techCall_('exitFullScreen') ;
} else {
this.exitFullWindow() ;
}
}
/**
* Lorsque le plein écran n'est pas pris en charge, nous pouvons étirer l'image
* le conteneur vidéo est aussi large que le navigateur le permet.
*
* @fires Player#enterFullWindow
*/
enterFullWindow() {
this.isFullscreen(true) ;
this.isFullWindow = true ;
// Stockage de la valeur de débordement de la documentation originale pour revenir à la valeur de débordement lorsque le plein écran est désactivé
this.docOrigOverflow = document.documentElement.style.overflow ;
// Ajout d'un récepteur pour la touche esc pour quitter le plein écran
Events.on(document, 'keydown', this.boundFullWindowOnEscKey_) ;
// Masquer les barres de défilement
document.documentElement.style.overflow = 'hidden' ;
// Appliquer les styles de plein écran
Dom.addClass(document.body, 'vjs-full-window') ;
/**
* @event Player#enterFullWindow
* @type {EventTarget~Event}
*/
this.trigger('enterFullWindow') ;
}
/**
* Vérifier s'il y a un appel à sortir de la fenêtre complète ou
* plein écran sur la touche ESC
*
* @param {string} event
* Événement à vérifier en cas de pression sur une touche
*/
fullWindowOnEscKey(event) {
if (keycode.isEventKey(event, 'Esc')) {
if (this.isFullscreen() === true) {
if (!this.isFullWindow) {
this.exitFullscreen() ;
} else {
this.exitFullWindow() ;
}
}
}
}
/**
* Quitter la fenêtre complète
*
* @fires Player#exitFullWindow
*/
exitFullWindow() {
this.isFullscreen(false) ;
this.isFullWindow = false ;
Events.off(document, 'keydown', this.boundFullWindowOnEscKey_) ;
// Désactiver les barres de défilement.
document.documentElement.style.overflow = this.docOrigOverflow ;
// Supprimer les styles de plein écran
Dom.removeClass(document.body, 'vjs-full-window') ;
// Redimensionner la boîte, le contrôleur et l'affiche aux tailles d'origine
// this.positionAll() ;
/**
* @event Player#exitFullWindow
* @type {EventTarget~Event}
*/
this.trigger('exitFullWindow') ;
}
/**
* Désactiver le mode Image dans l'image.
*
* @param {boolean} value
* - true désactive le mode Image dans l'image (Picture-in-Picture)
* - false active le mode Image dans l'image
*/
disablePictureInPicture(value) {
if (value === undefined) {
return this.techGet_('disablePictureInPicture') ;
}
this.techCall_('setDisablePictureInPicture', value) ;
this.options_.disablePictureInPicture = value ;
this.trigger('disablepictureinpicturechanged') ;
}
/**
* Vérifiez si le lecteur est en mode Image dans l'image ou indiquez au lecteur qu'il est en mode Image dans l'image
* est ou n'est pas en mode Image dans l'image.
*
* @param {boolean} [isPiP]
* Définir l'état actuel de l'image dans l'image du lecteur
*
* @return {boolean}
* - vrai si l'incrustation d'image est activée et en cours d'acquisition
* - faux si l'incrustation d'image est désactivée et en cours d'acquisition
*/
isInPictureInPicture(isPiP) {
if (isPiP !== undefined) {
this.isInPictureInPicture_ = !!isPiP ;
this.togglePictureInPictureClass_() ;
retour ;
}
return !!this.isInPictureInPicture_ ;
}
/**
* Créer une fenêtre vidéo flottante toujours au-dessus des autres fenêtres afin que les utilisateurs puissent
* continuer à consommer des médias tout en interagissant avec d'autres sites de contenu, ou
* sur leur appareil.
*
* @see [Spec]{@link https://wicg.github.io/picture-in-picture}
*
* @fires Player#enterpictureinpicture
*
* @return {Promesse}
* Une promesse avec une fenêtre d'image dans l'image.
*/
requestPictureInPicture() {
if ('pictureInPictureEnabled' in document && this.disablePictureInPicture() === false) {
/**
* Cet événement se déclenche lorsque le lecteur passe en mode image par image
*
* @event Player#enterpictureinpicture
* @type {EventTarget~Event}
*/
return this.techGet_('requestPictureInPicture') ;
}
}
/**
* Quitter le mode Image dans l'image.
*
* @see [Spec]{@link https://wicg.github.io/picture-in-picture}
*
* @fires Player#leavepictureinpicture
*
* @return {Promesse}
* Une promesse.
*/
exitPictureInPicture() {
if ("pictureInPictureEnabled" in document) {
/**
* Cet événement se déclenche lorsque le joueur quitte le mode image par image
*
* @event Player#leavepictureinpicture
* @type {EventTarget~Event}
*/
return document.exitPictureInPicture() ;
}
}
/**
* Appelé lorsque ce lecteur est au centre de l'attention et qu'une touche est enfoncée, ou lorsque
* tout composant de ce lecteur reçoit une pression de touche qu'il ne gère pas.
* Cela permet d'utiliser des raccourcis clavier pour l'ensemble du joueur (tels que définis ci-dessous, ou optionnellement
* par une fonction externe).
*
* @param {EventTarget~Event} event
* L'événement `keydown` qui a provoqué l'appel de cette fonction.
*
* @listens keydown
*/
handleKeyDown(event) {
const {userActions} = this.options_ ;
// S'en sortir si les raccourcis clavier ne sont pas configurés.
if (!userActions || !userActions.hotkeys) {
retour ;
}
// Fonction qui détermine s'il faut ou non exclure un élément de la liste des éléments à exclure de la liste des éléments à exclure de la liste des éléments à exclure
// gestion des touches de raccourci.
const excludeElement = (el) => {
const tagName = el.tagName.toLowerCase() ;
// Le premier test, le plus simple, concerne les éléments `contentable`.
if (el.isContentEditable) {
retourner vrai ;
}
// Les entrées correspondant à ces types déclencheront toujours la gestion des touches de raccourci en tant que
// ce ne sont pas des entrées de texte.
const allowedInputTypes = [
'bouton',
case à cocher",
caché",
'radio',
'reset',
soumettre
] ;
if (tagName === 'input') {
return allowedInputTypes.indexOf(el.type) === -1 ;
}
// Le test final se fait par nom de balise. Ces étiquettes seront entièrement exclues.
const excludedTags = ['textarea'] ;
return excludedTags.indexOf(tagName) !== -1 ;
};
// S'en sortir si l'utilisateur se concentre sur un élément de formulaire interactif.
if (excludeElement(this.el_.ownerDocument.activeElement)) {
retour ;
}
if (typeof userActions.hotkeys === 'function') {
userActions.hotkeys.call(this, event) ;
} else {
this.handleHotkeys(event) ;
}
}
/**
* Appelé lorsque ce lecteur reçoit un événement de raccourci clavier.
* Les raccourcis clavier pris en charge sont les suivants :
*
* f - basculer en mode plein écran
* m - activer la mise en sourdine
* k ou Espace - basculer lecture/pause
*
* @param {EventTarget~Event} event
* L'événement `keydown` qui a provoqué l'appel de cette fonction.
*/
handleHotkeys(event) {
const hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {} ;
// définir fullscreenKey, muteKey, playPauseKey à partir de `hotkeys`, utiliser les valeurs par défaut si elles ne sont pas définies
const {
fullscreenKey = keydownEvent => keycode.isEventKey(keydownEvent, 'f'),
muteKey = keydownEvent => keycode.isEventKey(keydownEvent, 'm'),
playPauseKey = keydownEvent => (keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space'))
} = hotkeys ;
if (fullscreenKey.call(this, event)) {
event.preventDefault() ;
event.stopPropagation() ;
const FSToggle = Component.getComponent('FullscreenToggle') ;
if (document[this.fsApi_.fullscreenEnabled] !== false) {
FSToggle.prototype.handleClick.call(this, event) ;
}
} else if (muteKey.call(this, event)) {
event.preventDefault() ;
event.stopPropagation() ;
const MuteToggle = Component.getComponent('MuteToggle') ;
MuteToggle.prototype.handleClick.call(this, event) ;
} else if (playPauseKey.call(this, event)) {
event.preventDefault() ;
event.stopPropagation() ;
const PlayToggle = Component.getComponent('PlayToggle') ;
PlayToggle.prototype.handleClick.call(this, event) ;
}
}
/**
* Vérifier si le lecteur peut jouer un type d'image donné
*
* voir https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
*
* @param {string} type
* Le type d'image à vérifier
*
* @return {string}
* "probablement", "peut-être" ou '' (chaîne vide)
*/
canPlayType(type) {
peuvent le faire ;
// Boucle sur chaque technologie de lecture dans l'ordre des options
for (let i = 0, j = this.options_.techOrder ; i < j.length ; i++) {
const techName = j[i] ;
let tech = Tech.getTech(techName) ;
// Prise en charge de l'ancien comportement des technologies enregistrées en tant que composants.
// Supprimer une fois que ce comportement déprécié est supprimé.
if (!tech) {
tech = Component.getComponent(techName) ;
}
// Vérifier si la technologie actuelle est définie avant de continuer
if (!tech) {
log.error(`La technologie "${techName}" est indéfinie. La vérification de la prise en charge du navigateur a été omise pour cette technologie ;)
continuer ;
}
// Vérifier si le navigateur supporte cette technologie
if (tech.isSupported()) {
can = tech.canPlayType(type) ;
if (can) {
peuvent être retournés ;
}
}
}
retourner '' ;
}
/**
* Sélectionner la source en fonction de l'ordre technique ou de l'ordre de service
* Utilise la sélection de l'ordre des sources si `options.sourceOrder` est vrai. Dans le cas contraire,
* sélection par défaut de l'ordre technique
*
* @param {Array} sources
* Les sources d'un média
*
* @return {Object|boolean}
* Objet de l'ordre source et technique ou faux
*/
selectSource(sources) {
// Obtenir uniquement les technologies spécifiées dans `techOrder` qui existent et sont prises en charge par l'entreprise
// plateforme actuelle
const techs =
this.options_.techOrder
.map((techName) => {
return [techName, Tech.getTech(techName)] ;
})
.filter(([techName, tech]) => {
// Vérifier si la technologie actuelle est définie avant de continuer
if (tech) {
// Vérifier si le navigateur supporte cette technologie
return tech.isSupported() ;
}
log.error(`La technologie "${techName}" est indéfinie. La vérification de la prise en charge du navigateur a été omise pour cette technologie ;)
retourner faux ;
}) ;
// Itère sur chaque élément `innerArray` une fois par élément `outerArray` et exécute
// `tester` avec les deux. Si `tester` renvoie une valeur non erronée, on quitte prématurément et on renvoie
// cette valeur.
const findFirstPassingTechSourcePair = function(outerArray, innerArray, tester) {
ont été trouvés ;
outerArray.some((outerChoice) => {
return innerArray.some((innerChoice) => {
found = tester(outerChoice, innerChoice) ;
if (found) {
retourner vrai ;
}
}) ;
}) ;
retour trouvé ;
};
let foundSourceAndTech ;
const flip = (fn) => (a, b) => fn(b, a) ;
const finder = ([techName, tech], source) => {
if (tech.canPlaySource(source, this.options_[techName.toLowerCase()])) {
return {source, tech : techName} ;
}
};
// En fonction de la véracité de `options.sourceOrder`, nous intervertissons l'ordre des technologies et des sources
// de les sélectionner en fonction de leur priorité.
if (this.options_.sourceOrder) {
// Ordonnancement à la source d'abord
foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder)) ;
} else {
// La commande de technologie d'abord
foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder) ;
}
return foundSourceAndTech || false ;
}
/**
* Exécute la logique de réglage et d'obtention de la source
*
* @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
* Un SourceObject, un tableau de SourceObjects, ou une chaîne de caractères faisant référence à
* une URL vers une source de média. Il est _hautement recommandé_ qu'un objet
* ou un tableau d'objets est utilisé ici, de sorte que la sélection de la source
* les algorithmes peuvent prendre en compte le `type`.
*
* Si elle n'est pas fournie, cette méthode agit comme un getter.
* @param {boolean} isRetry
* Indique si cette action est appelée en interne à la suite d'une nouvelle tentative
*
* @return {string|undefined}
* Si l'argument `source` est manquant, renvoie la source courante
* URL. Sinon, ne renvoie rien ou non défini.
*/
handleSrc_(source, isRetry) {
// utilisation du getter
if (typeof source === 'undefined') {
return this.cache_.src || '' ;
}
// Réinitialiser le comportement de réessai pour la nouvelle source
if (this.resetRetryOnError_) {
this.resetRetryOnError_() ;
}
// filtrer les sources non valides et transformer notre source en
// un tableau d'objets sources
const sources = filterSource(source) ;
// si une source a été transmise, elle n'est pas valide car
// il a été filtré en un tableau de longueur nulle. Nous devons donc
// afficher une erreur
if (!sources.length) {
this.setTimeout(function() {
this.error({ code : 4, message : this.options_.notSupportedMessage }) ;
}, 0) ;
retour ;
}
// sources initiales
this.changingSrc_ = true ;
// Ne mettre à jour la liste des sources en cache que si nous n'essayons pas de trouver une nouvelle source après une erreur,
// puisque dans ce cas, nous voulons inclure la ou les source(s) défaillante(s) dans le cache
if (!isRetry) {
this.cache_.sources = sources ;
}
this.updateSourceCaches_(sources[0]) ;
// middlewareSource est la source après qu'elle a été modifiée par le middleware
middleware.setSource(this, sources[0], (middlewareSource, mws) => {
this.middleware_ = mws ;
// puisque sourceSet est asynchrone, nous devons à nouveau mettre à jour le cache après avoir sélectionné une source puisque
// la source sélectionnée peut être désordonnée par rapport à la mise à jour du cache qui précède ce callback.
if (!isRetry) {
this.cache_.sources = sources ;
}
this.updateSourceCaches_(middlewareSource) ;
const err = this.src_(middlewareSource) ;
if (err) {
if (sources.length > 1) {
return this.handleSrc_(sources.slice(1)) ;
}
this.changingSrc_ = false ;
// Nous devons envelopper ceci dans un délai d'attente pour donner aux gens une chance d'ajouter des gestionnaires d'événements d'erreur
this.setTimeout(function() {
this.error({ code : 4, message : this.options_.notSupportedMessage }) ;
}, 0) ;
// nous n'avons pas pu trouver de tech approprié, mais notifions tout de même le délégué que c'est ça
// cela nécessite un meilleur commentaire sur la raison de cette nécessité
this.triggerReady() ;
retour ;
}
middleware.setTech(mws, this.tech_) ;
}) ;
// Essayer une autre source disponible si celle-ci échoue avant la lecture.
if (this.options_.retryOnError && sources.length > 1) {
const retry = () => {
// Supprimer la fenêtre modale d'erreur
this.error(null) ;
this.handleSrc_(sources.slice(1), true) ;
};
const stopListeningForErrors = () => {
this.off('error', retry) ;
};
this.one('error', retry) ;
this.one('playing', stopListeningForErrors) ;
this.resetRetryOnError_ = () => {
this.off('error', retry) ;
this.off('playing', stopListeningForErrors) ;
};
}
}
/**
* Permet d'obtenir ou de définir la source vidéo.
*
* @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
* Un SourceObject, un tableau de SourceObjects, ou une chaîne de caractères faisant référence à
* une URL vers une source de média. Il est _hautement recommandé_ qu'un objet
* ou un tableau d'objets est utilisé ici, de sorte que la sélection de la source
* les algorithmes peuvent prendre en compte le `type`.
*
* Si elle n'est pas fournie, cette méthode agit comme un getter.
*
* @return {string|undefined}
* Si l'argument `source` est manquant, renvoie la source courante
* URL. Sinon, ne renvoie rien ou non défini.
*/
src(source) {
return this.handleSrc_(source, false) ;
}
/**
* Définit l'objet source sur le tech, renvoie un booléen qui indique si
* il y a un technicien qui peut jouer la source ou non
*
* @param {Tech~SourceObject} source
* L'objet source à placer sur le Tech
*
* @return {boolean}
* - Vrai s'il n'y a pas de technicien pour lire cette source
* - Faux sinon
*
* @private
*/
src_(source) {
const sourceTech = this.selectSource([source]) ;
if (!sourceTech) {
retourner vrai ;
}
if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
this.changingSrc_ = true ;
// charger cette technologie avec la source choisie
this.loadTech_(sourceTech.tech, sourceTech.source) ;
this.tech_.ready(() => {
this.changingSrc_ = false ;
}) ;
retourner faux ;
}
// attendre que le technicien soit prêt à régler la source
// et le définir de manière synchrone si possible (#2326)
this.ready(function() {
// La méthode setSource tech a été ajoutée avec les gestionnaires de sources
// les techniciens plus âgés ne le prendront pas en charge
// Nous devons vérifier le prototype direct pour le cas où les sous-classes
// of the tech do not support source handlers (en anglais)
if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
this.techCall_('setSource', source) ;
} else {
this.techCall_('src', source.src) ;
}
this.changingSrc_ = false ;
}, true) ;
retourner faux ;
}
/**
* Commencer à charger les données src.
*/
load() {
this.techCall_('load') ;
}
/**
* Réinitialiser le lecteur. Charge la première technologie dans le techOrder,
* supprime toutes les pistes de texte dans la `tech` existante,
* et appelle le `reset` sur le `tech`.
*/
reset() {
const PromiseClass = this.options_.Promise || window.Promise ;
if (this.paused() || !PromiseClass) {
this.doReset_() ;
} else {
const playPromise = this.play() ;
silencePromesse(playPromise.then(() => this.doReset_())) ;
}
}
doReset_() {
if (this.tech_) {
this.tech_.clearTracks('text') ;
}
this.resetCache_() ;
this.poster('') ;
this.loadTech_(this.options_.techOrder[0], null) ;
this.techCall_('reset') ;
this.resetControlBarUI_() ;
if (isEvented(this)) {
this.trigger('playerreset') ;
}
}
/**
* Réinitialiser l'interface utilisateur de la barre de contrôle en appelant les sous-méthodes qui réinitialisent l'interface utilisateur de la barre de contrôle
* tous les composants de la barre de contrôle
*/
resetControlBarUI_() {
this.resetProgressBar_() ;
this.resetPlaybackRate_() ;
this.resetVolumeBar_() ;
}
/**
* Réinitialiser la progression de la technologie pour que la barre de progression soit réinitialisée dans l'interface utilisateur
*/
resetProgressBar_() {
this.currentTime(0) ;
const { durationDisplay, remainingTimeDisplay } = this.controlBar || {} ;
if (durationDisplay) {
durationDisplay.updateContent() ;
}
if (remainingTimeDisplay) {
remainingTimeDisplay.updateContent() ;
}
}
/**
* Réinitialiser le rapport de lecture
*/
resetPlaybackRate_() {
this.playbackRate(this.defaultPlaybackRate()) ;
this.handleTechRateChange_() ;
}
/**
* Réinitialiser la barre de volume
*/
resetVolumeBar_() {
ce.volume(1.0) ;
this.trigger('volumechange') ;
}
/**
* Renvoie tous les objets sources actuels.
*
* @return {Tech~SourceObject[]}
* Les objets sources actuels
*/
currentSources() {
const source = this.currentSource() ;
const sources = [] ;
// suppose `{}` ou `{ src }`
if (Object.keys(source).length !== 0) {
sources.push(source) ;
}
return this.cache_.sources || sources ;
}
/**
* Renvoie l'objet source actuel.
*
* @return {Tech~SourceObject}
* L'objet source actuel
*/
currentSource() {
return this.cache_.source || {} ;
}
/**
* Renvoie l'URL complète de la valeur source actuelle, par exemple http://mysite.com/video.mp4
* Peut être utilisé en conjonction avec `currentType` pour aider à reconstruire l'objet source actuel.
*
* @return {string}
* La source de courant
*/
currentSrc() {
return this.currentSource() && this.currentSource().src || '' ;
}
/**
* Obtenir le type de source actuel, par exemple vidéo/mp4
* Cela peut vous permettre de reconstruire l'objet source actuel afin de pouvoir charger le même
* source et technologie plus tard
*
* @return {string}
* Le type MIME source
*/
currentType() {
return this.currentSource() && this.currentSource().type || '' ;
}
/**
* Obtenir ou définir l'attribut de précharge
*
* @param {boolean} [valeur]
* - true signifie que nous devons précharger
* - false signifie que nous ne devons pas précharger
*
* @return {string}
* La valeur de l'attribut de préchargement lors de l'obtention de
*/
preload(value) {
if (value !== undefined) {
this.techCall_('setPreload', value) ;
this.options_.preload = value ;
retour ;
}
return this.techGet_('preload') ;
}
/**
* Obtenir ou définir l'option de lecture automatique. Lorsqu'il s'agit d'un booléen, il s'agit de
* modifier l'attribut sur le tech. Lorsqu'il s'agit d'une chaîne de caractères, l'attribut
* la technologie sera supprimée et `Player` gérera l'autoplay lors des démarrages de chargement.
*
* @param {boolean|string} [valeur]
* - true : lecture automatique utilisant le comportement du navigateur
* - false : ne pas jouer automatiquement
* - 'play' : appelle play() à chaque démarrage de chargement
* - 'muted' : appelle muted() puis play() à chaque début de chargement
* - 'any' : appelle play() à chaque démarrage de chargement. si cela échoue, appelle muted() puis play().
* - * : les valeurs autres que celles listées ici seront fixées à true pour `autoplay`
*
* @return {boolean|string}
* La valeur actuelle de l'autoplay lors de l'obtention de
*/
autoplay(value) {
// utilisation du getter
if (value === undefined) {
return this.options_.autoplay || false ;
}
let techAutoplay ;
// si la valeur est une chaîne de caractères valide, la mettre à cette valeur, ou normaliser `true` en 'play', si nécessaire
if (typeof value === 'string' && (/(any|play|muted)/).test(value) || value === true && this.options_.normalizeAutoplay) {
this.options_.autoplay = valeur ;
this.manualAutoplay_(typeof value === 'string' ? value : 'play') ;
techAutoplay = false ;
// toute valeur falsy définit autoplay à false dans le navigateur,
// faisons de même
} else if (!value) {
this.options_.autoplay = false ;
// toute autre valeur (c'est à dire vraie) met autoplay à vrai
} else {
this.options_.autoplay = true ;
}
techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay ;
// si nous n'avons pas de technicien, nous ne faisons pas la queue
// un appel à setAutoplay sur tech ready. Nous le faisons parce que la
// l'option autoplay sera passée dans le constructeur et nous
// il n'est pas nécessaire de le définir deux fois
if (this.tech_) {
this.techCall_('setAutoplay', techAutoplay) ;
}
}
/**
* Active ou désactive l'attribut playsinline.
* Playsinline indique au navigateur que la lecture en mode non-fullscreen est privilégiée.
*
* @param {boolean} [valeur]
* - true signifie que nous devrions essayer de jouer en ligne par défaut
* - faux signifie que nous devons utiliser le mode de lecture par défaut du navigateur,
* qui, dans la plupart des cas, est en ligne. iOS Safari est une exception notable
* et joue en plein écran par défaut.
*
* @return {string|Player}
* - la valeur actuelle de playsinline
* - le lecteur lors du réglage
*
* @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
*/
playsinline(value) {
if (value !== undefined) {
this.techCall_('setPlaysinline', value) ;
this.options_.playsinline = value ;
retournez ceci ;
}
return this.techGet_('playsinline') ;
}
/**
* Permet d'obtenir ou de définir l'attribut loop de l'élément vidéo.
*
* @param {boolean} [valeur]
* - true signifie que la vidéo doit être mise en boucle
* - false signifie que la vidéo ne doit pas être mise en boucle
*
* @return {boolean}
* La valeur actuelle de la boucle lors de l'obtention
*/
loop(value) {
if (value !== undefined) {
this.techCall_('setLoop', value) ;
this.options_.loop = value ;
retour ;
}
return this.techGet_('loop') ;
}
/**
* Obtenir ou définir l'url de la source de l'image de l'affiche
*
* @fires Player#posterchange
*
* @param {string} [src]
* URL de la source de l'image du poster
*
* @return {string}
* La valeur actuelle de l'affiche lors de l'obtention
*/
poster(src) {
if (src === undefined) {
return this.poster_ ;
}
// La bonne façon de supprimer une affiche est de la définir comme une chaîne vide
// d'autres valeurs erronées provoqueront des erreurs
if (!src) {
src = '' ;
}
if (src === this.poster_) {
retour ;
}
// mise à jour de la variable interne de l'affiche
this.poster_ = src ;
// mettre à jour l'affiche du technicien
this.techCall_('setPoster', src) ;
this.isPosterFromTech_ = false ;
// avertit les composants que l'affiche a été placée
/**
* Cet événement se déclenche lorsque l'image de l'affiche est modifiée sur le lecteur.
*
* @event Player#posterchange
* @type {EventTarget~Event}
*/
this.trigger('posterchange') ;
}
/**
* Certaines technologies (par exemple YouTube) peuvent fournir une source d'affichage dans un format
* de manière asynchrone. Nous voulons que le composant "poster" utilise ceci
* de manière à masquer les contrôles de la technologie.
* (bouton de lecture de YouTube). Cependant, nous ne voulons utiliser que cette
* si l'utilisateur du lecteur n'a pas défini de poster par le biais de
* les API normales.
*
* @fires Player#posterchange
* @listens Tech#posterchange
* @private
*/
handleTechPosterChange_() {
if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
const newPoster = this.tech_.poster() || '' ;
if (newPoster !== this.poster_) {
this.poster_ = newPoster ;
this.isPosterFromTech_ = true ;
// Indique aux composants que l'affiche a changé
this.trigger('posterchange') ;
}
}
}
/**
* Permet d'obtenir ou de définir si les contrôles sont affichés ou non.
*
* @fires Player#controlsenabled
*
* @param {boolean} [bool]
* - true pour activer les contrôles
* - false pour désactiver les contrôles
*
* @return {boolean}
* La valeur actuelle des contrôles lors de l'obtention de
*/
controls(bool) {
if (bool === undefined) {
return !!this.controls_ ;
}
bool = !!bool ;
// Ne déclenche pas d'événement de changement à moins qu'il n'ait réellement changé
if (this.controls_ === bool) {
retour ;
}
this.controls_ = bool ;
if (this.usingNativeControls()) {
this.techCall_('setControls', bool) ;
}
if (this.controls_) {
this.removeClass('vjs-controls-disabled') ;
this.addClass('vjs-controls-enabled') ;
/**
* @event Player#controlsenabled
* @type {EventTarget~Event}
*/
this.trigger('controlsenabled') ;
if (!this.usingNativeControls()) {
this.addTechControlsListeners_() ;
}
} else {
this.removeClass('vjs-controls-enabled') ;
this.addClass('vjs-controls-disabled') ;
/**
* @event Player#controlsdisabled
* @type {EventTarget~Event}
*/
this.trigger('controlsdisabled') ;
if (!this.usingNativeControls()) {
this.removeTechControlsListeners_() ;
}
}
}
/**
* Activer/désactiver les contrôles natifs. Les contrôles natifs sont les contrôles intégrés dans les
* (par exemple, les commandes par défaut de l'iPhone) ou d'autres techs
* (par exemple, contrôles Vimeo)
* **Ceci ne devrait être défini que par le technicien en place, car seul le technicien sait
* s'il peut prendre en charge les contrôles natifs**
*
* @fires Player#usingnativecontrols
* @fires Player#utiliser des commandes personnalisées
*
* @param {boolean} [bool]
* - true pour activer les contrôles natifs
* - false pour désactiver les contrôles natifs
*
* @return {boolean}
* La valeur actuelle des contrôles natifs lors de l'obtention de
*/
usingNativeControls(bool) {
if (bool === undefined) {
return !!this.usingNativeControls_ ;
}
bool = !!bool ;
// Ne déclenche pas d'événement de changement à moins qu'il n'ait réellement changé
if (this.usingNativeControls_ === bool) {
retour ;
}
this.usingNativeControls_ = bool ;
if (this.usingNativeControls_) {
this.addClass('vjs-using-native-controls') ;
/**
* le lecteur utilise les commandes natives de l'appareil
*
* @event Player#usingnativecontrols
* @type {EventTarget~Event}
*/
this.trigger('usingnativecontrols') ;
} else {
this.removeClass('vjs-using-native-controls') ;
/**
* le lecteur utilise les contrôles HTML personnalisés
*
* @event Player#utiliser des commandes personnalisées
* @type {EventTarget~Event}
*/
this.trigger('usingcustomcontrols') ;
}
}
/**
* Définir ou obtenir l'erreur MediaError actuelle
*
* @fires Player#error
*
* @param {MediaError|string|number} [err]
* Une MediaError ou une chaîne/un nombre à transformer
* en une MediaError
*
* @return {MediaError|null}
* L'erreur MediaError actuelle lors de l'obtention (ou null)
*/
error(err) {
if (err === undefined) {
return this.error_ || null ;
}
// permet aux crochets de modifier l'objet d'erreur
hooks('beforeerror').forEach((hookFunction) => {
const newErr = hookFunction(this, err) ;
si ( !(
(isObject(newErr) && !Array.isArray(newErr)) ||
typeof newErr === 'string' ||
typeof newErr === 'number' ||
newErr === null
)) {
this.log.error('please return a value that MediaError expects in beforeerror hooks') ;
retour ;
}
err = newErr ;
}) ;
// Supprimer le premier message d'erreur en l'absence de source compatible jusqu'à ce que
// interaction avec l'utilisateur
si (this.options_.suppressNotSupportedError &&
err && err.code === 4
) {
const triggerSuppressedError = function() {
this.error(err) ;
};
this.options_.suppressNotSupportedError = false ;
this.any(['click', 'touchstart'], triggerSuppressedError) ;
this.one('loadstart', function() {
this.off(['click', 'touchstart'], triggerSuppressedError) ;
}) ;
retour ;
}
// rétablissement de la valeur par défaut
if (err === null) {
this.error_ = err ;
this.removeClass('vjs-error') ;
if (this.errorDisplay) {
this.errorDisplay.close() ;
}
retour ;
}
this.error_ = new MediaError(err) ;
// ajouter le nom de classe vjs-error au lecteur
this.addClass('vjs-error') ;
// enregistre le nom du type d'erreur et le message éventuel
// IE11 enregistre "[objet objet]" et vous oblige à développer le message pour voir l'objet de l'erreur
log.error(`(CODE:${this.error_.code} ${MediaError.errorTypes[this.error_.code]})`, this.error_.message, this.error_) ;
/**
* @event Player#error
* @type {EventTarget~Event}
*/
this.trigger('error') ;
// notifie les hooks de l'erreur par joueur
hooks('error').forEach((hookFunction) => hookFunction(this, this.error_)) ;
retour ;
}
/**
* Signaler l'activité d'un utilisateur
*
* @param {Objet} événement
* Objet de l'événement
*/
reportUserActivity(event) {
this.userActivity_ = true ;
}
/**
* Obtenir ou définir si l'utilisateur est actif
*
* @fires Player#useractive
* @fires Player#userinactive
*
* @param {boolean} [bool]
* - true si l'utilisateur est actif
* - false si l'utilisateur est inactif
*
* @return {boolean}
* La valeur actuelle de userActive lors de l'obtention de
*/
userActive(bool) {
if (bool === undefined) {
return this.userActive_ ;
}
bool = !!bool ;
if (bool === this.userActive_) {
retour ;
}
this.userActive_ = bool ;
if (this.userActive_) {
this.userActivity_ = true ;
this.removeClass('vjs-user-inactive') ;
this.addClass('vjs-user-active') ;
/**
* @event Player#useractive
* @type {EventTarget~Event}
*/
this.trigger('useractive') ;
retour ;
}
// Chrome/Safari/IE ont des bugs où lorsque vous changez le curseur il peut
// déclenche un événement de déplacement de la souris. Cela pose un problème lorsque vous cachez
// le curseur lorsque l'utilisateur est inactif, et un déplacement de souris signale que l'utilisateur est inactif
// activité. Ce qui rend impossible le passage en mode inactif. En particulier
// cela se produit en plein écran lorsque nous avons vraiment besoin de cacher le curseur.
//
// Lorsque ce problème sera résolu dans TOUS les navigateurs, il pourra être supprimé
// https://code.google.com/p/chromium/issues/detail?id=103041
if (this.tech_) {
this.tech_.one('mousemove', function(e) {
e.stopPropagation() ;
e.preventDefault() ;
}) ;
}
this.userActivity_ = false ;
this.removeClass('vjs-user-active') ;
this.addClass('vjs-user-inactive') ;
/**
* @event Player#userinactive
* @type {EventTarget~Event}
*/
this.trigger('userinactive') ;
}
/**
* Écoute de l'activité de l'utilisateur en fonction de la valeur du délai d'attente
*
* @private
*/
listenForUserActivity_() {
let mouseInProgress ;
let lastMoveX ;
let lastMoveY ;
const handleActivity = Fn.bind(this, this.reportUserActivity) ;
const handleMouseMove = function(e) {
// #1068 - Empêcher le spam de mousemove
// Bug Chrome : https://code.google.com/p/chromium/issues/detail?id=366970
if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
lastMoveX = e.screenX ;
lastMoveY = e.screenY ;
handleActivity() ;
}
};
const handleMouseDown = function() {
handleActivity() ;
// Tant qu'ils touchent l'appareil ou qu'ils ont la souris enfoncée,
// nous les considérons comme actifs même s'ils ne bougent pas le doigt ou la souris.
// Nous voulons donc continuer à mettre à jour qu'ils sont actifs
this.clearInterval(mouseInProgress) ;
// Fixer userActivity=true maintenant et fixer l'intervalle au même moment
// comme l'intervalle de contrôle de l'activité (250) devrait garantir que nous ne manquons jamais le contrôle de l'activité
// Contrôle de l'activité suivante
mouseInProgress = this.setInterval(handleActivity, 250) ;
};
const handleMouseUpAndMouseLeave = function(event) {
handleActivity() ;
// Arrêter l'intervalle qui maintient l'activité si la souris/touche est abaissée
this.clearInterval(mouseInProgress) ;
};
// Tout mouvement de la souris sera considéré comme une activité de l'utilisateur
this.on('mousedown', handleMouseDown) ;
this.on('mousemove', handleMouseMove) ;
this.on('mouseup', handleMouseUpAndMouseLeave) ;
this.on('mouseleave', handleMouseUpAndMouseLeave) ;
const controlBar = this.getChild('controlBar') ;
// Corrige un bug sur Android & iOS où lorsque l'on tape sur la barre de progression (lorsque la barre de contrôle est affichée)
// la barre de contrôle ne serait plus cachée par défaut timeout.
if (controlBar && !browser.IS_IOS && !browser.IS_ANDROID) {
controlBar.on('mouseenter', function(event) {
if (this.player().options_.inactivityTimeout !== 0) {
this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout ;
}
this.player().options_.inactivityTimeout = 0 ;
}) ;
controlBar.on('mouseleave', function(event) {
this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout ;
}) ;
}
// Écoute de la navigation au clavier
// Il ne devrait pas être nécessaire d'utiliser l'intervalle inProgress en raison de la répétition des touches
this.on('keydown', handleActivity) ;
this.on('keyup', handleActivity) ;
// Exécute un intervalle toutes les 250 millisecondes au lieu de tout mettre dans
// la fonction mousemove/touchmove elle-même, afin d'éviter toute dégradation des performances.
// `this.reportUserActivity` met simplement this.userActivity_ à true, qui
// est ensuite repris par cette boucle
// http://ejohn.org/blog/learning-from-twitter/
let inactivityTimeout ;
this.setInterval(function() {
// Vérifie s'il y a eu une activité de la souris ou du toucher
if (!this.userActivity_) {
retour ;
}
// Réinitialiser le tracker d'activité
this.userActivity_ = false ;
// Si l'état de l'utilisateur était inactif, le mettre à l'état actif
this.userActive(true) ;
// Effacer tout délai d'inactivité existant pour redémarrer le minuteur
this.clearTimeout(inactivityTimeout) ;
const timeout = this.options_.inactivityTimeout ;
if (timeout <= 0) {
retour ;
}
// Dans le délai d'attente <> millisecondes, si aucune autre activité ne s'est produite, le
// l'utilisateur sera considéré comme inactif
inactivityTimeout = this.setTimeout(function() {
// Se prémunir contre le cas où l'inactivityTimeout peut se déclencher juste après la fin de la période d'inactivité
// avant que l'activité suivante de l'utilisateur ne soit prise en compte par la boucle de vérification de l'activité
// provoquant un scintillement
if (!this.userActivity_) {
this.userActive(false) ;
}
}, timeout) ;
}, 250) ;
}
/**
* Permet d'obtenir ou de définir le taux de lecture actuel. Une vitesse de lecture de
* 1,0 représente la vitesse normale et 0,5 la demi-vitesse
* par exemple.
*
* voir https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
*
* @param {number} [rate]
* Nouveau taux de lecture à définir.
*
* @return {number}
* Le taux de lecture actuel lors de l'obtention ou de la 1.0
*/
playbackRate(rate) {
if (rate !== undefined) {
// NOTE : this.cache_.lastPlaybackRate est défini par le tech handler
// qui est enregistré ci-dessus
this.techCall_('setPlaybackRate', rate) ;
retour ;
}
if (this.tech_ && this.tech_.featuresPlaybackRate) {
return this.cache_.lastPlaybackRate || this.techGet_('playbackRate') ;
}
retour 1.0 ;
}
/**
* Obtient ou définit le taux de lecture par défaut actuel. La vitesse de lecture par défaut est de
* 1,0 représente la vitesse normale et 0,5 indique une lecture à demi-vitesse, par exemple.
* defaultPlaybackRate ne représentera que le taux de lecture initial d'une vidéo, et non le taux de lecture par défaut
* et non le taux de lecture actuel.
*
* voir https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
*
* @param {number} [rate]
* Nouveau taux de lecture par défaut à définir.
*
* @return {number|Player}
* - Taux de lecture par défaut lorsque l'on obtient ou 1.0
* - le lecteur lors du réglage
*/
defaultPlaybackRate(rate) {
if (rate !== undefined) {
return this.techCall_('setDefaultPlaybackRate', rate) ;
}
if (this.tech_ && this.tech_.featuresPlaybackRate) {
return this.techGet_('defaultPlaybackRate') ;
}
retour 1.0 ;
}
/**
* Obtient ou définit le drapeau audio
*
* @param {boolean} bool
* - true signale qu'il s'agit d'un lecteur audio
* - faux signaux indiquant qu'il ne s'agit pas d'un lecteur audio
*
* @return {boolean}
* La valeur actuelle de isAudio lors de l'obtention de
*/
isAudio(bool) {
if (bool !== undefined) {
this.isAudio_ = !!bool ;
retour ;
}
return !!this.isAudio_ ;
}
enableAudioOnlyUI_() {
// Mise à jour immédiate du style pour afficher la barre de contrôle afin d'obtenir sa hauteur
this.addClass('vjs-audio-only-mode') ;
const playerChildren = this.children() ;
const controlBar = this.getChild('ControlBar') ;
const controlBarHeight = controlBar && controlBar.currentHeight() ;
// Masquer tous les composants du lecteur à l'exception de la barre de contrôle. Composants de la barre de contrôle
// nécessaires uniquement pour la vidéo sont cachés avec CSS
playerChildren.forEach(child => {
if (child === controlBar) {
retour ;
}
if (child.el_ && !child.hasClass('vjs-hidden')) {
child.hide() ;
this.audioOnlyCache_.hiddenChildren.push(child) ;
}
}) ;
this.audioOnlyCache_.playerHeight = this.currentHeight() ;
// Fixer la hauteur du lecteur à la même hauteur que la barre de contrôle
this.height(controlBarHeight) ;
this.trigger('audioonlymodechange') ;
}
disableAudioOnlyUI_() {
this.removeClass('vjs-audio-only-mode') ;
// Afficher les composants du lecteur qui étaient auparavant cachés
this.audioOnlyCache_.hiddenChildren.forEach(child => child.show()) ;
// Réinitialisation de la hauteur du joueur
this.height(this.audioOnlyCache_.playerHeight) ;
this.trigger('audioonlymodechange') ;
}
/**
* Obtenir l'état actuel de l'audioOnlyMode ou définir l'audioOnlyMode sur true ou false.
*
* La valeur `true` permet de cacher tous les composants du lecteur à l'exception de la barre de contrôle,
* ainsi que les composants de la barre de contrôle nécessaires uniquement pour la vidéo.
*
* @param {boolean} [valeur]
* Valeur à attribuer à audioOnlyMode.
*
* @return {Promesse|boolean}
* Une promesse est renvoyée lors de la définition de l'état, et un booléen lors de la récupération
* l'état actuel
*/
audioOnlyMode(value) {
if (typeof value !== 'boolean' || value === this.audioOnlyMode_) {
return this.audioOnlyMode_ ;
}
this.audioOnlyMode_ = valeur ;
const PromiseClass = this.options_.Promise || window.Promise ;
if (PromiseClass) {
// Activer le mode audio uniquement
if (value) {
const exitPromises = [] ;
// Le plein écran et le PiP ne sont pas pris en charge en mode audioOnly, il faut donc sortir si nécessaire.
if (this.isInPictureInPicture()) {
exitPromises.push(this.exitPictureInPicture()) ;
}
if (this.isFullscreen()) {
exitPromises.push(this.exitFullscreen()) ;
}
if (this.audioPosterMode()) {
exitPromises.push(this.audioPosterMode(false)) ;
}
return PromiseClass.all(exitPromises).then(() => this.enableAudioOnlyUI_()) ;
}
// Désactiver le mode audio uniquement
return PromiseClass.resolve().then(() => this.disableAudioOnlyUI_()) ;
}
if (value) {
if (this.isInPictureInPicture()) {
this.exitPictureInPicture() ;
}
if (this.isFullscreen()) {
this.exitFullscreen() ;
}
this.enableAudioOnlyUI_() ;
} else {
this.disableAudioOnlyUI_() ;
}
}
enablePosterModeUI_() {
// Masquer l'élément vidéo et afficher l'image du poster pour activer le posterModeUI
const tech = this.tech_ && this.tech_ ;
tech.hide() ;
this.addClass('vjs-audio-poster-mode') ;
this.trigger('audiopostermodechange') ;
}
disablePosterModeUI_() {
// Afficher l'élément vidéo et cacher l'image du poster pour désactiver le posterModeUI
const tech = this.tech_ && this.tech_ ;
tech.show() ;
this.removeClass('vjs-audio-poster-mode') ;
this.trigger('audiopostermodechange') ;
}
/**
* Obtenir l'état actuel de audioPosterMode ou définir audioPosterMode à true ou false
*
* @param {boolean} [valeur]
* La valeur à attribuer à audioPosterMode.
*
* @return {Promesse|boolean}
* Une promesse est renvoyée lors de la définition de l'état, et un booléen lors de la récupération
* l'état actuel
*/
audioPosterMode(value) {
if (typeof value !== 'boolean' || value === this.audioPosterMode_) {
return this.audioPosterMode_ ;
}
this.audioPosterMode_ = valeur ;
const PromiseClass = this.options_.Promise || window.Promise ;
if (PromiseClass) {
if (value) {
if (this.audioOnlyMode()) {
const audioOnlyModePromise = this.audioOnlyMode(false) ;
return audioOnlyModePromise.then(() => {
// activer le mode poster audio après avoir désactivé le mode audio seul
this.enablePosterModeUI_() ;
}) ;
}
return PromiseClass.resolve().then(() => {
// activer le mode poster audio
this.enablePosterModeUI_() ;
}) ;
}
return PromiseClass.resolve().then(() => {
// désactive le mode poster audio
this.disablePosterModeUI_() ;
}) ;
}
if (value) {
if (this.audioOnlyMode()) {
this.audioOnlyMode(false) ;
}
this.enablePosterModeUI_() ;
retour ;
}
this.disablePosterModeUI_() ;
}
/**
* Une méthode d'aide pour ajouter un {@link TextTrack} à notre
* {@link TextTrackList}.
*
* En plus des paramètres du W3C, nous permettons d'ajouter des informations supplémentaires par le biais d'options.
*
* voir http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
*
* @param {string} [kind]
* le type de TextTrack que vous ajoutez
*
* @param {string} [label]
* l'étiquette à donner à l'étiquette du TextTrack
*
* @param {string} [langue]
* la langue à définir sur le TextTrack
*
* @return {TextTrack|undefined}
* la piste de texte qui a été ajoutée ou non définie
* s'il n'y a pas de technologie
*/
addTextTrack(kind, label, language) {
if (this.tech_) {
return this.tech_.addTextTrack(kind, label, language) ;
}
}
/**
* Créez un {@link TextTrack} et un {@link HTMLTrackElement} distants.
* Lorsque la valeur de manualCleanup est fixée à false, la piste est automatiquement supprimée
* sur les changements de source.
*
* @param {Objet} options
* Options à transmettre à {@link HTMLTrackElement} lors de la création. Voir
* {@link HTMLTrackElement} pour les propriétés de l'objet que vous devez utiliser.
*
* @param {boolean} [manualCleanup=true] si la valeur est false, le TextTrack sera nettoyé à la main
* supprimé lors d'un changement de source
*
* @return {HtmlTrackElement}
* l'élément HTMLTrackElement qui a été créé et ajouté
* à la liste HtmlTrackElementList et à l'élément distant
* TextTrackList
*
* @deprecated La valeur par défaut du paramètre "manualCleanup" sera par défaut
* à "false" dans les prochaines versions de Video.js
*/
addRemoteTextTrack(options, manualCleanup) {
if (this.tech_) {
return this.tech_.addRemoteTextTrack(options, manualCleanup) ;
}
}
/**
* Retirer un {@link TextTrack} distant de l'élément correspondant
* {@link TextTrackList} et {@link HtmlTrackElementList}.
*
* @param {Object} track
* {@link TextTrack} distant à supprimer
*
* @return {undefined}
* ne renvoie rien
*/
removeRemoteTextTrack(obj = {}) {
let {track} = obj ;
if (!track) {
piste = obj ;
}
// déstructure l'entrée en un objet avec un argument de piste, par défaut arguments[0]
// l'argument entier est remplacé par défaut par un objet vide si rien n'a été transmis
if (this.tech_) {
return this.tech_.removeRemoteTextTrack(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 {Objet|non défini}
* Un objet avec les mesures de qualité de lecture des médias prises en charge ou non défini s'il n'y a pas de mesures de qualité de lecture des médias prises en charge
* il n'y a pas de technologie ou la technologie n'est pas compatible.
*/
getVideoPlaybackQuality() {
return this.techGet_('getVideoPlaybackQuality') ;
}
/**
* Obtenir la largeur de la vidéo
*
* @return {number}
* largeur actuelle de la vidéo
*/
videoWidth() {
return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0 ;
}
/**
* Obtenir la hauteur de la vidéo
*
* @return {number}
* hauteur actuelle de la vidéo
*/
videoHeight() {
return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0 ;
}
/**
* Code de la langue du joueur.
*
* Le changement de langue déclenchera
* [languagechange]{@link Player#event:languagechange}
* que les composants peuvent utiliser pour mettre à jour le texte du contrôle.
* ClickableComponent mettra à jour le texte de son contrôle par défaut sur
* [changement de langue]{@link Player#event:changement de langue}.
*
* @fires Player#languagechange
*
* @param {string} [code]
* le code langue pour régler le lecteur sur
*
* @return {string}
* Le code de la langue actuelle lors de l'obtention
*/
language(code) {
if (code === undefined) {
return this.language_ ;
}
if (this.language_ !== String(code).toLowerCase()) {
this.language_ = String(code).toLowerCase() ;
// lors du premier init, il est possible que certaines choses ne soient pas événementielles
if (isEvented(this)) {
/**
* se déclenche lorsque la langue du joueur change
*
* @event Player#languagechange
* @type {EventTarget~Event}
*/
this.trigger('languagechange') ;
}
}
}
/**
* Obtenir le dictionnaire de langue du joueur
* Fusionner à chaque fois, car un plugin nouvellement ajouté peut appeler videojs.addLanguage() à tout moment
* Les langues spécifiées directement dans les options du lecteur sont prioritaires
*
* @return {Array}
* Un tableau des langues prises en charge
*/
languages() {
return mergeOptions(Player.prototype.options_.languages, this.languages_) ;
}
/**
* renvoie un objet JavaScript représentant la piste actuelle
* informations. **ne les renvoie pas sous forme de JSON**
*
* @return {Object}
* Objet représentant le courant de l'information sur la piste
*/
toJSON() {
const options = mergeOptions(this.options_) ;
const tracks = options.tracks ;
options.tracks = [] ;
for (let i = 0 ; i < tracks.length ; i++) {
let track = tracks[i] ;
// fusionner les pistes et annuler le lecteur afin d'éviter les références circulaires
track = mergeOptions(track) ;
track.player = undefined ;
options.tracks[i] = track ;
}
les options de retour ;
}
/**
* Crée un dialogue modal simple (une instance de {@link ModalDialog}
* ) qui recouvre immédiatement le joueur d'une image arbitraire de
* et se retire lorsqu'il est fermé.
*
* @param {string|Function|Element|Array|null} content
* Identique au paramètre du même nom de {@link ModalDialog#content}.
* L'utilisation la plus simple consiste à fournir une chaîne ou un DOM
* élément.
*
* @param {Objet} [options]
* Options supplémentaires qui seront transmises au {@link ModalDialog}.
*
* @return {ModalDialog}
* le {@link ModalDialog} qui a été créé
*/
createModal(content, options) {
options = options || {} ;
options.content = content || '' ;
const modal = new ModalDialog(this, options) ;
this.addChild(modal) ;
modal.on('dispose', () => {
this.removeChild(modal) ;
}) ;
modal.open() ;
retour modal ;
}
/**
* Modifier les classes de points d'arrêt lorsque le lecteur se redimensionne.
*
* @private
*/
updateCurrentBreakpoint_() {
if (!this.responsive()) {
retour ;
}
const currentBreakpoint = this.currentBreakpoint() ;
const currentWidth = this.currentWidth() ;
for (let i = 0 ; i < BREAKPOINT_ORDER.length ; i++) {
const candidateBreakpoint = BREAKPOINT_ORDER[i] ;
const maxWidth = this.breakpoints_[candidateBreakpoint] ;
if (currentWidth <= maxWidth) {
// Le point d'arrêt actuel n'a pas changé, il n'y a rien à faire.
if (currentBreakpoint === candidateBreakpoint) {
retour ;
}
// Ne supprimer une classe que s'il y a un point d'arrêt en cours.
if (currentBreakpoint) {
this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]) ;
}
this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]) ;
this.breakpoint_ = candidateBreakpoint ;
pause ;
}
}
}
/**
* Supprime le point d'arrêt actuel.
*
* @private
*/
removeCurrentBreakpoint_() {
const className = this.currentBreakpointClass() ;
this.breakpoint_ = '' ;
if (className) {
this.removeClass(className) ;
}
}
/**
* Obtenir ou définir des points d'arrêt sur le lecteur.
*
* L'appel de cette méthode avec un objet ou `true` supprimera tout objet précédent
* les points d'arrêt personnalisés et recommencer à partir des valeurs par défaut.
*
* @param {Objet|booléen} [points d'arrêt]
* Si un objet est donné, il peut être utilisé pour fournir des informations personnalisées
* points d'arrêt. Si `true` est donné, des points d'arrêt par défaut seront mis en place.
* Si cet argument n'est pas fourni, le système renvoie simplement la valeur actuelle du
* points d'arrêt.
*
* @param {number} [breakpoints.tiny]
* La largeur maximale de la classe "vjs-layout-tiny".
*
* @param {number} [breakpoints.xsmall]
* Largeur maximale de la classe "vjs-layout-x-small".
*
* @param {number} [breakpoints.small]
* Largeur maximale de la classe "vjs-layout-small".
*
* @param {number} [breakpoints.medium]
* La largeur maximale de la classe "vjs-layout-medium".
*
* @param {number} [breakpoints.large]
* Largeur maximale de la classe "vjs-layout-large".
*
* @param {number} [breakpoints.xlarge]
* Largeur maximale de la classe "vjs-layout-x-large".
*
* @param {number} [breakpoints.huge]
* La largeur maximale de la classe "vjs-layout-huge".
*
* @return {Object}
* Objet associant les noms des points d'arrêt à des valeurs de largeur maximale.
*/
breakpoints(breakpoints) {
// Utilisé comme getter.
if (breakpoints === undefined) {
return assign(this.breakpoints_) ;
}
this.breakpoint_ = '' ;
this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, breakpoints) ;
// Lorsque les définitions des points d'arrêt changent, nous devons mettre à jour la liste des points d'arrêt en cours
// point d'arrêt sélectionné.
this.updateCurrentBreakpoint_() ;
// Clone les points d'arrêt avant de revenir.
return assign(this.breakpoints_) ;
}
/**
* Obtenir ou définir un drapeau indiquant si ce joueur doit ou non être ajusté
* son interface utilisateur en fonction de ses dimensions.
*
* @param {boolean} value
* Doit être `true` si le joueur doit ajuster son UI en fonction de son
* sinon, elle doit être `false`.
*
* @return {boolean}
* Sera `vrai` si ce joueur doit ajuster son interface utilisateur en fonction de sa position dans la liste des joueurs
* sinon, ce sera `false`.
*/
responsive(value) {
// Utilisé comme getter.
if (value === undefined) {
return this.responsive_ ;
}
valeur = Booléen(valeur) ;
const current = this.responsive_ ;
// Rien n'a changé.
if (value === current) {
retour ;
}
// La valeur a changé, il faut la définir.
this.responsive_ = value ;
// Commencer à écouter les points d'arrêt et mettre en place le point d'arrêt initial si l'option
// le lecteur est maintenant réactif.
if (value) {
this.on('playerresize', this.boundUpdateCurrentBreakpoint_) ;
this.updateCurrentBreakpoint_() ;
// Arrêtez d'écouter les points d'arrêt si le lecteur n'est plus réactif.
} else {
this.off('playerresize', this.boundUpdateCurrentBreakpoint_) ;
this.removeCurrentBreakpoint_() ;
}
valeur de retour ;
}
/**
* Obtenir le nom du point d'arrêt actuel, s'il y en a un.
*
* @return {string}
* Si un point d'arrêt est actuellement défini, renvoie une clé de l'élément
* l'objet des points d'arrêt correspondant. Sinon, renvoie une chaîne vide.
*/
currentBreakpoint() {
return this.breakpoint_ ;
}
/**
* Obtenir le nom de la classe du point d'arrêt actuel.
*
* @return {string}
* Le nom de la classe correspondante (par exemple `"vjs-layout-tiny"` ou
* `"vjs-layout-large"`) pour le point d'arrêt actuel. Chaîne vide si
* il n'y a pas de point d'arrêt actuel.
*/
currentBreakpointClass() {
return BREAKPOINT_CLASSES[this.breakpoint_] || '' ;
}
/**
* Objet décrivant un seul élément de média.
*
* Les propriétés qui ne font pas partie de cette description de type seront conservées,
* il peut également être considéré comme un mécanisme générique de stockage des métadonnées.
*
* @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
* @typedef {Objet} Player~MediaObject
*
* @property {string} [album]
* Inutilisé, sauf si cet objet est passé à la `MediaSession`
* API.
*
* @property {string} [artiste]
* Inutilisé, sauf si cet objet est passé à la `MediaSession`
* API.
*
* @property {Objet[]} [artwork]
* Inutilisé, sauf si cet objet est passé à la `MediaSession`
* API. S'il n'est pas spécifié, il sera alimenté par le "poster", si
* disponible.
*
* @property {string} [poster]
* URL d'une image qui s'affichera avant la lecture.
*
* @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
* Un seul objet source, un tableau d'objets sources ou une chaîne de caractères
* le référencement d'une URL vers une source de média. Il est _hautement recommandé_
* qu'un objet ou un tableau d'objets est utilisé ici, de sorte que la source
* les algorithmes de sélection peuvent prendre en compte le `type`.
*
* @property {string} [title]
* Inutilisé, sauf si cet objet est passé à la `MediaSession`
* API.
*
* @property {Object[]} [textTracks]
* Un tableau d'objets à utiliser pour créer des pistes de texte, comme suit
* le {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|format de l'élément de piste natif}.
* Pour faciliter le retrait, ces textes seront créés en tant que textes "distants"
* et configuré pour nettoyer automatiquement les changements de source.
*
* Ces objets peuvent avoir des propriétés telles que `src`, `kind`, `label`,
* et `language`, voir {@link Tech#createRemoteTextTrack}.
*/
/**
* Remplir le lecteur à l'aide d'un {@link Player~MediaObject|MediaObject}.
*
* @param {Player~MediaObject} media
* Un objet média.
*
* @param {Fonction} ready
* Un rappel à appeler lorsque le lecteur est prêt.
*/
loadMedia(media, ready) {
if (!media || typeof media !== 'object') {
retour ;
}
this.reset() ;
// Clone l'objet média afin qu'il ne puisse pas être modifié de l'extérieur.
this.cache_.media = mergeOptions(media) ;
const {artwork, poster, src, textTracks} = this.cache_.media ;
// Si `artwork` n'est pas donné, créez-le en utilisant `poster`.
if (!artwork && poster) {
this.cache_.media.artwork = [{
src : poster,
type : getMimetype(poster)
}] ;
}
if (src) {
this.src(src) ;
}
if (poster) {
this.poster(poster) ;
}
if (Array.isArray(textTracks)) {
textTracks.forEach(tt => this.addRemoteTextTrack(tt, false)) ;
}
this.ready(ready) ;
}
/**
* Obtenir un clone du {@link Player~MediaObject} actuel pour ce lecteur.
*
* Si la méthode `loadMedia` n'a pas été utilisée, elle tentera de renvoyer un fichier
* {@link Player~MediaObject} en fonction de l'état actuel du lecteur.
*
* @return {Player~MediaObject}
*/
getMedia() {
if (!this.cache_.media) {
const poster = this.poster() ;
const src = this.currentSources() ;
const textTracks = Array.prototype.map.call(this.remoteTextTracks(), (tt) => ({
type : tt.kind,
label : tt.label,
langue : tt.langue,
src : tt.src
})) ;
const media = {src, textTracks} ;
if (poster) {
media.poster = poster ;
media.artwork = [{
src : media.poster,
type : getMimetype(media.poster)
}] ;
}
les médias de retour ;
}
return mergeOptions(this.cache_.media) ;
}
/**
* Obtient les paramètres de l'étiquette
*
* @param {Element} tag
* L'étiquette du joueur
*
* @return {Object}
* Un objet contenant tous les paramètres
* pour une étiquette de joueur
*/
static getTagSettings(tag) {
const baseOptions = {
sources : [],
pistes : []
};
const tagOptions = Dom.getAttributes(tag) ;
const dataSetup = tagOptions['data-setup'] ;
if (Dom.hasClass(tag, 'vjs-fill')) {
tagOptions.fill = true ;
}
if (Dom.hasClass(tag, 'vjs-fluid')) {
tagOptions.fluid = true ;
}
// Vérifier si data-setup attr existe.
if (dataSetup !== null) {
// Analyse des options JSON
// S'il s'agit d'une chaîne vide, il s'agit d'un objet json analysable.
const [err, data] = safeParseTuple(dataSetup || '{}') ;
if (err) {
log.error(err) ;
}
assign(tagOptions, data) ;
}
assign(baseOptions, tagOptions) ;
// Obtenir les paramètres des enfants de l'étiquette
if (tag.hasChildNodes()) {
const children = tag.childNodes ;
for (let i = 0, j = children.length ; i < j ; i++) {
const child = children[i] ;
// Cas de changement nécessaire : http://ejohn.org/blog/nodename-case-sensitivity/
const childName = child.nodeName.toLowerCase() ;
if (childName === 'source') {
baseOptions.sources.push(Dom.getAttributes(child)) ;
else if (childName === 'track') {
baseOptions.tracks.push(Dom.getAttributes(child)) ;
}
}
}
return baseOptions ;
}
/**
* Déterminer si la boîte à outils est prise en charge ou non
*
* @return {boolean}
* - true si la boîte à outils est prise en charge
* - false si la boîte à outils n'est pas prise en charge
*/
flexNotSupported_() {
const elem = document.createElement('i') ;
// Note : Nous n'utilisons pas réellement flexBasis (ou flexOrder), mais c'est l'un des éléments les plus importants de notre système
// caractéristiques communes de flex sur lesquelles nous pouvons nous appuyer pour vérifier la prise en charge de flex.
return !('flexBasis' in elem.style ||
'webkitFlexBasis' dans elem.style ||
'mozFlexBasis' dans elem.style ||
'msFlexBasis' dans elem.style ||
// Spécifique à IE10 (2012 flex spec), disponible pour l'exhaustivité
'msFlexOrder' dans elem.style) ;
}
/**
* Définir le mode de débogage pour activer/désactiver les journaux au niveau des informations.
*
* @param {boolean} enabled
* @fires Player#debugon
* @fires Player#debugoff
*/
debug(enabled) {
if (enabled === undefined) {
return this.debugEnabled_ ;
}
if (enabled) {
this.trigger('debugon') ;
this.previousLogLevel_ = this.log.level ;
this.log.level('debug') ;
this.debugEnabled_ = true ;
} else {
this.trigger('debugoff') ;
this.log.level(this.previousLogLevel_) ;
this.previousLogLevel_ = undefined ;
this.debugEnabled_ = false ;
}
}
/**
* Définir ou obtenir les taux de lecture actuels.
* Prend un tableau et met à jour le menu des taux de lecture avec les nouveaux éléments.
* Passez un tableau vide pour cacher le menu.
* Les valeurs autres que les tableaux sont ignorées.
*
* @fires Player#playbackrateschange
* @param {number[]} newRates
* Les nouveaux taux que le menu des taux de lecture doit mettre à jour.
* Un tableau vide masquera le menu
* @return {number[]} Utilisé en tant que getter, il renvoie les taux de lecture actuels
*/
playbackRates(newRates) {
if (newRates === undefined) {
return this.cache_.playbackRates ;
}
// ignore toute valeur qui n'est pas un tableau
if (!Array.isArray(newRates)) {
retour ;
}
// ignorer les tableaux qui ne contiennent pas que des nombres
if (!newRates.every((rate) => typeof rate === 'number')) {
retour ;
}
this.cache_.playbackRates = newRates ;
/**
* se déclenche lorsque les taux de lecture d'un lecteur sont modifiés
*
* @event Player#playbackrateschange
* @type {EventTarget~Event}
*/
this.trigger('playbackrateschange') ;
}
}
/**
* Obtenir la {@link VideoTrackList}
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
*
* @return {VideoTrackList}
* la liste des pistes de la vidéo en cours
*
* @method Player.prototype.videoTracks
*/
/**
* Obtenir la {@link AudioTrackList}
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
*
* @return {AudioTrackList}
* la liste des pistes audio en cours
*
* @method Player.prototype.audioTracks
*/
/**
* Obtenir la {@link TextTrackList}
*
* @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
*
* @return {TextTrackList}
* la liste des pistes de texte en cours
*
* @method Player.prototype.textTracks
*/
/**
* Obtenir la {@link TextTrackList} distante
*
* @return {TextTrackList}
* Liste des pistes du texte à distance en cours
*
* @method Player.prototype.remoteTextTracks
*/
/**
* Obtenir les {@link HtmlTrackElementList} de pistes distantes.
*
* @return {HtmlTrackElementList}
* Liste des éléments de la piste de texte à distance
*
* @method Player.prototype.remoteTextTrackEls
*/
TRACK_TYPES.names.forEach(function(name) {
const props = TRACK_TYPES[name] ;
Player.prototype[props.getterName] = function() {
if (this.tech_) {
return this.tech_[props.getterName]() ;
}
// si nous n'avons pas encore chargéTech_, nous créons des {vidéo, audio, texte}Pistes_
// ces éléments seront transmis à la technologie lors du chargement
this[props.privateName] = this[props.privateName] || new props.ListClass() ;
return this[props.privateName] ;
};
}) ;
/**
* Obtient ou définit l'option crossorigin du `Player`. Pour le lecteur HTML5, il s'agit de
* définit la propriété `crossOrigin` sur la balise `<video>` pour contrôler le CORS
* comportement.
*
* @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
*
* @param {string} [valeur]
* La valeur à donner au crossorigin du `Player`. Si un argument est
* doit être `anonymous` ou `use-credentials`.
*
* @return {string|undefined}
* - La valeur crossoriginale actuelle du `Player` lors de l'obtention.
* - indéfinie lors de la mise en place
*/
Player.prototype.crossorigin = Player.prototype.crossOrigin ;
/**
* Énumération globale des acteurs.
*
* Les clés sont les ID des joueurs et les valeurs sont soit les {@link Player}
* ou `null` pour les joueurs éliminés.
*
* @type {Objet}
*/
Player.players = {} ;
const navigator = window.navigator ;
/*
* Options de l'instance du joueur, surfacées à l'aide d'options
* options = Player.prototype.options_
* Effectuez les changements dans les options, pas ici.
*
* @type {Objet}
* @private
*/
Player.prototype.options_ = {
// Ordre par défaut de la technologie de repli
techOrder : Tech.defaultTechOrder_,
html5 : {},
// délai d'inactivité par défaut
inactivityTimeout : 2000,
// taux de lecture par défaut
playbackRates : [],
// Ajouter la sélection du taux de lecture en ajoutant des taux
// 'playbackRates' : [0.5, 1, 1.5, 2],
liveui : faux,
// Ensembles de contrôle inclus
enfants : [
'mediaLoader',
image de l'affiche",
'textTrackDisplay',
'loadingSpinner',
'bigPlayButton',
'liveTracker',
'controlBar',
'errorDisplay',
'textTrackSettings',
'resizeManager' (gestionnaire de redimensionnement)
],
langue : navigateur && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
// les locales et leurs traductions linguistiques
langues : {},
// Message par défaut à afficher lorsqu'une vidéo ne peut pas être lue.
notSupportedMessage : aucune source compatible n'a été trouvée pour ce média,
normalizeAutoplay : false,
fullscreen : {
options : {
navigationUI : "hide" (cacher)
}
},
points d'arrêt : {},
responsive : false,
audioOnlyMode : false,
audioPosterMode : false
};
[
/**
* Retourne si le joueur est dans l'état "terminé" ou non.
*
* @return {Boolean} True si le joueur est dans l'état terminé, false si ce n'est pas le cas.
* @method Player#ended
*/
terminé",
/**
* Indique si le joueur est dans l'état de "recherche" ou non.
*
* @return {Boolean} True si le joueur est en état de recherche, false s'il ne l'est pas.
* @method Player#seeking
*/
recherche",
/**
* Renvoie les intervalles de temps des médias qui sont actuellement disponibles
* pour avoir cherché à.
*
* @return {TimeRanges} les intervalles recherchables de la chronologie des médias
* @method Player#seekable
*/
'cherchable',
/**
* Renvoie l'état actuel de l'activité du réseau pour l'élément, de
* les codes dans la liste ci-dessous.
* - NETWORK_EMPTY (valeur numérique 0)
* L'élément n'a pas encore été initialisé. Tous les attributs sont en
* leur état initial.
* - NETWORK_IDLE (valeur numérique 1)
* L'algorithme de sélection des ressources de l'élément est actif et a
* a sélectionné une ressource, mais il n'utilise pas réellement le réseau à l'heure actuelle
* cette fois-ci.
* - NETWORK_LOADING (valeur numérique 2)
* L'agent utilisateur essaie activement de télécharger des données.
* - NETWORK_NO_SOURCE (valeur numérique 3)
* L'algorithme de sélection des ressources de l'élément est actif, mais il a
* n'a pas encore trouvé de ressource à utiliser.
*
* voir https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
* @return {number} l'état actuel de l'activité du réseau
* @méthode Joueur#état du réseau
*/
'networkState',
/**
* Renvoie une valeur qui exprime l'état actuel de l'élément
* en ce qui concerne le rendu de la position de lecture actuelle, à partir de l'élément
* dans la liste ci-dessous.
* - HAVE_NOTHING (valeur numérique 0)
* Aucune information concernant la ressource médiatique n'est disponible.
* - HAVE_METADATA (valeur numérique 1)
* La ressource a été obtenue en quantité suffisante pour que la durée de la
* est disponible.
* - HAVE_CURRENT_DATA (valeur numérique 2)
* Les données relatives à la position de lecture actuelle sont disponibles.
* - HAVE_FUTURE_DATA (valeur numérique 3)
* Les données relatives à la position de lecture actuelle sont disponibles sous la forme suivante
* ainsi que suffisamment de données pour permettre à l'agent utilisateur de faire avancer le dossier en cours
* position de lecture dans le sens de la lecture.
* - HAVE_ENOUGH_DATA (valeur numérique 4)
* L'agent utilisateur estime qu'il y a suffisamment de données disponibles pour
* la lecture se poursuit sans interruption.
*
* voir https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
* @return {number} l'état actuel du rendu de la lecture
* @method Player#readyState
*/
état de préparation
].forEach(function(fn) {
Player.prototype[fn] = function() {
return this.techGet_(fn) ;
};
}) ;
TECH_EVENTS_RETRIGGER.forEach(function(event) {
Player.prototype[`handleTech${toTitleCase(event)}_`] = function() {
return this.trigger(event) ;
};
}) ;
/**
* Déclenché lorsque le lecteur dispose des informations initiales sur la durée et les dimensions
*
* @event Player#loadedmetadata
* @type {EventTarget~Event}
*/
/**
* Déclenché lorsque le lecteur a téléchargé des données à la position de lecture actuelle
*
* @event Player#loadeddata
* @type {EventTarget~Event}
*/
/**
* Déclenché lorsque la position de lecture actuelle a changé *
* Pendant la lecture, cette fonction est activée toutes les 15 à 250 millisecondes, en fonction de l'intensité du son
* la technologie de lecture utilisée.
*
* @event Player#timeupdate
* @type {EventTarget~Event}
*/
/**
* Déclenché en cas de changement de volume
*
* @event Player#volumechange
* @type {EventTarget~Event}
*/
/**
* Indique si un joueur dispose ou non d'un plugin.
*
* Cela ne permet pas de savoir si le plugin a été initialisé ou non
* sur ce lecteur. Pour cela, [usingPlugin]{@link Player#usingPlugin}.
*
* @method Player#hasPlugin
* @param {string} name
* Le nom d'un plugin.
*
* @return {boolean}
* Le lecteur dispose ou non du plugin demandé.
*/
/**
* Indique si un lecteur utilise ou non un plugin par son nom.
*
* Pour les plugins de base, il s'agit uniquement de savoir si le plugin a _jamais_ été utilisé
* initialisé sur ce lecteur.
*
* @method Player#usingPlugin
* @param {string} name
* Le nom d'un plugin.
*
* @return {boolean}
* Indique si le lecteur utilise ou non le plugin demandé.
*/
Component.registerComponent('Player', Player) ;
exporter le lecteur par défaut ;