Compare commits

...

8 Commits

Author SHA1 Message Date
Sam Potts 3b20dbd9fd v3.1.0 2018-04-03 22:57:34 +10:00
Sam Potts e4d975af00 Styling fixes 2018-04-03 22:56:19 +10:00
Sam Potts 2782a00e7c v3.1.0-beta.2 2018-04-03 22:31:55 +10:00
Sam Potts 91d192dd7c YouTube speed menu fix 2018-04-03 22:30:29 +10:00
Sam Potts b1e3abc795 v3.1.0-beta.1 2018-04-02 22:52:02 +10:00
Sam Potts 3395e8df90 HTML5 quality selection 2018-04-02 22:40:03 +10:00
Sam Potts cce143a7da v3.0.11 2018-03-30 23:14:07 +11:00
Sam Potts 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
+1
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
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}"
}
]
}
+10
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
+1 -1
View File
File diff suppressed because one or more lines are too long
+16 -3
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: [{
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+8 -5
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>
+23 -5
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: [
{ {
+1
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;
+1 -1
View File
File diff suppressed because one or more lines are too long
+441 -176
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;
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+441 -176
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;
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -4
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"],
+3 -3
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
+43 -51
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;
+13 -11
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"
], ],
}, },
+6 -4
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
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;
+22 -9
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
+3 -24
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;
+21 -13
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);
}
}); });
} }
+71 -15
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;
+44 -15
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;
+1 -1
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)
// ========================================================================== // ==========================================================================
+4 -2
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);
+5
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) {
+5 -2
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);
+21 -3
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
+5
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;
+1
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%;
+2 -1
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;
+207 -16
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: