aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/js/captions.js94
-rw-r--r--src/js/controls.js36
-rw-r--r--src/js/defaults.js20
-rw-r--r--src/js/html5.js10
-rw-r--r--src/js/i18n.js8
-rw-r--r--src/js/listeners.js11
-rw-r--r--src/js/plyr.js48
-rw-r--r--src/js/plyr.polyfilled.js2
-rw-r--r--src/js/storage.js2
-rw-r--r--src/js/ui.js12
-rw-r--r--src/js/utils.js31
-rw-r--r--src/sass/components/progress.scss30
-rw-r--r--src/sass/plyr.scss4
13 files changed, 151 insertions, 157 deletions
diff --git a/src/js/captions.js b/src/js/captions.js
index fadab43f..538882da 100644
--- a/src/js/captions.js
+++ b/src/js/captions.js
@@ -16,28 +16,6 @@ const captions = {
return;
}
- // Set default language if not set
- const stored = this.storage.get('language');
-
- if (!utils.is.empty(stored)) {
- this.captions.language = stored;
- }
-
- if (utils.is.empty(this.captions.language)) {
- this.captions.language = this.config.captions.language.toLowerCase();
- }
-
- // Set captions enabled state if not set
- if (!utils.is.boolean(this.captions.active)) {
- const active = this.storage.get('captions');
-
- if (utils.is.boolean(active)) {
- this.captions.active = active;
- } else {
- this.captions.active = this.config.captions.active;
- }
- }
-
// Only Vimeo and HTML5 video supported at this point
if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
// Clear menu and hide
@@ -55,17 +33,6 @@ const captions = {
utils.insertAfter(this.elements.captions, this.elements.wrapper);
}
- // Set the class hook
- utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
-
- // Get tracks
- const tracks = captions.getTracks.call(this);
-
- // If no caption file exists, hide container for caption text
- if (utils.is.empty(tracks)) {
- return;
- }
-
// Get browser info
const browser = utils.getBrowser();
@@ -94,14 +61,45 @@ const captions = {
});
}
- // Set language
- captions.setLanguage.call(this);
+ // Try to load the value from storage
+ let active = this.storage.get('captions');
+
+ // Otherwise fall back to the default config
+ if (!utils.is.boolean(active)) {
+ ({ active } = this.config.captions);
+ }
- // Enable UI
- captions.show.call(this);
+ // Set toggled state
+ this.toggleCaptions(active);
- // Set available languages in list
- if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
+ // Watch changes to textTracks and update captions menu
+ if (this.config.captions.update) {
+ utils.on(this.media.textTracks, 'addtrack removetrack', captions.update.bind(this));
+ }
+
+ // Update available languages in list next tick (the event must not be triggered before the listeners)
+ setTimeout(captions.update.bind(this), 0);
+ },
+
+ update() {
+ // Update tracks
+ const tracks = captions.getTracks.call(this);
+ this.options.captions = tracks.map(({ language }) => language);
+
+ // Set language if it hasn't been set already
+ if (!this.language) {
+ let { language } = this.config.captions;
+ if (language === 'auto') {
+ [language] = (navigator.language || navigator.userLanguage).split('-');
+ }
+ this.language = this.storage.get('language') || (language || '').toLowerCase();
+ }
+
+ // Toggle the class hooks
+ utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
+
+ // Update available languages in list
+ if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this);
}
},
@@ -247,24 +245,6 @@ const captions = {
this.debug.warn('No captions element to render to');
}
},
-
- // Display captions container and button (for initialization)
- show() {
- // Try to load the value from storage
- let active = this.storage.get('captions');
-
- // Otherwise fall back to the default config
- if (!utils.is.boolean(active)) {
- ({ active } = this.config.captions);
- } else {
- this.captions.active = active;
- }
-
- if (active) {
- utils.toggleClass(this.elements.container, this.config.classNames.captions.active, true);
- this.elements.buttons.captions.pressed = true;
- }
- },
};
export default captions;
diff --git a/src/js/controls.js b/src/js/controls.js
index fc000b52..e2b4ed1a 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -659,27 +659,7 @@ const controls = {
// Get the badge HTML for HD, 4K etc
const getBadge = quality => {
- let label = '';
-
- switch (quality) {
- case 2160:
- label = '4K';
- break;
-
- case 1440:
- case 1080:
- case 720:
- label = 'HD';
- break;
-
- case 576:
- case 480:
- label = 'SD';
- break;
-
- default:
- break;
- }
+ const label = i18n.get(`qualityBadge.${quality}`, this.config);
if (!label.length) {
return null;
@@ -703,7 +683,6 @@ const controls = {
},
// Translate a value into a nice label
- // TODO: Localisation
getLabel(setting, value) {
switch (setting) {
case 'speed':
@@ -711,7 +690,13 @@ const controls = {
case 'quality':
if (utils.is.number(value)) {
- return `${value}p`;
+ const label = i18n.get(`qualityLabel.${value}`, this.config);
+
+ if (!label.length) {
+ return `${value}p`;
+ }
+
+ return label;
}
return utils.toTitleCase(value);
@@ -878,13 +863,10 @@ const controls = {
'language',
track.label,
track.language !== 'enabled' ? controls.createBadge.call(this, track.language.toUpperCase()) : null,
- track.language.toLowerCase() === this.captions.language.toLowerCase(),
+ track.language.toLowerCase() === this.language,
);
});
- // Store reference
- this.options.captions = tracks.map(track => track.language);
-
controls.updateSetting.call(this, type, list);
},
diff --git a/src/js/defaults.js b/src/js/defaults.js
index 54c19f94..78371d68 100644
--- a/src/js/defaults.js
+++ b/src/js/defaults.js
@@ -60,7 +60,7 @@ const defaults = {
// Sprite (for icons)
loadSprite: true,
iconPrefix: 'plyr',
- iconUrl: 'https://cdn.plyr.io/3.3.8/plyr.svg',
+ iconUrl: 'https://cdn.plyr.io/3.3.10/plyr.svg',
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@@ -119,7 +119,10 @@ const defaults = {
// Captions settings
captions: {
active: false,
- language: (navigator.language || navigator.userLanguage).split('-')[0],
+ language: 'auto',
+ // Listen to new tracks added after Plyr is initialized.
+ // This is needed for streaming captions, but may result in unselectable options
+ update: false,
},
// Fullscreen settings
@@ -191,6 +194,14 @@ const defaults = {
disabled: 'Disabled',
enabled: 'Enabled',
advertisement: 'Ad',
+ qualityBadge: {
+ 2160: '4K',
+ 1440: 'HD',
+ 1080: 'HD',
+ 720: 'HD',
+ 576: 'SD',
+ 480: 'SD',
+ },
},
// URLs
@@ -315,9 +326,8 @@ const defaults = {
display: {
currentTime: '.plyr__time--current',
duration: '.plyr__time--duration',
- buffer: '.plyr__progress--buffer',
- played: '.plyr__progress--played',
- loop: '.plyr__progress--loop',
+ buffer: '.plyr__progress__buffer',
+ loop: '.plyr__progress__loop', // Used later
volume: '.plyr__volume--display',
},
progress: '.plyr__progress',
diff --git a/src/js/html5.js b/src/js/html5.js
index 3818a441..63596cfc 100644
--- a/src/js/html5.js
+++ b/src/js/html5.js
@@ -99,6 +99,13 @@ const html5 = {
// Set new source
player.media.src = supported[0].getAttribute('src');
+ // Restore time
+ const onLoadedMetaData = () => {
+ player.currentTime = currentTime;
+ player.off('loadedmetadata', onLoadedMetaData);
+ };
+ player.on('loadedmetadata', onLoadedMetaData);
+
// Load new source
player.media.load();
@@ -107,9 +114,6 @@ const html5 = {
player.play();
}
- // Restore time
- player.currentTime = currentTime;
-
// Trigger change event
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
quality: input,
diff --git a/src/js/i18n.js b/src/js/i18n.js
index 58c3e7cf..62e5bdb0 100644
--- a/src/js/i18n.js
+++ b/src/js/i18n.js
@@ -6,11 +6,15 @@ import utils from './utils';
const i18n = {
get(key = '', config = {}) {
- if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
+ if (utils.is.empty(key) || utils.is.empty(config)) {
return '';
}
- let string = config.i18n[key];
+ let string = utils.getDeep(config.i18n, key);
+
+ if (utils.is.empty(string)) {
+ return '';
+ }
const replace = {
'{seektime}': config.seekTime,
diff --git a/src/js/listeners.js b/src/js/listeners.js
index 86236fe3..81f5271c 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -74,7 +74,10 @@ class Listeners {
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
const focused = utils.getFocusElement();
- if (utils.is.element(focused) && utils.matches(focused, this.player.config.selectors.editable)) {
+ if (utils.is.element(focused) && (
+ focused !== this.player.elements.inputs.seek &&
+ utils.matches(focused, this.player.config.selectors.editable))
+ ) {
return;
}
@@ -560,6 +563,12 @@ class Listeners {
on(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
const seek = event.currentTarget;
+ const code = event.keyCode ? event.keyCode : event.which;
+ const eventType = event.type;
+
+ if ((eventType === 'keydown' || eventType === 'keyup') && (code !== 39 && code !== 37)) {
+ return;
+ }
// Was playing before?
const play = seek.hasAttribute('play-on-seeked');
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 2cf5d58d..ce3d3be5 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
-// plyr.js v3.3.8
+// plyr.js v3.3.10
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
@@ -432,21 +432,16 @@ class Plyr {
* @param {number} input - where to seek to in seconds. Defaults to 0 (the start)
*/
set currentTime(input) {
- let targetTime = 0;
-
- if (utils.is.number(input)) {
- targetTime = input;
+ // Bail if media duration isn't available yet
+ if (!this.duration) {
+ return;
}
- // Normalise targetTime
- if (targetTime < 0) {
- targetTime = 0;
- } else if (targetTime > this.duration) {
- targetTime = this.duration;
- }
+ // Validate input
+ const inputIsValid = utils.is.number(input) && input > 0;
// Set
- this.media.currentTime = targetTime;
+ this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0;
// Logging
this.debug.log(`Seeking to ${this.currentTime} seconds`);
@@ -494,11 +489,11 @@ class Plyr {
// Faux duration set via config
const fauxDuration = parseFloat(this.config.duration);
- // True duration
- const realDuration = this.media ? Number(this.media.duration) : 0;
+ // Media duration can be NaN before the media has loaded
+ const duration = (this.media || {}).duration || 0;
- // If custom duration is funky, use regular duration
- return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
+ // If config duration is funky, use regular duration
+ return fauxDuration || duration;
}
/**
@@ -680,7 +675,7 @@ class Plyr {
quality = Number(input);
}
- if (!utils.is.number(quality) || quality === 0) {
+ if (!utils.is.number(quality)) {
quality = this.storage.get('quality');
}
@@ -843,24 +838,19 @@ class Plyr {
}
// If the method is called without parameter, toggle based on current value
- const show = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
-
- // Nothing to change...
- if (this.captions.active === show) {
- return;
- }
-
- // Set global
- this.captions.active = show;
+ const active = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
// Toggle state
- this.elements.buttons.captions.pressed = this.captions.active;
+ this.elements.buttons.captions.pressed = active;
// Add class hook
- utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.captions.active);
+ utils.toggleClass(this.elements.container, this.config.classNames.captions.active, active);
- // Trigger an event
+ // Update state and trigger event
+ if (active !== this.captions.active) {
+ this.captions.active = active;
utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
+ }
}
/**
diff --git a/src/js/plyr.polyfilled.js b/src/js/plyr.polyfilled.js
index 9570d753..f66a82de 100644
--- a/src/js/plyr.polyfilled.js
+++ b/src/js/plyr.polyfilled.js
@@ -1,6 +1,6 @@
// ==========================================================================
// Plyr Polyfilled Build
-// plyr.js v3.3.8
+// plyr.js v3.3.10
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
diff --git a/src/js/storage.js b/src/js/storage.js
index 5b914331..e4dc9e1b 100644
--- a/src/js/storage.js
+++ b/src/js/storage.js
@@ -31,7 +31,7 @@ class Storage {
}
get(key) {
- if (!Storage.supported) {
+ if (!Storage.supported || !this.enabled) {
return null;
}
diff --git a/src/js/ui.js b/src/js/ui.js
index 5b14e2fe..e90a1492 100644
--- a/src/js/ui.js
+++ b/src/js/ui.js
@@ -55,8 +55,10 @@ const ui = {
// Remove native controls
ui.toggleNativeControls.call(this);
- // Captions
- captions.setup.call(this);
+ // Setup captions for HTML5
+ if (this.isHTML5) {
+ captions.setup.call(this);
+ }
// Reset volume
this.volume = null;
@@ -109,6 +111,12 @@ const ui = {
if (this.poster && this.elements.poster && !this.elements.poster.style.backgroundImage) {
ui.setPoster.call(this, this.poster);
}
+
+ // Manually set the duration if user has overridden it.
+ // The event listeners for it doesn't get called if preload is disabled (#701)
+ if (this.config.duration) {
+ controls.durationUpdate.call(this);
+ }
},
// Setup aria attribute for play and iframe title
diff --git a/src/js/utils.js b/src/js/utils.js
index 201c06c8..216cd341 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -44,7 +44,7 @@ const utils = {
return this.instanceof(input, Event);
},
cue(input) {
- return this.instanceof(input, TextTrackCue) || this.instanceof(input, VTTCue);
+ return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue);
},
track(input) {
return this.instanceof(input, TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind));
@@ -151,24 +151,23 @@ const utils = {
return;
}
- const prefix = 'cache-';
+ const prefix = 'cache';
const hasId = utils.is.string(id);
let isCached = false;
- const exists = () => document.querySelectorAll(`#${id}`).length;
+ const exists = () => document.getElementById(id) !== null;
+
+ const update = (container, data) => {
+ container.innerHTML = data;
- function injectSprite(data) {
// Check again incase of race condition
if (hasId && exists()) {
return;
}
- // Inject content
- this.innerHTML = data;
-
// Inject the SVG to the body
- document.body.insertBefore(this, document.body.childNodes[0]);
- }
+ document.body.insertAdjacentElement('afterbegin', container);
+ };
// Only load once if ID set
if (!hasId || !exists()) {
@@ -184,13 +183,12 @@ const utils = {
// Check in cache
if (useStorage) {
- const cached = window.localStorage.getItem(prefix + id);
+ const cached = window.localStorage.getItem(`${prefix}-${id}`);
isCached = cached !== null;
if (isCached) {
const data = JSON.parse(cached);
- injectSprite.call(container, data.content);
- return;
+ update(container, data.content);
}
}
@@ -204,14 +202,14 @@ const utils = {
if (useStorage) {
window.localStorage.setItem(
- prefix + id,
+ `${prefix}-${id}`,
JSON.stringify({
content: result,
}),
);
}
- injectSprite.call(container, result);
+ update(container, result);
})
.catch(() => {});
}
@@ -706,6 +704,11 @@ const utils = {
return JSON.parse(JSON.stringify(object));
},
+ // Get a nested value in an object
+ getDeep(object, path) {
+ return path.split('.').reduce((obj, key) => obj && obj[key], object);
+ },
+
// Get the closest value in an array
closest(array, value) {
if (!utils.is.array(array) || !array.length) {
diff --git a/src/sass/components/progress.scss b/src/sass/components/progress.scss
index 60994f99..eddd32ab 100644
--- a/src/sass/components/progress.scss
+++ b/src/sass/components/progress.scss
@@ -5,16 +5,21 @@
.plyr__progress {
display: flex;
flex: 1;
- position: relative;
- margin-right: $plyr-range-thumb-height;
left: $plyr-range-thumb-height / 2;
+ margin-right: $plyr-range-thumb-height;
+ position: relative;
+
+ input[type='range'],
+ &__buffer {
+ margin-left: -($plyr-range-thumb-height / 2);
+ margin-right: -($plyr-range-thumb-height / 2);
+ // Offset the range thumb in order to be able to calculate the relative progress (#954)
+ width: calc(100% + #{$plyr-range-thumb-height});
+ }
input[type='range'] {
position: relative;
z-index: 2;
- // Offset the range thumb in order to be able to calculate the relative progress (#954)
- width: calc(100% + #{$plyr-range-thumb-height}) !important;
- margin: 0 -#{$plyr-range-thumb-height / 2} !important;
}
// Seek tooltip to show time
@@ -24,18 +29,17 @@
}
}
-.plyr__progress--buffer {
+.plyr__progress__buffer {
-webkit-appearance: none; /* stylelint-disable-line */
background: transparent;
border: 0;
border-radius: 100px;
height: $plyr-range-track-height;
left: 0;
- margin: -($plyr-range-track-height / 2) 0 0;
+ margin-top: -($plyr-range-track-height / 2);
padding: 0;
position: absolute;
top: 50%;
- width: 100%;
&::-webkit-progress-bar {
background: transparent;
@@ -63,17 +67,17 @@
}
}
-.plyr--video .plyr__progress--buffer {
+.plyr--video .plyr__progress__buffer {
box-shadow: 0 1px 1px rgba(#000, 0.15);
color: $plyr-video-progress-buffered-bg;
}
-.plyr--audio .plyr__progress--buffer {
+.plyr--audio .plyr__progress__buffer {
color: $plyr-audio-progress-buffered-bg;
}
// Loading state
-.plyr--loading .plyr__progress--buffer {
+.plyr--loading .plyr__progress__buffer {
animation: plyr-progress 1s linear infinite;
background-image: linear-gradient(
-45deg,
@@ -90,10 +94,10 @@
color: transparent;
}
-.plyr--video.plyr--loading .plyr__progress--buffer {
+.plyr--video.plyr--loading .plyr__progress__buffer {
background-color: $plyr-video-progress-buffered-bg;
}
-.plyr--audio.plyr--loading .plyr__progress--buffer {
+.plyr--audio.plyr--loading .plyr__progress__buffer {
background-color: $plyr-audio-progress-buffered-bg;
}
diff --git a/src/sass/plyr.scss b/src/sass/plyr.scss
index e934cf92..3d824f7d 100644
--- a/src/sass/plyr.scss
+++ b/src/sass/plyr.scss
@@ -31,12 +31,12 @@
@import 'components/controls';
@import 'components/embed';
@import 'components/menus';
-@import 'components/progress';
-@import 'components/poster';
@import 'components/sliders';
+@import 'components/poster';
@import 'components/times';
@import 'components/tooltips';
@import 'components/video';
+@import 'components/progress';
@import 'components/volume';
@import 'states/fullscreen';