Merge pull request #918 from sampotts/master

Merge back
This commit is contained in:
Sam Potts 2018-04-25 07:37:18 +10:00 committed by GitHub
commit e1183d6049
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 695 additions and 320 deletions

View File

@ -1,3 +1,23 @@
## v3.2.1
* Accessibility improvements for the controls (part of #905 fixes)
* Fix for context menu showing on YouTube (thanks Anthony Recenello in Slack)
* Vimeo fix for their API not returning the right duration until playback begins (fixes #891)
## v3.2.0
* Fullscreen fixes (thanks @friday)
* Menu fix for if speed not in config
* Menu z-index fix (thanks @danielsarin)
* i18n fix for missing "Normal" string (thanks @danielsarin)
* Safer check for active caption (thanks @Antonio-Laguna)
* Add custom property fallback (thanks @friday)
* Fixed bug for captions with no srclang and labels and improved logic (fixes #875)
* Fix for `playing` false positive (fixes #898)
* Fix for IE issue with navigator.language (thanks @nicolasthy) (fixes #893)
* Fix for Vimeo controls missing on iOS (thanks @verde-io) (fixes #807)
* Fix for double vimeo caption rendering (fixes #877)
## v3.1.0 ## v3.1.0
* Styling fixes * Styling fixes

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

3
demo/dist/demo.js vendored
View File

@ -3887,7 +3887,7 @@ singleton.Client = Client;
}); });
// Setup the player // Setup the player
var player = new Plyr('video', { var player = new Plyr('#player', {
debug: true, debug: true,
title: 'View From A Blue Moon', title: 'View From A Blue Moon',
iconUrl: '../dist/plyr.svg', iconUrl: '../dist/plyr.svg',
@ -4011,7 +4011,6 @@ singleton.Client = Client;
case types.youtube: case types.youtube:
player.source = { player.source = {
type: 'video', type: 'video',
title: 'View From A Blue Moon',
sources: [{ sources: [{
src: 'https://youtube.com/watch?v=bTqVqk7FSmY', src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
provider: 'youtube' provider: 'youtube'

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -114,7 +114,7 @@
<title>HTML5</title> <title>HTML5</title>
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path> <path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
</svg> </svg>
<a href="http://viewfromabluemoon.com/" target="_blank">View From A Blue Moon</a> &copy; Brainfarm <a href="https://itunes.apple.com/au/movie/view-from-a-blue-moon/id1041586323" target="_blank">View From A Blue Moon</a> &copy; Brainfarm
</small> </small>
</li> </li>
<li class="plyr__cite plyr__cite--audio" hidden> <li class="plyr__cite plyr__cite--audio" hidden>
@ -141,7 +141,7 @@
</li> </li>
<li class="plyr__cite plyr__cite--vimeo" hidden> <li class="plyr__cite plyr__cite--vimeo" hidden>
<small> <small>
<a href="https://vimeo.com/ondemand/viewfromabluemoon4k" target="_blank">View From A Blue Moon</a> on&nbsp; <a href="https://vimeo.com/76979871" target="_blank">The New Vimeo Player</a> on&nbsp;
<span class="color--vimeo"> <span class="color--vimeo">
<svg class="icon" role="presentation"> <svg class="icon" role="presentation">
<title>Vimeo</title> <title>Vimeo</title>

View File

@ -47,7 +47,7 @@ import Raven from 'raven-js';
}); });
// Setup the player // Setup the player
const player = new Plyr('video', { const player = new Plyr('#player', {
debug: true, debug: true,
title: 'View From A Blue Moon', title: 'View From A Blue Moon',
iconUrl: '../dist/plyr.svg', iconUrl: '../dist/plyr.svg',
@ -182,7 +182,6 @@ import Raven from 'raven-js';
case types.youtube: case types.youtube:
player.source = { player.source = {
type: 'video', type: 'video',
title: 'View From A Blue Moon',
sources: [{ sources: [{
src: 'https://youtube.com/watch?v=bTqVqk7FSmY', src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
provider: 'youtube', provider: 'youtube',

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

325
dist/plyr.js vendored
View File

@ -77,7 +77,7 @@ var defaults = {
// Sprite (for icons) // Sprite (for icons)
loadSprite: true, loadSprite: true,
iconPrefix: 'plyr', 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) // Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4', blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@ -116,7 +116,7 @@ var defaults = {
// Captions settings // Captions settings
captions: { captions: {
active: false, active: false,
language: window.navigator.language.split('-')[0] language: (navigator.language || navigator.userLanguage).split('-')[0]
}, },
// Fullscreen settings // Fullscreen settings
@ -164,6 +164,7 @@ var defaults = {
captions: 'Captions', captions: 'Captions',
settings: 'Settings', settings: 'Settings',
speed: 'Speed', speed: 'Speed',
normal: 'Normal',
quality: 'Quality', quality: 'Quality',
loop: 'Loop', loop: 'Loop',
start: 'Start', start: 'Start',
@ -171,6 +172,7 @@ var defaults = {
all: 'All', all: 'All',
reset: 'Reset', reset: 'Reset',
disabled: 'Disabled', disabled: 'Disabled',
enabled: 'Enabled',
advertisement: 'Ad' advertisement: 'Ad'
}, },
@ -1702,16 +1704,16 @@ var support = {
// Check for support // Check for support
// Basic functionality vs full UI // Basic functionality vs full UI
check: function check(type, provider, inline) { check: function check(type, provider, playsinline) {
var api = false; var api = false;
var ui = false; var ui = false;
var browser = utils.getBrowser(); var browser = utils.getBrowser();
var playsInline = browser.isIPhone && inline && support.inline; var canPlayInline = browser.isIPhone && playsinline && support.playsinline;
switch (provider + ':' + type) { switch (provider + ':' + type) {
case 'html5:video': case 'html5:video':
api = support.video; api = support.video;
ui = api && support.rangeInput && (!browser.isIPhone || playsInline); ui = api && support.rangeInput && (!browser.isIPhone || canPlayInline);
break; break;
case 'html5:audio': case 'html5:audio':
@ -1722,7 +1724,7 @@ var support = {
case 'youtube:video': case 'youtube:video':
case 'vimeo:video': case 'vimeo:video':
api = true; api = true;
ui = support.rangeInput && (!browser.isIPhone || playsInline); ui = support.rangeInput && (!browser.isIPhone || canPlayInline);
break; break;
default: default:
@ -1750,7 +1752,7 @@ var support = {
// Inline playback support // Inline playback support
// https://webkit.org/blog/6784/new-video-policies-for-ios/ // 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 // Check for mime type support against a player instance
// Credits: http://diveintohtml5.info/everything.html // Credits: http://diveintohtml5.info/everything.html
@ -2034,7 +2036,7 @@ var Fullscreen = function () {
} else if (!Fullscreen.native) { } else if (!Fullscreen.native) {
toggleFallback.call(this, false); toggleFallback.call(this, false);
} else if (!this.prefix) { } else if (!this.prefix) {
document.cancelFullScreen(); (document.cancelFullScreen || document.exitFullscreen).call(document);
} else if (!utils.is.empty(this.prefix)) { } else if (!utils.is.empty(this.prefix)) {
var action = this.prefix === 'moz' ? 'Cancel' : 'Exit'; var action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
document['' + this.prefix + action + this.name](); document['' + this.prefix + action + this.name]();
@ -2100,7 +2102,7 @@ var Fullscreen = function () {
get: function get$$1() { get: function get$$1() {
// No prefix // No prefix
if (utils.is.function(document.exitFullscreen)) { if (utils.is.function(document.exitFullscreen)) {
return false; return '';
} }
// Check for fullscreen support by vendor prefix // Check for fullscreen support by vendor prefix
@ -2129,6 +2131,36 @@ var Fullscreen = function () {
// ========================================================================== // ==========================================================================
var i18n = {
get: function get$$1() {
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
return '';
}
var string = config.i18n[key];
var replace = {
'{seektime}': config.seekTime,
'{title}': config.title
};
Object.entries(replace).forEach(function (_ref) {
var _ref2 = slicedToArray(_ref, 2),
key = _ref2[0],
value = _ref2[1];
string = utils.replaceAll(string, key, value);
});
return string;
}
};
// ==========================================================================
var captions = { var captions = {
// Setup captions // Setup captions
setup: function setup() { setup: function setup() {
@ -2168,6 +2200,7 @@ var captions = {
return; return;
} }
// Inject the container // Inject the container
if (!utils.is.element(this.elements.captions)) { if (!utils.is.element(this.elements.captions)) {
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions)); this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
@ -2272,9 +2305,54 @@ var captions = {
getCurrentTrack: function getCurrentTrack() { getCurrentTrack: function getCurrentTrack() {
var _this2 = this; var _this2 = this;
return captions.getTracks.call(this).find(function (track) { var tracks = captions.getTracks.call(this);
if (!tracks.length) {
return null;
}
// Get track based on current language
var track = tracks.find(function (track) {
return track.language.toLowerCase() === _this2.language; return track.language.toLowerCase() === _this2.language;
}); });
// Get the <track> with default attribute
if (!track) {
track = utils.getElement.call(this, 'track[default]');
}
// Get the first track
if (!track) {
var _tracks = slicedToArray(tracks, 1);
track = _tracks[0];
}
return track;
},
// Get UI label for track
getLabel: function getLabel(track) {
var 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);
}, },
@ -2336,11 +2414,6 @@ var captions = {
// Display captions container and button (for initialization) // Display captions container and button (for initialization)
show: function show() { show: function show() {
// If there's no caption toggle, bail
if (!utils.is.element(this.elements.buttons.captions)) {
return;
}
// Try to load the value from storage // Try to load the value from storage
var active = this.storage.get('captions'); var active = this.storage.get('captions');
@ -2360,36 +2433,6 @@ var captions = {
// ========================================================================== // ==========================================================================
var i18n = {
get: function get$$1() {
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
return '';
}
var string = config.i18n[key];
var replace = {
'{seektime}': config.seekTime,
'{title}': config.title
};
Object.entries(replace).forEach(function (_ref) {
var _ref2 = slicedToArray(_ref, 2),
key = _ref2[0],
value = _ref2[1];
string = utils.replaceAll(string, key, value);
});
return string;
}
};
// ==========================================================================
var ui = { var ui = {
addStyleHook: function addStyleHook() { addStyleHook: function addStyleHook() {
utils.toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true); utils.toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
@ -2892,10 +2935,6 @@ var browser$1 = utils.getBrowser();
var controls = { var controls = {
// Webkit polyfill for lower fill range // Webkit polyfill for lower fill range
updateRangeFill: function updateRangeFill(target) { updateRangeFill: function updateRangeFill(target) {
// WebKit only
if (!browser$1.isWebkit) {
return;
}
// Get range from event if event passed // Get range from event if event passed
var range = utils.is.event(target) ? target.target : target; var range = utils.is.event(target) ? target.target : target;
@ -2905,6 +2944,14 @@ var controls = {
return; return;
} }
// Set aria value for https://github.com/sampotts/plyr/issues/905
range.setAttribute('aria-valuenow', range.value);
// WebKit only
if (!browser$1.isWebkit) {
return;
}
// Set CSS custom property // Set CSS custom property
range.style.setProperty('--value', range.value / range.max * 100 + '%'); range.style.setProperty('--value', range.value / range.max * 100 + '%');
}, },
@ -2928,7 +2975,8 @@ var controls = {
// Create <svg> // Create <svg>
var icon = document.createElementNS(namespace, 'svg'); var icon = document.createElementNS(namespace, 'svg');
utils.setAttributes(icon, utils.extend(attributes, { utils.setAttributes(icon, utils.extend(attributes, {
role: 'presentation' role: 'presentation',
focusable: 'false'
})); }));
// Create the <use> to reference sprite // Create the <use> to reference sprite
@ -3110,6 +3158,7 @@ var controls = {
// Seek label // Seek label
var label = utils.createElement('label', { var label = utils.createElement('label', {
for: attributes.id, for: attributes.id,
id: attributes.id + '-label',
class: this.config.classNames.hidden class: this.config.classNames.hidden
}, i18n.get(type, this.config)); }, i18n.get(type, this.config));
@ -3120,7 +3169,13 @@ var controls = {
max: 100, max: 100,
step: 0.01, step: 0.01,
value: 0, value: 0,
autocomplete: 'off' 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)); }, attributes));
this.elements.inputs[type] = input; this.elements.inputs[type] = input;
@ -3140,7 +3195,9 @@ var controls = {
var progress = utils.createElement('progress', utils.extend(utils.getAttributesFromSelector(this.config.selectors.display[type]), { var progress = utils.createElement('progress', utils.extend(utils.getAttributesFromSelector(this.config.selectors.display[type]), {
min: 0, min: 0,
max: 100, max: 100,
value: 0 value: 0,
role: 'presentation',
'aria-hidden': true
}, attributes)); }, attributes));
// Create the label inside // Create the label inside
@ -3310,6 +3367,9 @@ var controls = {
var toggle = !utils.is.empty(this.options.quality) && this.options.quality.length > 1; var toggle = !utils.is.empty(this.options.quality) && this.options.quality.length > 1;
controls.toggleTab.call(this, type, toggle); 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 we're hiding, nothing more to do
if (!toggle) { if (!toggle) {
return; return;
@ -3366,16 +3426,17 @@ var controls = {
getLabel: function getLabel(setting, value) { getLabel: function getLabel(setting, value) {
switch (setting) { switch (setting) {
case 'speed': case 'speed':
return value === 1 ? 'Normal' : value + '&times;'; return value === 1 ? i18n.get('normal', this.config) : value + '&times;';
case 'quality': case 'quality':
if (utils.is.number(value)) { if (utils.is.number(value)) {
return value + 'p'; return value + 'p';
} }
return utils.toTitleCase(value); return utils.toTitleCase(value);
case 'captions': case 'captions':
return controls.getLanguage.call(this); return captions.getLabel.call(this);
default: default:
return null; return null;
@ -3391,7 +3452,18 @@ var controls = {
switch (setting) { switch (setting) {
case 'captions': 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(function (lang) {
return lang === 'enabled';
})) {
value = this.captions.language;
} else {
value = 'enabled';
}
} else {
value = '';
}
break; break;
default: default:
@ -3422,17 +3494,19 @@ var controls = {
list = pane && pane.querySelector('ul'); list = pane && pane.querySelector('ul');
} }
// Update the label // If there's no list it means it's not been rendered...
if (!utils.is.empty(value)) { if (!utils.is.element(list)) {
var label = this.elements.settings.tabs[setting].querySelector('.' + this.config.classNames.menu.value); return;
label.innerHTML = controls.getLabel.call(this, setting, value);
} }
// Find the radio option // Update the label
var 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
var target = list && list.querySelector('input[value="' + value + '"]'); var target = list && list.querySelector('input[value="' + value + '"]');
if (utils.is.element(target)) { if (utils.is.element(target)) {
// Check it
target.checked = true; target.checked = true;
} }
}, },
@ -3476,21 +3550,6 @@ var controls = {
// Get current selected caption language // Get current selected caption language
// TODO: rework this to user the getter in the API? // TODO: rework this to user the getter in the API?
getLanguage: function getLanguage() {
if (!this.supported.ui) {
return null;
}
if (support.textTracks && captions.getTracks.call(this).length && this.captions.active) {
var 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 // Set a list of available captions languages
@ -3508,6 +3567,9 @@ var controls = {
// Empty the menu // Empty the menu
utils.emptyElement(list); utils.emptyElement(list);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If there's no captions, bail // If there's no captions, bail
if (!toggle) { if (!toggle) {
return; return;
@ -3516,8 +3578,8 @@ var controls = {
// Re-map the tracks into just the data we need // Re-map the tracks into just the data we need
var tracks = captions.getTracks.call(this).map(function (track) { var tracks = captions.getTracks.call(this).map(function (track) {
return { return {
language: track.language, language: !utils.is.empty(track.language) ? track.language : 'enabled',
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase() label: captions.getLabel.call(_this3, track)
}; };
}); });
@ -3529,7 +3591,12 @@ var controls = {
// Generate options // Generate options
tracks.forEach(function (track) { tracks.forEach(function (track) {
controls.createMenuItem.call(_this3, track.language, list, 'language', track.label || track.language, controls.createBadge.call(_this3, track.language.toUpperCase()), track.language.toLowerCase() === _this3.captions.language.toLowerCase()); controls.createMenuItem.call(_this3, track.language, list, 'language', track.label, track.language !== 'enabled' ? controls.createBadge.call(_this3, track.language.toUpperCase()) : null, track.language.toLowerCase() === _this3.captions.language.toLowerCase());
});
// Store reference
this.options.captions = tracks.map(function (track) {
return track.language;
}); });
controls.updateSetting.call(this, type, list); controls.updateSetting.call(this, type, list);
@ -4048,7 +4115,7 @@ var controls = {
seektime: this.config.seekTime, seektime: this.config.seekTime,
speed: this.speed, speed: this.speed,
quality: this.quality, quality: this.quality,
captions: controls.getLanguage.call(this) captions: captions.getLabel.call(this)
// TODO: Looping // TODO: Looping
// loop: 'None', // loop: 'None',
}); });
@ -4070,7 +4137,7 @@ var controls = {
// Inject controls HTML // Inject controls HTML
if (utils.is.element(container)) { if (utils.is.element(container)) {
target.appendChild(container); target.appendChild(container);
} else { } else if (container) {
target.insertAdjacentHTML('beforeend', container); target.insertAdjacentHTML('beforeend', container);
} }
@ -4402,6 +4469,10 @@ var Listeners = function () {
// If autoplay, then load advertisement if required // 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 // 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', function () { utils.on(this.player.media, 'playing', function () {
if (!_this3.player.ads) {
return;
}
// If ads are enabled, wait for them first // If ads are enabled, wait for them first
if (_this3.player.ads.enabled && !_this3.player.ads.initialized) { if (_this3.player.ads.enabled && !_this3.player.ads.initialized) {
// Wait for manager response // Wait for manager response
@ -4443,7 +4514,7 @@ var Listeners = function () {
// Disable right click // Disable right click
if (this.player.supported.ui && this.player.config.disableContextMenu) { if (this.player.supported.ui && this.player.config.disableContextMenu) {
utils.on(this.player.media, 'contextmenu', function (event) { utils.on(this.player.elements.wrapper, 'contextmenu', function (event) {
event.preventDefault(); event.preventDefault();
}, false); }, false);
} }
@ -5729,7 +5800,11 @@ var youtube = {
return Number(instance.getCurrentTime()); return Number(instance.getCurrentTime());
}, },
set: function set(time) { set: function set(time) {
// Vimeo will automatically play on seek
var paused = player.media.paused;
// Set seeking flag // Set seeking flag
player.media.seeking = true; player.media.seeking = true;
// Trigger seeking // Trigger seeking
@ -5737,6 +5812,11 @@ var youtube = {
// Seek after events sent // Seek after events sent
instance.seekTo(time); instance.seekTo(time);
// Restore pause state
if (paused) {
player.pause();
}
} }
}); });
@ -5974,10 +6054,14 @@ var vimeo = {
setAspectRatio: function setAspectRatio(input) { setAspectRatio: function setAspectRatio(input) {
var ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':'); var ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
var padding = 100 / ratio[0] * ratio[1]; var padding = 100 / ratio[0] * ratio[1];
this.elements.wrapper.style.paddingBottom = padding + '%';
if (this.supported.ui) {
var height = 240; var height = 240;
var offset = (height - padding) / (height / 50); var offset = (height - padding) / (height / 50);
this.elements.wrapper.style.paddingBottom = padding + '%';
this.media.style.transform = 'translateY(-' + offset + '%)'; this.media.style.transform = 'translateY(-' + offset + '%)';
}
}, },
@ -5996,7 +6080,8 @@ var vimeo = {
title: false, title: false,
speed: true, speed: true,
transparent: 0, transparent: 0,
gesture: 'media' gesture: 'media',
playsinline: !this.config.fullscreen.iosNative
}; };
var params = utils.buildUrlParams(options); var params = utils.buildUrlParams(options);
@ -6030,6 +6115,11 @@ var vimeo = {
player.media.paused = true; player.media.paused = true;
player.media.currentTime = 0; 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 // Create a faux HTML5 API using the Vimeo API
player.media.play = function () { player.media.play = function () {
player.embed.play().then(function () { player.embed.play().then(function () {
@ -6068,7 +6158,9 @@ var vimeo = {
utils.dispatchEvent.call(player, player.media, 'seeking'); utils.dispatchEvent.call(player, player.media, 'seeking');
// Seek after events // Seek after events
player.embed.setCurrentTime(time); player.embed.setCurrentTime(time).catch(function () {
// Do nothing
});
// Restore pause state // Restore pause state
if (paused) { if (paused) {
@ -6248,6 +6340,15 @@ var vimeo = {
if (parseInt(data.percent, 10) === 1) { if (parseInt(data.percent, 10) === 1) {
utils.dispatchEvent.call(player, player.media, 'canplaythrough'); 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(function (value) {
if (value !== player.media.duration) {
player.media.duration = value;
utils.dispatchEvent.call(player, player.media, 'durationchange');
}
});
}); });
player.embed.on('seeked', function () { player.embed.on('seeked', function () {
@ -6399,7 +6500,7 @@ var source = {
_this2.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5; _this2.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5;
// Check for support // Check for support
_this2.supported = support.check(_this2.type, _this2.provider, _this2.config.inline); _this2.supported = support.check(_this2.type, _this2.provider, _this2.config.playsinline);
// Create new markup // Create new markup
switch (_this2.provider + ':' + _this2.type) { switch (_this2.provider + ':' + _this2.type) {
@ -6447,7 +6548,7 @@ var source = {
if (_this2.config.muted) { if (_this2.config.muted) {
_this2.media.setAttribute('muted', ''); _this2.media.setAttribute('muted', '');
} }
if (_this2.config.inline) { if (_this2.config.playsinline) {
_this2.media.setAttribute('playsinline', ''); _this2.media.setAttribute('playsinline', '');
} }
} }
@ -6528,7 +6629,7 @@ var Plyr = function () {
} }
// Set config // Set config
this.config = utils.extend({}, defaults, options, function () { this.config = utils.extend({}, defaults, options || {}, function () {
try { try {
return JSON.parse(_this.media.getAttribute('data-plyr-config')); return JSON.parse(_this.media.getAttribute('data-plyr-config'));
} catch (e) { } catch (e) {
@ -6565,7 +6666,8 @@ var Plyr = function () {
// Options // Options
this.options = { this.options = {
speed: [], speed: [],
quality: [] quality: [],
captions: []
}; };
// Debugging // Debugging
@ -6650,12 +6752,17 @@ var Plyr = function () {
if (truthy.includes(params.autoplay)) { if (truthy.includes(params.autoplay)) {
this.config.autoplay = true; this.config.autoplay = true;
} }
if (truthy.includes(params.playsinline)) {
this.config.inline = true;
}
if (truthy.includes(params.loop)) { if (truthy.includes(params.loop)) {
this.config.loop.active = true; 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 { } else {
// <div> with attributes // <div> with attributes
@ -6689,7 +6796,7 @@ var Plyr = function () {
this.config.autoplay = true; this.config.autoplay = true;
} }
if (this.media.hasAttribute('playsinline')) { if (this.media.hasAttribute('playsinline')) {
this.config.inline = true; this.config.playsinline = true;
} }
if (this.media.hasAttribute('muted')) { if (this.media.hasAttribute('muted')) {
this.config.muted = true; this.config.muted = true;
@ -6706,7 +6813,7 @@ var Plyr = function () {
} }
// Check for support again but with type // 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 no support for even API, bail
if (!this.supported.api) { if (!this.supported.api) {
@ -6926,13 +7033,13 @@ var Plyr = function () {
* @param {boolean} input - Whether to enable captions * @param {boolean} input - Whether to enable captions
*/ */
value: function toggleCaptions(input) { value: function toggleCaptions(input) {
// If there's no full support, or there's no caption toggle // If there's no full support
if (!this.supported.ui || !utils.is.element(this.elements.buttons.captions)) { if (!this.supported.ui) {
return; return;
} }
// If the method is called without parameter, toggle based on current value // If the method is called without parameter, toggle based on current value
var show = utils.is.boolean(input) ? input : this.elements.container.className.indexOf(this.config.classNames.captions.active) === -1; var show = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
// Nothing to change... // Nothing to change...
if (this.captions.active === show) { if (this.captions.active === show) {
@ -7291,7 +7398,7 @@ var Plyr = function () {
}, { }, {
key: 'playing', key: 'playing',
get: function get$$1() { get: function get$$1() {
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));
} }
/** /**
@ -7320,7 +7427,7 @@ var Plyr = function () {
} }
// Set // Set
this.media.currentTime = parseFloat(targetTime.toFixed(4)); this.media.currentTime = targetTime;
// Logging // Logging
this.debug.log('Seeking to ' + this.currentTime + ' seconds'); this.debug.log('Seeking to ' + this.currentTime + ' seconds');
@ -7377,7 +7484,7 @@ var Plyr = function () {
key: 'duration', key: 'duration',
get: function get$$1() { get: function get$$1() {
// Faux duration set via config // Faux duration set via config
var fauxDuration = parseInt(this.config.duration, 10); var fauxDuration = parseFloat(this.config.duration);
// True duration // True duration
var realDuration = this.media ? Number(this.media.duration) : 0; var realDuration = this.media ? Number(this.media.duration) : 0;
@ -7728,17 +7835,29 @@ var Plyr = function () {
return; return;
} }
// Toggle captions based on input
this.toggleCaptions(!utils.is.empty(input));
// If empty string is passed, assume disable captions // If empty string is passed, assume disable captions
if (utils.is.empty(input)) { if (utils.is.empty(input)) {
this.toggleCaptions(false);
return; return;
} }
// Normalize // Normalize
var language = input.toLowerCase(); var 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 nothing to change, bail
if (this.language === language) { if (this.language === language) {
return; return;

2
dist/plyr.js.map vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5117,7 +5117,7 @@ var defaults = {
// Sprite (for icons) // Sprite (for icons)
loadSprite: true, loadSprite: true,
iconPrefix: 'plyr', 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) // Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4', blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@ -5156,7 +5156,7 @@ var defaults = {
// Captions settings // Captions settings
captions: { captions: {
active: false, active: false,
language: window.navigator.language.split('-')[0] language: (navigator.language || navigator.userLanguage).split('-')[0]
}, },
// Fullscreen settings // Fullscreen settings
@ -5204,6 +5204,7 @@ var defaults = {
captions: 'Captions', captions: 'Captions',
settings: 'Settings', settings: 'Settings',
speed: 'Speed', speed: 'Speed',
normal: 'Normal',
quality: 'Quality', quality: 'Quality',
loop: 'Loop', loop: 'Loop',
start: 'Start', start: 'Start',
@ -5211,6 +5212,7 @@ var defaults = {
all: 'All', all: 'All',
reset: 'Reset', reset: 'Reset',
disabled: 'Disabled', disabled: 'Disabled',
enabled: 'Enabled',
advertisement: 'Ad' advertisement: 'Ad'
}, },
@ -6736,16 +6738,16 @@ var support = {
// Check for support // Check for support
// Basic functionality vs full UI // Basic functionality vs full UI
check: function check(type, provider, inline) { check: function check(type, provider, playsinline) {
var api = false; var api = false;
var ui = false; var ui = false;
var browser = utils.getBrowser(); var browser = utils.getBrowser();
var playsInline = browser.isIPhone && inline && support.inline; var canPlayInline = browser.isIPhone && playsinline && support.playsinline;
switch (provider + ':' + type) { switch (provider + ':' + type) {
case 'html5:video': case 'html5:video':
api = support.video; api = support.video;
ui = api && support.rangeInput && (!browser.isIPhone || playsInline); ui = api && support.rangeInput && (!browser.isIPhone || canPlayInline);
break; break;
case 'html5:audio': case 'html5:audio':
@ -6756,7 +6758,7 @@ var support = {
case 'youtube:video': case 'youtube:video':
case 'vimeo:video': case 'vimeo:video':
api = true; api = true;
ui = support.rangeInput && (!browser.isIPhone || playsInline); ui = support.rangeInput && (!browser.isIPhone || canPlayInline);
break; break;
default: default:
@ -6784,7 +6786,7 @@ var support = {
// Inline playback support // Inline playback support
// https://webkit.org/blog/6784/new-video-policies-for-ios/ // 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 // Check for mime type support against a player instance
// Credits: http://diveintohtml5.info/everything.html // Credits: http://diveintohtml5.info/everything.html
@ -7068,7 +7070,7 @@ var Fullscreen = function () {
} else if (!Fullscreen.native) { } else if (!Fullscreen.native) {
toggleFallback.call(this, false); toggleFallback.call(this, false);
} else if (!this.prefix) { } else if (!this.prefix) {
document.cancelFullScreen(); (document.cancelFullScreen || document.exitFullscreen).call(document);
} else if (!utils.is.empty(this.prefix)) { } else if (!utils.is.empty(this.prefix)) {
var action = this.prefix === 'moz' ? 'Cancel' : 'Exit'; var action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
document['' + this.prefix + action + this.name](); document['' + this.prefix + action + this.name]();
@ -7134,7 +7136,7 @@ var Fullscreen = function () {
get: function get() { get: function get() {
// No prefix // No prefix
if (utils.is.function(document.exitFullscreen)) { if (utils.is.function(document.exitFullscreen)) {
return false; return '';
} }
// Check for fullscreen support by vendor prefix // Check for fullscreen support by vendor prefix
@ -7163,6 +7165,36 @@ var Fullscreen = function () {
// ========================================================================== // ==========================================================================
var i18n = {
get: function get() {
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
return '';
}
var string = config.i18n[key];
var replace = {
'{seektime}': config.seekTime,
'{title}': config.title
};
Object.entries(replace).forEach(function (_ref) {
var _ref2 = slicedToArray(_ref, 2),
key = _ref2[0],
value = _ref2[1];
string = utils.replaceAll(string, key, value);
});
return string;
}
};
// ==========================================================================
var captions = { var captions = {
// Setup captions // Setup captions
setup: function setup() { setup: function setup() {
@ -7202,6 +7234,7 @@ var captions = {
return; return;
} }
// Inject the container // Inject the container
if (!utils.is.element(this.elements.captions)) { if (!utils.is.element(this.elements.captions)) {
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions)); this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
@ -7306,9 +7339,54 @@ var captions = {
getCurrentTrack: function getCurrentTrack() { getCurrentTrack: function getCurrentTrack() {
var _this2 = this; var _this2 = this;
return captions.getTracks.call(this).find(function (track) { var tracks = captions.getTracks.call(this);
if (!tracks.length) {
return null;
}
// Get track based on current language
var track = tracks.find(function (track) {
return track.language.toLowerCase() === _this2.language; return track.language.toLowerCase() === _this2.language;
}); });
// Get the <track> with default attribute
if (!track) {
track = utils.getElement.call(this, 'track[default]');
}
// Get the first track
if (!track) {
var _tracks = slicedToArray(tracks, 1);
track = _tracks[0];
}
return track;
},
// Get UI label for track
getLabel: function getLabel(track) {
var 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);
}, },
@ -7370,11 +7448,6 @@ var captions = {
// Display captions container and button (for initialization) // Display captions container and button (for initialization)
show: function show() { show: function show() {
// If there's no caption toggle, bail
if (!utils.is.element(this.elements.buttons.captions)) {
return;
}
// Try to load the value from storage // Try to load the value from storage
var active = this.storage.get('captions'); var active = this.storage.get('captions');
@ -7394,36 +7467,6 @@ var captions = {
// ========================================================================== // ==========================================================================
var i18n = {
get: function get() {
var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
return '';
}
var string = config.i18n[key];
var replace = {
'{seektime}': config.seekTime,
'{title}': config.title
};
Object.entries(replace).forEach(function (_ref) {
var _ref2 = slicedToArray(_ref, 2),
key = _ref2[0],
value = _ref2[1];
string = utils.replaceAll(string, key, value);
});
return string;
}
};
// ==========================================================================
var ui = { var ui = {
addStyleHook: function addStyleHook() { addStyleHook: function addStyleHook() {
utils.toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true); utils.toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
@ -7926,10 +7969,6 @@ var browser$1 = utils.getBrowser();
var controls = { var controls = {
// Webkit polyfill for lower fill range // Webkit polyfill for lower fill range
updateRangeFill: function updateRangeFill(target) { updateRangeFill: function updateRangeFill(target) {
// WebKit only
if (!browser$1.isWebkit) {
return;
}
// Get range from event if event passed // Get range from event if event passed
var range = utils.is.event(target) ? target.target : target; var range = utils.is.event(target) ? target.target : target;
@ -7939,6 +7978,14 @@ var controls = {
return; return;
} }
// Set aria value for https://github.com/sampotts/plyr/issues/905
range.setAttribute('aria-valuenow', range.value);
// WebKit only
if (!browser$1.isWebkit) {
return;
}
// Set CSS custom property // Set CSS custom property
range.style.setProperty('--value', range.value / range.max * 100 + '%'); range.style.setProperty('--value', range.value / range.max * 100 + '%');
}, },
@ -7962,7 +8009,8 @@ var controls = {
// Create <svg> // Create <svg>
var icon = document.createElementNS(namespace, 'svg'); var icon = document.createElementNS(namespace, 'svg');
utils.setAttributes(icon, utils.extend(attributes, { utils.setAttributes(icon, utils.extend(attributes, {
role: 'presentation' role: 'presentation',
focusable: 'false'
})); }));
// Create the <use> to reference sprite // Create the <use> to reference sprite
@ -8144,6 +8192,7 @@ var controls = {
// Seek label // Seek label
var label = utils.createElement('label', { var label = utils.createElement('label', {
for: attributes.id, for: attributes.id,
id: attributes.id + '-label',
class: this.config.classNames.hidden class: this.config.classNames.hidden
}, i18n.get(type, this.config)); }, i18n.get(type, this.config));
@ -8154,7 +8203,13 @@ var controls = {
max: 100, max: 100,
step: 0.01, step: 0.01,
value: 0, value: 0,
autocomplete: 'off' 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)); }, attributes));
this.elements.inputs[type] = input; this.elements.inputs[type] = input;
@ -8174,7 +8229,9 @@ var controls = {
var progress = utils.createElement('progress', utils.extend(utils.getAttributesFromSelector(this.config.selectors.display[type]), { var progress = utils.createElement('progress', utils.extend(utils.getAttributesFromSelector(this.config.selectors.display[type]), {
min: 0, min: 0,
max: 100, max: 100,
value: 0 value: 0,
role: 'presentation',
'aria-hidden': true
}, attributes)); }, attributes));
// Create the label inside // Create the label inside
@ -8344,6 +8401,9 @@ var controls = {
var toggle = !utils.is.empty(this.options.quality) && this.options.quality.length > 1; var toggle = !utils.is.empty(this.options.quality) && this.options.quality.length > 1;
controls.toggleTab.call(this, type, toggle); 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 we're hiding, nothing more to do
if (!toggle) { if (!toggle) {
return; return;
@ -8400,16 +8460,17 @@ var controls = {
getLabel: function getLabel(setting, value) { getLabel: function getLabel(setting, value) {
switch (setting) { switch (setting) {
case 'speed': case 'speed':
return value === 1 ? 'Normal' : value + '&times;'; return value === 1 ? i18n.get('normal', this.config) : value + '&times;';
case 'quality': case 'quality':
if (utils.is.number(value)) { if (utils.is.number(value)) {
return value + 'p'; return value + 'p';
} }
return utils.toTitleCase(value); return utils.toTitleCase(value);
case 'captions': case 'captions':
return controls.getLanguage.call(this); return captions.getLabel.call(this);
default: default:
return null; return null;
@ -8425,7 +8486,18 @@ var controls = {
switch (setting) { switch (setting) {
case 'captions': 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(function (lang) {
return lang === 'enabled';
})) {
value = this.captions.language;
} else {
value = 'enabled';
}
} else {
value = '';
}
break; break;
default: default:
@ -8456,17 +8528,19 @@ var controls = {
list = pane && pane.querySelector('ul'); list = pane && pane.querySelector('ul');
} }
// Update the label // If there's no list it means it's not been rendered...
if (!utils.is.empty(value)) { if (!utils.is.element(list)) {
var label = this.elements.settings.tabs[setting].querySelector('.' + this.config.classNames.menu.value); return;
label.innerHTML = controls.getLabel.call(this, setting, value);
} }
// Find the radio option // Update the label
var 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
var target = list && list.querySelector('input[value="' + value + '"]'); var target = list && list.querySelector('input[value="' + value + '"]');
if (utils.is.element(target)) { if (utils.is.element(target)) {
// Check it
target.checked = true; target.checked = true;
} }
}, },
@ -8510,21 +8584,6 @@ var controls = {
// Get current selected caption language // Get current selected caption language
// TODO: rework this to user the getter in the API? // TODO: rework this to user the getter in the API?
getLanguage: function getLanguage() {
if (!this.supported.ui) {
return null;
}
if (support.textTracks && captions.getTracks.call(this).length && this.captions.active) {
var 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 // Set a list of available captions languages
@ -8542,6 +8601,9 @@ var controls = {
// Empty the menu // Empty the menu
utils.emptyElement(list); utils.emptyElement(list);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If there's no captions, bail // If there's no captions, bail
if (!toggle) { if (!toggle) {
return; return;
@ -8550,8 +8612,8 @@ var controls = {
// Re-map the tracks into just the data we need // Re-map the tracks into just the data we need
var tracks = captions.getTracks.call(this).map(function (track) { var tracks = captions.getTracks.call(this).map(function (track) {
return { return {
language: track.language, language: !utils.is.empty(track.language) ? track.language : 'enabled',
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase() label: captions.getLabel.call(_this3, track)
}; };
}); });
@ -8563,7 +8625,12 @@ var controls = {
// Generate options // Generate options
tracks.forEach(function (track) { tracks.forEach(function (track) {
controls.createMenuItem.call(_this3, track.language, list, 'language', track.label || track.language, controls.createBadge.call(_this3, track.language.toUpperCase()), track.language.toLowerCase() === _this3.captions.language.toLowerCase()); controls.createMenuItem.call(_this3, track.language, list, 'language', track.label, track.language !== 'enabled' ? controls.createBadge.call(_this3, track.language.toUpperCase()) : null, track.language.toLowerCase() === _this3.captions.language.toLowerCase());
});
// Store reference
this.options.captions = tracks.map(function (track) {
return track.language;
}); });
controls.updateSetting.call(this, type, list); controls.updateSetting.call(this, type, list);
@ -9082,7 +9149,7 @@ var controls = {
seektime: this.config.seekTime, seektime: this.config.seekTime,
speed: this.speed, speed: this.speed,
quality: this.quality, quality: this.quality,
captions: controls.getLanguage.call(this) captions: captions.getLabel.call(this)
// TODO: Looping // TODO: Looping
// loop: 'None', // loop: 'None',
}); });
@ -9104,7 +9171,7 @@ var controls = {
// Inject controls HTML // Inject controls HTML
if (utils.is.element(container)) { if (utils.is.element(container)) {
target.appendChild(container); target.appendChild(container);
} else { } else if (container) {
target.insertAdjacentHTML('beforeend', container); target.insertAdjacentHTML('beforeend', container);
} }
@ -9436,6 +9503,10 @@ var Listeners = function () {
// If autoplay, then load advertisement if required // 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 // 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', function () { utils.on(this.player.media, 'playing', function () {
if (!_this3.player.ads) {
return;
}
// If ads are enabled, wait for them first // If ads are enabled, wait for them first
if (_this3.player.ads.enabled && !_this3.player.ads.initialized) { if (_this3.player.ads.enabled && !_this3.player.ads.initialized) {
// Wait for manager response // Wait for manager response
@ -9477,7 +9548,7 @@ var Listeners = function () {
// Disable right click // Disable right click
if (this.player.supported.ui && this.player.config.disableContextMenu) { if (this.player.supported.ui && this.player.config.disableContextMenu) {
utils.on(this.player.media, 'contextmenu', function (event) { utils.on(this.player.elements.wrapper, 'contextmenu', function (event) {
event.preventDefault(); event.preventDefault();
}, false); }, false);
} }
@ -10763,7 +10834,11 @@ var youtube = {
return Number(instance.getCurrentTime()); return Number(instance.getCurrentTime());
}, },
set: function set(time) { set: function set(time) {
// Vimeo will automatically play on seek
var paused = player.media.paused;
// Set seeking flag // Set seeking flag
player.media.seeking = true; player.media.seeking = true;
// Trigger seeking // Trigger seeking
@ -10771,6 +10846,11 @@ var youtube = {
// Seek after events sent // Seek after events sent
instance.seekTo(time); instance.seekTo(time);
// Restore pause state
if (paused) {
player.pause();
}
} }
}); });
@ -11008,10 +11088,14 @@ var vimeo = {
setAspectRatio: function setAspectRatio(input) { setAspectRatio: function setAspectRatio(input) {
var ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':'); var ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
var padding = 100 / ratio[0] * ratio[1]; var padding = 100 / ratio[0] * ratio[1];
this.elements.wrapper.style.paddingBottom = padding + '%';
if (this.supported.ui) {
var height = 240; var height = 240;
var offset = (height - padding) / (height / 50); var offset = (height - padding) / (height / 50);
this.elements.wrapper.style.paddingBottom = padding + '%';
this.media.style.transform = 'translateY(-' + offset + '%)'; this.media.style.transform = 'translateY(-' + offset + '%)';
}
}, },
@ -11030,7 +11114,8 @@ var vimeo = {
title: false, title: false,
speed: true, speed: true,
transparent: 0, transparent: 0,
gesture: 'media' gesture: 'media',
playsinline: !this.config.fullscreen.iosNative
}; };
var params = utils.buildUrlParams(options); var params = utils.buildUrlParams(options);
@ -11064,6 +11149,11 @@ var vimeo = {
player.media.paused = true; player.media.paused = true;
player.media.currentTime = 0; 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 // Create a faux HTML5 API using the Vimeo API
player.media.play = function () { player.media.play = function () {
player.embed.play().then(function () { player.embed.play().then(function () {
@ -11102,7 +11192,9 @@ var vimeo = {
utils.dispatchEvent.call(player, player.media, 'seeking'); utils.dispatchEvent.call(player, player.media, 'seeking');
// Seek after events // Seek after events
player.embed.setCurrentTime(time); player.embed.setCurrentTime(time).catch(function () {
// Do nothing
});
// Restore pause state // Restore pause state
if (paused) { if (paused) {
@ -11282,6 +11374,15 @@ var vimeo = {
if (parseInt(data.percent, 10) === 1) { if (parseInt(data.percent, 10) === 1) {
utils.dispatchEvent.call(player, player.media, 'canplaythrough'); 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(function (value) {
if (value !== player.media.duration) {
player.media.duration = value;
utils.dispatchEvent.call(player, player.media, 'durationchange');
}
});
}); });
player.embed.on('seeked', function () { player.embed.on('seeked', function () {
@ -11433,7 +11534,7 @@ var source = {
_this2.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5; _this2.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5;
// Check for support // Check for support
_this2.supported = support.check(_this2.type, _this2.provider, _this2.config.inline); _this2.supported = support.check(_this2.type, _this2.provider, _this2.config.playsinline);
// Create new markup // Create new markup
switch (_this2.provider + ':' + _this2.type) { switch (_this2.provider + ':' + _this2.type) {
@ -11481,7 +11582,7 @@ var source = {
if (_this2.config.muted) { if (_this2.config.muted) {
_this2.media.setAttribute('muted', ''); _this2.media.setAttribute('muted', '');
} }
if (_this2.config.inline) { if (_this2.config.playsinline) {
_this2.media.setAttribute('playsinline', ''); _this2.media.setAttribute('playsinline', '');
} }
} }
@ -11562,7 +11663,7 @@ var Plyr = function () {
} }
// Set config // Set config
this.config = utils.extend({}, defaults, options, function () { this.config = utils.extend({}, defaults, options || {}, function () {
try { try {
return JSON.parse(_this.media.getAttribute('data-plyr-config')); return JSON.parse(_this.media.getAttribute('data-plyr-config'));
} catch (e) { } catch (e) {
@ -11599,7 +11700,8 @@ var Plyr = function () {
// Options // Options
this.options = { this.options = {
speed: [], speed: [],
quality: [] quality: [],
captions: []
}; };
// Debugging // Debugging
@ -11684,12 +11786,17 @@ var Plyr = function () {
if (truthy.includes(params.autoplay)) { if (truthy.includes(params.autoplay)) {
this.config.autoplay = true; this.config.autoplay = true;
} }
if (truthy.includes(params.playsinline)) {
this.config.inline = true;
}
if (truthy.includes(params.loop)) { if (truthy.includes(params.loop)) {
this.config.loop.active = true; 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 { } else {
// <div> with attributes // <div> with attributes
@ -11723,7 +11830,7 @@ var Plyr = function () {
this.config.autoplay = true; this.config.autoplay = true;
} }
if (this.media.hasAttribute('playsinline')) { if (this.media.hasAttribute('playsinline')) {
this.config.inline = true; this.config.playsinline = true;
} }
if (this.media.hasAttribute('muted')) { if (this.media.hasAttribute('muted')) {
this.config.muted = true; this.config.muted = true;
@ -11740,7 +11847,7 @@ var Plyr = function () {
} }
// Check for support again but with type // 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 no support for even API, bail
if (!this.supported.api) { if (!this.supported.api) {
@ -11960,13 +12067,13 @@ var Plyr = function () {
* @param {boolean} input - Whether to enable captions * @param {boolean} input - Whether to enable captions
*/ */
value: function toggleCaptions(input) { value: function toggleCaptions(input) {
// If there's no full support, or there's no caption toggle // If there's no full support
if (!this.supported.ui || !utils.is.element(this.elements.buttons.captions)) { if (!this.supported.ui) {
return; return;
} }
// If the method is called without parameter, toggle based on current value // If the method is called without parameter, toggle based on current value
var show = utils.is.boolean(input) ? input : this.elements.container.className.indexOf(this.config.classNames.captions.active) === -1; var show = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
// Nothing to change... // Nothing to change...
if (this.captions.active === show) { if (this.captions.active === show) {
@ -12325,7 +12432,7 @@ var Plyr = function () {
}, { }, {
key: 'playing', key: 'playing',
get: function get() { get: function get() {
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));
} }
/** /**
@ -12354,7 +12461,7 @@ var Plyr = function () {
} }
// Set // Set
this.media.currentTime = parseFloat(targetTime.toFixed(4)); this.media.currentTime = targetTime;
// Logging // Logging
this.debug.log('Seeking to ' + this.currentTime + ' seconds'); this.debug.log('Seeking to ' + this.currentTime + ' seconds');
@ -12411,7 +12518,7 @@ var Plyr = function () {
key: 'duration', key: 'duration',
get: function get() { get: function get() {
// Faux duration set via config // Faux duration set via config
var fauxDuration = parseInt(this.config.duration, 10); var fauxDuration = parseFloat(this.config.duration);
// True duration // True duration
var realDuration = this.media ? Number(this.media.duration) : 0; var realDuration = this.media ? Number(this.media.duration) : 0;
@ -12762,17 +12869,29 @@ var Plyr = function () {
return; return;
} }
// Toggle captions based on input
this.toggleCaptions(!utils.is.empty(input));
// If empty string is passed, assume disable captions // If empty string is passed, assume disable captions
if (utils.is.empty(input)) { if (utils.is.empty(input)) {
this.toggleCaptions(false);
return; return;
} }
// Normalize // Normalize
var language = input.toLowerCase(); var 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 nothing to change, bail
if (this.language === language) { if (this.language === language) {
return; return;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"name": "plyr", "name": "plyr",
"version": "3.1.0", "version": "3.2.1",
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player", "description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
"homepage": "https://plyr.io", "homepage": "https://plyr.io",
"main": "./dist/plyr.js", "main": "./dist/plyr.js",
@ -9,14 +9,14 @@
"style": "./dist/plyr.css", "style": "./dist/plyr.css",
"devDependencies": { "devDependencies": {
"babel-core": "^6.26.0", "babel-core": "^6.26.0",
"babel-eslint": "^8.2.2", "babel-eslint": "^8.2.3",
"babel-plugin-external-helpers": "^6.22.0", "babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"del": "^3.0.0", "del": "^3.0.0",
"eslint": "^4.19.1", "eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0", "eslint-config-airbnb-base": "^12.1.0",
"eslint-config-prettier": "^2.9.0", "eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.10.0", "eslint-plugin-import": "^2.11.0",
"git-branch": "^2.0.1", "git-branch": "^2.0.1",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"gulp-autoprefixer": "^5.0.0", "gulp-autoprefixer": "^5.0.0",
@ -28,7 +28,7 @@
"gulp-rename": "^1.2.2", "gulp-rename": "^1.2.2",
"gulp-replace": "^0.6.1", "gulp-replace": "^0.6.1",
"gulp-s3": "^0.11.0", "gulp-s3": "^0.11.0",
"gulp-sass": "^3.2.1", "gulp-sass": "^4.0.1",
"gulp-size": "^3.0.0", "gulp-size": "^3.0.0",
"gulp-sourcemaps": "^2.6.4", "gulp-sourcemaps": "^2.6.4",
"gulp-svgmin": "^1.2.4", "gulp-svgmin": "^1.2.4",
@ -42,7 +42,7 @@
"rollup-plugin-node-resolve": "^3.3.0", "rollup-plugin-node-resolve": "^3.3.0",
"run-sequence": "^2.2.1", "run-sequence": "^2.2.1",
"stylelint": "^9.2.0", "stylelint": "^9.2.0",
"stylelint-config-prettier": "^3.0.4", "stylelint-config-prettier": "^3.2.0",
"stylelint-config-recommended": "^2.1.0", "stylelint-config-recommended": "^2.1.0",
"stylelint-config-sass-guidelines": "^5.0.0", "stylelint-config-sass-guidelines": "^5.0.0",
"stylelint-order": "^0.8.1", "stylelint-order": "^0.8.1",

View File

@ -128,7 +128,7 @@ See [initialising](#initialising) for more information on advanced setups.
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following: If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
```html ```html
<script src="https://cdn.plyr.io/3.1.0/plyr.js"></script> <script src="https://cdn.plyr.io/3.2.1/plyr.js"></script>
``` ```
_Note_: Be sure to read the [polyfills](#polyfills) section below about browser compatibility _Note_: Be sure to read the [polyfills](#polyfills) section below about browser compatibility
@ -144,13 +144,13 @@ Include the `plyr.css` stylsheet into your `<head>`
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following: If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
```html ```html
<link rel="stylesheet" href="https://cdn.plyr.io/3.1.0/plyr.css"> <link rel="stylesheet" href="https://cdn.plyr.io/3.2.1/plyr.css">
``` ```
### SVG Sprite ### SVG Sprite
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.1.0/plyr.svg`. reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.2.1/plyr.svg`.
## Ads ## Ads

View File

@ -6,6 +6,7 @@
import support from './support'; import support from './support';
import utils from './utils'; import utils from './utils';
import controls from './controls'; import controls from './controls';
import i18n from './i18n';
const captions = { const captions = {
// Setup captions // Setup captions
@ -46,6 +47,7 @@ const captions = {
return; return;
} }
// Inject the container // Inject the container
if (!utils.is.element(this.elements.captions)) { if (!utils.is.element(this.elements.captions)) {
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.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 // Get the current track for the current language
getCurrentTrack() { 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 // Display active caption if it contains text
@ -206,11 +250,6 @@ const captions = {
// Display captions container and button (for initialization) // Display captions container and button (for initialization)
show() { show() {
// If there's no caption toggle, bail
if (!utils.is.element(this.elements.buttons.captions)) {
return;
}
// Try to load the value from storage // Try to load the value from storage
let active = this.storage.get('captions'); let active = this.storage.get('captions');

90
src/js/controls.js vendored
View File

@ -15,10 +15,7 @@ const browser = utils.getBrowser();
const controls = { const controls = {
// Webkit polyfill for lower fill range // Webkit polyfill for lower fill range
updateRangeFill(target) { updateRangeFill(target) {
// WebKit only
if (!browser.isWebkit) {
return;
}
// Get range from event if event passed // Get range from event if event passed
const range = utils.is.event(target) ? target.target : target; const range = utils.is.event(target) ? target.target : target;
@ -28,6 +25,14 @@ const controls = {
return; 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 // Set CSS custom property
range.style.setProperty('--value', `${range.value / range.max * 100}%`); range.style.setProperty('--value', `${range.value / range.max * 100}%`);
}, },
@ -52,6 +57,7 @@ const controls = {
icon, icon,
utils.extend(attributes, { utils.extend(attributes, {
role: 'presentation', role: 'presentation',
focusable: 'false',
}), }),
); );
@ -238,6 +244,7 @@ const controls = {
'label', 'label',
{ {
for: attributes.id, for: attributes.id,
id: `${attributes.id}-label`,
class: this.config.classNames.hidden, class: this.config.classNames.hidden,
}, },
i18n.get(type, this.config), i18n.get(type, this.config),
@ -255,6 +262,12 @@ const controls = {
step: 0.01, step: 0.01,
value: 0, value: 0,
autocomplete: 'off', 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, attributes,
), ),
@ -281,6 +294,8 @@ const controls = {
min: 0, min: 0,
max: 100, max: 100,
value: 0, value: 0,
role: 'presentation',
'aria-hidden': true,
}, },
attributes, attributes,
), ),
@ -456,6 +471,9 @@ const controls = {
const toggle = !utils.is.empty(this.options.quality) && this.options.quality.length > 1; const toggle = !utils.is.empty(this.options.quality) && this.options.quality.length > 1;
controls.toggleTab.call(this, type, toggle); 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 we're hiding, nothing more to do
if (!toggle) { if (!toggle) {
return; return;
@ -495,10 +513,12 @@ const controls = {
}; };
// Sort options by the config and then render options // Sort options by the config and then render options
this.options.quality.sort((a, b) => { this.options.quality
.sort((a, b) => {
const sorting = this.config.quality.options; const sorting = this.config.quality.options;
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1; return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
}).forEach(quality => { })
.forEach(quality => {
const label = controls.getLabel.call(this, 'quality', quality); const label = controls.getLabel.call(this, 'quality', quality);
controls.createMenuItem.call(this, quality, list, type, label, getBadge(quality)); controls.createMenuItem.call(this, quality, list, type, label, getBadge(quality));
}); });
@ -517,10 +537,11 @@ const controls = {
if (utils.is.number(value)) { if (utils.is.number(value)) {
return `${value}p`; return `${value}p`;
} }
return utils.toTitleCase(value); return utils.toTitleCase(value);
case 'captions': case 'captions':
return controls.getLanguage.call(this); return captions.getLabel.call(this);
default: default:
return null; return null;
@ -535,7 +556,16 @@ const controls = {
switch (setting) { switch (setting) {
case 'captions': 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; break;
default: default:
@ -566,17 +596,19 @@ const controls = {
list = pane && pane.querySelector('ul'); list = pane && pane.querySelector('ul');
} }
// Update the label // If there's no list it means it's not been rendered...
if (!utils.is.empty(value)) { if (!utils.is.element(list)) {
const label = this.elements.settings.tabs[setting].querySelector(`.${this.config.classNames.menu.value}`); return;
label.innerHTML = controls.getLabel.call(this, setting, value);
} }
// 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}"]`); const target = list && list.querySelector(`input[value="${value}"]`);
if (utils.is.element(target)) { if (utils.is.element(target)) {
// Check it
target.checked = true; target.checked = true;
} }
}, },
@ -627,21 +659,7 @@ const controls = {
// Get current selected caption language // Get current selected caption language
// TODO: rework this to user the getter in the API? // 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 // Set a list of available captions languages
setCaptionsMenu() { setCaptionsMenu() {
@ -656,6 +674,9 @@ const controls = {
// Empty the menu // Empty the menu
utils.emptyElement(list); utils.emptyElement(list);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If there's no captions, bail // If there's no captions, bail
if (!toggle) { if (!toggle) {
return; return;
@ -663,8 +684,8 @@ const controls = {
// Re-map the tracks into just the data we need // Re-map the tracks into just the data we need
const tracks = captions.getTracks.call(this).map(track => ({ const tracks = captions.getTracks.call(this).map(track => ({
language: track.language, language: !utils.is.empty(track.language) ? track.language : 'enabled',
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(), label: captions.getLabel.call(this, track),
})); }));
// Add the "Disabled" option to turn off captions // Add the "Disabled" option to turn off captions
@ -680,12 +701,15 @@ const controls = {
track.language, track.language,
list, list,
'language', 'language',
track.label || track.language, track.label,
controls.createBadge.call(this, track.language.toUpperCase()), track.language !== 'enabled' ? controls.createBadge.call(this, track.language.toUpperCase()) : null,
track.language.toLowerCase() === this.captions.language.toLowerCase(), track.language.toLowerCase() === this.captions.language.toLowerCase(),
); );
}); });
// Store reference
this.options.captions = tracks.map(track => track.language);
controls.updateSetting.call(this, type, list); controls.updateSetting.call(this, type, list);
}, },
@ -1211,7 +1235,7 @@ const controls = {
seektime: this.config.seekTime, seektime: this.config.seekTime,
speed: this.speed, speed: this.speed,
quality: this.quality, quality: this.quality,
captions: controls.getLanguage.call(this), captions: captions.getLabel.call(this),
// TODO: Looping // TODO: Looping
// loop: 'None', // loop: 'None',
}); });

View File

@ -56,7 +56,7 @@ const defaults = {
// Sprite (for icons) // Sprite (for icons)
loadSprite: true, loadSprite: true,
iconPrefix: 'plyr', 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) // Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4', blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@ -115,7 +115,7 @@ const defaults = {
// Captions settings // Captions settings
captions: { captions: {
active: false, active: false,
language: window.navigator.language ? window.navigator.language.split('-')[0] : 'en', language: (navigator.language || navigator.userLanguage).split('-')[0],
}, },
// Fullscreen settings // Fullscreen settings
@ -185,6 +185,7 @@ const defaults = {
all: 'All', all: 'All',
reset: 'Reset', reset: 'Reset',
disabled: 'Disabled', disabled: 'Disabled',
enabled: 'Enabled',
advertisement: 'Ad', advertisement: 'Ad',
}, },

View File

@ -90,7 +90,7 @@ class Fullscreen {
static get prefix() { static get prefix() {
// No prefix // No prefix
if (utils.is.function(document.exitFullscreen)) { if (utils.is.function(document.exitFullscreen)) {
return false; return '';
} }
// Check for fullscreen support by vendor prefix // Check for fullscreen support by vendor prefix

View File

@ -2,7 +2,6 @@
// Plyr Event Listeners // Plyr Event Listeners
// ========================================================================== // ==========================================================================
import support from './support';
import utils from './utils'; import utils from './utils';
import controls from './controls'; import controls from './controls';
import ui from './ui'; import ui from './ui';
@ -293,6 +292,10 @@ class Listeners {
// If autoplay, then load advertisement if required // 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 // 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', () => { utils.on(this.player.media, 'playing', () => {
if (!this.player.ads) {
return;
}
// If ads are enabled, wait for them first // If ads are enabled, wait for them first
if (this.player.ads.enabled && !this.player.ads.initialized) { if (this.player.ads.enabled && !this.player.ads.initialized) {
// Wait for manager response // Wait for manager response
@ -331,7 +334,7 @@ class Listeners {
// Disable right click // Disable right click
if (this.player.supported.ui && this.player.config.disableContextMenu) { if (this.player.supported.ui && this.player.config.disableContextMenu) {
utils.on( utils.on(
this.player.media, this.player.elements.wrapper,
'contextmenu', 'contextmenu',
event => { event => {
event.preventDefault(); event.preventDefault();

View File

@ -35,10 +35,14 @@ const vimeo = {
setAspectRatio(input) { setAspectRatio(input) {
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':'); const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
const padding = 100 / ratio[0] * ratio[1]; const padding = 100 / ratio[0] * ratio[1];
this.elements.wrapper.style.paddingBottom = `${padding}%`;
if (this.supported.ui) {
const height = 240; const height = 240;
const offset = (height - padding) / (height / 50); const offset = (height - padding) / (height / 50);
this.elements.wrapper.style.paddingBottom = `${padding}%`;
this.media.style.transform = `translateY(-${offset}%)`; this.media.style.transform = `translateY(-${offset}%)`;
}
}, },
// API Ready // API Ready
@ -55,6 +59,7 @@ const vimeo = {
speed: true, speed: true,
transparent: 0, transparent: 0,
gesture: 'media', gesture: 'media',
playsinline: !this.config.fullscreen.iosNative,
}; };
const params = utils.buildUrlParams(options); const params = utils.buildUrlParams(options);
@ -88,6 +93,11 @@ const vimeo = {
player.media.paused = true; player.media.paused = true;
player.media.currentTime = 0; 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 // Create a faux HTML5 API using the Vimeo API
player.media.play = () => { player.media.play = () => {
player.embed.play().then(() => { player.embed.play().then(() => {
@ -124,7 +134,9 @@ const vimeo = {
utils.dispatchEvent.call(player, player.media, 'seeking'); utils.dispatchEvent.call(player, player.media, 'seeking');
// Seek after events // Seek after events
player.embed.setCurrentTime(time); player.embed.setCurrentTime(time).catch(() => {
// Do nothing
});
// Restore pause state // Restore pause state
if (paused) { if (paused) {
@ -310,6 +322,15 @@ const vimeo = {
if (parseInt(data.percent, 10) === 1) { if (parseInt(data.percent, 10) === 1) {
utils.dispatchEvent.call(player, player.media, 'canplaythrough'); 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', () => { player.embed.on('seeked', () => {

View File

@ -270,6 +270,9 @@ const youtube = {
return Number(instance.getCurrentTime()); return Number(instance.getCurrentTime());
}, },
set(time) { set(time) {
// Vimeo will automatically play on seek
const { paused } = player.media;
// Set seeking flag // Set seeking flag
player.media.seeking = true; player.media.seeking = true;
@ -278,6 +281,11 @@ const youtube = {
// Seek after events sent // Seek after events sent
instance.seekTo(time); instance.seekTo(time);
// Restore pause state
if (paused) {
player.pause();
}
}, },
}); });

View File

@ -1,6 +1,6 @@
// ========================================================================== // ==========================================================================
// Plyr // Plyr
// plyr.js v3.1.0 // plyr.js v3.2.1
// https://github.com/sampotts/plyr // https://github.com/sampotts/plyr
// License: The MIT License (MIT) // License: The MIT License (MIT)
// ========================================================================== // ==========================================================================
@ -97,6 +97,7 @@ class Plyr {
this.options = { this.options = {
speed: [], speed: [],
quality: [], quality: [],
captions: [],
}; };
// Debugging // Debugging
@ -184,12 +185,17 @@ class Plyr {
if (truthy.includes(params.autoplay)) { if (truthy.includes(params.autoplay)) {
this.config.autoplay = true; this.config.autoplay = true;
} }
if (truthy.includes(params.playsinline)) {
this.config.inline = true;
}
if (truthy.includes(params.loop)) { if (truthy.includes(params.loop)) {
this.config.loop.active = true; 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 { } else {
// <div> with attributes // <div> with attributes
@ -223,7 +229,7 @@ class Plyr {
this.config.autoplay = true; this.config.autoplay = true;
} }
if (this.media.hasAttribute('playsinline')) { if (this.media.hasAttribute('playsinline')) {
this.config.inline = true; this.config.playsinline = true;
} }
if (this.media.hasAttribute('muted')) { if (this.media.hasAttribute('muted')) {
this.config.muted = true; this.config.muted = true;
@ -240,7 +246,7 @@ class Plyr {
} }
// Check for support again but with type // 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 no support for even API, bail
if (!this.supported.api) { if (!this.supported.api) {
@ -368,7 +374,7 @@ class Plyr {
* Get playing state * Get playing state
*/ */
get playing() { 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 // Set
this.media.currentTime = parseFloat(targetTime.toFixed(4)); this.media.currentTime = targetTime;
// Logging // Logging
this.debug.log(`Seeking to ${this.currentTime} seconds`); this.debug.log(`Seeking to ${this.currentTime} seconds`);
@ -492,7 +498,7 @@ class Plyr {
*/ */
get duration() { get duration() {
// Faux duration set via config // Faux duration set via config
const fauxDuration = parseInt(this.config.duration, 10); const fauxDuration = parseFloat(this.config.duration);
// True duration // True duration
const realDuration = this.media ? Number(this.media.duration) : 0; const realDuration = this.media ? Number(this.media.duration) : 0;
@ -839,8 +845,8 @@ class Plyr {
* @param {boolean} input - Whether to enable captions * @param {boolean} input - Whether to enable captions
*/ */
toggleCaptions(input) { toggleCaptions(input) {
// If there's no full support, or there's no caption toggle // If there's no full support
if (!this.supported.ui || !utils.is.element(this.elements.buttons.captions)) { if (!this.supported.ui) {
return; return;
} }
@ -875,17 +881,29 @@ class Plyr {
return; return;
} }
// Toggle captions based on input
this.toggleCaptions(!utils.is.empty(input));
// If empty string is passed, assume disable captions // If empty string is passed, assume disable captions
if (utils.is.empty(input)) { if (utils.is.empty(input)) {
this.toggleCaptions(false);
return; return;
} }
// Normalize // Normalize
const language = input.toLowerCase(); 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 nothing to change, bail
if (this.language === language) { if (this.language === language) {
return; return;

View File

@ -1,6 +1,6 @@
// ========================================================================== // ==========================================================================
// Plyr Polyfilled Build // Plyr Polyfilled Build
// plyr.js v3.1.0 // plyr.js v3.2.1
// https://github.com/sampotts/plyr // https://github.com/sampotts/plyr
// License: The MIT License (MIT) // License: The MIT License (MIT)
// ========================================================================== // ==========================================================================

View File

@ -55,7 +55,7 @@ const source = {
this.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5; this.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5;
// Check for support // 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 // Create new markup
switch (`${this.provider}:${this.type}`) { switch (`${this.provider}:${this.type}`) {
@ -103,7 +103,7 @@ const source = {
if (this.config.muted) { if (this.config.muted) {
this.media.setAttribute('muted', ''); this.media.setAttribute('muted', '');
} }
if (this.config.inline) { if (this.config.playsinline) {
this.media.setAttribute('playsinline', ''); this.media.setAttribute('playsinline', '');
} }
} }

View File

@ -12,16 +12,16 @@ const support = {
// Check for support // Check for support
// Basic functionality vs full UI // Basic functionality vs full UI
check(type, provider, inline) { check(type, provider, playsinline) {
let api = false; let api = false;
let ui = false; let ui = false;
const browser = utils.getBrowser(); const browser = utils.getBrowser();
const playsInline = browser.isIPhone && inline && support.inline; const canPlayInline = browser.isIPhone && playsinline && support.playsinline;
switch (`${provider}:${type}`) { switch (`${provider}:${type}`) {
case 'html5:video': case 'html5:video':
api = support.video; api = support.video;
ui = api && support.rangeInput && (!browser.isIPhone || playsInline); ui = api && support.rangeInput && (!browser.isIPhone || canPlayInline);
break; break;
case 'html5:audio': case 'html5:audio':
@ -32,7 +32,7 @@ const support = {
case 'youtube:video': case 'youtube:video':
case 'vimeo:video': case 'vimeo:video':
api = true; api = true;
ui = support.rangeInput && (!browser.isIPhone || playsInline); ui = support.rangeInput && (!browser.isIPhone || canPlayInline);
break; break;
default: default:
@ -59,7 +59,7 @@ const support = {
// Inline playback support // Inline playback support
// https://webkit.org/blog/6784/new-video-policies-for-ios/ // 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 // Check for mime type support against a player instance
// Credits: http://diveintohtml5.info/everything.html // Credits: http://diveintohtml5.info/everything.html

View File

@ -3,14 +3,12 @@
// YouTube, Vimeo, etc // YouTube, Vimeo, etc
// -------------------------------------------------------------- // --------------------------------------------------------------
.plyr__video-embed {
// Default to 16:9 ratio but this is set by JavaScript based on config // Default to 16:9 ratio but this is set by JavaScript based on config
$padding: ((100 / 16) * 9); $embed-padding: ((100 / 16) * 9);
$height: 240;
$offset: to-percentage(($height - $padding) / ($height / 50));
.plyr__video-embed {
height: 0; height: 0;
padding-bottom: to-percentage($padding); padding-bottom: to-percentage($embed-padding);
position: relative; position: relative;
iframe { iframe {
@ -22,6 +20,17 @@
user-select: none; user-select: none;
width: 100%; 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 // Vimeo hack
> div { > div {
@ -30,7 +39,3 @@
transform: translateY(-$offset); transform: translateY(-$offset);
} }
} }
// To allow mouse events to be captured if full support
.plyr--full-ui .plyr__video-embed iframe {
pointer-events: none;
}