aboutsummaryrefslogtreecommitdiffstats
path: root/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js')
-rw-r--r--src/js/captions.js3
-rw-r--r--src/js/controls.js171
-rw-r--r--src/js/defaults.js17
-rw-r--r--src/js/listeners.js39
-rw-r--r--src/js/plugins/youtube.js28
-rw-r--r--src/js/plyr.js27
-rw-r--r--src/js/ui.js139
-rw-r--r--src/js/utils.js54
8 files changed, 262 insertions, 216 deletions
diff --git a/src/js/captions.js b/src/js/captions.js
index 247bf5db..2cab9414 100644
--- a/src/js/captions.js
+++ b/src/js/captions.js
@@ -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}`);
}
});
};
diff --git a/src/js/controls.js b/src/js/controls.js
index 022fab0d..7df41371 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -115,6 +115,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,
});
@@ -319,6 +323,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
@@ -353,7 +390,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}%`;
@@ -390,6 +427,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
@@ -401,7 +439,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) {
@@ -443,35 +481,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,
- })
- );
-
- 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);
- });
+ this.options.quality.forEach(quality =>
+ controls.createMenuItem.call(
+ this,
+ quality,
+ list,
+ type,
+ controls.getLabel.call(this, 'quality', quality),
+ getBadge(quality)
+ )
+ );
- controls.updateSetting.call(this, 'quality', list);
+ controls.updateSetting.call(this, type, list);
},
// Translate a value into a nice label
@@ -573,7 +594,7 @@ const controls = {
},
// Set the looping options
- setLoopMenu() {
+ /* setLoopMenu() {
const options = ['start', 'end', 'all', 'reset'];
const list = this.elements.settings.panes.loop.querySelector('ul');
@@ -609,7 +630,7 @@ const controls = {
item.appendChild(button);
list.appendChild(item);
});
- },
+ }, */
// Get current selected caption language
// TODO: rework this to user the getter in the API?
@@ -631,11 +652,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);
@@ -648,7 +671,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(),
}));
@@ -660,41 +682,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));
@@ -704,7 +709,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) {
@@ -722,39 +727,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,
- })
- );
-
- label.appendChild(radio);
- label.insertAdjacentHTML('beforeend', controls.getLabel.call(this, 'speed', speed));
- item.appendChild(label);
- list.appendChild(item);
- });
+ this.options.speed.forEach(speed =>
+ controls.createMenuItem.call(this, speed, list, type, controls.getLabel.call(this, 'speed', speed))
+ );
- 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
@@ -771,10 +760,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) {
@@ -882,6 +872,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
diff --git a/src/js/defaults.js b/src/js/defaults.js
index 14c7204d..c9a81842 100644
--- a/src/js/defaults.js
+++ b/src/js/defaults.js
@@ -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
@@ -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;
diff --git a/src/js/listeners.js b/src/js/listeners.js
index 5c366803..61df32aa 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -209,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);
}
@@ -217,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
@@ -230,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', () => {
@@ -463,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
@@ -479,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', () => {
@@ -488,7 +493,7 @@ const listeners = {
this.console.warn('Set loop');
});
- }
+ } */
});
// Seek
@@ -498,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', () => {
@@ -533,7 +552,7 @@ const listeners = {
// TODO: Check we need capture here
utils.on(
this.elements.controls,
- 'focus blur',
+ 'focusin focusout',
event => {
this.toggleControls(event);
},
diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js
index 6b70af4c..a46773a0 100644
--- a/src/js/plugins/youtube.js
+++ b/src/js/plugins/youtube.js
@@ -23,19 +23,21 @@ const youtube = {
// Set ID
this.media.setAttribute('id', utils.generateId(this.type));
- // Get the title
- const key = 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c';
- const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&fields=items(snippet(title))&part=snippet&key=${key}`;
-
- fetch(url)
- .then(response => response.json())
- .then(obj => {
- if (utils.is.object(obj)) {
- this.config.title = obj.items[0].snippet.title;
- ui.setTitle.call(this);
- }
- })
- .catch(() => {});
+ // 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)) {
diff --git a/src/js/plyr.js b/src/js/plyr.js
index df751efe..87fb014a 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -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;
}
@@ -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);
diff --git a/src/js/ui.js b/src/js/ui.js
index b9328888..fe8fac0f 100644
--- a/src/js/ui.js
+++ b/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 {
@@ -100,26 +100,6 @@ const ui = {
ui.setTitle.call(this);
},
- // 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);
- },
-
// Setup aria attribute for play and iframe title
setTitle() {
// Find the current text
@@ -165,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';
@@ -199,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;
}
@@ -214,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)) {
@@ -232,7 +211,7 @@ const ui = {
// Update <progress> elements
updateProgress(event) {
- if (!this.supported.ui) {
+ if (!this.supported.ui || !utils.is.event(event)) {
return;
}
@@ -280,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);
- // Do we need to display hours?
- const displayHours = parseInt((this.duration / 60 / 60) % 60, 10) > 0;
+ // Breakdown to hours, mins, secs
+ let hours = getHours(time);
+ const mins = getMinutes(time);
+ const secs = getSeconds(time);
- // 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}`;
+ // Do we need to display hours?
+ 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) {
@@ -324,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;
diff --git a/src/js/utils.js b/src/js/utils.js
index 53a16e4b..bb576576 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -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) {
@@ -140,8 +143,12 @@ const utils = {
// Get the sprite
fetch(url)
- .then(response => response.text())
+ .then(response => (response.ok ? response.text() : null))
.then(text => {
+ if (text === null) {
+ return;
+ }
+
if (support.storage) {
window.localStorage.setItem(
prefix + id,
@@ -152,7 +159,8 @@ const utils = {
}
updateSprite.call(container, text);
- });
+ })
+ .catch(() => {});
}
},
@@ -201,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>
@@ -236,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;
@@ -433,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,