aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSam Potts <sam@potts.es>2018-04-25 07:38:17 +1000
committerGitHub <noreply@github.com>2018-04-25 07:38:17 +1000
commitf13260c10aad1e7e95f1d13a31c7f362c674ddb6 (patch)
treead4e351f2d3603325e2d388bdc64df8f15cd98b8 /src
parente138e6d51e3cd85d85ccbd32674f75a63a3771ef (diff)
parentf1b275aedce897b42f025afac7a0937dc5871235 (diff)
downloadplyr-f13260c10aad1e7e95f1d13a31c7f362c674ddb6.tar.lz
plyr-f13260c10aad1e7e95f1d13a31c7f362c674ddb6.tar.xz
plyr-f13260c10aad1e7e95f1d13a31c7f362c674ddb6.zip
Merge pull request #919 from sampotts/master
Merge back
Diffstat (limited to 'src')
-rw-r--r--src/js/captions.js51
-rw-r--r--src/js/controls.js100
-rw-r--r--src/js/defaults.js5
-rw-r--r--src/js/fullscreen.js2
-rw-r--r--src/js/listeners.js7
-rw-r--r--src/js/plugins/vimeo.js29
-rw-r--r--src/js/plugins/youtube.js8
-rw-r--r--src/js/plyr.js46
-rw-r--r--src/js/plyr.polyfilled.js2
-rw-r--r--src/js/source.js4
-rw-r--r--src/js/support.js10
-rw-r--r--src/sass/components/embed.scss25
12 files changed, 204 insertions, 85 deletions
diff --git a/src/js/captions.js b/src/js/captions.js
index c8bc5833..c6618fda 100644
--- a/src/js/captions.js
+++ b/src/js/captions.js
@@ -6,6 +6,7 @@
import support from './support';
import utils from './utils';
import controls from './controls';
+import i18n from './i18n';
const captions = {
// Setup captions
@@ -46,6 +47,7 @@ const captions = {
return;
}
+
// Inject the container
if (!utils.is.element(this.elements.captions)) {
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
@@ -148,7 +150,49 @@ const captions = {
// Get the current track for the current language
getCurrentTrack() {
- return captions.getTracks.call(this).find(track => track.language.toLowerCase() === this.language);
+ const tracks = captions.getTracks.call(this);
+
+ if (!tracks.length) {
+ return null;
+ }
+
+ // Get track based on current language
+ let track = tracks.find(track => track.language.toLowerCase() === this.language);
+
+ // Get the <track> with default attribute
+ if (!track) {
+ track = utils.getElement.call(this, 'track[default]');
+ }
+
+ // Get the first track
+ if (!track) {
+ [track] = tracks;
+ }
+
+ return track;
+ },
+
+ // Get UI label for track
+ getLabel(track) {
+ let currentTrack = track;
+
+ if (!utils.is.track(currentTrack) && support.textTracks && this.captions.active) {
+ currentTrack = captions.getCurrentTrack.call(this);
+ }
+
+ if (utils.is.track(currentTrack)) {
+ if (!utils.is.empty(currentTrack.label)) {
+ return currentTrack.label;
+ }
+
+ if (!utils.is.empty(currentTrack.language)) {
+ return track.language.toUpperCase();
+ }
+
+ return i18n.get('enabled', this.config);
+ }
+
+ return i18n.get('disabled', this.config);
},
// Display active caption if it contains text
@@ -206,11 +250,6 @@ const captions = {
// Display captions container and button (for initialization)
show() {
- // If there's no caption toggle, bail
- if (!utils.is.element(this.elements.buttons.captions)) {
- return;
- }
-
// Try to load the value from storage
let active = this.storage.get('captions');
diff --git a/src/js/controls.js b/src/js/controls.js
index 160c3665..615da39e 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -15,10 +15,7 @@ const browser = utils.getBrowser();
const controls = {
// Webkit polyfill for lower fill range
updateRangeFill(target) {
- // WebKit only
- if (!browser.isWebkit) {
- return;
- }
+
// Get range from event if event passed
const range = utils.is.event(target) ? target.target : target;
@@ -28,6 +25,14 @@ const controls = {
return;
}
+ // Set aria value for https://github.com/sampotts/plyr/issues/905
+ range.setAttribute('aria-valuenow', range.value);
+
+ // WebKit only
+ if (!browser.isWebkit) {
+ return;
+ }
+
// Set CSS custom property
range.style.setProperty('--value', `${range.value / range.max * 100}%`);
},
@@ -52,6 +57,7 @@ const controls = {
icon,
utils.extend(attributes, {
role: 'presentation',
+ focusable: 'false',
}),
);
@@ -238,6 +244,7 @@ const controls = {
'label',
{
for: attributes.id,
+ id: `${attributes.id}-label`,
class: this.config.classNames.hidden,
},
i18n.get(type, this.config),
@@ -255,6 +262,12 @@ const controls = {
step: 0.01,
value: 0,
autocomplete: 'off',
+ // A11y fixes for https://github.com/sampotts/plyr/issues/905
+ role: 'slider',
+ 'aria-labelledby': `${attributes.id}-label`,
+ 'aria-valuemin': 0,
+ 'aria-valuemax': 100,
+ 'aria-valuenow': 0,
},
attributes,
),
@@ -281,6 +294,8 @@ const controls = {
min: 0,
max: 100,
value: 0,
+ role: 'presentation',
+ 'aria-hidden': true,
},
attributes,
),
@@ -456,6 +471,9 @@ const controls = {
const toggle = !utils.is.empty(this.options.quality) && this.options.quality.length > 1;
controls.toggleTab.call(this, type, toggle);
+ // Check if we need to toggle the parent
+ controls.checkMenu.call(this);
+
// If we're hiding, nothing more to do
if (!toggle) {
return;
@@ -495,13 +513,15 @@ const controls = {
};
// Sort options by the config and then render options
- this.options.quality.sort((a, b) => {
- const sorting = this.config.quality.options;
- return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
- }).forEach(quality => {
- const label = controls.getLabel.call(this, 'quality', quality);
- controls.createMenuItem.call(this, quality, list, type, label, getBadge(quality));
- });
+ this.options.quality
+ .sort((a, b) => {
+ const sorting = this.config.quality.options;
+ return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
+ })
+ .forEach(quality => {
+ const label = controls.getLabel.call(this, 'quality', quality);
+ controls.createMenuItem.call(this, quality, list, type, label, getBadge(quality));
+ });
controls.updateSetting.call(this, type, list);
},
@@ -517,10 +537,11 @@ const controls = {
if (utils.is.number(value)) {
return `${value}p`;
}
+
return utils.toTitleCase(value);
case 'captions':
- return controls.getLanguage.call(this);
+ return captions.getLabel.call(this);
default:
return null;
@@ -535,7 +556,16 @@ const controls = {
switch (setting) {
case 'captions':
- value = this.captions.active ? this.captions.language : i18n.get('disabled', this.config);
+ if (this.captions.active) {
+ if (this.options.captions.length > 2 || !this.options.captions.some(lang => lang === 'enabled')) {
+ value = this.captions.language;
+ } else {
+ value = 'enabled';
+ }
+ } else {
+ value = '';
+ }
+
break;
default:
@@ -566,17 +596,19 @@ const controls = {
list = pane && pane.querySelector('ul');
}
- // Update the label
- if (!utils.is.empty(value)) {
- const label = this.elements.settings.tabs[setting].querySelector(`.${this.config.classNames.menu.value}`);
- label.innerHTML = controls.getLabel.call(this, setting, value);
+ // If there's no list it means it's not been rendered...
+ if (!utils.is.element(list)) {
+ return;
}
- // Find the radio option
+ // Update the label
+ const label = this.elements.settings.tabs[setting].querySelector(`.${this.config.classNames.menu.value}`);
+ label.innerHTML = controls.getLabel.call(this, setting, value);
+
+ // Find the radio option and check it
const target = list && list.querySelector(`input[value="${value}"]`);
if (utils.is.element(target)) {
- // Check it
target.checked = true;
}
},
@@ -627,21 +659,7 @@ const controls = {
// Get current selected caption language
// TODO: rework this to user the getter in the API?
- getLanguage() {
- if (!this.supported.ui) {
- return null;
- }
- if (support.textTracks && captions.getTracks.call(this).length && this.captions.active) {
- const currentTrack = captions.getCurrentTrack.call(this);
-
- if (utils.is.track(currentTrack)) {
- return currentTrack.label;
- }
- }
-
- return i18n.get('disabled', this.config);
- },
// Set a list of available captions languages
setCaptionsMenu() {
@@ -656,6 +674,9 @@ const controls = {
// Empty the menu
utils.emptyElement(list);
+ // Check if we need to toggle the parent
+ controls.checkMenu.call(this);
+
// If there's no captions, bail
if (!toggle) {
return;
@@ -663,8 +684,8 @@ const controls = {
// Re-map the tracks into just the data we need
const tracks = captions.getTracks.call(this).map(track => ({
- language: track.language,
- label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
+ language: !utils.is.empty(track.language) ? track.language : 'enabled',
+ label: captions.getLabel.call(this, track),
}));
// Add the "Disabled" option to turn off captions
@@ -680,12 +701,15 @@ const controls = {
track.language,
list,
'language',
- track.label || track.language,
- controls.createBadge.call(this, track.language.toUpperCase()),
+ track.label,
+ track.language !== 'enabled' ? controls.createBadge.call(this, track.language.toUpperCase()) : null,
track.language.toLowerCase() === this.captions.language.toLowerCase(),
);
});
+ // Store reference
+ this.options.captions = tracks.map(track => track.language);
+
controls.updateSetting.call(this, type, list);
},
@@ -1211,7 +1235,7 @@ const controls = {
seektime: this.config.seekTime,
speed: this.speed,
quality: this.quality,
- captions: controls.getLanguage.call(this),
+ captions: captions.getLabel.call(this),
// TODO: Looping
// loop: 'None',
});
diff --git a/src/js/defaults.js b/src/js/defaults.js
index 8e6b9bd5..e96587f7 100644
--- a/src/js/defaults.js
+++ b/src/js/defaults.js
@@ -56,7 +56,7 @@ const defaults = {
// Sprite (for icons)
loadSprite: true,
iconPrefix: 'plyr',
- iconUrl: 'https://cdn.plyr.io/3.1.0/plyr.svg',
+ iconUrl: 'https://cdn.plyr.io/3.2.1/plyr.svg',
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@@ -115,7 +115,7 @@ const defaults = {
// Captions settings
captions: {
active: false,
- language: window.navigator.language ? window.navigator.language.split('-')[0] : 'en',
+ language: (navigator.language || navigator.userLanguage).split('-')[0],
},
// Fullscreen settings
@@ -185,6 +185,7 @@ const defaults = {
all: 'All',
reset: 'Reset',
disabled: 'Disabled',
+ enabled: 'Enabled',
advertisement: 'Ad',
},
diff --git a/src/js/fullscreen.js b/src/js/fullscreen.js
index 857a2edc..8795f460 100644
--- a/src/js/fullscreen.js
+++ b/src/js/fullscreen.js
@@ -90,7 +90,7 @@ class Fullscreen {
static get prefix() {
// No prefix
if (utils.is.function(document.exitFullscreen)) {
- return false;
+ return '';
}
// Check for fullscreen support by vendor prefix
diff --git a/src/js/listeners.js b/src/js/listeners.js
index be7a53ef..5887c3ab 100644
--- a/src/js/listeners.js
+++ b/src/js/listeners.js
@@ -2,7 +2,6 @@
// Plyr Event Listeners
// ==========================================================================
-import support from './support';
import utils from './utils';
import controls from './controls';
import ui from './ui';
@@ -293,6 +292,10 @@ class Listeners {
// If autoplay, then load advertisement if required
// TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows
utils.on(this.player.media, 'playing', () => {
+ if (!this.player.ads) {
+ return;
+ }
+
// If ads are enabled, wait for them first
if (this.player.ads.enabled && !this.player.ads.initialized) {
// Wait for manager response
@@ -331,7 +334,7 @@ class Listeners {
// Disable right click
if (this.player.supported.ui && this.player.config.disableContextMenu) {
utils.on(
- this.player.media,
+ this.player.elements.wrapper,
'contextmenu',
event => {
event.preventDefault();
diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js
index 3aac5b2d..24003d3f 100644
--- a/src/js/plugins/vimeo.js
+++ b/src/js/plugins/vimeo.js
@@ -35,10 +35,14 @@ const vimeo = {
setAspectRatio(input) {
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
const padding = 100 / ratio[0] * ratio[1];
- const height = 240;
- const offset = (height - padding) / (height / 50);
this.elements.wrapper.style.paddingBottom = `${padding}%`;
- this.media.style.transform = `translateY(-${offset}%)`;
+
+ if (this.supported.ui) {
+ const height = 240;
+ const offset = (height - padding) / (height / 50);
+
+ this.media.style.transform = `translateY(-${offset}%)`;
+ }
},
// API Ready
@@ -55,6 +59,7 @@ const vimeo = {
speed: true,
transparent: 0,
gesture: 'media',
+ playsinline: !this.config.fullscreen.iosNative,
};
const params = utils.buildUrlParams(options);
@@ -88,6 +93,11 @@ const vimeo = {
player.media.paused = true;
player.media.currentTime = 0;
+ // Disable native text track rendering
+ if (player.supported.ui) {
+ player.embed.disableTextTrack();
+ }
+
// Create a faux HTML5 API using the Vimeo API
player.media.play = () => {
player.embed.play().then(() => {
@@ -124,7 +134,9 @@ const vimeo = {
utils.dispatchEvent.call(player, player.media, 'seeking');
// Seek after events
- player.embed.setCurrentTime(time);
+ player.embed.setCurrentTime(time).catch(() => {
+ // Do nothing
+ });
// Restore pause state
if (paused) {
@@ -310,6 +322,15 @@ const vimeo = {
if (parseInt(data.percent, 10) === 1) {
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
}
+
+ // Get duration as if we do it before load, it gives an incorrect value
+ // https://github.com/sampotts/plyr/issues/891
+ player.embed.getDuration().then(value => {
+ if (value !== player.media.duration) {
+ player.media.duration = value;
+ utils.dispatchEvent.call(player, player.media, 'durationchange');
+ }
+ });
});
player.embed.on('seeked', () => {
diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js
index d1ab537b..12bc2b11 100644
--- a/src/js/plugins/youtube.js
+++ b/src/js/plugins/youtube.js
@@ -270,6 +270,9 @@ const youtube = {
return Number(instance.getCurrentTime());
},
set(time) {
+ // Vimeo will automatically play on seek
+ const { paused } = player.media;
+
// Set seeking flag
player.media.seeking = true;
@@ -278,6 +281,11 @@ const youtube = {
// Seek after events sent
instance.seekTo(time);
+
+ // Restore pause state
+ if (paused) {
+ player.pause();
+ }
},
});
diff --git a/src/js/plyr.js b/src/js/plyr.js
index 04a68d02..2e64aa1a 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
-// plyr.js v3.1.0
+// plyr.js v3.2.1
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
@@ -97,6 +97,7 @@ class Plyr {
this.options = {
speed: [],
quality: [],
+ captions: [],
};
// Debugging
@@ -184,12 +185,17 @@ class Plyr {
if (truthy.includes(params.autoplay)) {
this.config.autoplay = true;
}
- if (truthy.includes(params.playsinline)) {
- this.config.inline = true;
- }
if (truthy.includes(params.loop)) {
this.config.loop.active = true;
}
+
+ // TODO: replace fullscreen.iosNative with this playsinline config option
+ // YouTube requires the playsinline in the URL
+ if (this.isYouTube) {
+ this.config.playsinline = truthy.includes(params.playsinline);
+ } else {
+ this.config.playsinline = true;
+ }
}
} else {
// <div> with attributes
@@ -223,7 +229,7 @@ class Plyr {
this.config.autoplay = true;
}
if (this.media.hasAttribute('playsinline')) {
- this.config.inline = true;
+ this.config.playsinline = true;
}
if (this.media.hasAttribute('muted')) {
this.config.muted = true;
@@ -240,7 +246,7 @@ class Plyr {
}
// Check for support again but with type
- this.supported = support.check(this.type, this.provider, this.config.inline);
+ this.supported = support.check(this.type, this.provider, this.config.playsinline);
// If no support for even API, bail
if (!this.supported.api) {
@@ -368,7 +374,7 @@ class Plyr {
* Get playing state
*/
get playing() {
- return Boolean(!this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true));
+ return Boolean(this.ready && !this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true));
}
/**
@@ -446,7 +452,7 @@ class Plyr {
}
// Set
- this.media.currentTime = parseFloat(targetTime.toFixed(4));
+ this.media.currentTime = targetTime;
// Logging
this.debug.log(`Seeking to ${this.currentTime} seconds`);
@@ -492,7 +498,7 @@ class Plyr {
*/
get duration() {
// Faux duration set via config
- const fauxDuration = parseInt(this.config.duration, 10);
+ const fauxDuration = parseFloat(this.config.duration);
// True duration
const realDuration = this.media ? Number(this.media.duration) : 0;
@@ -839,8 +845,8 @@ class Plyr {
* @param {boolean} input - Whether to enable captions
*/
toggleCaptions(input) {
- // If there's no full support, or there's no caption toggle
- if (!this.supported.ui || !utils.is.element(this.elements.buttons.captions)) {
+ // If there's no full support
+ if (!this.supported.ui) {
return;
}
@@ -875,17 +881,29 @@ class Plyr {
return;
}
- // Toggle captions based on input
- this.toggleCaptions(!utils.is.empty(input));
-
// If empty string is passed, assume disable captions
if (utils.is.empty(input)) {
+ this.toggleCaptions(false);
return;
}
// Normalize
const language = input.toLowerCase();
+ // Check for support
+ if (!this.options.captions.includes(language)) {
+ this.debug.warn(`Unsupported language option: ${language}`);
+ return;
+ }
+
+ // Ensure captions are enabled
+ this.toggleCaptions(true);
+
+ // Enabled only
+ if (language === 'enabled') {
+ return;
+ }
+
// If nothing to change, bail
if (this.language === language) {
return;
diff --git a/src/js/plyr.polyfilled.js b/src/js/plyr.polyfilled.js
index 50e0c589..10da2f83 100644
--- a/src/js/plyr.polyfilled.js
+++ b/src/js/plyr.polyfilled.js
@@ -1,6 +1,6 @@
// ==========================================================================
// Plyr Polyfilled Build
-// plyr.js v3.1.0
+// plyr.js v3.2.1
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
diff --git a/src/js/source.js b/src/js/source.js
index 3e713102..4e3f9186 100644
--- a/src/js/source.js
+++ b/src/js/source.js
@@ -55,7 +55,7 @@ const source = {
this.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5;
// Check for support
- this.supported = support.check(this.type, this.provider, this.config.inline);
+ this.supported = support.check(this.type, this.provider, this.config.playsinline);
// Create new markup
switch (`${this.provider}:${this.type}`) {
@@ -103,7 +103,7 @@ const source = {
if (this.config.muted) {
this.media.setAttribute('muted', '');
}
- if (this.config.inline) {
+ if (this.config.playsinline) {
this.media.setAttribute('playsinline', '');
}
}
diff --git a/src/js/support.js b/src/js/support.js
index a9a302f3..5528e898 100644
--- a/src/js/support.js
+++ b/src/js/support.js
@@ -12,16 +12,16 @@ const support = {
// Check for support
// Basic functionality vs full UI
- check(type, provider, inline) {
+ check(type, provider, playsinline) {
let api = false;
let ui = false;
const browser = utils.getBrowser();
- const playsInline = browser.isIPhone && inline && support.inline;
+ const canPlayInline = browser.isIPhone && playsinline && support.playsinline;
switch (`${provider}:${type}`) {
case 'html5:video':
api = support.video;
- ui = api && support.rangeInput && (!browser.isIPhone || playsInline);
+ ui = api && support.rangeInput && (!browser.isIPhone || canPlayInline);
break;
case 'html5:audio':
@@ -32,7 +32,7 @@ const support = {
case 'youtube:video':
case 'vimeo:video':
api = true;
- ui = support.rangeInput && (!browser.isIPhone || playsInline);
+ ui = support.rangeInput && (!browser.isIPhone || canPlayInline);
break;
default:
@@ -59,7 +59,7 @@ const support = {
// Inline playback support
// https://webkit.org/blog/6784/new-video-policies-for-ios/
- inline: 'playsInline' in document.createElement('video'),
+ playsinline: 'playsInline' in document.createElement('video'),
// Check for mime type support against a player instance
// Credits: http://diveintohtml5.info/everything.html
diff --git a/src/sass/components/embed.scss b/src/sass/components/embed.scss
index c15ee522..d72836de 100644
--- a/src/sass/components/embed.scss
+++ b/src/sass/components/embed.scss
@@ -3,14 +3,12 @@
// YouTube, Vimeo, etc
// --------------------------------------------------------------
-.plyr__video-embed {
- // Default to 16:9 ratio but this is set by JavaScript based on config
- $padding: ((100 / 16) * 9);
- $height: 240;
- $offset: to-percentage(($height - $padding) / ($height / 50));
+// Default to 16:9 ratio but this is set by JavaScript based on config
+$embed-padding: ((100 / 16) * 9);
+.plyr__video-embed {
height: 0;
- padding-bottom: to-percentage($padding);
+ padding-bottom: to-percentage($embed-padding);
position: relative;
iframe {
@@ -22,6 +20,17 @@
user-select: none;
width: 100%;
}
+}
+
+// If the full custom UI is supported
+.plyr--full-ui .plyr__video-embed {
+ $height: 240;
+ $offset: to-percentage(($height - $embed-padding) / ($height / 50));
+
+ // To allow mouse events to be captured if full support
+ iframe {
+ pointer-events: none;
+ }
// Vimeo hack
> div {
@@ -30,7 +39,3 @@
transform: translateY(-$offset);
}
}
-// To allow mouse events to be captured if full support
-.plyr--full-ui .plyr__video-embed iframe {
- pointer-events: none;
-}