import window from 'global/window' ;
import document from 'global/document' ;
import mergeOptions from '../utils/merge-options' ;
import {getAbsoluteURL} de '../utils/url' ;
/**
* Cette fonction est utilisée pour déclencher un jeu de sources lorsqu'il y a quelque chose à faire
* similaire à l'appel de `mediaEl.load()`. Il essaiera de trouver la source via
* l'attribut `src` puis les éléments `<source>`. Il lancera alors `sourceset`
* avec la source trouvée ou une chaîne vide si nous ne pouvons pas savoir. S'il ne peut pas
* ne trouve pas de source, alors `sourceset` ne sera pas déclenché.
*
* @param {Html5} tech
* L'objet technique sur lequel le sourceset a été configuré
*
* @return {boolean}
* renvoie false si le jeu de sources n'a pas été déclenché et true dans le cas contraire.
*/
const sourcesetLoad = (tech) => {
const el = tech.el() ;
// si `el.src` est défini, cette source sera chargée.
if (el.hasAttribute('src')) {
tech.triggerSourceset(el.src) ;
retourner vrai ;
}
/**
* Comme il n'y a pas de propriété src sur l'élément media, les éléments source seront utilisés pour les éléments
* la mise en œuvre de l'algorithme de sélection des sources. Cela se produit de manière asynchrone et
* dans la plupart des cas où il y a plus d'une source, nous ne pouvons pas dire quelle source sera utilisée
* être chargée, sans réimplémenter l'algorithme de sélection de la source. Pour l'instant, nous ne sommes pas
* va le faire. Il existe cependant trois cas particuliers que nous traitons ici :
*
* 1. S'il n'y a pas de sources, ne pas lancer `sourceset`.
* 2. S'il n'y a qu'une seule `<source>` avec une propriété/attribut `src` qui est notre `src`
* 3. S'il y a plus d'un `<source>` mais que tous ont la même url `src`.
* Ce sera notre src.
*/
const sources = tech.$$('source') ;
const srcUrls = [] ;
let src = '' ;
// s'il n'y a pas de sources, ne pas déclencher le jeu de sources
if (!sources.length) {
retourner faux ;
}
// ne compte que les éléments sources valides/non dupliqués
for (let i = 0 ; i < sources.length ; i++) {
const url = sources[i].src ;
if (url && srcUrls.indexOf(url) === -1) {
srcUrls.push(url) ;
}
}
// il n'y avait pas de sources valables
if (!srcUrls.length) {
retourner faux ;
}
// il n'y a qu'un seul élément source valide url
// utiliser cela
if (srcUrls.length === 1) {
src = srcUrls[0] ;
}
tech.triggerSourceset(src) ;
retourner vrai ;
};
/**
* notre implémentation d'un descripteur `innerHTML` pour les navigateurs
* qui n'en ont pas.
*/
const innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
get() {
return this.cloneNode(true).innerHTML ;
},
set(v) {
// créer un nœud factice pour utiliser innerHTML
const dummy = document.createElement(this.nodeName.toLowerCase()) ;
// fixer la valeur de innerHTML à la valeur fournie
dummy.innerHTML = v ;
// créer un fragment de document pour contenir les nœuds de dummy
const docFrag = document.createDocumentFragment() ;
// copie tous les nœuds créés par le innerHTML sur dummy
// au fragment de document
while (dummy.childNodes.length) {
docFrag.appendChild(dummy.childNodes[0]) ;
}
// supprimer le contenu
this.innerText = '' ;
// maintenant nous ajoutons tout ce html en un seul en ajoutant la balise
// fragment de document. Voici comment innerHTML procède.
window.Element.prototype.appendChild.call(this, docFrag) ;
// puis renvoie le résultat que le setter de innerHTML aurait obtenu
return this.innerHTML ;
}
}) ;
/**
* Obtenir un descripteur de propriété à partir d'une liste de priorités et de l'option
* à obtenir.
*/
const getDescriptor = (priority, prop) => {
let descripteur = {} ;
for (let i = 0 ; i < priority.length ; i++) {
descriptor = Object.getOwnPropertyDescriptor(priority[i], prop) ;
if (descriptor && descriptor.set && descriptor.get) {
pause ;
}
}
descriptor.enumerable = true ;
descriptor.configurable = true ;
retourner le descripteur ;
};
const getInnerHTMLDescriptor = (tech) => getDescriptor([
tech.el(),
window.HTMLMediaElement.prototype,
window.Element.prototype,
Polyfill de descripteur HTML interne
], 'innerHTML') ;
/**
* Les fonctions internes du navigateur sont corrigées afin que nous puissions dire de manière synchrone
* si un `<source>` a été ajouté à l'élément multimédia. Pour une raison ou une autre, cette
* provoque un `sourceset` si l'élément média est prêt et n'a pas de source.
* C'est le cas lorsque
* - La page vient d'être chargée et l'élément média n'a pas de source.
* - L'élément média a été vidé de toutes ses sources, puis `load()` a été appelé.
*
* Pour ce faire, il corrige les fonctions/propriétés suivantes lorsqu'elles sont prises en charge :
*
* - `append()` - peut être utilisé pour ajouter un élément `<source>` à l'élément média
* - `appendChild()` - peut être utilisé pour ajouter un élément `<source>` à l'élément média
* - `insertAdjacentHTML()` - peut être utilisé pour ajouter un élément `<source>` à l'élément media
* - `innerHTML` - peut être utilisé pour ajouter un élément `<source>` à l'élément media
*
* @param {Html5} tech
* L'objet technique sur lequel le sourceset est configuré.
*/
const firstSourceWatch = function(tech) {
const el = tech.el() ;
// s'assurer que firstSourceWatch n'est pas configuré deux fois.
if (el.resetSourceWatch_) {
retour ;
}
const old = {} ;
const innerDescriptor = getInnerHTMLDescriptor(tech) ;
const appendWrapper = (appendFn) => (...args) => {
const retval = appendFn.apply(el, args) ;
sourcesetLoad(tech) ;
retourner retval ;
};
['append', 'appendChild', 'insertAdjacentHTML'].forEach((k) => {
if (!el[k]) {
retour ;
}
// stocker l'ancienne fonction
old[k] = el[k] ;
// appeler l'ancienne fonction avec un jeu de sources si une source
// a été chargé
el[k] = appendWrapper(old[k]) ;
}) ;
Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
set : appendWrapper(innerDescriptor.set)
})) ;
el.resetSourceWatch_ = () => {
el.resetSourceWatch_ = null ;
Object.keys(old).forEach((k) => {
el[k] = old[k] ;
}) ;
Object.defineProperty(el, 'innerHTML', innerDescriptor) ;
};
// sur le premier jeu de sources, nous devons revenir sur nos modifications
tech.one('sourceset', el.resetSourceWatch_) ;
};
/**
* notre implémentation d'un descripteur `src` pour les navigateurs
* qui n'en ont pas.
*/
const srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
get() {
if (this.hasAttribute('src')) {
return getAbsoluteURL(window.Element.prototype.getAttribute.call(this, 'src')) ;
}
retourner '' ;
},
set(v) {
window.Element.prototype.setAttribute.call(this, 'src', v) ;
retour v ;
}
}) ;
const getSrcDescriptor = (tech) => getDescriptor([tech.el(), window.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src') ;
/**
* configurer la gestion du `sourceset` sur la technologie `Html5`. Cette fonction
* corrige les propriétés/fonctions des éléments suivants :
*
* - `src` - pour déterminer quand `src` est défini
* - `setAttribute()` - pour déterminer quand `src` est défini
* - `load()` - cela redéclenche l'algorithme de sélection de la source, et peut
* provoquer un jeu de sources.
*
* S'il n'y a pas de source lorsque nous ajoutons le support `sourceset` ou pendant un `load()`
* nous patchons également les fonctions listées dans `firstSourceWatch`.
*
* @param {Html5} tech
* La technologie au service de la correction
*/
const setupSourceset = function(tech) {
if (!tech.featuresSourceset) {
retour ;
}
const el = tech.el() ;
// s'assurer que le jeu de sources n'est pas configuré deux fois.
if (el.resetSourceset_) {
retour ;
}
const srcDescriptor = getSrcDescriptor(tech) ;
const oldSetAttribute = el.setAttribute ;
const oldLoad = el.load ;
Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
set : (v) => {
const retval = srcDescriptor.set.call(el, v) ;
// nous utilisons le getter ici pour obtenir la valeur réelle définie sur src
tech.triggerSourceset(el.src) ;
retourner retval ;
}
})) ;
el.setAttribute = (n, v) => {
const retval = oldSetAttribute.call(el, n, v) ;
if ((/src/i).test(n)) {
tech.triggerSourceset(el.src) ;
}
retourner retval ;
};
el.load = () => {
const retval = oldLoad.call(el) ;
// si le chargement a été appelé, mais qu'il n'y avait pas de source à déclencher
// sourceset on. Nous devons surveiller l'ajout d'une source
// car cela peut déclencher un `sourceset` lorsque l'élément média
// n'a pas de source
if (!sourcesetLoad(tech)) {
tech.triggerSourceset('') ;
premièreSourceWatch(tech) ;
}
retourner retval ;
};
if (el.currentSrc) {
tech.triggerSourceset(el.currentSrc) ;
} else if (!sourcesetLoad(tech)) {
premièreSourceWatch(tech) ;
}
el.resetSourceset_ = () => {
el.resetSourceset_ = null ;
el.load = oldLoad ;
el.setAttribute = oldSetAttribute ;
Object.defineProperty(el, 'src', srcDescriptor) ;
if (el.resetSourceWatch_) {
el.resetSourceWatch_() ;
}
};
};
export default setupSourceset ;