Compare commits

..

8 Commits

Author SHA1 Message Date
3b20dbd9fd v3.1.0 2018-04-03 22:57:34 +10:00
e4d975af00 Styling fixes 2018-04-03 22:56:19 +10:00
2782a00e7c v3.1.0-beta.2 2018-04-03 22:31:55 +10:00
91d192dd7c YouTube speed menu fix 2018-04-03 22:30:29 +10:00
b1e3abc795 v3.1.0-beta.1 2018-04-02 22:52:02 +10:00
3395e8df90 HTML5 quality selection 2018-04-02 22:40:03 +10:00
cce143a7da v3.0.11 2018-03-30 23:14:07 +11:00
d593005b32 Muted and autoplay fixes, small bug fixes 2018-03-30 23:09:17 +11:00
40 changed files with 1594 additions and 550 deletions

View File

@ -9,6 +9,7 @@
"ignore": ["attribute", "class"] "ignore": ["attribute", "class"]
} }
], ],
"string-no-newline": null,
"indentation": 4, "indentation": 4,
"string-quotes": "single", "string-quotes": "single",
"max-nesting-depth": 2, "max-nesting-depth": 2,

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost/dev/plyr/demo",
"webRoot": "${workspaceFolder}"
}
]
}

View File

@ -1,3 +1,13 @@
## v3.1.0-beta.1
* HTML5 quality selection
* Improvements to the YouTube quality selection
## v3.0.11
* Muted and autoplay fixes
* Small bug fixes from Sentry logs
## v3.0.10 ## v3.0.10
* Docs fix * Docs fix

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

19
demo/dist/demo.js vendored
View File

