Merge branch 'develop' of https://github.com/Selz/plyr into develop
# Conflicts: # dist/plyr.js # dist/plyr.js.map # src/js/controls.js
This commit is contained in:
		| @ -79,8 +79,9 @@ const captions = { | ||||
|  | ||||
|             // Filter doesn't seem to work for a TextTrackList :-( | ||||
|             Array.from(this.captions.tracks).forEach(track => { | ||||
|                 if (track.language === this.captions.language.toLowerCase()) { | ||||
|                 if (track.language.toLowerCase() === this.language.toLowerCase()) { | ||||
|                     this.captions.currentTrack = track; | ||||
|                     console.warn(`Set current track to ${this.language}`); | ||||
|                 } | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
							
								
								
									
										169
									
								
								src/js/controls.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										169
									
								
								src/js/controls.js
									
									
									
									
										vendored
									
									
								
							| @ -75,7 +75,7 @@ const controls = { | ||||
|         const use = document.createElementNS(namespace, 'use'); | ||||
|         const path = `${iconPath}-${type}`; | ||||
|  | ||||
|         // If the new `href` attribute is supported, use that | ||||
|         // Set `href` attributes | ||||
|         // https://github.com/sampotts/plyr/issues/460 | ||||
|         // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href | ||||
|         if ('href' in use) { | ||||
| @ -118,6 +118,10 @@ const controls = { | ||||
|  | ||||
|     // Create a badge | ||||
|     createBadge(text) { | ||||
|         if (utils.is.empty(text)) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         const badge = utils.createElement('span', { | ||||
|             class: this.config.classNames.menu.value, | ||||
|         }); | ||||
| @ -322,6 +326,39 @@ const controls = { | ||||
|         return container; | ||||
|     }, | ||||
|  | ||||
|     // Create a settings menu item | ||||
|     createMenuItem(value, list, type, title, badge = null, checked = false) { | ||||
|         const item = utils.createElement('li'); | ||||
|  | ||||
|         const label = utils.createElement('label', { | ||||
|             class: this.config.classNames.control, | ||||
|         }); | ||||
|  | ||||
|         const radio = utils.createElement( | ||||
|             'input', | ||||
|             utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs[type]), { | ||||
|                 type: 'radio', | ||||
|                 name: `plyr-${type}`, | ||||
|                 value, | ||||
|                 checked, | ||||
|                 class: 'plyr__sr-only', | ||||
|             }) | ||||
|         ); | ||||
|  | ||||
|         const faux = utils.createElement('span', { 'aria-hidden': true }); | ||||
|  | ||||
|         label.appendChild(radio); | ||||
|         label.appendChild(faux); | ||||
|         label.insertAdjacentHTML('beforeend', title); | ||||
|  | ||||
|         if (utils.is.htmlElement(badge)) { | ||||
|             label.appendChild(badge); | ||||
|         } | ||||
|  | ||||
|         item.appendChild(label); | ||||
|         list.appendChild(item); | ||||
|     }, | ||||
|  | ||||
|     // Update hover tooltip for seeking | ||||
|     updateSeekTooltip(event) { | ||||
|         // Bail if setting not true | ||||
| @ -356,7 +393,7 @@ const controls = { | ||||
|         } | ||||
|  | ||||
|         // Display the time a click would seek to | ||||
|         ui.updateTimeDisplay.call(this, this.duration / 100 * percent, this.elements.display.seekTooltip); | ||||
|         ui.updateTimeDisplay.call(this, this.elements.display.seekTooltip, this.duration / 100 * percent); | ||||
|  | ||||
|         // Set position | ||||
|         this.elements.display.seekTooltip.style.left = `${percent}%`; | ||||
| @ -393,6 +430,7 @@ const controls = { | ||||
|     // Set the YouTube quality menu | ||||
|     // TODO: Support for HTML5 | ||||
|     setQualityMenu(options) { | ||||
|         const type = 'quality'; | ||||
|         const list = this.elements.settings.panes.quality.querySelector('ul'); | ||||
|  | ||||
|         // Set options if passed and filter based on config | ||||
| @ -404,7 +442,7 @@ const controls = { | ||||
|  | ||||
|         // Toggle the pane and tab | ||||
|         const toggle = !utils.is.empty(this.options.quality) && this.type === 'youtube'; | ||||
|         controls.toggleTab.call(this, 'quality', toggle); | ||||
|         controls.toggleTab.call(this, type, toggle); | ||||
|  | ||||
|         // If we're hiding, nothing more to do | ||||
|         if (!toggle) { | ||||
| @ -446,35 +484,18 @@ const controls = { | ||||
|             return controls.createBadge.call(this, label); | ||||
|         }; | ||||
|  | ||||
|         this.options.quality.forEach(quality => { | ||||
|             const item = utils.createElement('li'); | ||||
|  | ||||
|             const label = utils.createElement('label', { | ||||
|                 class: this.config.classNames.control, | ||||
|             }); | ||||
|  | ||||
|             const radio = utils.createElement( | ||||
|                 'input', | ||||
|                 utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs.quality), { | ||||
|                     type: 'radio', | ||||
|                     name: 'plyr-quality', | ||||
|                     value: quality, | ||||
|                 }) | ||||
|         this.options.quality.forEach(quality => | ||||
|             controls.createMenuItem.call( | ||||
|                 this, | ||||
|                 quality, | ||||
|                 list, | ||||
|                 type, | ||||
|                 controls.getLabel.call(this, 'quality', quality), | ||||
|                 getBadge(quality) | ||||
|             ) | ||||
|             ); | ||||
|  | ||||
|             label.appendChild(radio); | ||||
|             label.appendChild(document.createTextNode(controls.getLabel.call(this, 'quality', quality))); | ||||
|  | ||||
|             const badge = getBadge(quality); | ||||
|             if (utils.is.htmlElement(badge)) { | ||||
|                 label.appendChild(badge); | ||||
|             } | ||||
|  | ||||
|             item.appendChild(label); | ||||
|             list.appendChild(item); | ||||
|         }); | ||||
|  | ||||
|         controls.updateSetting.call(this, 'quality', list); | ||||
|         controls.updateSetting.call(this, type, list); | ||||
|     }, | ||||
|  | ||||
|     // Translate a value into a nice label | ||||
| @ -576,7 +597,7 @@ const controls = { | ||||
|     }, | ||||
|  | ||||
|     // Set the looping options | ||||
|     setLoopMenu() { | ||||
|     /* setLoopMenu() { | ||||
|         const options = ['start', 'end', 'all', 'reset']; | ||||
|         const list = this.elements.settings.panes.loop.querySelector('ul'); | ||||
|  | ||||
| @ -612,7 +633,7 @@ const controls = { | ||||
|             item.appendChild(button); | ||||
|             list.appendChild(item); | ||||
|         }); | ||||
|     }, | ||||
|     }, */ | ||||
|  | ||||
|     // Get current selected caption language | ||||
|     // TODO: rework this to user the getter in the API? | ||||
| @ -634,11 +655,13 @@ const controls = { | ||||
|  | ||||
|     // Set a list of available captions languages | ||||
|     setCaptionsMenu() { | ||||
|         // TODO: Captions or language? Currently it's mixed | ||||
|         const type = 'captions'; | ||||
|         const list = this.elements.settings.panes.captions.querySelector('ul'); | ||||
|  | ||||
|         // Toggle the pane and tab | ||||
|         const toggle = !utils.is.empty(this.captions.tracks); | ||||
|         controls.toggleTab.call(this, 'captions', toggle); | ||||
|         controls.toggleTab.call(this, type, toggle); | ||||
|  | ||||
|         // Empty the menu | ||||
|         utils.emptyElement(list); | ||||
| @ -651,7 +674,6 @@ const controls = { | ||||
|         // Re-map the tracks into just the data we need | ||||
|         const tracks = Array.from(this.captions.tracks).map(track => ({ | ||||
|             language: track.language, | ||||
|             badge: true, | ||||
|             label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(), | ||||
|         })); | ||||
|  | ||||
| @ -663,41 +685,24 @@ const controls = { | ||||
|  | ||||
|         // Generate options | ||||
|         tracks.forEach(track => { | ||||
|             const item = utils.createElement('li'); | ||||
|  | ||||
|             const label = utils.createElement('label', { | ||||
|                 class: this.config.classNames.control, | ||||
|             }); | ||||
|  | ||||
|             const radio = utils.createElement( | ||||
|                 'input', | ||||
|                 utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs.language), { | ||||
|                     type: 'radio', | ||||
|                     name: 'plyr-language', | ||||
|                     value: track.language, | ||||
|                 }) | ||||
|             controls.createMenuItem.call( | ||||
|                 this, | ||||
|                 track.language, | ||||
|                 list, | ||||
|                 'language', | ||||
|                 track.label || track.language, | ||||
|                 controls.createBadge.call(this, track.language.toUpperCase()), | ||||
|                 track.language.toLowerCase() === this.captions.language.toLowerCase() | ||||
|             ); | ||||
|  | ||||
|             if (track.language.toLowerCase() === this.captions.language.toLowerCase()) { | ||||
|                 radio.checked = true; | ||||
|             } | ||||
|  | ||||
|             label.appendChild(radio); | ||||
|             label.appendChild(document.createTextNode(track.label || track.language)); | ||||
|  | ||||
|             if (track.badge) { | ||||
|                 label.appendChild(controls.createBadge.call(this, track.language.toUpperCase())); | ||||
|             } | ||||
|  | ||||
|             item.appendChild(label); | ||||
|             list.appendChild(item); | ||||
|         }); | ||||
|  | ||||
|         controls.updateSetting.call(this, 'captions', list); | ||||
|         controls.updateSetting.call(this, type, list); | ||||
|     }, | ||||
|  | ||||
|     // Set a list of available captions languages | ||||
|     setSpeedMenu(options) { | ||||
|         const type = 'speed'; | ||||
|  | ||||
|         // Set options if passed and filter based on config | ||||
|         if (utils.is.array(options)) { | ||||
|             this.options.speed = options.filter(speed => this.config.speed.options.includes(speed)); | ||||
| @ -707,7 +712,7 @@ const controls = { | ||||
|  | ||||
|         // Toggle the pane and tab | ||||
|         const toggle = !utils.is.empty(this.options.speed); | ||||
|         controls.toggleTab.call(this, 'speed', toggle); | ||||
|         controls.toggleTab.call(this, type, toggle); | ||||
|  | ||||
|         // If we're hiding, nothing more to do | ||||
|         if (!toggle) { | ||||
| @ -725,39 +730,23 @@ const controls = { | ||||
|         utils.emptyElement(list); | ||||
|  | ||||
|         // Create items | ||||
|         this.options.speed.forEach(speed => { | ||||
|             const item = utils.createElement('li'); | ||||
|  | ||||
|             const label = utils.createElement('label', { | ||||
|                 class: this.config.classNames.control, | ||||
|             }); | ||||
|  | ||||
|             const radio = utils.createElement( | ||||
|                 'input', | ||||
|                 utils.extend(utils.getAttributesFromSelector(this.config.selectors.inputs.speed), { | ||||
|                     type: 'radio', | ||||
|                     name: 'plyr-speed', | ||||
|                     value: speed, | ||||
|                 }) | ||||
|         this.options.speed.forEach(speed => | ||||
|             controls.createMenuItem.call(this, speed, list, type, controls.getLabel.call(this, 'speed', speed)) | ||||
|             ); | ||||
|  | ||||
|             label.appendChild(radio); | ||||
|             label.insertAdjacentHTML('beforeend', controls.getLabel.call(this, 'speed', speed)); | ||||
|             item.appendChild(label); | ||||
|             list.appendChild(item); | ||||
|         }); | ||||
|  | ||||
|         controls.updateSetting.call(this, 'speed', list); | ||||
|         controls.updateSetting.call(this, type, list); | ||||
|     }, | ||||
|  | ||||
|     // Show/hide menu | ||||
|     toggleMenu(event) { | ||||
|         const { form } = this.elements.settings; | ||||
|         const button = this.elements.buttons.settings; | ||||
|         const show = utils.is.boolean(event) ? event : form && form.getAttribute('aria-hidden') === 'true'; | ||||
|         const show = utils.is.boolean(event) | ||||
|             ? event | ||||
|             : utils.is.htmlElement(form) && form.getAttribute('aria-hidden') === 'true'; | ||||
|  | ||||
|         if (utils.is.event(event)) { | ||||
|             const isMenuItem = form && form.contains(event.target); | ||||
|             const isMenuItem = utils.is.htmlElement(form) && form.contains(event.target); | ||||
|             const isButton = event.target === this.elements.buttons.settings; | ||||
|  | ||||
|             // If the click was inside the form or if the click | ||||
| @ -774,10 +763,11 @@ const controls = { | ||||
|         } | ||||
|  | ||||
|         // Set form and button attributes | ||||
|         if (button) { | ||||
|         if (utils.is.htmlElement(button)) { | ||||
|             button.setAttribute('aria-expanded', show); | ||||
|         } | ||||
|         if (form) { | ||||
|  | ||||
|         if (utils.is.htmlElement(form)) { | ||||
|             form.setAttribute('aria-hidden', !show); | ||||
|  | ||||
|             if (show) { | ||||
| @ -885,6 +875,9 @@ const controls = { | ||||
|         pane.setAttribute('aria-hidden', !show); | ||||
|         tab.setAttribute('aria-expanded', show); | ||||
|         pane.removeAttribute('tabindex'); | ||||
|  | ||||
|         // Focus the first item | ||||
|         pane.querySelectorAll('button:not(:disabled), input:not(:disabled), [tabindex]')[0].focus(); | ||||
|     }, | ||||
|  | ||||
|     // Build the default HTML | ||||
|  | ||||
| @ -22,13 +22,20 @@ const defaults = { | ||||
|     // Pass a custom duration | ||||
|     duration: null, | ||||
|  | ||||
|     // Display the media duration | ||||
|     // Display the media duration on load in the current time position | ||||
|     // If you have opted to display both duration and currentTime, this is ignored | ||||
|     displayDuration: true, | ||||
|  | ||||
|     // Invert the current time to be a countdown | ||||
|     invertTime: true, | ||||
|  | ||||
|     // Clicking the currentTime inverts it's value to show time left rather than elapsed | ||||
|     toggleInvert: true, | ||||
|  | ||||
|     // Aspect ratio (for embeds) | ||||
|     ratio: '16:9', | ||||
|  | ||||
|     // Click video to play | ||||
|     // Click video container to play/pause | ||||
|     clickToPlay: true, | ||||
|  | ||||
|     // Auto hide the controls | ||||
| @ -203,7 +210,7 @@ const defaults = { | ||||
|         'exitfullscreen', | ||||
|         'captionsenabled', | ||||
|         'captionsdisabled', | ||||
|         'captionchange', | ||||
|         'languagechange', | ||||
|         'controlshidden', | ||||
|         'controlsshown', | ||||
|         'ready', | ||||
| @ -276,6 +283,7 @@ const defaults = { | ||||
|         isIos: 'plyr--is-ios', | ||||
|         isTouch: 'plyr--is-touch', | ||||
|         uiSupported: 'plyr--full-ui', | ||||
|         noTransition: 'plyr--no-transition', | ||||
|         menu: { | ||||
|             value: 'plyr__menu__value', | ||||
|             badge: 'plyr__badge', | ||||
| @ -298,6 +306,11 @@ const defaults = { | ||||
|         }, | ||||
|         tabFocus: 'plyr__tab-focus', | ||||
|     }, | ||||
|  | ||||
|     // API keys | ||||
|     keys: { | ||||
|         google: null, | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| export default defaults; | ||||
|  | ||||
| @ -101,7 +101,6 @@ const listeners = { | ||||
|                     case 75: | ||||
|                         // Space and K key | ||||
|                         if (!held) { | ||||
|                             this.console.warn('togglePlay', event.type); | ||||
|                             this.togglePlay(); | ||||
|                         } | ||||
|                         break; | ||||
| @ -119,7 +118,7 @@ const listeners = { | ||||
|                     case 77: | ||||
|                         // M key | ||||
|                         if (!held) { | ||||
|                             this.muted = 'toggle'; | ||||
|                             this.muted = !this.muted; | ||||
|                         } | ||||
|                         break; | ||||
|  | ||||
| @ -145,6 +144,11 @@ const listeners = { | ||||
|                         } | ||||
|                         break; | ||||
|  | ||||
|                     case 76: | ||||
|                         // L key | ||||
|                         this.loop = !this.loop; | ||||
|                         break; | ||||
|  | ||||
|                     /* case 73: | ||||
|                         this.setLoop('start'); | ||||
|                         break; | ||||
| @ -205,7 +209,7 @@ const listeners = { | ||||
|             // Toggle controls on mouse events and entering fullscreen | ||||
|             utils.on( | ||||
|                 this.elements.container, | ||||
|                 'mouseenter mouseleave mousemove touchstart touchend touchcancel touchmove enterfullscreen', | ||||
|                 'click mouseenter mouseleave mousemove touchmove enterfullscreen exitfullscreen', | ||||
|                 event => { | ||||
|                     this.toggleControls(event); | ||||
|                 } | ||||
| @ -213,11 +217,11 @@ const listeners = { | ||||
|         } | ||||
|  | ||||
|         // Handle user exiting fullscreen by escaping etc | ||||
|         if (fullscreen.enabled) { | ||||
|         /* if (fullscreen.enabled) { | ||||
|             utils.on(document, fullscreen.eventType, event => { | ||||
|                 this.toggleFullscreen(event); | ||||
|             }); | ||||
|         } | ||||
|         } */ | ||||
|     }, | ||||
|  | ||||
|     // Listen for media events | ||||
| @ -226,7 +230,7 @@ const listeners = { | ||||
|         utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event)); | ||||
|  | ||||
|         // Display duration | ||||
|         utils.on(this.media, 'durationchange loadedmetadata', event => ui.displayDuration.call(this, event)); | ||||
|         utils.on(this.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this, event)); | ||||
|  | ||||
|         // Handle the media finishing | ||||
|         utils.on(this.media, 'ended', () => { | ||||
| @ -314,7 +318,7 @@ const listeners = { | ||||
|         }); | ||||
|  | ||||
|         // Caption language change | ||||
|         utils.on(this.media, 'captionchange', () => { | ||||
|         utils.on(this.media, 'languagechange', () => { | ||||
|             // Save to storage | ||||
|             storage.set.call(this, { language: this.language }); | ||||
|         }); | ||||
| @ -337,7 +341,14 @@ const listeners = { | ||||
|         // Proxy events to container | ||||
|         // Bubble up key events for Edge | ||||
|         utils.on(this.media, this.config.events.concat(['keyup', 'keydown']).join(' '), event => { | ||||
|             utils.dispatchEvent.call(this, this.elements.container, event.type, true); | ||||
|             let detail = {}; | ||||
|  | ||||
|             // Get error details from media | ||||
|             if (event.type === 'error') { | ||||
|                 detail = this.media.error; | ||||
|             } | ||||
|  | ||||
|             utils.dispatchEvent.call(this, this.elements.container, event.type, true, detail); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
| @ -452,11 +463,16 @@ const listeners = { | ||||
|             controls.showTab.call(this, event); | ||||
|  | ||||
|             // Settings menu items - use event delegation as items are added/removed | ||||
|             // Settings - Language | ||||
|             if (utils.matches(event.target, this.config.selectors.inputs.language)) { | ||||
|                 // Settings - Language | ||||
|                 proxy(event, 'language', () => { | ||||
|                     this.toggleCaptions(true); | ||||
|                     this.language = event.target.value.toLowerCase(); | ||||
|                     const language = event.target.value; | ||||
|  | ||||
|                     this.toggleCaptions(!utils.is.empty(language)); | ||||
|  | ||||
|                     if (!utils.is.empty(language)) { | ||||
|                         this.language = event.target.value.toLowerCase(); | ||||
|                     } | ||||
|                 }); | ||||
|             } else if (utils.matches(event.target, this.config.selectors.inputs.quality)) { | ||||
|                 // Settings - Quality | ||||
| @ -468,7 +484,7 @@ const listeners = { | ||||
|                 proxy(event, 'speed', () => { | ||||
|                     this.speed = parseFloat(event.target.value); | ||||
|                 }); | ||||
|             } else if (utils.matches(event.target, this.config.selectors.buttons.loop)) { | ||||
|             } /* else if (utils.matches(event.target, this.config.selectors.buttons.loop)) { | ||||
|                 // Settings - Looping | ||||
|                 // TODO: use toggle buttons | ||||
|                 proxy(event, 'loop', () => { | ||||
| @ -477,7 +493,7 @@ const listeners = { | ||||
|  | ||||
|                     this.console.warn('Set loop'); | ||||
|                 }); | ||||
|             } | ||||
|             } */ | ||||
|         }); | ||||
|  | ||||
|         // Seek | ||||
| @ -487,6 +503,20 @@ const listeners = { | ||||
|             }) | ||||
|         ); | ||||
|  | ||||
|         // Current time invert | ||||
|         // Only if one time element is used for both currentTime and duration | ||||
|         if (this.config.toggleInvert && !utils.is.htmlElement(this.elements.display.duration)) { | ||||
|             utils.on(this.elements.display.currentTime, 'click', () => { | ||||
|                 // Do nothing if we're at the start | ||||
|                 if (this.currentTime === 0) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 this.config.invertTime = !this.config.invertTime; | ||||
|                 ui.timeUpdate.call(this); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Volume | ||||
|         utils.on(this.elements.inputs.volume, inputEvent, event => | ||||
|             proxy(event, 'volume', () => { | ||||
| @ -522,7 +552,7 @@ const listeners = { | ||||
|             // TODO: Check we need capture here | ||||
|             utils.on( | ||||
|                 this.elements.controls, | ||||
|                 'focus blur', | ||||
|                 'focusin focusout', | ||||
|                 event => { | ||||
|                     this.toggleControls(event); | ||||
|                 }, | ||||
|  | ||||
| @ -65,7 +65,6 @@ const media = { | ||||
|             utils.wrap(this.media, this.elements.wrapper); | ||||
|         } | ||||
|  | ||||
|         // Embeds | ||||
|         if (this.isEmbed) { | ||||
|             switch (this.type) { | ||||
|                 case 'youtube': | ||||
| @ -79,9 +78,9 @@ const media = { | ||||
|                 default: | ||||
|                     break; | ||||
|             } | ||||
|         } else { | ||||
|             ui.setTitle.call(this); | ||||
|         } | ||||
|  | ||||
|         ui.setTitle.call(this); | ||||
|     }, | ||||
|  | ||||
|     // Cancel current network requests | ||||
|  | ||||
| @ -37,7 +37,8 @@ const vimeo = { | ||||
|     setAspectRatio(input) { | ||||
|         const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':'); | ||||
|         const padding = 100 / ratio[0] * ratio[1]; | ||||
|         const offset = (300 - padding) / 6; | ||||
|         const height = 200; | ||||
|         const offset = (height - padding) / (height / 50); | ||||
|         this.elements.wrapper.style.paddingBottom = `${padding}%`; | ||||
|         this.media.style.transform = `translateY(-${offset}%)`; | ||||
|     }, | ||||
| @ -55,6 +56,7 @@ const vimeo = { | ||||
|             title: false, | ||||
|             speed: true, | ||||
|             transparent: 0, | ||||
|             gesture: 'media', | ||||
|         }; | ||||
|         const params = utils.buildUrlParameters(options); | ||||
|         const id = utils.parseVimeoId(player.embedId); | ||||
| @ -70,23 +72,27 @@ const vimeo = { | ||||
|         // https://github.com/vimeo/player.js | ||||
|         player.embed = new window.Vimeo.Player(iframe); | ||||
|  | ||||
|         // Create a faux HTML5 API using the Vimeo API | ||||
|         player.media.play = () => { | ||||
|             player.embed.play(); | ||||
|             player.media.paused = false; | ||||
|         }; | ||||
|         player.media.pause = () => { | ||||
|             player.embed.pause(); | ||||
|             player.media.paused = true; | ||||
|         }; | ||||
|         player.media.stop = () => { | ||||
|             player.embed.stop(); | ||||
|             player.media.paused = true; | ||||
|         }; | ||||
|  | ||||
|         player.media.paused = true; | ||||
|         player.media.currentTime = 0; | ||||
|  | ||||
|         // Create a faux HTML5 API using the Vimeo API | ||||
|         player.media.play = () => { | ||||
|             player.embed.play().then(() => { | ||||
|                 player.media.paused = false; | ||||
|             }); | ||||
|         }; | ||||
|         player.media.pause = () => { | ||||
|             player.embed.pause().then(() => { | ||||
|                 player.media.paused = true; | ||||
|             }); | ||||
|         }; | ||||
|         player.media.stop = () => { | ||||
|             player.embed.stop().then(() => { | ||||
|                 player.media.paused = true; | ||||
|                 player.currentTime = 0; | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Seeking | ||||
|         let { currentTime } = player.media; | ||||
|         Object.defineProperty(player.media, 'currentTime', { | ||||
| @ -121,9 +127,10 @@ const vimeo = { | ||||
|                 return speed; | ||||
|             }, | ||||
|             set(input) { | ||||
|                 speed = input; | ||||
|                 player.embed.setPlaybackRate(input); | ||||
|                 utils.dispatchEvent.call(player, player.media, 'ratechange'); | ||||
|                 player.embed.setPlaybackRate(input).then(() => { | ||||
|                     speed = input; | ||||
|                     utils.dispatchEvent.call(player, player.media, 'ratechange'); | ||||
|                 }); | ||||
|             }, | ||||
|         }); | ||||
|  | ||||
| @ -134,9 +141,10 @@ const vimeo = { | ||||
|                 return volume; | ||||
|             }, | ||||
|             set(input) { | ||||
|                 volume = input; | ||||
|                 player.embed.setVolume(input); | ||||
|                 utils.dispatchEvent.call(player, player.media, 'volumechange'); | ||||
|                 player.embed.setVolume(input).then(() => { | ||||
|                     volume = input; | ||||
|                     utils.dispatchEvent.call(player, player.media, 'volumechange'); | ||||
|                 }); | ||||
|             }, | ||||
|         }); | ||||
|  | ||||
| @ -148,9 +156,11 @@ const vimeo = { | ||||
|             }, | ||||
|             set(input) { | ||||
|                 const toggle = utils.is.boolean(input) ? input : false; | ||||
|                 muted = toggle; | ||||
|                 player.embed.setVolume(toggle ? 0 : player.config.volume); | ||||
|                 utils.dispatchEvent.call(player, player.media, 'volumechange'); | ||||
|  | ||||
|                 player.embed.setVolume(toggle ? 0 : player.config.volume).then(() => { | ||||
|                     muted = toggle; | ||||
|                     utils.dispatchEvent.call(player, player.media, 'volumechange'); | ||||
|                 }); | ||||
|             }, | ||||
|         }); | ||||
|  | ||||
| @ -161,8 +171,11 @@ const vimeo = { | ||||
|                 return loop; | ||||
|             }, | ||||
|             set(input) { | ||||
|                 loop = utils.is.boolean(input) ? input : player.config.loop.active; | ||||
|                 player.embed.setLoop(loop); | ||||
|                 const toggle = utils.is.boolean(input) ? input : player.config.loop.active; | ||||
|  | ||||
|                 player.embed.setLoop(toggle).then(() => { | ||||
|                     loop = toggle; | ||||
|                 }); | ||||
|             }, | ||||
|         }); | ||||
|  | ||||
| @ -191,6 +204,7 @@ const vimeo = { | ||||
|         // Get title | ||||
|         player.embed.getVideoTitle().then(title => { | ||||
|             player.config.title = title; | ||||
|             ui.setTitle.call(this); | ||||
|         }); | ||||
|  | ||||
|         // Get current time | ||||
| @ -269,6 +283,11 @@ const vimeo = { | ||||
|             utils.dispatchEvent.call(player, player.media, 'ended'); | ||||
|         }); | ||||
|  | ||||
|         player.embed.on('error', detail => { | ||||
|             player.media.error = detail; | ||||
|             utils.dispatchEvent.call(player, player.media, 'error'); | ||||
|         }); | ||||
|  | ||||
|         // Rebuild UI | ||||
|         window.setTimeout(() => ui.build.call(player), 0); | ||||
|     }, | ||||
|  | ||||
| @ -23,6 +23,22 @@ const youtube = { | ||||
|         // Set ID | ||||
|         this.media.setAttribute('id', utils.generateId(this.type)); | ||||
|  | ||||
|         // Get the media title via Google API | ||||
|         const key = this.config.keys.google; | ||||
|         if (utils.is.string(key) && !utils.is.empty(key)) { | ||||
|             const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${key}&fields=items(snippet(title))&part=snippet`; | ||||
|  | ||||
|             fetch(url) | ||||
|                 .then(response => (response.ok ? response.json() : null)) | ||||
|                 .then(result => { | ||||
|                     if (result !== null && utils.is.object(result)) { | ||||
|                         this.config.title = result.items[0].snippet.title; | ||||
|                         ui.setTitle.call(this); | ||||
|                     } | ||||
|                 }) | ||||
|                 .catch(() => {}); | ||||
|         } | ||||
|  | ||||
|         // Setup API | ||||
|         if (utils.is.object(window.YT)) { | ||||
|             youtube.ready.call(this, videoId); | ||||
| @ -81,10 +97,47 @@ const youtube = { | ||||
|             }, | ||||
|             events: { | ||||
|                 onError(event) { | ||||
|                     utils.dispatchEvent.call(player, player.media, 'error', true, { | ||||
|                     // If we've already fired an error, don't do it again | ||||
|                     // YouTube fires onError twice | ||||
|                     if (utils.is.object(player.media.error)) { | ||||
|                         return; | ||||
|                     } | ||||
|  | ||||
|                     const detail = { | ||||
|                         code: event.data, | ||||
|                         embed: event.target, | ||||
|                     }); | ||||
|                     }; | ||||
|  | ||||
|                     // Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError | ||||
|                     switch (event.data) { | ||||
|                         case 2: | ||||
|                             detail.message = | ||||
|                                 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.'; | ||||
|                             break; | ||||
|  | ||||
|                         case 5: | ||||
|                             detail.message = | ||||
|                                 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.'; | ||||
|                             break; | ||||
|  | ||||
|                         case 100: | ||||
|                             detail.message = | ||||
|                                 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.'; | ||||
|                             break; | ||||
|  | ||||
|                         case 101: | ||||
|                         case 150: | ||||
|                             detail.message = | ||||
|                                 'The owner of the requested video does not allow it to be played in embedded players.'; | ||||
|                             break; | ||||
|  | ||||
|                         default: | ||||
|                             detail.message = 'An unknown error occured'; | ||||
|                             break; | ||||
|                     } | ||||
|  | ||||
|                     player.media.error = detail; | ||||
|  | ||||
|                     utils.dispatchEvent.call(player, player.media, 'error'); | ||||
|                 }, | ||||
|                 onPlaybackQualityChange(event) { | ||||
|                     // Get the instance | ||||
| @ -207,7 +260,9 @@ const youtube = { | ||||
|                     } | ||||
|  | ||||
|                     // Set title | ||||
|                     player.config.title = instance.getVideoData().title; | ||||
|                     if (utils.is.function(instance.getVideoData)) { | ||||
|                         player.config.title = instance.getVideoData().title; | ||||
|                     } | ||||
|  | ||||
|                     // Set the tabindex to avoid focus entering iframe | ||||
|                     if (player.supported.ui) { | ||||
|  | ||||
| @ -669,7 +669,7 @@ class Plyr { | ||||
|         const language = input.toLowerCase(); | ||||
|  | ||||
|         // If nothing to change, bail | ||||
|         if (this.captions.language === language) { | ||||
|         if (this.language === language) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @ -680,7 +680,7 @@ class Plyr { | ||||
|         this.captions.language = language; | ||||
|  | ||||
|         // Trigger an event | ||||
|         utils.dispatchEvent.call(this, this.media, 'captionchange'); | ||||
|         utils.dispatchEvent.call(this, this.media, 'languagechange'); | ||||
|  | ||||
|         // Clear caption | ||||
|         captions.set.call(this); | ||||
| @ -797,31 +797,28 @@ class Plyr { | ||||
|  | ||||
|     // Show the player controls in fullscreen mode | ||||
|     toggleControls(toggle) { | ||||
|         const player = this; | ||||
|  | ||||
|         // We need controls of course... | ||||
|         if (!utils.is.htmlElement(this.elements.controls)) { | ||||
|             return player; | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         // Don't hide if config says not to, it's audio, or not ready or loading | ||||
|         if (!this.supported.ui || !this.config.hideControls || this.type === 'audio') { | ||||
|             return player; | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         let delay = 0; | ||||
|         let show = toggle; | ||||
|         let isEnterFullscreen = false; | ||||
|         const loading = utils.hasClass(this.elements.container, this.config.classNames.loading); | ||||
|  | ||||
|         // Default to false if no boolean | ||||
|         // Get toggle state if not set | ||||
|         if (!utils.is.boolean(toggle)) { | ||||
|             if (utils.is.event(toggle)) { | ||||
|                 // Is the enter fullscreen event | ||||
|                 isEnterFullscreen = toggle.type === 'enterfullscreen'; | ||||
|  | ||||
|                 // Whether to show controls | ||||
|                 show = ['mousemove', 'touchstart', 'mouseenter', 'focus'].includes(toggle.type); | ||||
|                 show = ['click', 'mousemove', 'touchmove', 'mouseenter', 'focusin'].includes(toggle.type); | ||||
|  | ||||
|                 // Delay hiding on move events | ||||
|                 if (['mousemove', 'touchmove'].includes(toggle.type)) { | ||||
| @ -829,8 +826,9 @@ class Plyr { | ||||
|                 } | ||||
|  | ||||
|                 // Delay a little more for keyboard users | ||||
|                 if (toggle.type === 'focus') { | ||||
|                 if (toggle.type === 'focusin') { | ||||
|                     delay = 3000; | ||||
|                     utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true); | ||||
|                 } | ||||
|             } else { | ||||
|                 show = utils.hasClass(this.elements.container, this.config.classNames.hideControls); | ||||
| @ -841,7 +839,7 @@ class Plyr { | ||||
|         window.clearTimeout(this.timers.hover); | ||||
|  | ||||
|         // If the mouse is not over the controls, set a timeout to hide them | ||||
|         if (show || this.media.paused || loading) { | ||||
|         if (show || this.media.paused || this.loading) { | ||||
|             // Check if controls toggled | ||||
|             const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, false); | ||||
|  | ||||
| @ -851,8 +849,8 @@ class Plyr { | ||||
|             } | ||||
|  | ||||
|             // Always show controls when paused or if touch | ||||
|             if (this.media.paused || loading) { | ||||
|                 return player; | ||||
|             if (this.media.paused || this.loading) { | ||||
|                 return this; | ||||
|             } | ||||
|  | ||||
|             // Delay for hiding on touch | ||||
| @ -870,6 +868,11 @@ class Plyr { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 // Restore transition behaviour | ||||
|                 if (!utils.hasClass(this.elements.container, this.config.classNames.hideControls)) { | ||||
|                     utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, false); | ||||
|                 } | ||||
|  | ||||
|                 // Check if controls toggled | ||||
|                 const toggled = utils.toggleClass(this.elements.container, this.config.classNames.hideControls, true); | ||||
|  | ||||
|  | ||||
							
								
								
									
										157
									
								
								src/js/ui.js
									
									
									
									
									
								
							
							
						
						
									
										157
									
								
								src/js/ui.js
									
									
									
									
									
								
							| @ -15,7 +15,7 @@ const ui = { | ||||
|     }, | ||||
|  | ||||
|     // Toggle native HTML5 media controls | ||||
|     toggleNativeControls(toggle) { | ||||
|     toggleNativeControls(toggle = false) { | ||||
|         if (toggle && this.isHTML5) { | ||||
|             this.media.setAttribute('controls', ''); | ||||
|         } else { | ||||
| @ -96,31 +96,8 @@ const ui = { | ||||
|         // Ready event at end of execution stack | ||||
|         utils.dispatchEvent.call(this, this.media, 'ready'); | ||||
|  | ||||
|         // Autoplay | ||||
|         // TODO: check we still need this? | ||||
|         /* if (this.isEmbed && this.config.autoplay) { | ||||
|             this.play(); | ||||
|         } */ | ||||
|     }, | ||||
|  | ||||
|     // Show the duration on metadataloaded | ||||
|     displayDuration() { | ||||
|         if (!this.supported.ui) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If there's only one time display, display duration there | ||||
|         if (!this.elements.display.duration && this.config.displayDuration && this.paused) { | ||||
|             ui.updateTimeDisplay.call(this, this.duration, this.elements.display.currentTime); | ||||
|         } | ||||
|  | ||||
|         // If there's a duration element, update content | ||||
|         if (this.elements.display.duration) { | ||||
|             ui.updateTimeDisplay.call(this, this.duration, this.elements.display.duration); | ||||
|         } | ||||
|  | ||||
|         // Update the tooltip (if visible) | ||||
|         controls.updateSeekTooltip.call(this); | ||||
|         // Set the title | ||||
|         ui.setTitle.call(this); | ||||
|     }, | ||||
|  | ||||
|     // Setup aria attribute for play and iframe title | ||||
| @ -137,13 +114,10 @@ const ui = { | ||||
|         } | ||||
|  | ||||
|         // If there's a play button, set label | ||||
|         if (this.supported.ui) { | ||||
|             if (utils.is.htmlElement(this.elements.buttons.play)) { | ||||
|                 this.elements.buttons.play.setAttribute('aria-label', label); | ||||
|             } | ||||
|             if (utils.is.htmlElement(this.elements.buttons.playLarge)) { | ||||
|                 this.elements.buttons.playLarge.setAttribute('aria-label', label); | ||||
|             } | ||||
|         if (utils.is.nodeList(this.elements.buttons.play)) { | ||||
|             Array.from(this.elements.buttons.play).forEach(button => { | ||||
|                 button.setAttribute('aria-label', label); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Set iframe title | ||||
| @ -171,23 +145,6 @@ const ui = { | ||||
|         this.toggleControls(this.paused); | ||||
|     }, | ||||
|  | ||||
|     // Update volume UI and storage | ||||
|     updateVolume() { | ||||
|         if (!this.supported.ui) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Update range | ||||
|         if (utils.is.htmlElement(this.elements.inputs.volume)) { | ||||
|             ui.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume); | ||||
|         } | ||||
|  | ||||
|         // Update checkbox for mute state | ||||
|         if (utils.is.htmlElement(this.elements.buttons.mute)) { | ||||
|             utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     // Check if media is loading | ||||
|     checkLoading(event) { | ||||
|         this.loading = event.type === 'waiting'; | ||||
| @ -205,8 +162,25 @@ const ui = { | ||||
|         }, this.loading ? 250 : 0); | ||||
|     }, | ||||
|  | ||||
|     // Update volume UI and storage | ||||
|     updateVolume() { | ||||
|         if (!this.supported.ui) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Update range | ||||
|         if (utils.is.htmlElement(this.elements.inputs.volume)) { | ||||
|             ui.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume); | ||||
|         } | ||||
|  | ||||
|         // Update checkbox for mute state | ||||
|         if (utils.is.htmlElement(this.elements.buttons.mute)) { | ||||
|             utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     // Update seek value and lower fill | ||||
|     setRange(target, value) { | ||||
|     setRange(target, value = 0) { | ||||
|         if (!utils.is.htmlElement(target)) { | ||||
|             return; | ||||
|         } | ||||
| @ -220,9 +194,8 @@ const ui = { | ||||
|  | ||||
|     // Set <progress> value | ||||
|     setProgress(target, input) { | ||||
|         // Default to 0 | ||||
|         const value = !utils.is.undefined(input) ? input : 0; | ||||
|         const progress = !utils.is.undefined(target) ? target : this.elements.display.buffer; | ||||
|         const value = utils.is.number(input) ? input : 0; | ||||
|         const progress = utils.is.htmlElement(target) ? target : this.elements.display.buffer; | ||||
|  | ||||
|         // Update value and label | ||||
|         if (utils.is.htmlElement(progress)) { | ||||
| @ -238,7 +211,7 @@ const ui = { | ||||
|  | ||||
|     // Update <progress> elements | ||||
|     updateProgress(event) { | ||||
|         if (!this.supported.ui) { | ||||
|         if (!this.supported.ui || !utils.is.event(event)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @ -286,41 +259,49 @@ const ui = { | ||||
|     }, | ||||
|  | ||||
|     // Update the displayed time | ||||
|     updateTimeDisplay(value, element) { | ||||
|         // Bail if there's no duration display | ||||
|         if (!utils.is.htmlElement(element)) { | ||||
|             return null; | ||||
|     updateTimeDisplay(target = null, time = 0, inverted = false) { | ||||
|         // Bail if there's no element to display or the value isn't a number | ||||
|         if (!utils.is.htmlElement(target) || !utils.is.number(time)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Fallback to 0 | ||||
|         const time = !Number.isNaN(value) ? value : 0; | ||||
|         // Format time component to add leading zero | ||||
|         const format = value => `0${value}`.slice(-2); | ||||
|  | ||||
|         let secs = parseInt(time % 60, 10); | ||||
|         let mins = parseInt((time / 60) % 60, 10); | ||||
|         const hours = parseInt((time / 60 / 60) % 60, 10); | ||||
|         // Helpers | ||||
|         const getHours = value => parseInt((value / 60 / 60) % 60, 10); | ||||
|         const getMinutes = value => parseInt((value / 60) % 60, 10); | ||||
|         const getSeconds = value => parseInt(value % 60, 10); | ||||
|  | ||||
|         // Breakdown to hours, mins, secs | ||||
|         let hours = getHours(time); | ||||
|         const mins = getMinutes(time); | ||||
|         const secs = getSeconds(time); | ||||
|  | ||||
|         // Do we need to display hours? | ||||
|         const displayHours = parseInt((this.duration / 60 / 60) % 60, 10) > 0; | ||||
|  | ||||
|         // Ensure it's two digits. For example, 03 rather than 3. | ||||
|         secs = `0${secs}`.slice(-2); | ||||
|         mins = `0${mins}`.slice(-2); | ||||
|  | ||||
|         // Generate display | ||||
|         const display = `${(displayHours ? `${hours}:` : '') + mins}:${secs}`; | ||||
|         if (getHours(this.duration) > 0) { | ||||
|             hours = `${hours}:`; | ||||
|         } else { | ||||
|             hours = ''; | ||||
|         } | ||||
|  | ||||
|         // Render | ||||
|         // eslint-disable-next-line | ||||
|         element.textContent = display; | ||||
|  | ||||
|         // Return for looping | ||||
|         return display; | ||||
|         // eslint-disable-next-line no-param-reassign | ||||
|         target.textContent = `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`; | ||||
|     }, | ||||
|  | ||||
|     // Handle time change event | ||||
|     timeUpdate(event) { | ||||
|         // Only invert if only one time element is displayed and used for both duration and currentTime | ||||
|         const invert = !utils.is.htmlElement(this.elements.display.duration) && this.config.invertTime; | ||||
|  | ||||
|         // Duration | ||||
|         ui.updateTimeDisplay.call(this, this.currentTime, this.elements.display.currentTime); | ||||
|         ui.updateTimeDisplay.call( | ||||
|             this, | ||||
|             this.elements.display.currentTime, | ||||
|             invert ? this.duration - this.currentTime : this.currentTime, | ||||
|             invert | ||||
|         ); | ||||
|  | ||||
|         // Ignore updates while seeking | ||||
|         if (event && event.type === 'timeupdate' && this.media.seeking) { | ||||
| @ -330,6 +311,26 @@ const ui = { | ||||
|         // Playing progress | ||||
|         ui.updateProgress.call(this, event); | ||||
|     }, | ||||
|  | ||||
|     // Show the duration on metadataloaded | ||||
|     durationUpdate() { | ||||
|         if (!this.supported.ui) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // If there's only one time display, display duration there | ||||
|         if (!utils.is.htmlElement(this.elements.display.duration) && this.config.displayDuration && this.paused) { | ||||
|             ui.updateTimeDisplay.call(this, this.elements.display.currentTime, this.duration); | ||||
|         } | ||||
|  | ||||
|         // If there's a duration element, update content | ||||
|         if (utils.is.htmlElement(this.elements.display.duration)) { | ||||
|             ui.updateTimeDisplay.call(this, this.elements.display.duration, this.duration); | ||||
|         } | ||||
|  | ||||
|         // Update the tooltip (if visible) | ||||
|         controls.updateSeekTooltip.call(this); | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| export default ui; | ||||
|  | ||||
| @ -31,6 +31,9 @@ const utils = { | ||||
|         htmlElement(input) { | ||||
|             return !this.undefined(input) && input instanceof HTMLElement; | ||||
|         }, | ||||
|         textNode(input) { | ||||
|             return this.getConstructor(input) === Text; | ||||
|         }, | ||||
|         event(input) { | ||||
|             return !this.undefined(input) && input instanceof Event; | ||||
|         }, | ||||
| @ -49,8 +52,8 @@ const utils = { | ||||
|             return ( | ||||
|                 input === null || | ||||
|                 typeof input === 'undefined' || | ||||
|                 ((this.string(input) || this.array(input) || this.nodeList(input)) && input.length === 0) || | ||||
|                 (this.object(input) && Object.keys(input).length === 0) | ||||
|                 ((this.string(input) || this.array(input) || this.nodeList(input)) && !input.length) || | ||||
|                 (this.object(input) && !Object.keys(input).length) | ||||
|             ); | ||||
|         }, | ||||
|         getConstructor(input) { | ||||
| @ -100,12 +103,12 @@ const utils = { | ||||
|  | ||||
|     // Load an external SVG sprite | ||||
|     loadSprite(url, id) { | ||||
|         if (typeof url !== 'string') { | ||||
|         if (!utils.is.string(url)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         const prefix = 'cache-'; | ||||
|         const hasId = typeof id === 'string'; | ||||
|         const hasId = utils.is.string(id); | ||||
|         let isCached = false; | ||||
|  | ||||
|         function updateSprite(data) { | ||||
| @ -134,34 +137,30 @@ const utils = { | ||||
|                 if (isCached) { | ||||
|                     const data = JSON.parse(cached); | ||||
|                     updateSprite.call(container, data.content); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // ReSharper disable once InconsistentNaming | ||||
|             const xhr = new XMLHttpRequest(); | ||||
|             // Get the sprite | ||||
|             fetch(url) | ||||
|                 .then(response => (response.ok ? response.text() : null)) | ||||
|                 .then(text => { | ||||
|                     if (text === null) { | ||||
|                         return; | ||||
|                     } | ||||
|  | ||||
|             // XHR for Chrome/Firefox/Opera/Safari | ||||
|             if ('withCredentials' in xhr) { | ||||
|                 xhr.open('GET', url, true); | ||||
|             } else { | ||||
|                 return; | ||||
|             } | ||||
|                     if (support.storage) { | ||||
|                         window.localStorage.setItem( | ||||
|                             prefix + id, | ||||
|                             JSON.stringify({ | ||||
|                                 content: text, | ||||
|                             }) | ||||
|                         ); | ||||
|                     } | ||||
|  | ||||
|             // Once loaded, inject to container and body | ||||
|             xhr.onload = () => { | ||||
|                 if (support.storage) { | ||||
|                     window.localStorage.setItem( | ||||
|                         prefix + id, | ||||
|                         JSON.stringify({ | ||||
|                             content: xhr.responseText, | ||||
|                         }) | ||||
|                     ); | ||||
|                 } | ||||
|  | ||||
|                 updateSprite.call(container, xhr.responseText); | ||||
|             }; | ||||
|  | ||||
|             xhr.send(); | ||||
|                     updateSprite.call(container, text); | ||||
|                 }) | ||||
|                 .catch(() => {}); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| @ -210,22 +209,6 @@ const utils = { | ||||
|             }); | ||||
|     }, | ||||
|  | ||||
|     // Remove an element | ||||
|     removeElement(element) { | ||||
|         if (!utils.is.htmlElement(element) || !utils.is.htmlElement(element.parentNode)) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         element.parentNode.removeChild(element); | ||||
|  | ||||
|         return element; | ||||
|     }, | ||||
|  | ||||
|     // Inaert an element after another | ||||
|     insertAfter(element, target) { | ||||
|         target.parentNode.insertBefore(element, target.nextSibling); | ||||
|     }, | ||||
|  | ||||
|     // Create a DocumentFragment | ||||
|     createElement(type, attributes, text) { | ||||
|         // Create a new <element> | ||||
| @ -245,12 +228,28 @@ const utils = { | ||||
|         return element; | ||||
|     }, | ||||
|  | ||||
|     // Inaert an element after another | ||||
|     insertAfter(element, target) { | ||||
|         target.parentNode.insertBefore(element, target.nextSibling); | ||||
|     }, | ||||
|  | ||||
|     // Insert a DocumentFragment | ||||
|     insertElement(type, parent, attributes, text) { | ||||
|         // Inject the new <element> | ||||
|         parent.appendChild(utils.createElement(type, attributes, text)); | ||||
|     }, | ||||
|  | ||||
|     // Remove an element | ||||
|     removeElement(element) { | ||||
|         if (!utils.is.htmlElement(element) || !utils.is.htmlElement(element.parentNode)) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         element.parentNode.removeChild(element); | ||||
|  | ||||
|         return element; | ||||
|     }, | ||||
|  | ||||
|     // Remove all child elements | ||||
|     emptyElement(element) { | ||||
|         let { length } = element.childNodes; | ||||
| @ -442,9 +441,9 @@ const utils = { | ||||
|  | ||||
|     // Trap focus inside container | ||||
|     trapFocus() { | ||||
|         const tabbables = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]'); | ||||
|         const first = tabbables[0]; | ||||
|         const last = tabbables[tabbables.length - 1]; | ||||
|         const focusable = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]'); | ||||
|         const first = focusable[0]; | ||||
|         const last = focusable[focusable.length - 1]; | ||||
|  | ||||
|         utils.on( | ||||
|             this.elements.container, | ||||
| @ -525,7 +524,7 @@ const utils = { | ||||
|     }, | ||||
|  | ||||
|     // Trigger event | ||||
|     dispatchEvent(element, type, bubbles, properties) { | ||||
|     dispatchEvent(element, type, bubbles, detail) { | ||||
|         // Bail if no element | ||||
|         if (!element || !type) { | ||||
|             return; | ||||
| @ -534,7 +533,7 @@ const utils = { | ||||
|         // Create and dispatch the event | ||||
|         const event = new CustomEvent(type, { | ||||
|             bubbles: utils.is.boolean(bubbles) ? bubbles : false, | ||||
|             detail: Object.assign({}, properties, { | ||||
|             detail: Object.assign({}, detail, { | ||||
|                 plyr: this instanceof Plyr ? this : null, | ||||
|             }), | ||||
|         }); | ||||
|  | ||||
		Reference in New Issue
	
	Block a user