@ -3887,7 +3887,7 @@ singleton.Client = Client;
}); });
// Setup the player // Setup the player
var player = new Plyr('#player', { var player = new Plyr('video', {
debug: true, debug: true,
title: 'View From A Blue Moon', title: 'View From A Blue Moon',
iconUrl: '../dist/plyr.svg', iconUrl: '../dist/plyr.svg',
@ -3960,8 +3960,21 @@ singleton.Client = Client;
type: 'video', type: 'video',
title: 'View From A Blue Moon', title: 'View From A Blue Moon',
sources: [{ sources: [{
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4', src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
type: 'video/mp4' type: 'video/mp4',
size: 576
}, {
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
type: 'video/mp4',
size: 720
}, {
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
type: 'video/mp4',
size: 1080
}, {
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
type: 'video/mp4',
size: 1440
}], }],
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg', poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
tracks: [{ tracks: [{

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

@ -93,16 +93,18 @@
<main> <main>
<video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player"> <video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
<!-- Video files --> <!-- Video files -->
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" type="video/mp4"> <source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" type="video/mp4" size="576">
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.webm" type="video/webm"> <source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4" type="video/mp4" size="720">
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4" type="video/mp4" size="1080">
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4" type="video/mp4" size="1440">
<!-- Text track file --> <!-- Caption files -->
<track kind="captions" label="English" srclang="en" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt" <track kind="captions" label="English" srclang="en" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
default> default>
<track kind="captions" label="Français" srclang="fr" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"> <track kind="captions" label="Français" srclang="fr" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
<!-- Fallback for browsers that don't support the <video> element --> <!-- Fallback for browsers that don't support the <video> element -->
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" download>Download</a> <a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" download>Download</a>
</video> </video>
<ul> <ul>
@ -169,7 +171,8 @@
</aside> </aside>
<!-- Polyfills --> <!-- Polyfills -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent" crossorigin="anonymous"></script> <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent,Object.entries,Object.values"
crossorigin="anonymous"></script>
<!-- Plyr core script --> <!-- Plyr core script -->
<script src="../dist/plyr.js" crossorigin="anonymous"></script> <script src="../dist/plyr.js" crossorigin="anonymous"></script>

View File

@ -47,7 +47,7 @@ import Raven from 'raven-js';
}); });
// Setup the player // Setup the player
const player = new Plyr('#player', { const player = new Plyr('video', {
debug: true, debug: true,
title: 'View From A Blue Moon', title: 'View From A Blue Moon',
iconUrl: '../dist/plyr.svg', iconUrl: '../dist/plyr.svg',
@ -119,10 +119,28 @@ import Raven from 'raven-js';
player.source = { player.source = {
type: 'video', type: 'video',
title: 'View From A Blue Moon', title: 'View From A Blue Moon',
sources: [{ sources: [
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4', {
type: 'video/mp4', src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
}], type: 'video/mp4',
size: 576,
},
{
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
type: 'video/mp4',
size: 720,
},
{
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
type: 'video/mp4',
size: 1080,
},
{
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
type: 'video/mp4',
size: 1440,
},
],
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg', poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
tracks: [ tracks: [
{ {

View File

@ -16,3 +16,4 @@ $plyr-font-size-captions-base: $plyr-font-size-base;
$plyr-font-size-captions-small: $plyr-font-size-small; $plyr-font-size-captions-small: $plyr-font-size-small;
$plyr-font-size-captions-medium: 18px; $plyr-font-size-captions-medium: 18px;
$plyr-font-size-captions-large: 21px; $plyr-font-size-captions-large: 21px;
$plyr-font-size-menu: $plyr-font-size-base;

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

617
dist/plyr.js vendored
View File

@ -77,15 +77,15 @@ var defaults = {
// Sprite (for icons) // Sprite (for icons)
loadSprite: true, loadSprite: true,
iconPrefix: 'plyr', iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.0.9/plyr.svg', iconUrl: 'https://cdn.plyr.io/3.1.0-beta.2/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',
// Quality default // Quality default
quality: { quality: {
default: 'default', default: 576,
options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240, 'default']
}, },
// Set loops // Set loops
@ -1339,15 +1339,19 @@ var utils = {
// Trigger event // Trigger event
dispatchEvent: function dispatchEvent(element, type, bubbles, detail) { dispatchEvent: function dispatchEvent(element) {
var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
var bubbles = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var detail = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
// Bail if no element // Bail if no element
if (!utils.is.element(element) || !utils.is.string(type)) { if (!utils.is.element(element) || utils.is.empty(type)) {
return; return;
} }
// Create and dispatch the event // Create and dispatch the event
var event = new CustomEvent(type, { var event = new CustomEvent(type, {
bubbles: utils.is.boolean(bubbles) ? bubbles : false, bubbles: bubbles,
detail: Object.assign({}, detail, { detail: Object.assign({}, detail, {
plyr: utils.is.plyr(this) ? this : null plyr: utils.is.plyr(this) ? this : null
}) })
@ -1526,6 +1530,30 @@ var utils = {
}, },
// Remove duplicates in an array
dedupe: function dedupe(array) {
if (!utils.is.array(array)) {
return array;
}
return array.filter(function (item, index) {
return array.indexOf(item) === index;
});
},
// Get the closest value in an array
closest: function closest(array, value) {
if (!utils.is.array(array) || !array.length) {
return null;
}
return array.reduce(function (prev, curr) {
return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev;
});
},
// Get the provider for a given URL // Get the provider for a given URL
getProviderByUrl: function getProviderByUrl(url) { getProviderByUrl: function getProviderByUrl(url) {
// YouTube // YouTube
@ -1737,6 +1765,11 @@ var support = {
return false; return false;
} }
// Check directly if codecs specified
if (type.includes('codecs=')) {
return media.canPlayType(type).replace(/no/, '');
}
// Type specific checks // Type specific checks
if (this.isVideo) { if (this.isVideo) {
switch (type) { switch (type) {
@ -1930,13 +1963,13 @@ var Fullscreen = function () {
}); });
// Fullscreen toggle on double click // Fullscreen toggle on double click
utils.on(this.player.elements.container, 'dblclick', function () { utils.on(this.player.elements.container, 'dblclick', function (event) {
_this.toggle(); // Ignore double click in controls
}); if (_this.player.elements.controls.contains(event.target)) {
return;
}
// Prevent double click on controls bubbling up _this.toggle();
utils.on(this.player.elements.controls, 'dblclick', function (event) {
return event.stopPropagation();
}); });
// Update the UI // Update the UI
@ -2427,8 +2460,11 @@ var ui = {
// Reset loop state // Reset loop state
this.loop = null; this.loop = null;
// Reset quality options // Reset quality setting
this.options.quality = []; this.quality = null;
// Reset volume display
ui.updateVolume.call(this);
// Reset time display // Reset time display
ui.timeUpdate.call(this); ui.timeUpdate.call(this);
@ -2697,6 +2733,159 @@ var ui = {
// ========================================================================== // ==========================================================================
var html5 = {
getSources: function getSources() {
if (!this.isHTML5) {
return null;
}
return this.media.querySelectorAll('source');
},
// Get quality levels
getQualityOptions: function getQualityOptions() {
if (!this.isHTML5) {
return null;
}
// Get sources
var sources = html5.getSources.call(this);
if (utils.is.empty(sources)) {
return null;
}
// Get <source> with size attribute
var sizes = Array.from(sources).filter(function (source) {
return !utils.is.empty(source.getAttribute('size'));
});
// If none, bail
if (utils.is.empty(sizes)) {
return null;
}
// Reduce to unique list
return utils.dedupe(sizes.map(function (source) {
return Number(source.getAttribute('size'));
}));
},
extend: function extend() {
if (!this.isHTML5) {
return;
}
var player = this;
// Quality
Object.defineProperty(player.media, 'quality', {
get: function get() {
// Get sources
var sources = html5.getSources.call(player);
if (utils.is.empty(sources)) {
return null;
}
var matches = Array.from(sources).filter(function (source) {
return source.getAttribute('src') === player.source;
});
if (utils.is.empty(matches)) {
return null;
}
return Number(matches[0].getAttribute('size'));
},
set: function set(input) {
// Get sources
var sources = html5.getSources.call(player);
if (utils.is.empty(sources)) {
return;
}
// Get matches for requested size
var matches = Array.from(sources).filter(function (source) {
return Number(source.getAttribute('size')) === input;
});
// No matches for requested size
if (utils.is.empty(matches)) {
return;
}
// Get supported sources
var supported = matches.filter(function (source) {
return support.mime.call(player, source.getAttribute('type'));
});
// No supported sources
if (utils.is.empty(supported)) {
return;
}
// Trigger change event
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
quality: input
});
// Get current state
var currentTime = player.currentTime,
playing = player.playing;
// Set new source
player.media.src = supported[0].getAttribute('src');
// Load new source
player.media.load();
// Resume playing
if (playing) {
player.play();
}
// Restore time
player.currentTime = currentTime;
// Trigger change event
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
quality: input
});
}
});
},
// Cancel current network requests
// See https://github.com/sampotts/plyr/issues/174
cancelRequests: function cancelRequests() {
if (!this.isHTML5) {
return;
}
// Remove child sources
utils.removeElement(html5.getSources());
// Set blank video src attribute
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
this.media.setAttribute('src', this.config.blankVideo);
// Load the new empty source
// This will cancel existing requests
// See https://github.com/sampotts/plyr/issues/174
this.media.load();
// Debugging
this.debug.log('Cancelled network requests');
}
};
// ==========================================================================
// Sniff out the browser // Sniff out the browser
var browser$1 = utils.getBrowser(); var browser$1 = utils.getBrowser();
@ -3097,8 +3286,8 @@ var controls = {
}, },
// Set the YouTube quality menu // Set the quality menu
// TODO: Support for HTML5 // TODO: Vimeo support
setQualityMenu: function setQualityMenu(options) { setQualityMenu: function setQualityMenu(options) {
var _this2 = this; var _this2 = this;
@ -3115,12 +3304,10 @@ var controls = {
this.options.quality = options.filter(function (quality) { this.options.quality = options.filter(function (quality) {
return _this2.config.quality.options.includes(quality); return _this2.config.quality.options.includes(quality);
}); });
} else {
this.options.quality = this.config.quality.options;
} }
// Toggle the pane and tab // Toggle the pane and tab
var toggle = !utils.is.empty(this.options.quality) && this.isYouTube; 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);
// If we're hiding, nothing more to do // If we're hiding, nothing more to do
@ -3136,20 +3323,18 @@ var controls = {
var label = ''; var label = '';
switch (quality) { switch (quality) {
case 'hd2160': case 2160:
label = '4K'; label = '4K';
break; break;
case 'hd1440': case 1440:
label = 'WQHD'; case 1080:
break; case 720:
case 'hd1080':
label = 'HD'; label = 'HD';
break; break;
case 'hd720': case 576:
label = 'HD'; label = 'SD';
break; break;
default: default:
@ -3163,8 +3348,13 @@ var controls = {
return controls.createBadge.call(_this2, label); return controls.createBadge.call(_this2, label);
}; };
this.options.quality.forEach(function (quality) { // Sort options by the config and then render options
return controls.createMenuItem.call(_this2, quality, list, type, controls.getLabel.call(_this2, 'quality', quality), getBadge(quality)); this.options.quality.sort(function (a, b) {
var sorting = _this2.config.quality.options;
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
}).forEach(function (quality) {
var label = controls.getLabel.call(_this2, 'quality', quality);
controls.createMenuItem.call(_this2, quality, list, type, label, getBadge(quality));
}); });
controls.updateSetting.call(this, type, list); controls.updateSetting.call(this, type, list);
@ -3179,28 +3369,10 @@ var controls = {
return value === 1 ? 'Normal' : value + '&times;'; return value === 1 ? 'Normal' : value + '&times;';
case 'quality': case 'quality':
switch (value) { if (utils.is.number(value)) {
case 'hd2160': return value + 'p';
return '2160P';
case 'hd1440':
return '1440P';
case 'hd1080':
return '1080P';
case 'hd720':
return '720P';
case 'large':
return '480P';
case 'medium':
return '360P';
case 'small':
return '240P';
case 'tiny':
return 'Tiny';
case 'default':
return 'Auto';
default:
return value;
} }
return utils.toTitleCase(value);
case 'captions': case 'captions':
return controls.getLanguage.call(this); return controls.getLanguage.call(this);
@ -3212,7 +3384,7 @@ var controls = {
// Update the selected setting // Update the selected setting
updateSetting: function updateSetting(setting, container) { updateSetting: function updateSetting(setting, container, input) {
var pane = this.elements.settings.panes[setting]; var pane = this.elements.settings.panes[setting];
var value = null; var value = null;
var list = container; var list = container;
@ -3223,7 +3395,7 @@ var controls = {
break; break;
default: default:
value = this[setting]; value = !utils.is.empty(input) ? input : this[setting];
// Get default // Get default
if (utils.is.empty(value)) { if (utils.is.empty(value)) {
@ -3231,7 +3403,7 @@ var controls = {
} }
// Unsupported value // Unsupported value
if (!this.options[setting].includes(value)) { if (!utils.is.empty(this.options[setting]) && !this.options[setting].includes(value)) {
this.debug.warn('Unsupported value of \'' + value + '\' for ' + setting); this.debug.warn('Unsupported value of \'' + value + '\' for ' + setting);
return; return;
} }
@ -3330,14 +3502,14 @@ var controls = {
var list = this.elements.settings.panes.captions.querySelector('ul'); var list = this.elements.settings.panes.captions.querySelector('ul');
// Toggle the pane and tab // Toggle the pane and tab
var hasTracks = captions.getTracks.call(this).length; var toggle = captions.getTracks.call(this).length;
controls.toggleTab.call(this, type, hasTracks); controls.toggleTab.call(this, type, toggle);
// Empty the menu // Empty the menu
utils.emptyElement(list); utils.emptyElement(list);
// If there's no captions, bail // If there's no captions, bail
if (!hasTracks) { if (!toggle) {
return; return;
} }
@ -3381,10 +3553,10 @@ var controls = {
var type = 'speed'; var type = 'speed';
// Set the speed options // Set the speed options
if (!utils.is.array(options)) { if (utils.is.array(options)) {
this.options.speed = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
} else {
this.options.speed = options; this.options.speed = options;
} else if (this.isHTML5 || this.isVimeo) {
this.options.speed = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
} }
// Set options if passed and filter based on config // Set options if passed and filter based on config
@ -3393,7 +3565,7 @@ var controls = {
}); });
// Toggle the pane and tab // Toggle the pane and tab
var toggle = !utils.is.empty(this.options.speed); var toggle = !utils.is.empty(this.options.speed) && this.options.speed.length > 1;
controls.toggleTab.call(this, type, toggle); controls.toggleTab.call(this, type, toggle);
// Check if we need to toggle the parent // Check if we need to toggle the parent
@ -3416,7 +3588,8 @@ var controls = {
// Create items // Create items
this.options.speed.forEach(function (speed) { this.options.speed.forEach(function (speed) {
return controls.createMenuItem.call(_this4, speed, list, type, controls.getLabel.call(_this4, 'speed', speed)); var label = controls.getLabel.call(_this4, 'speed', speed);
controls.createMenuItem.call(_this4, speed, list, type, label);
}); });
controls.updateSetting.call(this, type, list); controls.updateSetting.call(this, type, list);
@ -3425,10 +3598,13 @@ var controls = {
// Check if we need to hide/show the settings menu // Check if we need to hide/show the settings menu
checkMenu: function checkMenu() { checkMenu: function checkMenu() {
var speedHidden = this.elements.settings.tabs.speed.getAttribute('hidden') !== null; var tabs = this.elements.settings.tabs;
var languageHidden = this.elements.settings.tabs.captions.getAttribute('hidden') !== null;
utils.toggleHidden(this.elements.settings.menu, speedHidden && languageHidden); var visible = !utils.is.empty(tabs) && Object.values(tabs).some(function (tab) {
return !tab.hidden;
});
utils.toggleHidden(this.elements.settings.menu, !visible);
}, },
@ -3697,7 +3873,8 @@ var controls = {
// Settings button / menu // Settings button / menu
if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) { if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) {
var menu = utils.createElement('div', { var menu = utils.createElement('div', {
class: 'plyr__menu' class: 'plyr__menu',
hidden: ''
}); });
menu.appendChild(controls.createButton.call(this, 'settings', { menu.appendChild(controls.createButton.call(this, 'settings', {
@ -3822,6 +3999,10 @@ var controls = {
this.elements.controls = container; this.elements.controls = container;
if (this.isHTML5) {
controls.setQualityMenu.call(this, html5.getQualityOptions.call(this));
}
controls.setSpeedMenu.call(this); controls.setSpeedMenu.call(this);
return container; return container;
@ -4200,17 +4381,17 @@ var Listeners = function () {
return ui.updateProgress.call(_this3.player, event); return ui.updateProgress.call(_this3.player, event);
}); });
// Handle native mute // Handle volume changes
utils.on(this.player.media, 'volumechange', function (event) { utils.on(this.player.media, 'volumechange', function (event) {
return ui.updateVolume.call(_this3.player, event); return ui.updateVolume.call(_this3.player, event);
}); });
// Handle native play/pause // Handle play/pause
utils.on(this.player.media, 'playing play pause ended emptied', function (event) { utils.on(this.player.media, 'playing play pause ended emptied', function (event) {
return ui.checkPlaying.call(_this3.player, event); return ui.checkPlaying.call(_this3.player, event);
}); });
// Loading // Loading state
utils.on(this.player.media, 'waiting canplay seeked playing', function (event) { utils.on(this.player.media, 'waiting canplay seeked playing', function (event) {
return ui.checkLoading.call(_this3.player, event); return ui.checkLoading.call(_this3.player, event);
}); });
@ -4218,6 +4399,20 @@ var Listeners = function () {
// Check if media failed to load // Check if media failed to load
// utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event)); // utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event));
// 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', function () {
// If ads are enabled, wait for them first
if (_this3.player.ads.enabled && !_this3.player.ads.initialized) {
// Wait for manager response
_this3.player.ads.managerPromise.then(function () {
return _this3.player.ads.play();
}).catch(function () {
return _this3.player.play();
});
}
});
// Click video // Click video
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) { if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
// Re-fetch the wrapper // Re-fetch the wrapper
@ -4268,13 +4463,16 @@ var Listeners = function () {
_this3.player.storage.set({ speed: _this3.player.speed }); _this3.player.storage.set({ speed: _this3.player.speed });
}); });
// Quality change // Quality request
utils.on(this.player.media, 'qualitychange', function () { utils.on(this.player.media, 'qualityrequested', function (event) {
// Update UI
controls.updateSetting.call(_this3.player, 'quality');
// Save to storage // Save to storage
_this3.player.storage.set({ quality: _this3.player.quality }); _this3.player.storage.set({ quality: event.detail.quality });
});
// Quality change
utils.on(this.player.media, 'qualitychange', function (event) {
// Update UI
controls.updateSetting.call(_this3.player, 'quality', null, event.detail.quality);
}); });
// Caption language change // Caption language change
@ -4813,21 +5011,23 @@ var Ads = function () {
this.cuePoints = this.manager.getCuePoints(); this.cuePoints = this.manager.getCuePoints();
// Add advertisement cue's within the time line if available // Add advertisement cue's within the time line if available
this.cuePoints.forEach(function (cuePoint) { if (!utils.is.empty(this.cuePoints)) {
if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < _this6.player.duration) { this.cuePoints.forEach(function (cuePoint) {
var seekElement = _this6.player.elements.progress; if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < _this6.player.duration) {
var seekElement = _this6.player.elements.progress;
if (seekElement) { if (utils.is.element(seekElement)) {
var cuePercentage = 100 / _this6.player.duration * cuePoint; var cuePercentage = 100 / _this6.player.duration * cuePoint;
var cue = utils.createElement('span', { var cue = utils.createElement('span', {
class: _this6.player.config.classNames.cues class: _this6.player.config.classNames.cues
}); });
cue.style.left = cuePercentage.toString() + '%'; cue.style.left = cuePercentage.toString() + '%';
seekElement.appendChild(cue); seekElement.appendChild(cue);
}
} }
} });
}); }
// Get skippable state // Get skippable state
// TODO: Skip button // TODO: Skip button
@ -5011,6 +5211,10 @@ var Ads = function () {
this.player.on('seeked', function () { this.player.on('seeked', function () {
var seekedTime = _this8.player.currentTime; var seekedTime = _this8.player.currentTime;
if (utils.is.empty(_this8.cuePoints)) {
return;
}
_this8.cuePoints.forEach(function (cuePoint, index) { _this8.cuePoints.forEach(function (cuePoint, index) {
if (time < cuePoint && cuePoint < seekedTime) { if (time < cuePoint && cuePoint < seekedTime) {
_this8.manager.discardAdBreak(); _this8.manager.discardAdBreak();
@ -5022,7 +5226,9 @@ var Ads = function () {
// Listen to the resizing of the window. And resize ad accordingly // Listen to the resizing of the window. And resize ad accordingly
// TODO: eventually implement ResizeObserver // TODO: eventually implement ResizeObserver
window.addEventListener('resize', function () { window.addEventListener('resize', function () {
_this8.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); if (_this8.manager) {
_this8.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
}
}); });
} }
@ -5255,6 +5461,66 @@ var Ads = function () {
// ========================================================================== // ==========================================================================
// Standardise YouTube quality unit
function mapQualityUnit(input) {
switch (input) {
case 'hd2160':
return 2160;
case 2160:
return 'hd2160';
case 'hd1440':
return 1440;
case 1440:
return 'hd1440';
case 'hd1080':
return 1080;
case 1080:
return 'hd1080';
case 'hd720':
return 720;
case 720:
return 'hd720';
case 'large':
return 480;
case 480:
return 'large';
case 'medium':
return 360;
case 360:
return 'medium';
case 'small':
return 240;
case 240:
return 'small';
default:
return 'default';
}
}
function mapQualityUnits(levels) {
if (utils.is.empty(levels)) {
return levels;
}
return utils.dedupe(levels.map(function (level) {
return mapQualityUnit(level);
}));
}
var youtube = { var youtube = {
setup: function setup() { setup: function setup() {
var _this = this; var _this = this;
@ -5419,14 +5685,10 @@ var youtube = {
utils.dispatchEvent.call(player, player.media, 'error'); utils.dispatchEvent.call(player, player.media, 'error');
}, },
onPlaybackQualityChange: function onPlaybackQualityChange(event) { onPlaybackQualityChange: function onPlaybackQualityChange() {
// Get the instance utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
var instance = event.target; quality: player.media.quality
});
// Get current quality
player.media.quality = instance.getPlaybackQuality();
utils.dispatchEvent.call(player, player.media, 'qualitychange');
}, },
onPlaybackRateChange: function onPlaybackRateChange(event) { onPlaybackRateChange: function onPlaybackRateChange(event) {
// Get the instance // Get the instance
@ -5491,15 +5753,18 @@ var youtube = {
// Quality // Quality
Object.defineProperty(player.media, 'quality', { Object.defineProperty(player.media, 'quality', {
get: function get() { get: function get() {
return instance.getPlaybackQuality(); return mapQualityUnit(instance.getPlaybackQuality());
}, },
set: function set(input) { set: function set(input) {
var quality = input;
// Set via API
instance.setPlaybackQuality(mapQualityUnit(quality));
// Trigger request event // Trigger request event
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, { utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
quality: input quality: quality
}); });
instance.setPlaybackQuality(input);
} }
}); });
@ -5547,8 +5812,7 @@ var youtube = {
}); });
// Get available speeds // Get available speeds
var options = instance.getAvailablePlaybackRates(); player.options.speed = instance.getAvailablePlaybackRates();
controls.setSpeedMenu.call(player, options);
// Set the tabindex to avoid focus entering iframe // Set the tabindex to avoid focus entering iframe
if (player.supported.ui) { if (player.supported.ui) {
@ -5656,7 +5920,7 @@ var youtube = {
} }
// Get quality // Get quality
controls.setQualityMenu.call(player, instance.getAvailableQualityLevels()); controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels()));
break; break;
@ -6078,32 +6342,9 @@ var media = {
} }
} else if (this.isHTML5) { } else if (this.isHTML5) {
ui.setTitle.call(this); ui.setTitle.call(this);
html5.extend.call(this);
} }
},
// Cancel current network requests
// See https://github.com/sampotts/plyr/issues/174
cancelRequests: function cancelRequests() {
if (!this.isHTML5) {
return;
}
// Remove child sources
utils.removeElement(this.media.querySelectorAll('source'));
// Set blank video src attribute
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
this.media.setAttribute('src', this.config.blankVideo);
// Load the new empty source
// This will cancel existing requests
// See https://github.com/sampotts/plyr/issues/174
this.media.load();
// Debugging
this.debug.log('Cancelled network requests');
} }
}; };
@ -6137,11 +6378,12 @@ var source = {
} }
// Cancel current network requests // Cancel current network requests
media.cancelRequests.call(this); html5.cancelRequests.call(this);
// Destroy instance and re-setup // Destroy instance and re-setup
this.destroy.call(this, function () { this.destroy.call(this, function () {
// TODO: Reset menus here // Reset quality options
_this2.options.quality = [];
// Remove elements // Remove elements
utils.removeElement(_this2.media); utils.removeElement(_this2.media);
@ -6360,7 +6602,17 @@ var Plyr = function () {
} }
// Cache original element state for .destroy() // Cache original element state for .destroy()
this.elements.original = this.media.cloneNode(true); // TODO: Investigate a better solution as I suspect this causes reported double load issues?
setTimeout(function () {
var clone = _this.media.cloneNode(true);
// Prevent the clone autoplaying
if (clone.getAttribute('autoplay')) {
clone.pause();
}
_this.elements.original = clone;
}, 0);
// Set media type based on tag or data attribute // Set media type based on tag or data attribute
// Supported: video, audio, vimeo, youtube // Supported: video, audio, vimeo, youtube
@ -6510,6 +6762,11 @@ var Plyr = function () {
// Setup ads if provided // Setup ads if provided
this.ads = new Ads(this); this.ads = new Ads(this);
// Autoplay if required
if (this.config.autoplay) {
this.play();
}
} }
// --------------------------------------- // ---------------------------------------
@ -6529,20 +6786,14 @@ var Plyr = function () {
* Play the media, or play the advertisement (if they are not blocked) * Play the media, or play the advertisement (if they are not blocked)
*/ */
value: function play() { value: function play() {
var _this2 = this;
if (!utils.is.function(this.media.play)) { if (!utils.is.function(this.media.play)) {
return null; return null;
} }
// If ads are enabled, wait for them first // If ads are enabled, wait for them first
if (this.ads.enabled && !this.ads.initialized) { /* if (this.ads.enabled && !this.ads.initialized) {
return this.ads.managerPromise.then(function () { return this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play());
return _this2.ads.play(); } */
}).catch(function () {
return _this2.media.play();
});
}
// Return the promise (for HTML5) // Return the promise (for HTML5)
return this.media.play(); return this.media.play();
@ -6594,7 +6845,7 @@ var Plyr = function () {
value: function stop() { value: function stop() {
if (this.isHTML5) { if (this.isHTML5) {
this.media.load(); this.media.load();
} else { } else if (utils.is.function(this.media.stop)) {
this.media.stop(); this.media.stop();
} }
} }
@ -6729,7 +6980,7 @@ var Plyr = function () {
}, { }, {
key: 'toggleControls', key: 'toggleControls',
value: function toggleControls(toggle) { value: function toggleControls(toggle) {
var _this3 = this; var _this2 = this;
// We need controls of course... // We need controls of course...
if (!utils.is.element(this.elements.controls)) { if (!utils.is.element(this.elements.controls)) {
@ -6803,25 +7054,30 @@ var Plyr = function () {
// then set the timer to hide the controls // then set the timer to hide the controls
if (!show || this.playing) { if (!show || this.playing) {
this.timers.controls = setTimeout(function () { this.timers.controls = setTimeout(function () {
// We need controls of course...
if (!utils.is.element(_this2.elements.controls)) {
return;
}
// If the mouse is over the controls (and not entering fullscreen), bail // If the mouse is over the controls (and not entering fullscreen), bail
if ((_this3.elements.controls.pressed || _this3.elements.controls.hover) && !isEnterFullscreen) { if ((_this2.elements.controls.pressed || _this2.elements.controls.hover) && !isEnterFullscreen) {
return; return;
} }
// Restore transition behaviour // Restore transition behaviour
if (!utils.hasClass(_this3.elements.container, _this3.config.classNames.hideControls)) { if (!utils.hasClass(_this2.elements.container, _this2.config.classNames.hideControls)) {
utils.toggleClass(_this3.elements.controls, _this3.config.classNames.noTransition, false); utils.toggleClass(_this2.elements.controls, _this2.config.classNames.noTransition, false);
} }
// Check if controls toggled // Check if controls toggled
var toggled = utils.toggleClass(_this3.elements.container, _this3.config.classNames.hideControls, true); var toggled = utils.toggleClass(_this2.elements.container, _this2.config.classNames.hideControls, true);
// Trigger event and close menu // Trigger event and close menu
if (toggled) { if (toggled) {
utils.dispatchEvent.call(_this3, _this3.media, 'controlshidden'); utils.dispatchEvent.call(_this2, _this2.media, 'controlshidden');
if (_this3.config.controls.includes('settings') && !utils.is.empty(_this3.config.settings)) { if (_this2.config.controls.includes('settings') && !utils.is.empty(_this2.config.settings)) {
controls.toggleMenu.call(_this3, false); controls.toggleMenu.call(_this2, false);
} }
} }
}, delay); }, delay);
@ -6863,7 +7119,7 @@ var Plyr = function () {
}, { }, {
key: 'destroy', key: 'destroy',
value: function destroy(callback) { value: function destroy(callback) {
var _this4 = this; var _this3 = this;
var soft = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var soft = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
@ -6876,22 +7132,22 @@ var Plyr = function () {
document.body.style.overflow = ''; document.body.style.overflow = '';
// GC for embed // GC for embed
_this4.embed = null; _this3.embed = null;
// If it's a soft destroy, make minimal changes // If it's a soft destroy, make minimal changes
if (soft) { if (soft) {
if (Object.keys(_this4.elements).length) { if (Object.keys(_this3.elements).length) {
// Remove elements // Remove elements
utils.removeElement(_this4.elements.buttons.play); utils.removeElement(_this3.elements.buttons.play);
utils.removeElement(_this4.elements.captions); utils.removeElement(_this3.elements.captions);
utils.removeElement(_this4.elements.controls); utils.removeElement(_this3.elements.controls);
utils.removeElement(_this4.elements.wrapper); utils.removeElement(_this3.elements.wrapper);
// Clear for GC // Clear for GC
_this4.elements.buttons.play = null; _this3.elements.buttons.play = null;
_this4.elements.captions = null; _this3.elements.captions = null;
_this4.elements.controls = null; _this3.elements.controls = null;
_this4.elements.wrapper = null; _this3.elements.wrapper = null;
} }
// Callback // Callback
@ -6900,26 +7156,26 @@ var Plyr = function () {
} }
} else { } else {
// Unbind listeners // Unbind listeners
_this4.listeners.clear(); _this3.listeners.clear();
// Replace the container with the original element provided // Replace the container with the original element provided
utils.replaceElement(_this4.elements.original, _this4.elements.container); utils.replaceElement(_this3.elements.original, _this3.elements.container);
// Event // Event
utils.dispatchEvent.call(_this4, _this4.elements.original, 'destroyed', true); utils.dispatchEvent.call(_this3, _this3.elements.original, 'destroyed', true);
// Callback // Callback
if (utils.is.function(callback)) { if (utils.is.function(callback)) {
callback.call(_this4.elements.original); callback.call(_this3.elements.original);
} }
// Reset state // Reset state
_this4.ready = false; _this3.ready = false;
// Clear for garbage collection // Clear for garbage collection
setTimeout(function () { setTimeout(function () {
_this4.elements = null; _this3.elements = null;
_this4.media = null; _this3.media = null;
}, 200); }, 200);
} }
}; };
@ -7171,8 +7427,8 @@ var Plyr = function () {
// Set the player volume // Set the player volume
this.media.volume = volume; this.media.volume = volume;
// If muted, and we're increasing volume, reset muted state // If muted, and we're increasing volume manually, reset muted state
if (this.muted && volume > 0) { if (!utils.is.empty(value) && this.muted && volume > 0) {
this.muted = false; this.muted = false;
} }
} }
@ -7286,8 +7542,8 @@ var Plyr = function () {
/** /**
* Set playback quality * Set playback quality
* Currently YouTube only * Currently HTML5 & YouTube only
* @param {string} input - Quality level * @param {number} input - Quality level
*/ */
}, { }, {
@ -7295,23 +7551,32 @@ var Plyr = function () {
set: function set$$1(input) { set: function set$$1(input) {
var quality = null; var quality = null;
if (utils.is.string(input)) { if (!utils.is.empty(input)) {
quality = input; quality = Number(input);
} }
if (!utils.is.string(quality)) { if (!utils.is.number(quality) || quality === 0) {
quality = this.storage.get('quality'); quality = this.storage.get('quality');
} }
if (!utils.is.string(quality)) { if (!utils.is.number(quality)) {
quality = this.config.quality.selected; quality = this.config.quality.selected;
} }
if (!this.options.quality.includes(quality)) { if (!utils.is.number(quality)) {
this.debug.warn('Unsupported quality option (' + quality + ')'); quality = this.config.quality.default;
}
if (!this.options.quality.length) {
return; return;
} }
if (!this.options.quality.includes(quality)) {
var closest = utils.closest(this.options.quality, quality);
this.debug.warn('Unsupported quality option: ' + quality + ', using ' + closest + ' instead');
quality = closest;
}
// Update config // Update config
this.config.quality.selected = quality; this.config.quality.selected = quality;

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,15 +5117,15 @@ var defaults = {
// Sprite (for icons) // Sprite (for icons)
loadSprite: true, loadSprite: true,
iconPrefix: 'plyr', iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.0.10/plyr.svg', iconUrl: 'https://cdn.plyr.io/3.1.0/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',
// Quality default // Quality default
quality: { quality: {
default: 'default', default: 576,
options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240, 'default']
}, },
// Set loops // Set loops
@ -6373,15 +6373,19 @@ var utils = {
// Trigger event // Trigger event
dispatchEvent: function dispatchEvent(element, type, bubbles, detail) { dispatchEvent: function dispatchEvent(element) {
var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
var bubbles = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var detail = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
// Bail if no element // Bail if no element
if (!utils.is.element(element) || !utils.is.string(type)) { if (!utils.is.element(element) || utils.is.empty(type)) {
return; return;
} }
// Create and dispatch the event // Create and dispatch the event
var event = new CustomEvent(type, { var event = new CustomEvent(type, {
bubbles: utils.is.boolean(bubbles) ? bubbles : false, bubbles: bubbles,
detail: Object.assign({}, detail, { detail: Object.assign({}, detail, {
plyr: utils.is.plyr(this) ? this : null plyr: utils.is.plyr(this) ? this : null
}) })
@ -6560,6 +6564,30 @@ var utils = {
}, },
// Remove duplicates in an array
dedupe: function dedupe(array) {
if (!utils.is.array(array)) {
return array;
}
return array.filter(function (item, index) {
return array.indexOf(item) === index;
});
},
// Get the closest value in an array
closest: function closest(array, value) {
if (!utils.is.array(array) || !array.length) {
return null;
}
return array.reduce(function (prev, curr) {
return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev;
});
},
// Get the provider for a given URL // Get the provider for a given URL
getProviderByUrl: function getProviderByUrl(url) { getProviderByUrl: function getProviderByUrl(url) {
// YouTube // YouTube
@ -6771,6 +6799,11 @@ var support = {
return false; return false;
} }
// Check directly if codecs specified
if (type.includes('codecs=')) {
return media.canPlayType(type).replace(/no/, '');
}
// Type specific checks // Type specific checks
if (this.isVideo) { if (this.isVideo) {
switch (type) { switch (type) {
@ -6964,13 +6997,13 @@ var Fullscreen = function () {
}); });
// Fullscreen toggle on double click // Fullscreen toggle on double click
utils.on(this.player.elements.container, 'dblclick', function () { utils.on(this.player.elements.container, 'dblclick', function (event) {
_this.toggle(); // Ignore double click in controls
}); if (_this.player.elements.controls.contains(event.target)) {
return;
}
// Prevent double click on controls bubbling up _this.toggle();
utils.on(this.player.elements.controls, 'dblclick', function (event) {
return event.stopPropagation();
}); });
// Update the UI // Update the UI
@ -7461,8 +7494,11 @@ var ui = {
// Reset loop state // Reset loop state
this.loop = null; this.loop = null;
// Reset quality options // Reset quality setting
this.options.quality = []; this.quality = null;
// Reset volume display
ui.updateVolume.call(this);
// Reset time display // Reset time display
ui.timeUpdate.call(this); ui.timeUpdate.call(this);
@ -7731,6 +7767,159 @@ var ui = {
// ========================================================================== // ==========================================================================
var html5 = {
getSources: function getSources() {
if (!this.isHTML5) {
return null;
}
return this.media.querySelectorAll('source');
},
// Get quality levels
getQualityOptions: function getQualityOptions() {
if (!this.isHTML5) {
return null;
}
// Get sources
var sources = html5.getSources.call(this);
if (utils.is.empty(sources)) {
return null;
}
// Get <source> with size attribute
var sizes = Array.from(sources).filter(function (source) {
return !utils.is.empty(source.getAttribute('size'));
});
// If none, bail
if (utils.is.empty(sizes)) {
return null;
}
// Reduce to unique list
return utils.dedupe(sizes.map(function (source) {
return Number(source.getAttribute('size'));
}));
},
extend: function extend() {
if (!this.isHTML5) {
return;
}
var player = this;
// Quality
Object.defineProperty(player.media, 'quality', {
get: function get() {
// Get sources
var sources = html5.getSources.call(player);
if (utils.is.empty(sources)) {
return null;
}
var matches = Array.from(sources).filter(function (source) {
return source.getAttribute('src') === player.source;
});
if (utils.is.empty(matches)) {
return null;
}
return Number(matches[0].getAttribute('size'));
},
set: function set(input) {
// Get sources
var sources = html5.getSources.call(player);
if (utils.is.empty(sources)) {
return;
}
// Get matches for requested size
var matches = Array.from(sources).filter(function (source) {
return Number(source.getAttribute('size')) === input;
});
// No matches for requested size
if (utils.is.empty(matches)) {
return;
}
// Get supported sources
var supported = matches.filter(function (source) {
return support.mime.call(player, source.getAttribute('type'));
});
// No supported sources
if (utils.is.empty(supported)) {
return;
}
// Trigger change event
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
quality: input
});
// Get current state
var currentTime = player.currentTime,
playing = player.playing;
// Set new source
player.media.src = supported[0].getAttribute('src');
// Load new source
player.media.load();
// Resume playing
if (playing) {
player.play();
}
// Restore time
player.currentTime = currentTime;
// Trigger change event
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
quality: input
});
}
});
},
// Cancel current network requests
// See https://github.com/sampotts/plyr/issues/174
cancelRequests: function cancelRequests() {
if (!this.isHTML5) {
return;
}
// Remove child sources
utils.removeElement(html5.getSources());
// Set blank video src attribute
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
this.media.setAttribute('src', this.config.blankVideo);
// Load the new empty source
// This will cancel existing requests
// See https://github.com/sampotts/plyr/issues/174
this.media.load();
// Debugging
this.debug.log('Cancelled network requests');
}
};
// ==========================================================================
// Sniff out the browser // Sniff out the browser
var browser$1 = utils.getBrowser(); var browser$1 = utils.getBrowser();
@ -8131,8 +8320,8 @@ var controls = {
}, },
// Set the YouTube quality menu // Set the quality menu
// TODO: Support for HTML5 // TODO: Vimeo support
setQualityMenu: function setQualityMenu(options) { setQualityMenu: function setQualityMenu(options) {
var _this2 = this; var _this2 = this;
@ -8149,12 +8338,10 @@ var controls = {
this.options.quality = options.filter(function (quality) { this.options.quality = options.filter(function (quality) {
return _this2.config.quality.options.includes(quality); return _this2.config.quality.options.includes(quality);
}); });
} else {
this.options.quality = this.config.quality.options;
} }
// Toggle the pane and tab // Toggle the pane and tab
var toggle = !utils.is.empty(this.options.quality) && this.isYouTube; 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);
// If we're hiding, nothing more to do // If we're hiding, nothing more to do
@ -8170,20 +8357,18 @@ var controls = {
var label = ''; var label = '';
switch (quality) { switch (quality) {
case 'hd2160': case 2160:
label = '4K'; label = '4K';
break; break;
case 'hd1440': case 1440:
label = 'WQHD'; case 1080:
break; case 720:
case 'hd1080':
label = 'HD'; label = 'HD';
break; break;
case 'hd720': case 576:
label = 'HD'; label = 'SD';
break; break;
default: default:
@ -8197,8 +8382,13 @@ var controls = {
return controls.createBadge.call(_this2, label); return controls.createBadge.call(_this2, label);
}; };
this.options.quality.forEach(function (quality) { // Sort options by the config and then render options
return controls.createMenuItem.call(_this2, quality, list, type, controls.getLabel.call(_this2, 'quality', quality), getBadge(quality)); this.options.quality.sort(function (a, b) {
var sorting = _this2.config.quality.options;
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
}).forEach(function (quality) {
var label = controls.getLabel.call(_this2, 'quality', quality);
controls.createMenuItem.call(_this2, quality, list, type, label, getBadge(quality));
}); });
controls.updateSetting.call(this, type, list); controls.updateSetting.call(this, type, list);
@ -8213,28 +8403,10 @@ var controls = {
return value === 1 ? 'Normal' : value + '&times;'; return value === 1 ? 'Normal' : value + '&times;';
case 'quality': case 'quality':
switch (value) { if (utils.is.number(value)) {
case 'hd2160': return value + 'p';
return '2160P';
case 'hd1440':
return '1440P';
case 'hd1080':
return '1080P';
case 'hd720':
return '720P';
case 'large':
return '480P';
case 'medium':
return '360P';
case 'small':
return '240P';
case 'tiny':
return 'Tiny';
case 'default':
return 'Auto';
default:
return value;
} }
return utils.toTitleCase(value);
case 'captions': case 'captions':
return controls.getLanguage.call(this); return controls.getLanguage.call(this);
@ -8246,7 +8418,7 @@ var controls = {
// Update the selected setting // Update the selected setting
updateSetting: function updateSetting(setting, container) { updateSetting: function updateSetting(setting, container, input) {
var pane = this.elements.settings.panes[setting]; var pane = this.elements.settings.panes[setting];
var value = null; var value = null;
var list = container; var list = container;
@ -8257,7 +8429,7 @@ var controls = {
break; break;
default: default:
value = this[setting]; value = !utils.is.empty(input) ? input : this[setting];
// Get default // Get default
if (utils.is.empty(value)) { if (utils.is.empty(value)) {
@ -8265,7 +8437,7 @@ var controls = {
} }
// Unsupported value // Unsupported value
if (!this.options[setting].includes(value)) { if (!utils.is.empty(this.options[setting]) && !this.options[setting].includes(value)) {
this.debug.warn('Unsupported value of \'' + value + '\' for ' + setting); this.debug.warn('Unsupported value of \'' + value + '\' for ' + setting);
return; return;
} }
@ -8364,14 +8536,14 @@ var controls = {
var list = this.elements.settings.panes.captions.querySelector('ul'); var list = this.elements.settings.panes.captions.querySelector('ul');
// Toggle the pane and tab // Toggle the pane and tab
var hasTracks = captions.getTracks.call(this).length; var toggle = captions.getTracks.call(this).length;
controls.toggleTab.call(this, type, hasTracks); controls.toggleTab.call(this, type, toggle);
// Empty the menu // Empty the menu
utils.emptyElement(list); utils.emptyElement(list);
// If there's no captions, bail // If there's no captions, bail
if (!hasTracks) { if (!toggle) {
return; return;
} }
@ -8415,10 +8587,10 @@ var controls = {
var type = 'speed'; var type = 'speed';
// Set the speed options // Set the speed options
if (!utils.is.array(options)) { if (utils.is.array(options)) {
this.options.speed = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
} else {
this.options.speed = options; this.options.speed = options;
} else if (this.isHTML5 || this.isVimeo) {
this.options.speed = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
} }
// Set options if passed and filter based on config // Set options if passed and filter based on config
@ -8427,7 +8599,7 @@ var controls = {
}); });
// Toggle the pane and tab // Toggle the pane and tab
var toggle = !utils.is.empty(this.options.speed); var toggle = !utils.is.empty(this.options.speed) && this.options.speed.length > 1;
controls.toggleTab.call(this, type, toggle); controls.toggleTab.call(this, type, toggle);
// Check if we need to toggle the parent // Check if we need to toggle the parent
@ -8450,7 +8622,8 @@ var controls = {
// Create items // Create items
this.options.speed.forEach(function (speed) { this.options.speed.forEach(function (speed) {
return controls.createMenuItem.call(_this4, speed, list, type, controls.getLabel.call(_this4, 'speed', speed)); var label = controls.getLabel.call(_this4, 'speed', speed);
controls.createMenuItem.call(_this4, speed, list, type, label);
}); });
controls.updateSetting.call(this, type, list); controls.updateSetting.call(this, type, list);
@ -8459,10 +8632,13 @@ var controls = {
// Check if we need to hide/show the settings menu // Check if we need to hide/show the settings menu
checkMenu: function checkMenu() { checkMenu: function checkMenu() {
var speedHidden = this.elements.settings.tabs.speed.getAttribute('hidden') !== null; var tabs = this.elements.settings.tabs;
var languageHidden = this.elements.settings.tabs.captions.getAttribute('hidden') !== null;
utils.toggleHidden(this.elements.settings.menu, speedHidden && languageHidden); var visible = !utils.is.empty(tabs) && Object.values(tabs).some(function (tab) {
return !tab.hidden;
});
utils.toggleHidden(this.elements.settings.menu, !visible);
}, },
@ -8731,7 +8907,8 @@ var controls = {
// Settings button / menu // Settings button / menu
if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) { if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) {
var menu = utils.createElement('div', { var menu = utils.createElement('div', {
class: 'plyr__menu' class: 'plyr__menu',
hidden: ''
}); });
menu.appendChild(controls.createButton.call(this, 'settings', { menu.appendChild(controls.createButton.call(this, 'settings', {
@ -8856,6 +9033,10 @@ var controls = {
this.elements.controls = container; this.elements.controls = container;
if (this.isHTML5) {
controls.setQualityMenu.call(this, html5.getQualityOptions.call(this));
}
controls.setSpeedMenu.call(this); controls.setSpeedMenu.call(this);
return container; return container;
@ -9234,17 +9415,17 @@ var Listeners = function () {
return ui.updateProgress.call(_this3.player, event); return ui.updateProgress.call(_this3.player, event);
}); });
// Handle native mute // Handle volume changes
utils.on(this.player.media, 'volumechange', function (event) { utils.on(this.player.media, 'volumechange', function (event) {
return ui.updateVolume.call(_this3.player, event); return ui.updateVolume.call(_this3.player, event);
}); });
// Handle native play/pause // Handle play/pause
utils.on(this.player.media, 'playing play pause ended emptied', function (event) { utils.on(this.player.media, 'playing play pause ended emptied', function (event) {
return ui.checkPlaying.call(_this3.player, event); return ui.checkPlaying.call(_this3.player, event);
}); });
// Loading // Loading state
utils.on(this.player.media, 'waiting canplay seeked playing', function (event) { utils.on(this.player.media, 'waiting canplay seeked playing', function (event) {
return ui.checkLoading.call(_this3.player, event); return ui.checkLoading.call(_this3.player, event);
}); });
@ -9252,6 +9433,20 @@ var Listeners = function () {
// Check if media failed to load // Check if media failed to load
// utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event)); // utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event));
// 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', function () {
// If ads are enabled, wait for them first
if (_this3.player.ads.enabled && !_this3.player.ads.initialized) {
// Wait for manager response
_this3.player.ads.managerPromise.then(function () {
return _this3.player.ads.play();
}).catch(function () {
return _this3.player.play();
});
}
});
// Click video // Click video
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) { if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
// Re-fetch the wrapper // Re-fetch the wrapper
@ -9302,13 +9497,16 @@ var Listeners = function () {
_this3.player.storage.set({ speed: _this3.player.speed }); _this3.player.storage.set({ speed: _this3.player.speed });
}); });
// Quality change // Quality request
utils.on(this.player.media, 'qualitychange', function () { utils.on(this.player.media, 'qualityrequested', function (event) {
// Update UI
controls.updateSetting.call(_this3.player, 'quality');
// Save to storage // Save to storage
_this3.player.storage.set({ quality: _this3.player.quality }); _this3.player.storage.set({ quality: event.detail.quality });
});
// Quality change
utils.on(this.player.media, 'qualitychange', function (event) {
// Update UI
controls.updateSetting.call(_this3.player, 'quality', null, event.detail.quality);
}); });
// Caption language change // Caption language change
@ -9847,21 +10045,23 @@ var Ads = function () {
this.cuePoints = this.manager.getCuePoints(); this.cuePoints = this.manager.getCuePoints();
// Add advertisement cue's within the time line if available // Add advertisement cue's within the time line if available
this.cuePoints.forEach(function (cuePoint) { if (!utils.is.empty(this.cuePoints)) {
if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < _this6.player.duration) { this.cuePoints.forEach(function (cuePoint) {
var seekElement = _this6.player.elements.progress; if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < _this6.player.duration) {
var seekElement = _this6.player.elements.progress;
if (seekElement) { if (utils.is.element(seekElement)) {
var cuePercentage = 100 / _this6.player.duration * cuePoint; var cuePercentage = 100 / _this6.player.duration * cuePoint;
var cue = utils.createElement('span', { var cue = utils.createElement('span', {
class: _this6.player.config.classNames.cues class: _this6.player.config.classNames.cues
}); });
cue.style.left = cuePercentage.toString() + '%'; cue.style.left = cuePercentage.toString() + '%';
seekElement.appendChild(cue); seekElement.appendChild(cue);
}
} }
} });
}); }
// Get skippable state // Get skippable state
// TODO: Skip button // TODO: Skip button
@ -10045,6 +10245,10 @@ var Ads = function () {
this.player.on('seeked', function () { this.player.on('seeked', function () {
var seekedTime = _this8.player.currentTime; var seekedTime = _this8.player.currentTime;
if (utils.is.empty(_this8.cuePoints)) {
return;
}
_this8.cuePoints.forEach(function (cuePoint, index) { _this8.cuePoints.forEach(function (cuePoint, index) {
if (time < cuePoint && cuePoint < seekedTime) { if (time < cuePoint && cuePoint < seekedTime) {
_this8.manager.discardAdBreak(); _this8.manager.discardAdBreak();
@ -10056,7 +10260,9 @@ var Ads = function () {
// Listen to the resizing of the window. And resize ad accordingly // Listen to the resizing of the window. And resize ad accordingly
// TODO: eventually implement ResizeObserver // TODO: eventually implement ResizeObserver
window.addEventListener('resize', function () { window.addEventListener('resize', function () {
_this8.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); if (_this8.manager) {
_this8.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
}
}); });
} }
@ -10289,6 +10495,66 @@ var Ads = function () {
// ========================================================================== // ==========================================================================
// Standardise YouTube quality unit
function mapQualityUnit(input) {
switch (input) {
case 'hd2160':
return 2160;
case 2160:
return 'hd2160';
case 'hd1440':
return 1440;
case 1440:
return 'hd1440';
case 'hd1080':
return 1080;
case 1080:
return 'hd1080';
case 'hd720':
return 720;
case 720:
return 'hd720';
case 'large':
return 480;
case 480:
return 'large';
case 'medium':
return 360;
case 360:
return 'medium';
case 'small':
return 240;
case 240:
return 'small';
default:
return 'default';
}
}
function mapQualityUnits(levels) {
if (utils.is.empty(levels)) {
return levels;
}
return utils.dedupe(levels.map(function (level) {
return mapQualityUnit(level);
}));
}
var youtube = { var youtube = {
setup: function setup() { setup: function setup() {
var _this = this; var _this = this;
@ -10453,14 +10719,10 @@ var youtube = {
utils.dispatchEvent.call(player, player.media, 'error'); utils.dispatchEvent.call(player, player.media, 'error');
}, },
onPlaybackQualityChange: function onPlaybackQualityChange(event) { onPlaybackQualityChange: function onPlaybackQualityChange() {
// Get the instance utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
var instance = event.target; quality: player.media.quality
});
// Get current quality
player.media.quality = instance.getPlaybackQuality();
utils.dispatchEvent.call(player, player.media, 'qualitychange');
}, },
onPlaybackRateChange: function onPlaybackRateChange(event) { onPlaybackRateChange: function onPlaybackRateChange(event) {
// Get the instance // Get the instance
@ -10525,15 +10787,18 @@ var youtube = {
// Quality // Quality
Object.defineProperty(player.media, 'quality', { Object.defineProperty(player.media, 'quality', {
get: function get() { get: function get() {
return instance.getPlaybackQuality(); return mapQualityUnit(instance.getPlaybackQuality());
}, },
set: function set(input) { set: function set(input) {
var quality = input;
// Set via API
instance.setPlaybackQuality(mapQualityUnit(quality));
// Trigger request event // Trigger request event
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, { utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
quality: input quality: quality
}); });
instance.setPlaybackQuality(input);
} }
}); });
@ -10581,8 +10846,7 @@ var youtube = {
}); });
// Get available speeds // Get available speeds
var options = instance.getAvailablePlaybackRates(); player.options.speed = instance.getAvailablePlaybackRates();
controls.setSpeedMenu.call(player, options);
// Set the tabindex to avoid focus entering iframe // Set the tabindex to avoid focus entering iframe
if (player.supported.ui) { if (player.supported.ui) {
@ -10690,7 +10954,7 @@ var youtube = {
} }
// Get quality // Get quality
controls.setQualityMenu.call(player, instance.getAvailableQualityLevels()); controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels()));
break; break;
@ -11112,32 +11376,9 @@ var media = {
} }
} else if (this.isHTML5) { } else if (this.isHTML5) {
ui.setTitle.call(this); ui.setTitle.call(this);
html5.extend.call(this);
} }
},
// Cancel current network requests
// See https://github.com/sampotts/plyr/issues/174
cancelRequests: function cancelRequests() {
if (!this.isHTML5) {
return;
}
// Remove child sources
utils.removeElement(this.media.querySelectorAll('source'));
// Set blank video src attribute
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
this.media.setAttribute('src', this.config.blankVideo);
// Load the new empty source
// This will cancel existing requests
// See https://github.com/sampotts/plyr/issues/174
this.media.load();
// Debugging
this.debug.log('Cancelled network requests');
} }
}; };
@ -11171,11 +11412,12 @@ var source = {
} }
// Cancel current network requests // Cancel current network requests
media.cancelRequests.call(this); html5.cancelRequests.call(this);
// Destroy instance and re-setup // Destroy instance and re-setup
this.destroy.call(this, function () { this.destroy.call(this, function () {
// TODO: Reset menus here // Reset quality options
_this2.options.quality = [];
// Remove elements // Remove elements
utils.removeElement(_this2.media); utils.removeElement(_this2.media);
@ -11394,7 +11636,17 @@ var Plyr = function () {
} }
// Cache original element state for .destroy() // Cache original element state for .destroy()
this.elements.original = this.media.cloneNode(true); // TODO: Investigate a better solution as I suspect this causes reported double load issues?
setTimeout(function () {
var clone = _this.media.cloneNode(true);
// Prevent the clone autoplaying
if (clone.getAttribute('autoplay')) {
clone.pause();
}
_this.elements.original = clone;
}, 0);
// Set media type based on tag or data attribute // Set media type based on tag or data attribute
// Supported: video, audio, vimeo, youtube // Supported: video, audio, vimeo, youtube
@ -11544,6 +11796,11 @@ var Plyr = function () {
// Setup ads if provided // Setup ads if provided
this.ads = new Ads(this); this.ads = new Ads(this);
// Autoplay if required
if (this.config.autoplay) {
this.play();
}
} }
// --------------------------------------- // ---------------------------------------
@ -11563,20 +11820,14 @@ var Plyr = function () {
* Play the media, or play the advertisement (if they are not blocked) * Play the media, or play the advertisement (if they are not blocked)
*/ */
value: function play() { value: function play() {
var _this2 = this;
if (!utils.is.function(this.media.play)) { if (!utils.is.function(this.media.play)) {
return null; return null;
} }
// If ads are enabled, wait for them first // If ads are enabled, wait for them first
if (this.ads.enabled && !this.ads.initialized) { /* if (this.ads.enabled && !this.ads.initialized) {
return this.ads.managerPromise.then(function () { return this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play());
return _this2.ads.play(); } */
}).catch(function () {
return _this2.media.play();
});
}
// Return the promise (for HTML5) // Return the promise (for HTML5)
return this.media.play(); return this.media.play();
@ -11628,7 +11879,7 @@ var Plyr = function () {
value: function stop() { value: function stop() {
if (this.isHTML5) { if (this.isHTML5) {
this.media.load(); this.media.load();
} else { } else if (utils.is.function(this.media.stop)) {
this.media.stop(); this.media.stop();
} }
} }
@ -11763,7 +12014,7 @@ var Plyr = function () {
}, { }, {
key: 'toggleControls', key: 'toggleControls',
value: function toggleControls(toggle) { value: function toggleControls(toggle) {
var _this3 = this; var _this2 = this;
// We need controls of course... // We need controls of course...
if (!utils.is.element(this.elements.controls)) { if (!utils.is.element(this.elements.controls)) {
@ -11837,25 +12088,30 @@ var Plyr = function () {
// then set the timer to hide the controls // then set the timer to hide the controls
if (!show || this.playing) { if (!show || this.playing) {
this.timers.controls = setTimeout(function () { this.timers.controls = setTimeout(function () {
// We need controls of course...
if (!utils.is.element(_this2.elements.controls)) {
return;
}
// If the mouse is over the controls (and not entering fullscreen), bail // If the mouse is over the controls (and not entering fullscreen), bail
if ((_this3.elements.controls.pressed || _this3.elements.controls.hover) && !isEnterFullscreen) { if ((_this2.elements.controls.pressed || _this2.elements.controls.hover) && !isEnterFullscreen) {
return; return;
} }
// Restore transition behaviour // Restore transition behaviour
if (!utils.hasClass(_this3.elements.container, _this3.config.classNames.hideControls)) { if (!utils.hasClass(_this2.elements.container, _this2.config.classNames.hideControls)) {
utils.toggleClass(_this3.elements.controls, _this3.config.classNames.noTransition, false); utils.toggleClass(_this2.elements.controls, _this2.config.classNames.noTransition, false);
} }
// Check if controls toggled // Check if controls toggled
var toggled = utils.toggleClass(_this3.elements.container, _this3.config.classNames.hideControls, true); var toggled = utils.toggleClass(_this2.elements.container, _this2.config.classNames.hideControls, true);
// Trigger event and close menu // Trigger event and close menu
if (toggled) { if (toggled) {
utils.dispatchEvent.call(_this3, _this3.media, 'controlshidden'); utils.dispatchEvent.call(_this2, _this2.media, 'controlshidden');
if (_this3.config.controls.includes('settings') && !utils.is.empty(_this3.config.settings)) { if (_this2.config.controls.includes('settings') && !utils.is.empty(_this2.config.settings)) {
controls.toggleMenu.call(_this3, false); controls.toggleMenu.call(_this2, false);
} }
} }
}, delay); }, delay);
@ -11897,7 +12153,7 @@ var Plyr = function () {
}, { }, {
key: 'destroy', key: 'destroy',
value: function destroy(callback) { value: function destroy(callback) {
var _this4 = this; var _this3 = this;
var soft = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var soft = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
@ -11910,22 +12166,22 @@ var Plyr = function () {
document.body.style.overflow = ''; document.body.style.overflow = '';
// GC for embed // GC for embed
_this4.embed = null; _this3.embed = null;
// If it's a soft destroy, make minimal changes // If it's a soft destroy, make minimal changes
if (soft) { if (soft) {
if (Object.keys(_this4.elements).length) { if (Object.keys(_this3.elements).length) {
// Remove elements // Remove elements
utils.removeElement(_this4.elements.buttons.play); utils.removeElement(_this3.elements.buttons.play);
utils.removeElement(_this4.elements.captions); utils.removeElement(_this3.elements.captions);
utils.removeElement(_this4.elements.controls); utils.removeElement(_this3.elements.controls);
utils.removeElement(_this4.elements.wrapper); utils.removeElement(_this3.elements.wrapper);
// Clear for GC // Clear for GC
_this4.elements.buttons.play = null; _this3.elements.buttons.play = null;
_this4.elements.captions = null; _this3.elements.captions = null;
_this4.elements.controls = null; _this3.elements.controls = null;
_this4.elements.wrapper = null; _this3.elements.wrapper = null;
} }
// Callback // Callback
@ -11934,26 +12190,26 @@ var Plyr = function () {
} }
} else { } else {
// Unbind listeners // Unbind listeners
_this4.listeners.clear(); _this3.listeners.clear();
// Replace the container with the original element provided // Replace the container with the original element provided
utils.replaceElement(_this4.elements.original, _this4.elements.container); utils.replaceElement(_this3.elements.original, _this3.elements.container);
// Event // Event
utils.dispatchEvent.call(_this4, _this4.elements.original, 'destroyed', true); utils.dispatchEvent.call(_this3, _this3.elements.original, 'destroyed', true);
// Callback // Callback
if (utils.is.function(callback)) { if (utils.is.function(callback)) {
callback.call(_this4.elements.original); callback.call(_this3.elements.original);
} }
// Reset state // Reset state
_this4.ready = false; _this3.ready = false;
// Clear for garbage collection // Clear for garbage collection
setTimeout(function () { setTimeout(function () {
_this4.elements = null; _this3.elements = null;
_this4.media = null; _this3.media = null;
}, 200); }, 200);
} }
}; };
@ -12205,8 +12461,8 @@ var Plyr = function () {
// Set the player volume // Set the player volume
this.media.volume = volume; this.media.volume = volume;
// If muted, and we're increasing volume, reset muted state // If muted, and we're increasing volume manually, reset muted state
if (this.muted && volume > 0) { if (!utils.is.empty(value) && this.muted && volume > 0) {
this.muted = false; this.muted = false;
} }
} }
@ -12320,8 +12576,8 @@ var Plyr = function () {
/** /**
* Set playback quality * Set playback quality
* Currently YouTube only * Currently HTML5 & YouTube only
* @param {string} input - Quality level * @param {number} input - Quality level
*/ */
}, { }, {
@ -12329,23 +12585,32 @@ var Plyr = function () {
set: function set(input) { set: function set(input) {
var quality = null; var quality = null;
if (utils.is.string(input)) { if (!utils.is.empty(input)) {
quality = input; quality = Number(input);
} }
if (!utils.is.string(quality)) { if (!utils.is.number(quality) || quality === 0) {
quality = this.storage.get('quality'); quality = this.storage.get('quality');
} }
if (!utils.is.string(quality)) { if (!utils.is.number(quality)) {
quality = this.config.quality.selected; quality = this.config.quality.selected;
} }
if (!this.options.quality.includes(quality)) { if (!utils.is.number(quality)) {
this.debug.warn('Unsupported quality option (' + quality + ')'); quality = this.config.quality.default;
}
if (!this.options.quality.length) {
return; return;
} }
if (!this.options.quality.includes(quality)) {
var closest = utils.closest(this.options.quality, quality);
this.debug.warn('Unsupported quality option: ' + quality + ', using ' + closest + ' instead');
quality = closest;
}
// Update config // Update config
this.config.quality.selected = quality; this.config.quality.selected = quality;

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.0.10", "version": "3.1.0",
"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",
@ -16,7 +16,7 @@
"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.9.0", "eslint-plugin-import": "^2.10.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",
@ -41,12 +41,12 @@
"rollup-plugin-commonjs": "^9.1.0", "rollup-plugin-commonjs": "^9.1.0",
"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.1.3", "stylelint": "^9.2.0",
"stylelint-config-prettier": "^3.0.4", "stylelint-config-prettier": "^3.0.4",
"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",
"stylelint-scss": "^2.5.0", "stylelint-scss": "^3.0.0",
"stylelint-selector-bem-pattern": "^2.0.0" "stylelint-selector-bem-pattern": "^2.0.0"
}, },
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"], "keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],

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.0.10/plyr.js"></script> <script src="https://cdn.plyr.io/3.1.0/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.0.10/plyr.css"> <link rel="stylesheet" href="https://cdn.plyr.io/3.1.0/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.0.10/plyr.svg`. reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.1.0/plyr.svg`.
## Ads ## Ads

94
src/js/controls.js vendored
View File

@ -7,6 +7,7 @@ import utils from './utils';
import ui from './ui'; import ui from './ui';
import i18n from './i18n'; import i18n from './i18n';
import captions from './captions'; import captions from './captions';
import html5 from './html5';
// Sniff out the browser // Sniff out the browser
const browser = utils.getBrowser(); const browser = utils.getBrowser();
@ -435,8 +436,8 @@ const controls = {
utils.toggleHidden(pane, !toggle); utils.toggleHidden(pane, !toggle);
}, },
// Set the YouTube quality menu // Set the quality menu
// TODO: Support for HTML5 // TODO: Vimeo support
setQualityMenu(options) { setQualityMenu(options) {
// Menu required // Menu required
if (!utils.is.element(this.elements.settings.panes.quality)) { if (!utils.is.element(this.elements.settings.panes.quality)) {
@ -449,12 +450,10 @@ const controls = {
// Set options if passed and filter based on config // Set options if passed and filter based on config
if (utils.is.array(options)) { if (utils.is.array(options)) {
this.options.quality = options.filter(quality => this.config.quality.options.includes(quality)); this.options.quality = options.filter(quality => this.config.quality.options.includes(quality));
} else {
this.options.quality = this.config.quality.options;
} }
// Toggle the pane and tab // Toggle the pane and tab
const toggle = !utils.is.empty(this.options.quality) && this.isYouTube; 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);
// If we're hiding, nothing more to do // If we're hiding, nothing more to do
@ -470,20 +469,18 @@ const controls = {
let label = ''; let label = '';
switch (quality) { switch (quality) {
case 'hd2160': case 2160:
label = '4K'; label = '4K';
break; break;
case 'hd1440': case 1440:
label = 'WQHD'; case 1080:
break; case 720:
case 'hd1080':
label = 'HD'; label = 'HD';
break; break;
case 'hd720': case 576:
label = 'HD'; label = 'SD';
break; break;
default: default:
@ -497,9 +494,14 @@ const controls = {
return controls.createBadge.call(this, label); return controls.createBadge.call(this, label);
}; };
this.options.quality.forEach(quality => // Sort options by the config and then render options
controls.createMenuItem.call(this, quality, list, type, controls.getLabel.call(this, 'quality', quality), 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); controls.updateSetting.call(this, type, list);
}, },
@ -512,28 +514,10 @@ const controls = {
return value === 1 ? 'Normal' : `${value}&times;`; return value === 1 ? 'Normal' : `${value}&times;`;
case 'quality': case 'quality':
switch (value) { if (utils.is.number(value)) {
case 'hd2160': return `${value}p`;
return '2160P';
case 'hd1440':
return '1440P';
case 'hd1080':
return '1080P';
case 'hd720':
return '720P';
case 'large':
return '480P';
case 'medium':
return '360P';
case 'small':
return '240P';
case 'tiny':
return 'Tiny';
case 'default':
return 'Auto';
default:
return value;
} }
return utils.toTitleCase(value);
case 'captions': case 'captions':
return controls.getLanguage.call(this); return controls.getLanguage.call(this);
@ -544,7 +528,7 @@ const controls = {
}, },
// Update the selected setting // Update the selected setting
updateSetting(setting, container) { updateSetting(setting, container, input) {
const pane = this.elements.settings.panes[setting]; const pane = this.elements.settings.panes[setting];
let value = null; let value = null;
let list = container; let list = container;
@ -555,7 +539,7 @@ const controls = {
break; break;
default: default:
value = this[setting]; value = !utils.is.empty(input) ? input : this[setting];
// Get default // Get default
if (utils.is.empty(value)) { if (utils.is.empty(value)) {
@ -563,7 +547,7 @@ const controls = {
} }
// Unsupported value // Unsupported value
if (!this.options[setting].includes(value)) { if (!utils.is.empty(this.options[setting]) && !this.options[setting].includes(value)) {
this.debug.warn(`Unsupported value of '${value}' for ${setting}`); this.debug.warn(`Unsupported value of '${value}' for ${setting}`);
return; return;
} }
@ -666,14 +650,14 @@ const controls = {
const list = this.elements.settings.panes.captions.querySelector('ul'); const list = this.elements.settings.panes.captions.querySelector('ul');
// Toggle the pane and tab // Toggle the pane and tab
const hasTracks = captions.getTracks.call(this).length; const toggle = captions.getTracks.call(this).length;
controls.toggleTab.call(this, type, hasTracks); controls.toggleTab.call(this, type, toggle);
// Empty the menu // Empty the menu
utils.emptyElement(list); utils.emptyElement(list);
// If there's no captions, bail // If there's no captions, bail
if (!hasTracks) { if (!toggle) {
return; return;
} }
@ -720,7 +704,9 @@ const controls = {
const type = 'speed'; const type = 'speed';
// Set the speed options // Set the speed options
if (!utils.is.array(options)) { if (utils.is.array(options)) {
this.options.speed = options;
} else if (this.isHTML5 || this.isVimeo) {
this.options.speed = [ this.options.speed = [
0.5, 0.5,
0.75, 0.75,
@ -730,15 +716,13 @@ const controls = {
1.75, 1.75,
2, 2,
]; ];
} else {
this.options.speed = options;
} }
// Set options if passed and filter based on config // Set options if passed and filter based on config
this.options.speed = this.options.speed.filter(speed => this.config.speed.options.includes(speed)); this.options.speed = this.options.speed.filter(speed => this.config.speed.options.includes(speed));
// Toggle the pane and tab // Toggle the pane and tab
const toggle = !utils.is.empty(this.options.speed); const toggle = !utils.is.empty(this.options.speed) && this.options.speed.length > 1;
controls.toggleTab.call(this, type, toggle); controls.toggleTab.call(this, type, toggle);
// Check if we need to toggle the parent // Check if we need to toggle the parent
@ -760,17 +744,20 @@ const controls = {
utils.emptyElement(list); utils.emptyElement(list);
// Create items // Create items
this.options.speed.forEach(speed => controls.createMenuItem.call(this, speed, list, type, controls.getLabel.call(this, 'speed', speed))); this.options.speed.forEach(speed => {
const label = controls.getLabel.call(this, 'speed', speed);
controls.createMenuItem.call(this, speed, list, type, label);
});
controls.updateSetting.call(this, type, list); controls.updateSetting.call(this, type, list);
}, },
// Check if we need to hide/show the settings menu // Check if we need to hide/show the settings menu
checkMenu() { checkMenu() {
const speedHidden = this.elements.settings.tabs.speed.getAttribute('hidden') !== null; const { tabs } = this.elements.settings;
const languageHidden = this.elements.settings.tabs.captions.getAttribute('hidden') !== null; const visible = !utils.is.empty(tabs) && Object.values(tabs).some(tab => !tab.hidden);
utils.toggleHidden(this.elements.settings.menu, speedHidden && languageHidden); utils.toggleHidden(this.elements.settings.menu, !visible);
}, },
// Show/hide menu // Show/hide menu
@ -1043,6 +1030,7 @@ const controls = {
if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) { if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) {
const menu = utils.createElement('div', { const menu = utils.createElement('div', {
class: 'plyr__menu', class: 'plyr__menu',
hidden: '',
}); });
menu.appendChild( menu.appendChild(
@ -1177,6 +1165,10 @@ const controls = {
this.elements.controls = container; this.elements.controls = container;
if (this.isHTML5) {
controls.setQualityMenu.call(this, html5.getQualityOptions.call(this));
}
controls.setSpeedMenu.call(this); controls.setSpeedMenu.call(this);
return container; return container;

View File

@ -56,24 +56,26 @@ const defaults = {
// Sprite (for icons) // Sprite (for icons)
loadSprite: true, loadSprite: true,
iconPrefix: 'plyr', iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.0.10/plyr.svg', iconUrl: 'https://cdn.plyr.io/3.1.0/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',
// Quality default // Quality default
quality: { quality: {
default: 'default', default: 576,
options: [ options: [
'hd2160', 4320,
'hd1440', 2880,
'hd1080', 2160,
'hd720', 1440,
'large', 1080,
'medium', 720,
'small', 576,
'tiny', 480,
'default', 360,
240,
'default', // YouTube's "auto"
], ],
}, },

View File

@ -68,13 +68,15 @@ class Fullscreen {
}); });
// Fullscreen toggle on double click // Fullscreen toggle on double click
utils.on(this.player.elements.container, 'dblclick', () => { utils.on(this.player.elements.container, 'dblclick', event => {
// Ignore double click in controls
if (this.player.elements.controls.contains(event.target)) {
return;
}
this.toggle(); this.toggle();
}); });
// Prevent double click on controls bubbling up
utils.on(this.player.elements.controls, 'dblclick', event => event.stopPropagation());
// Update the UI // Update the UI
this.update(); this.update();
} }

146
src/js/html5.js Normal file
View File

@ -0,0 +1,146 @@
// ==========================================================================
// Plyr HTML5 helpers
// ==========================================================================
import support from './support';
import utils from './utils';
const html5 = {
getSources() {
if (!this.isHTML5) {
return null;
}
return this.media.querySelectorAll('source');
},
// Get quality levels
getQualityOptions() {
if (!this.isHTML5) {
return null;
}
// Get sources
const sources = html5.getSources.call(this);
if (utils.is.empty(sources)) {
return null;
}
// Get <source> with size attribute
const sizes = Array.from(sources).filter(source => !utils.is.empty(source.getAttribute('size')));
// If none, bail
if (utils.is.empty(sizes)) {
return null;
}
// Reduce to unique list
return utils.dedupe(sizes.map(source => Number(source.getAttribute('size'))));
},
extend() {
if (!this.isHTML5) {
return;
}
const player = this;
// Quality
Object.defineProperty(player.media, 'quality', {
get() {
// Get sources
const sources = html5.getSources.call(player);
if (utils.is.empty(sources)) {
return null;
}
const matches = Array.from(sources).filter(source => source.getAttribute('src') === player.source);
if (utils.is.empty(matches)) {
return null;
}
return Number(matches[0].getAttribute('size'));
},
set(input) {
// Get sources
const sources = html5.getSources.call(player);
if (utils.is.empty(sources)) {
return;
}
// Get matches for requested size
const matches = Array.from(sources).filter(source => Number(source.getAttribute('size')) === input);
// No matches for requested size
if (utils.is.empty(matches)) {
return;
}
// Get supported sources
const supported = matches.filter(source => support.mime.call(player, source.getAttribute('type')));
// No supported sources
if (utils.is.empty(supported)) {
return;
}
// Trigger change event
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
quality: input,
});
// Get current state
const { currentTime, playing } = player;
// Set new source
player.media.src = supported[0].getAttribute('src');
// Load new source
player.media.load();
// Resume playing
if (playing) {
player.play();
}
// Restore time
player.currentTime = currentTime;
// Trigger change event
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
quality: input,
});
},
});
},
// Cancel current network requests
// See https://github.com/sampotts/plyr/issues/174
cancelRequests() {
if (!this.isHTML5) {
return;
}
// Remove child sources
utils.removeElement(html5.getSources());
// Set blank video src attribute
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
this.media.setAttribute('src', this.config.blankVideo);
// Load the new empty source
// This will cancel existing requests
// See https://github.com/sampotts/plyr/issues/174
this.media.load();
// Debugging
this.debug.log('Cancelled network requests');
},
};
export default html5;

View File

@ -278,18 +278,28 @@ class Listeners {
// Check for buffer progress // Check for buffer progress
utils.on(this.player.media, 'progress playing', event => ui.updateProgress.call(this.player, event)); utils.on(this.player.media, 'progress playing', event => ui.updateProgress.call(this.player, event));
// Handle native mute // Handle volume changes
utils.on(this.player.media, 'volumechange', event => ui.updateVolume.call(this.player, event)); utils.on(this.player.media, 'volumechange', event => ui.updateVolume.call(this.player, event));
// Handle native play/pause // Handle play/pause
utils.on(this.player.media, 'playing play pause ended emptied', event => ui.checkPlaying.call(this.player, event)); utils.on(this.player.media, 'playing play pause ended emptied', event => ui.checkPlaying.call(this.player, event));
// Loading // Loading state
utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event)); utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event));
// Check if media failed to load // Check if media failed to load
// utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event)); // utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event));
// 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 ads are enabled, wait for them first
if (this.player.ads.enabled && !this.player.ads.initialized) {
// Wait for manager response
this.player.ads.managerPromise.then(() => this.player.ads.play()).catch(() => this.player.play());
}
});
// Click video // Click video
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) { if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
// Re-fetch the wrapper // Re-fetch the wrapper
@ -345,13 +355,16 @@ class Listeners {
this.player.storage.set({ speed: this.player.speed }); this.player.storage.set({ speed: this.player.speed });
}); });
// Quality change // Quality request
utils.on(this.player.media, 'qualitychange', () => { utils.on(this.player.media, 'qualityrequested', event => {
// Update UI
controls.updateSetting.call(this.player, 'quality');
// Save to storage // Save to storage
this.player.storage.set({ quality: this.player.quality }); this.player.storage.set({ quality: event.detail.quality });
});
// Quality change
utils.on(this.player.media, 'qualitychange', event => {
// Update UI
controls.updateSetting.call(this.player, 'quality', null, event.detail.quality);
}); });
// Caption language change // Caption language change

View File

@ -6,6 +6,7 @@ import support from './support';
import utils from './utils'; import utils from './utils';
import youtube from './plugins/youtube'; import youtube from './plugins/youtube';
import vimeo from './plugins/vimeo'; import vimeo from './plugins/vimeo';
import html5 from './html5';
import ui from './ui'; import ui from './ui';
// Sniff out the browser // Sniff out the browser
@ -75,32 +76,10 @@ const media = {
} }
} else if (this.isHTML5) { } else if (this.isHTML5) {
ui.setTitle.call(this); ui.setTitle.call(this);
html5.extend.call(this);
} }
}, },
// Cancel current network requests
// See https://github.com/sampotts/plyr/issues/174
cancelRequests() {
if (!this.isHTML5) {
return;
}
// Remove child sources
utils.removeElement(this.media.querySelectorAll('source'));
// Set blank video src attribute
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
this.media.setAttribute('src', this.config.blankVideo);
// Load the new empty source
// This will cancel existing requests
// See https://github.com/sampotts/plyr/issues/174
this.media.load();
// Debugging
this.debug.log('Cancelled network requests');
},
}; };
export default media; export default media;

View File

@ -206,21 +206,23 @@ class Ads {
this.cuePoints = this.manager.getCuePoints(); this.cuePoints = this.manager.getCuePoints();
// Add advertisement cue's within the time line if available // Add advertisement cue's within the time line if available
this.cuePoints.forEach(cuePoint => { if (!utils.is.empty(this.cuePoints)) {
if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) { this.cuePoints.forEach(cuePoint => {
const seekElement = this.player.elements.progress; if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) {
const seekElement = this.player.elements.progress;
if (seekElement) { if (utils.is.element(seekElement)) {
const cuePercentage = 100 / this.player.duration * cuePoint; const cuePercentage = 100 / this.player.duration * cuePoint;
const cue = utils.createElement('span', { const cue = utils.createElement('span', {
class: this.player.config.classNames.cues, class: this.player.config.classNames.cues,
}); });
cue.style.left = `${cuePercentage.toString()}%`; cue.style.left = `${cuePercentage.toString()}%`;
seekElement.appendChild(cue); seekElement.appendChild(cue);
}
} }
} });
}); }
// Get skippable state // Get skippable state
// TODO: Skip button // TODO: Skip button
@ -385,6 +387,10 @@ class Ads {
this.player.on('seeked', () => { this.player.on('seeked', () => {
const seekedTime = this.player.currentTime; const seekedTime = this.player.currentTime;
if (utils.is.empty(this.cuePoints)) {
return;
}
this.cuePoints.forEach((cuePoint, index) => { this.cuePoints.forEach((cuePoint, index) => {
if (time < cuePoint && cuePoint < seekedTime) { if (time < cuePoint && cuePoint < seekedTime) {
this.manager.discardAdBreak(); this.manager.discardAdBreak();
@ -396,7 +402,9 @@ class Ads {
// Listen to the resizing of the window. And resize ad accordingly // Listen to the resizing of the window. And resize ad accordingly
// TODO: eventually implement ResizeObserver // TODO: eventually implement ResizeObserver
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
this.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); if (this.manager) {
this.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
}
}); });
} }

View File

@ -6,6 +6,64 @@ import utils from './../utils';
import controls from './../controls'; import controls from './../controls';
import ui from './../ui'; import ui from './../ui';
// Standardise YouTube quality unit
function mapQualityUnit(input) {
switch (input) {
case 'hd2160':
return 2160;
case 2160:
return 'hd2160';
case 'hd1440':
return 1440;
case 1440:
return 'hd1440';
case 'hd1080':
return 1080;
case 1080:
return 'hd1080';
case 'hd720':
return 720;
case 720:
return 'hd720';
case 'large':
return 480;
case 480:
return 'large';
case 'medium':
return 360;
case 360:
return 'medium';
case 'small':
return 240;
case 240:
return 'small';
default:
return 'default';
}
}
function mapQualityUnits(levels) {
if (utils.is.empty(levels)) {
return levels;
}
return utils.dedupe(levels.map(level => mapQualityUnit(level)));
}
const youtube = { const youtube = {
setup() { setup() {
// Add embed class for responsive // Add embed class for responsive
@ -168,14 +226,10 @@ const youtube = {
utils.dispatchEvent.call(player, player.media, 'error'); utils.dispatchEvent.call(player, player.media, 'error');
}, },
onPlaybackQualityChange(event) { onPlaybackQualityChange() {
// Get the instance utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
const instance = event.target; quality: player.media.quality,
});
// Get current quality
player.media.quality = instance.getPlaybackQuality();
utils.dispatchEvent.call(player, player.media, 'qualitychange');
}, },
onPlaybackRateChange(event) { onPlaybackRateChange(event) {
// Get the instance // Get the instance
@ -240,15 +294,18 @@ const youtube = {
// Quality // Quality
Object.defineProperty(player.media, 'quality', { Object.defineProperty(player.media, 'quality', {
get() { get() {
return instance.getPlaybackQuality(); return mapQualityUnit(instance.getPlaybackQuality());
}, },
set(input) { set(input) {
const quality = input;
// Set via API
instance.setPlaybackQuality(mapQualityUnit(quality));
// Trigger request event // Trigger request event
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, { utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
quality: input, quality,
}); });
instance.setPlaybackQuality(input);
}, },
}); });
@ -294,8 +351,7 @@ const youtube = {
}); });
// Get available speeds // Get available speeds
const options = instance.getAvailablePlaybackRates(); player.options.speed = instance.getAvailablePlaybackRates();
controls.setSpeedMenu.call(player, options);
// Set the tabindex to avoid focus entering iframe // Set the tabindex to avoid focus entering iframe
if (player.supported.ui) { if (player.supported.ui) {
@ -401,7 +457,7 @@ const youtube = {
} }
// Get quality // Get quality
controls.setQualityMenu.call(player, instance.getAvailableQualityLevels()); controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels()));
break; break;

View File

@ -1,6 +1,6 @@
// ========================================================================== // ==========================================================================
// Plyr // Plyr
// plyr.js v3.0.10 // plyr.js v3.1.0
// https://github.com/sampotts/plyr // https://github.com/sampotts/plyr
// License: The MIT License (MIT) // License: The MIT License (MIT)
// ========================================================================== // ==========================================================================
@ -133,7 +133,17 @@ class Plyr {
} }
// Cache original element state for .destroy() // Cache original element state for .destroy()
this.elements.original = this.media.cloneNode(true); // TODO: Investigate a better solution as I suspect this causes reported double load issues?
setTimeout(() => {
const clone = this.media.cloneNode(true);
// Prevent the clone autoplaying
if (clone.getAttribute('autoplay')) {
clone.pause();
}
this.elements.original = clone;
}, 0);
// Set media type based on tag or data attribute // Set media type based on tag or data attribute
// Supported: video, audio, vimeo, youtube // Supported: video, audio, vimeo, youtube
@ -286,6 +296,11 @@ class Plyr {
// Setup ads if provided // Setup ads if provided
this.ads = new Ads(this); this.ads = new Ads(this);
// Autoplay if required
if (this.config.autoplay) {
this.play();
}
} }
// --------------------------------------- // ---------------------------------------
@ -323,9 +338,9 @@ class Plyr {
} }
// If ads are enabled, wait for them first // If ads are enabled, wait for them first
if (this.ads.enabled && !this.ads.initialized) { /* if (this.ads.enabled && !this.ads.initialized) {
return this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play()); return this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play());
} } */
// Return the promise (for HTML5) // Return the promise (for HTML5)
return this.media.play(); return this.media.play();
@ -384,7 +399,7 @@ class Plyr {
stop() { stop() {
if (this.isHTML5) { if (this.isHTML5) {
this.media.load(); this.media.load();
} else { } else if (utils.is.function(this.media.stop)) {
this.media.stop(); this.media.stop();
} }
} }
@ -524,8 +539,8 @@ class Plyr {
// Set the player volume // Set the player volume
this.media.volume = volume; this.media.volume = volume;
// If muted, and we're increasing volume, reset muted state // If muted, and we're increasing volume manually, reset muted state
if (this.muted && volume > 0) { if (!utils.is.empty(value) && this.muted && volume > 0) {
this.muted = false; this.muted = false;
} }
} }
@ -655,29 +670,38 @@ class Plyr {
/** /**
* Set playback quality * Set playback quality
* Currently YouTube only * Currently HTML5 & YouTube only
* @param {string} input - Quality level * @param {number} input - Quality level
*/ */
set quality(input) { set quality(input) {
let quality = null; let quality = null;
if (utils.is.string(input)) { if (!utils.is.empty(input)) {
quality = input; quality = Number(input);
} }
if (!utils.is.string(quality)) { if (!utils.is.number(quality) || quality === 0) {
quality = this.storage.get('quality'); quality = this.storage.get('quality');
} }
if (!utils.is.string(quality)) { if (!utils.is.number(quality)) {
quality = this.config.quality.selected; quality = this.config.quality.selected;
} }
if (!this.options.quality.includes(quality)) { if (!utils.is.number(quality)) {
this.debug.warn(`Unsupported quality option (${quality})`); quality = this.config.quality.default;
}
if (!this.options.quality.length) {
return; return;
} }
if (!this.options.quality.includes(quality)) {
const closest = utils.closest(this.options.quality, quality);
this.debug.warn(`Unsupported quality option: ${quality}, using ${closest} instead`);
quality = closest;
}
// Update config // Update config
this.config.quality.selected = quality; this.config.quality.selected = quality;
@ -1019,6 +1043,11 @@ class Plyr {
// then set the timer to hide the controls // then set the timer to hide the controls
if (!show || this.playing) { if (!show || this.playing) {
this.timers.controls = setTimeout(() => { this.timers.controls = setTimeout(() => {
// We need controls of course...
if (!utils.is.element(this.elements.controls)) {
return;
}
// If the mouse is over the controls (and not entering fullscreen), bail // If the mouse is over the controls (and not entering fullscreen), bail
if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) { if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
return; return;

View File

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

View File

@ -4,6 +4,7 @@
import { providers } from './types'; import { providers } from './types';
import utils from './utils'; import utils from './utils';
import html5 from './html5';
import media from './media'; import media from './media';
import ui from './ui'; import ui from './ui';
import support from './support'; import support from './support';
@ -31,13 +32,14 @@ const source = {
} }
// Cancel current network requests // Cancel current network requests
media.cancelRequests.call(this); html5.cancelRequests.call(this);
// Destroy instance and re-setup // Destroy instance and re-setup
this.destroy.call( this.destroy.call(
this, this,
() => { () => {
// TODO: Reset menus here // Reset quality options
this.options.quality = [];
// Remove elements // Remove elements
utils.removeElement(this.media); utils.removeElement(this.media);

View File

@ -73,6 +73,11 @@ const support = {
return false; return false;
} }
// Check directly if codecs specified
if (type.includes('codecs=')) {
return media.canPlayType(type).replace(/no/, '');
}
// Type specific checks // Type specific checks
if (this.isVideo) { if (this.isVideo) {
switch (type) { switch (type) {

View File

@ -71,8 +71,11 @@ const ui = {
// Reset loop state // Reset loop state
this.loop = null; this.loop = null;
// Reset quality options // Reset quality setting
this.options.quality = []; this.quality = null;
// Reset volume display
ui.updateVolume.call(this);
// Reset time display // Reset time display
ui.timeUpdate.call(this); ui.timeUpdate.call(this);

View File

@ -586,15 +586,15 @@ const utils = {
}, },
// Trigger event // Trigger event
dispatchEvent(element, type, bubbles, detail) { dispatchEvent(element, type = '', bubbles = false, detail = {}) {
// Bail if no element // Bail if no element
if (!utils.is.element(element) || !utils.is.string(type)) { if (!utils.is.element(element) || utils.is.empty(type)) {
return; return;
} }
// Create and dispatch the event // Create and dispatch the event
const event = new CustomEvent(type, { const event = new CustomEvent(type, {
bubbles: utils.is.boolean(bubbles) ? bubbles : false, bubbles,
detail: Object.assign({}, detail, { detail: Object.assign({}, detail, {
plyr: utils.is.plyr(this) ? this : null, plyr: utils.is.plyr(this) ? this : null,
}), }),
@ -737,6 +737,24 @@ const utils = {
return utils.extend(target, ...sources); return utils.extend(target, ...sources);
}, },
// Remove duplicates in an array
dedupe(array) {
if (!utils.is.array(array)) {
return array;
}
return array.filter((item, index) => array.indexOf(item) === index);
},
// Get the closest value in an array
closest(array, value) {
if (!utils.is.array(array) || !array.length) {
return null;
}
return array.reduce((prev, curr) => Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev);
},
// Get the provider for a given URL // Get the provider for a given URL
getProviderByUrl(url) { getProviderByUrl(url) {
// YouTube // YouTube

View File

@ -26,6 +26,11 @@
width: 100%; width: 100%;
} }
button {
font: inherit;
line-height: inherit;
}
// Ignore focus // Ignore focus
&:focus { &:focus {
outline: 0; outline: 0;

View File

@ -74,6 +74,7 @@
align-items: center; align-items: center;
color: $plyr-menu-color; color: $plyr-menu-color;
display: flex; display: flex;
font-size: $plyr-font-size-menu;
padding: ceil($plyr-control-padding / 2) ($plyr-control-padding * 2); padding: ceil($plyr-control-padding / 2) ($plyr-control-padding * 2);
user-select: none; user-select: none;
width: 100%; width: 100%;

View File

@ -8,8 +8,9 @@ $plyr-font-size-small: 14px !default;
$plyr-font-size-large: 18px !default; $plyr-font-size-large: 18px !default;
$plyr-font-size-xlarge: 21px !default; $plyr-font-size-xlarge: 21px !default;
$plyr-font-size-time: 14px !default; $plyr-font-size-time: $plyr-font-size-small !default;
$plyr-font-size-badge: 9px !default; $plyr-font-size-badge: 9px !default;
$plyr-font-size-menu: $plyr-font-size-small !default;
$plyr-font-weight-regular: 500 !default; $plyr-font-weight-regular: 500 !default;
$plyr-font-weight-bold: 600 !default; $plyr-font-weight-bold: 600 !default;

223
yarn.lock
View File

@ -8,6 +8,26 @@
dependencies: dependencies:
"@babel/highlight" "7.0.0-beta.42" "@babel/highlight" "7.0.0-beta.42"
"@babel/core@^7.0.0-beta.42":
version "7.0.0-beta.42"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.0.0-beta.42.tgz#b3a838fddbd19663369a0b4892189fd8d3f82001"
dependencies:
"@babel/code-frame" "7.0.0-beta.42"
"@babel/generator" "7.0.0-beta.42"
"@babel/helpers" "7.0.0-beta.42"
"@babel/template" "7.0.0-beta.42"
"@babel/traverse" "7.0.0-beta.42"
"@babel/types" "7.0.0-beta.42"
babylon "7.0.0-beta.42"
convert-source-map "^1.1.0"
debug "^3.1.0"
json5 "^0.5.0"
lodash "^4.2.0"
micromatch "^2.3.11"
resolve "^1.3.2"
semver "^5.4.1"
source-map "^0.5.0"
"@babel/generator@7.0.0-beta.42": "@babel/generator@7.0.0-beta.42":
version "7.0.0-beta.42" version "7.0.0-beta.42"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.42.tgz#777bb50f39c94a7e57f73202d833141f8159af33" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.42.tgz#777bb50f39c94a7e57f73202d833141f8159af33"
@ -38,6 +58,14 @@
dependencies: dependencies:
"@babel/types" "7.0.0-beta.42" "@babel/types" "7.0.0-beta.42"
"@babel/helpers@7.0.0-beta.42":
version "7.0.0-beta.42"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.0.0-beta.42.tgz#151c1c4e9da1b6ce83d54c1be5fb8c9c57aa5044"
dependencies:
"@babel/template" "7.0.0-beta.42"
"@babel/traverse" "7.0.0-beta.42"
"@babel/types" "7.0.0-beta.42"
"@babel/highlight@7.0.0-beta.42": "@babel/highlight@7.0.0-beta.42":
version "7.0.0-beta.42" version "7.0.0-beta.42"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.42.tgz#a502a1c0d6f99b2b0e81d468a1b0c0e81e3f3623" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.42.tgz#a502a1c0d6f99b2b0e81d468a1b0c0e81e3f3623"
@ -55,7 +83,7 @@
babylon "7.0.0-beta.42" babylon "7.0.0-beta.42"
lodash "^4.2.0" lodash "^4.2.0"
"@babel/traverse@^7.0.0-beta.40": "@babel/traverse@7.0.0-beta.42", "@babel/traverse@^7.0.0-beta.40", "@babel/traverse@^7.0.0-beta.42":
version "7.0.0-beta.42" version "7.0.0-beta.42"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.42.tgz#f4bf4d1e33d41baf45205e2d0463591d57326285" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.42.tgz#f4bf4d1e33d41baf45205e2d0463591d57326285"
dependencies: dependencies:
@ -95,6 +123,13 @@
normalize-path "^2.0.1" normalize-path "^2.0.1"
through2 "^2.0.3" through2 "^2.0.3"
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
dependencies:
call-me-maybe "^1.0.1"
glob-to-regexp "^0.3.0"
abbrev@1: abbrev@1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@ -853,7 +888,7 @@ babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
lodash "^4.17.4" lodash "^4.17.4"
to-fast-properties "^1.0.3" to-fast-properties "^1.0.3"
babylon@7.0.0-beta.42, babylon@^7.0.0-beta.40: babylon@7.0.0-beta.42, babylon@^7.0.0-beta.40, babylon@^7.0.0-beta.42:
version "7.0.0-beta.42" version "7.0.0-beta.42"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.42.tgz#67cfabcd4f3ec82999d29031ccdea89d0ba99657" resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.42.tgz#67cfabcd4f3ec82999d29031ccdea89d0ba99657"
@ -1003,6 +1038,10 @@ cache-base@^1.0.1:
union-value "^1.0.0" union-value "^1.0.0"
unset-value "^1.0.0" unset-value "^1.0.0"
call-me-maybe@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
caller-path@^0.1.0: caller-path@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
@ -1323,7 +1362,7 @@ contains-path@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
convert-source-map@1.X, convert-source-map@^1.5.0: convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.5.0:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
@ -1738,23 +1777,23 @@ eslint-import-resolver-node@^0.3.1:
debug "^2.6.9" debug "^2.6.9"
resolve "^1.5.0" resolve "^1.5.0"
eslint-module-utils@^2.1.1: eslint-module-utils@^2.2.0:
version "2.1.1" version "2.2.0"
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746"
dependencies: dependencies:
debug "^2.6.8" debug "^2.6.8"
pkg-dir "^1.0.0" pkg-dir "^1.0.0"
eslint-plugin-import@^2.9.0: eslint-plugin-import@^2.10.0:
version "2.9.0" version "2.10.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.9.0.tgz#26002efbfca5989b7288ac047508bd24f217b169" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.10.0.tgz#fa09083d5a75288df9c6c7d09fe12255985655e7"
dependencies: dependencies:
builtin-modules "^1.1.1" builtin-modules "^1.1.1"
contains-path "^0.1.0" contains-path "^0.1.0"
debug "^2.6.8" debug "^2.6.8"
doctrine "1.5.0" doctrine "1.5.0"
eslint-import-resolver-node "^0.3.1" eslint-import-resolver-node "^0.3.1"
eslint-module-utils "^2.1.1" eslint-module-utils "^2.2.0"
has "^1.0.1" has "^1.0.1"
lodash "^4.17.4" lodash "^4.17.4"
minimatch "^3.0.3" minimatch "^3.0.3"
@ -2045,6 +2084,16 @@ fast-deep-equal@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
fast-glob@^2.0.2:
version "2.2.0"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.0.tgz#e9d032a69b86bef46fc03d935408f02fb211d9fc"
dependencies:
"@mrmlnc/readdir-enhanced" "^2.2.1"
glob-parent "^3.1.0"
is-glob "^4.0.0"
merge2 "^1.2.1"
micromatch "^3.1.8"
fast-json-stable-stringify@^2.0.0: fast-json-stable-stringify@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
@ -2256,6 +2305,10 @@ get-stdin@^5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
get-stdin@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
get-stream@^3.0.0: get-stream@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
@ -2289,6 +2342,13 @@ glob-parent@^2.0.0:
dependencies: dependencies:
is-glob "^2.0.0" is-glob "^2.0.0"
glob-parent@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
dependencies:
is-glob "^3.1.0"
path-dirname "^1.0.0"
glob-stream@^3.1.5: glob-stream@^3.1.5:
version "3.1.18" version "3.1.18"
resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b" resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b"
@ -2300,6 +2360,10 @@ glob-stream@^3.1.5:
through2 "^0.6.1" through2 "^0.6.1"
unique-stream "^1.0.0" unique-stream "^1.0.0"
glob-to-regexp@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
glob-watcher@^0.0.6: glob-watcher@^0.0.6:
version "0.0.6" version "0.0.6"
resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b" resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b"
@ -2414,6 +2478,18 @@ globby@^7.0.0:
pify "^3.0.0" pify "^3.0.0"
slash "^1.0.0" slash "^1.0.0"
globby@^8.0.0:
version "8.0.1"
resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.1.tgz#b5ad48b8aa80b35b814fc1281ecc851f1d2b5b50"
dependencies:
array-union "^1.0.1"
dir-glob "^2.0.0"
fast-glob "^2.0.2"
glob "^7.1.2"
ignore "^3.3.5"
pify "^3.0.0"
slash "^1.0.0"
globjoin@^0.1.4: globjoin@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
@ -2853,6 +2929,10 @@ import-lazy@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
import-lazy@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-3.1.0.tgz#891279202c8a2280fdbd6674dbd8da1a1dfc67cc"
import-local@^0.1.1: import-local@^0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-0.1.1.tgz#b1179572aacdc11c6a91009fb430dbcab5f668a8" resolved "https://registry.yarnpkg.com/import-local/-/import-local-0.1.1.tgz#b1179572aacdc11c6a91009fb430dbcab5f668a8"
@ -3042,7 +3122,7 @@ is-extglob@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
is-extglob@^2.1.0: is-extglob@^2.1.0, is-extglob@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@ -3074,6 +3154,12 @@ is-glob@^3.1.0:
dependencies: dependencies:
is-extglob "^2.1.0" is-extglob "^2.1.0"
is-glob@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
dependencies:
is-extglob "^2.1.1"
is-hexadecimal@^1.0.0: is-hexadecimal@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69" resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69"
@ -3326,7 +3412,7 @@ json-stringify-safe@~5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
json5@^0.5.1: json5@^0.5.0, json5@^0.5.1:
version "0.5.1" version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
@ -3864,6 +3950,10 @@ meow@^4.0.0:
redent "^2.0.0" redent "^2.0.0"
trim-newlines "^2.0.0" trim-newlines "^2.0.0"
merge2@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.1.tgz#271d2516ff52d4af7f7b710b8bf3e16e183fef66"
micromatch@^2.3.11: micromatch@^2.3.11:
version "2.3.11" version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
@ -3900,6 +3990,24 @@ micromatch@^3.0.4:
snapdragon "^0.8.1" snapdragon "^0.8.1"
to-regex "^3.0.1" to-regex "^3.0.1"
micromatch@^3.1.8:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
dependencies:
arr-diff "^4.0.0"
array-unique "^0.3.2"
braces "^2.3.1"
define-property "^2.0.2"
extend-shallow "^3.0.2"
extglob "^2.0.4"
fragment-cache "^0.2.1"
kind-of "^6.0.2"
nanomatch "^1.2.9"
object.pick "^1.3.0"
regex-not "^1.0.0"
snapdragon "^0.8.1"
to-regex "^3.0.2"
mime-db@~1.33.0: mime-db@~1.33.0:
version "1.33.0" version "1.33.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
@ -4348,6 +4456,10 @@ pascalcase@^0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
path-dirname@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
path-exists@^2.0.0: path-exists@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
@ -4502,12 +4614,29 @@ postcss-html@^0.15.0:
remark "^9.0.0" remark "^9.0.0"
unist-util-find-all-after "^1.0.1" unist-util-find-all-after "^1.0.1"
postcss-html@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/postcss-html/-/postcss-html-0.18.0.tgz#992a84117cc56f9f28915fbadba576489641e652"
dependencies:
"@babel/core" "^7.0.0-beta.42"
"@babel/traverse" "^7.0.0-beta.42"
babylon "^7.0.0-beta.42"
htmlparser2 "^3.9.2"
remark "^9.0.0"
unist-util-find-all-after "^1.0.1"
postcss-less@^1.1.0: postcss-less@^1.1.0:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-1.1.3.tgz#6930525271bfe38d5793d33ac09c1a546b87bb51" resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-1.1.3.tgz#6930525271bfe38d5793d33ac09c1a546b87bb51"
dependencies: dependencies:
postcss "^5.2.16" postcss "^5.2.16"
postcss-less@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-1.1.5.tgz#a6f0ce180cf3797eeee1d4adc0e9e6d6db665609"
dependencies:
postcss "^5.2.16"
postcss-media-query-parser@^0.2.3: postcss-media-query-parser@^0.2.3:
version "0.2.3" version "0.2.3"
resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244"
@ -5119,6 +5248,12 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.5.0:
dependencies: dependencies:
path-parse "^1.0.5" path-parse "^1.0.5"
resolve@^1.3.2:
version "1.6.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.6.0.tgz#0fbd21278b27b4004481c395349e7aba60a9ff5c"
dependencies:
path-parse "^1.0.5"
restore-cursor@^2.0.0: restore-cursor@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@ -5237,7 +5372,7 @@ semver-diff@^2.0.0:
dependencies: dependencies:
semver "^5.0.3" semver "^5.0.3"
"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0: "semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
@ -5620,7 +5755,7 @@ stylelint-order@^0.8.0, stylelint-order@^0.8.1:
postcss "^6.0.14" postcss "^6.0.14"
postcss-sorting "^3.1.0" postcss-sorting "^3.1.0"
stylelint-scss@^2.0.0, stylelint-scss@^2.5.0: stylelint-scss@^2.0.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-2.5.0.tgz#ac4c83474c53b19cc1f9e93d332786cf89c8d217" resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-2.5.0.tgz#ac4c83474c53b19cc1f9e93d332786cf89c8d217"
dependencies: dependencies:
@ -5630,6 +5765,16 @@ stylelint-scss@^2.0.0, stylelint-scss@^2.5.0:
postcss-selector-parser "^3.1.1" postcss-selector-parser "^3.1.1"
postcss-value-parser "^3.3.0" postcss-value-parser "^3.3.0"
stylelint-scss@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.0.0.tgz#15beb887117ccef20668a3f4728eb5be5fbda045"
dependencies:
lodash "^4.17.4"
postcss-media-query-parser "^0.2.3"
postcss-resolve-nested-selector "^0.1.1"
postcss-selector-parser "^3.1.1"
postcss-value-parser "^3.3.0"
stylelint-selector-bem-pattern@^2.0.0: stylelint-selector-bem-pattern@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/stylelint-selector-bem-pattern/-/stylelint-selector-bem-pattern-2.0.0.tgz#9a6130c9c90963b30e925c917079d6c8fed73f45" resolved "https://registry.yarnpkg.com/stylelint-selector-bem-pattern/-/stylelint-selector-bem-pattern-2.0.0.tgz#9a6130c9c90963b30e925c917079d6c8fed73f45"
@ -5639,7 +5784,7 @@ stylelint-selector-bem-pattern@^2.0.0:
postcss-bem-linter "^3.0.0" postcss-bem-linter "^3.0.0"
stylelint ">=3.0.2" stylelint ">=3.0.2"
stylelint@>=3.0.2, stylelint@^9.1.1, stylelint@^9.1.3: stylelint@>=3.0.2, stylelint@^9.1.1:
version "9.1.3" version "9.1.3"
resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-9.1.3.tgz#8260f2a221b98e4afafd9b2b8a785d2e38cbb8a4" resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-9.1.3.tgz#8260f2a221b98e4afafd9b2b8a785d2e38cbb8a4"
dependencies: dependencies:
@ -5728,6 +5873,52 @@ stylelint@^8.1.1:
svg-tags "^1.0.0" svg-tags "^1.0.0"
table "^4.0.1" table "^4.0.1"
stylelint@^9.2.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-9.2.0.tgz#f77a82518106074c1a795e962fd780da2c8af43b"
dependencies:
autoprefixer "^8.0.0"
balanced-match "^1.0.0"
chalk "^2.0.1"
cosmiconfig "^4.0.0"
debug "^3.0.0"
execall "^1.0.0"
file-entry-cache "^2.0.0"
get-stdin "^6.0.0"
globby "^8.0.0"
globjoin "^0.1.4"
html-tags "^2.0.0"
ignore "^3.3.3"
import-lazy "^3.1.0"
imurmurhash "^0.1.4"
known-css-properties "^0.6.0"
lodash "^4.17.4"
log-symbols "^2.0.0"
mathml-tag-names "^2.0.1"
meow "^4.0.0"
micromatch "^2.3.11"
normalize-selector "^0.2.0"
pify "^3.0.0"
postcss "^6.0.16"
postcss-html "^0.18.0"
postcss-less "^1.1.5"
postcss-media-query-parser "^0.2.3"
postcss-reporter "^5.0.0"
postcss-resolve-nested-selector "^0.1.1"
postcss-safe-parser "^3.0.1"
postcss-sass "^0.3.0"
postcss-scss "^1.0.2"
postcss-selector-parser "^3.1.0"
postcss-value-parser "^3.3.0"
resolve-from "^4.0.0"
signal-exit "^3.0.2"
specificity "^0.3.1"
string-width "^2.1.0"
style-search "^0.1.0"
sugarss "^1.0.0"
svg-tags "^1.0.0"
table "^4.0.1"
sugarss@^1.0.0: sugarss@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-1.0.1.tgz#be826d9003e0f247735f92365dc3fd7f1bae9e44" resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-1.0.1.tgz#be826d9003e0f247735f92365dc3fd7f1bae9e44"
@ -5909,7 +6100,7 @@ to-regex-range@^2.1.0:
is-number "^3.0.0" is-number "^3.0.0"
repeat-string "^1.6.1" repeat-string "^1.6.1"
to-regex@^3.0.1: to-regex@^3.0.1, to-regex@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
dependencies: dependencies: