IE & Edge fixes, Storage & Console classes

This commit is contained in:
Sam Potts 2017-12-08 10:05:38 +00:00
parent de54929bb7
commit c8990bd379
33 changed files with 1051 additions and 615 deletions

30
.vscode/settings.json vendored
View File

@ -1,29 +1 @@
{ {}
// Exclude from the editor
"files.exclude": {
"**/node_modules": true
},
// Exclude from search
"search.exclude": {
"dist/": true
},
// Linting
"stylelint.enable": true,
"css.validate": false,
"scss.validate": false,
"javascript.validate.enable": false,
// Prettier
"prettier.eslintIntegration": true,
"prettier.stylelintIntegration": true,
// Formatting
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.formatOnSave": true,
// Trim on save
"files.trimTrailingWhitespace": true
}

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

2
demo/dist/demo.js vendored
View File

@ -1,3 +1,3 @@
document.addEventListener("DOMContentLoaded",function(){function e(e,t,o){e&&e.classList[o?"add":"remove"](t)}function t(t,n){if(t in r&&(n||t!==a)&&(a.length||t!==r.video)){switch(t){case r.video:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"media/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"media/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"media/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case r.audio:o.source={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case r.youtube:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://youtube.com/watch?v=bTqVqk7FSmY",provider:"youtube"}]};break;case r.vimeo:o.source={type:"video",sources:[{src:"https://vimeo.com/76979871",provider:"vimeo"}]}}a=t,Array.from(i).forEach(function(t){return e(t.parentElement,"active",!1)}),e(document.querySelector('[data-source="'+t+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+t).removeAttribute("hidden")}}window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var o=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","fullscreen","pip","airplay"],keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"}});window.player=o;var i=document.querySelectorAll("[data-source]"),r={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},a=window.location.hash.replace("#",""),n=window.history&&window.history.pushState;if(Array.from(i).forEach(function(e){e.addEventListener("click",function(){var o=e.getAttribute("data-source");t(o),n&&window.history.pushState({type:o},"","#"+o)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),n){var s=!a.length;s&&(a=r.video),a in r&&window.history.replaceState({type:a},"",s?"":"#"+a),a!==r.video&&t(a,!0)}}),"plyr.io"===window.location.host&&(!function(e,t,o,i,r,a,n){e.GoogleAnalyticsObject=r,e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,a=t.createElement(o),n=t.getElementsByTagName(o)[0],a.async=1,a.src="//www.google-analytics.com/analytics.js",n.parentNode.insertBefore(a,n)}(window,document,"script",0,"ga"),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview")); document.addEventListener("DOMContentLoaded",function(){function e(e,t,o){e&&e.classList[o?"add":"remove"](t)}function t(t,n){if(t in r&&(n||t!==a)&&(a.length||t!==r.video)){switch(t){case r.video:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"media/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"media/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"media/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case r.audio:o.source={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case r.youtube:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://youtube.com/watch?v=bTqVqk7FSmY",provider:"youtube"}]};break;case r.vimeo:o.source={type:"video",sources:[{src:"https://vimeo.com/76979871",provider:"vimeo"}]}}a=t,Array.from(i).forEach(function(t){return e(t.parentElement,"active",!1)}),e(document.querySelector('[data-source="'+t+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+t).removeAttribute("hidden")}}window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var o=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","fullscreen","pip","airplay"],keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"}});window.player=o;var i=document.querySelectorAll("[data-source]"),r={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},a=window.location.hash.replace("#",""),n=window.history&&window.history.pushState;if(Array.from(i).forEach(function(e){e.addEventListener("click",function(){var o=e.getAttribute("data-source");t(o),n&&window.history.pushState({type:o},"","#"+o)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),n){var s=!a.length;s&&(a=r.video),a in r&&window.history.replaceState({type:a},"",s?"":"#"+a),a!==r.video&&t(a,!0)}}),"plyr.io"===window.location.host&&(!function(e,t,o,i,r,a,n){e.GoogleAnalyticsObject="ga",e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,a=t.createElement("script"),n=t.getElementsByTagName("script")[0],a.async=1,a.src="//www.google-analytics.com/analytics.js",n.parentNode.insertBefore(a,n)}(window,document),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"));
//# sourceMappingURL=demo.js.map //# sourceMappingURL=demo.js.map

File diff suppressed because one or more lines are too long

View File

@ -34,10 +34,6 @@ document.addEventListener('DOMContentLoaded', () => {
}, 0); }, 0);
}); });
/* document.body.addEventListener('ready', function(event) {
console.log(event);
}); */
// Setup the player // Setup the player
const player = new window.Plyr('#player', { const player = new window.Plyr('#player', {
debug: true, debug: true,

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.js vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
/* global require, __dirname */ /* global require, __dirname */
/* eslint no-console: "off" */ /* eslint no-console: "off" */
const fs = require('fs'); const del = require('del');
const path = require('path'); const path = require('path');
const gulp = require('gulp'); const gulp = require('gulp');
const gutil = require('gulp-util'); const gutil = require('gulp-util');
@ -70,6 +70,7 @@ const tasks = {
sass: [], sass: [],
js: [], js: [],
sprite: [], sprite: [],
clean: ['clean'],
}; };
// Size plugin // Size plugin
@ -97,10 +98,20 @@ const babelrc = {
exclude: 'node_modules/**', exclude: 'node_modules/**',
}; };
// Clean out /dist
gulp.task('clean', () => {
const dirs = [paths.plyr.output, paths.demo.output].map(dir => path.join(dir, '**/*'));
// Don't delete the mp4
dirs.push(`!${path.join(paths.plyr.output, '**/*.mp4')}`);
del(dirs);
});
const build = { const build = {
js(files, bundle, options) { js(files, bundle, options) {
Object.keys(files).forEach(key => { Object.keys(files).forEach(key => {
const name = `js-${key}`; const name = `js:${key}`;
tasks.js.push(name); tasks.js.push(name);
gulp.task(name, () => gulp.task(name, () =>
@ -124,7 +135,7 @@ const build = {
}, },
less(files, bundle) { less(files, bundle) {
Object.keys(files).forEach(key => { Object.keys(files).forEach(key => {
const name = `less-${key}`; const name = `less:${key}`;
tasks.less.push(name); tasks.less.push(name);
gulp.task(name, () => gulp.task(name, () =>
@ -142,7 +153,7 @@ const build = {
}, },
sass(files, bundle) { sass(files, bundle) {
Object.keys(files).forEach(key => { Object.keys(files).forEach(key => {
const name = `sass-${key}`; const name = `sass:${key}`;
tasks.sass.push(name); tasks.sass.push(name);
gulp.task(name, () => gulp.task(name, () =>
@ -159,7 +170,7 @@ const build = {
}); });
}, },
sprite(bundle) { sprite(bundle) {
const name = `sprite-${bundle}`; const name = `svg:sprite:${bundle}`;
tasks.sprite.push(name); tasks.sprite.push(name);
// Process Icons // Process Icons
@ -217,7 +228,7 @@ gulp.task('watch', () => {
// Default gulp task // Default gulp task
gulp.task('default', () => { gulp.task('default', () => {
run(tasks.js, tasks.less, tasks.sprite, 'watch'); run(tasks.clean, tasks.js, tasks.less, tasks.sprite, 'watch');
}); });
// Publish a version to CDN and demo // Publish a version to CDN and demo
@ -250,8 +261,7 @@ const options = {
// If aws is setup // If aws is setup
if ('cdn' in aws) { if ('cdn' in aws) {
const regex = const regex = '(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*).(?:0|[1-9][0-9]*)(?:-[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?';
'(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*).(?:0|[1-9][0-9]*)(?:-[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?';
const cdnpath = new RegExp(`${aws.cdn.domain}/${regex}`, 'gi'); const cdnpath = new RegExp(`${aws.cdn.domain}/${regex}`, 'gi');
const semver = new RegExp(`v${regex}`, 'gi'); const semver = new RegExp(`v${regex}`, 'gi');
const localPath = new RegExp('(../)?dist', 'gi'); const localPath = new RegExp('(../)?dist', 'gi');
@ -350,6 +360,6 @@ if ('cdn' in aws) {
// Do everything // Do everything
gulp.task('publish', () => { gulp.task('publish', () => {
run(tasks.js, tasks.less, tasks.sprite, 'cdn', 'demo'); run(tasks.clean, tasks.js, tasks.less, tasks.sprite, 'cdn', 'demo');
}); });
} }

965
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,9 +9,10 @@
"babel-eslint": "^8.0.2", "babel-eslint": "^8.0.2",
"babel-plugin-external-helpers": "^6.22.0", "babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"eslint": "^4.11.0", "del": "^3.0.0",
"eslint": "^4.12.1",
"eslint-config-airbnb": "^16.1.0", "eslint-config-airbnb": "^16.1.0",
"eslint-config-prettier": "^2.8.0", "eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.8.0", "eslint-plugin-import": "^2.8.0",
"eslint-plugin-jsx-a11y": "^6.0.2", "eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-react": "^7.5.1", "eslint-plugin-react": "^7.5.1",
@ -36,10 +37,10 @@
"rollup-plugin-node-resolve": "^3.0.0", "rollup-plugin-node-resolve": "^3.0.0",
"rollup-plugin-uglify": "^2.0.1", "rollup-plugin-uglify": "^2.0.1",
"run-sequence": "^2.2.0", "run-sequence": "^2.2.0",
"stylelint": "^8.2.0", "stylelint": "^8.3.1",
"stylelint-config-prettier": "^2.0.0", "stylelint-config-prettier": "^2.0.0",
"stylelint-config-standard": "^17.0.0", "stylelint-config-standard": "^18.0.0",
"uglify-es": "^3.1.10" "uglify-es": "^3.2.0"
}, },
"keywords": [ "keywords": [
"HTML5 Video", "HTML5 Video",

31
plyr.code-workspace Normal file
View File

@ -0,0 +1,31 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
// Exclude from the editor
"files.exclude": {
"**/node_modules": true
},
// Exclude from search
"search.exclude": {
"dist/": true
},
// Linting
"stylelint.enable": true,
"css.validate": false,
"scss.validate": false,
"javascript.validate.enable": false,
// Prettier
"prettier.eslintIntegration": true,
"prettier.stylelintIntegration": true,
// Formatting
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.formatOnSave": true,
// Trim on save
"files.trimTrailingWhitespace": true
}
}

View File

@ -5,7 +5,6 @@
import support from './support'; import support from './support';
import utils from './utils'; import utils from './utils';
import controls from './controls'; import controls from './controls';
import storage from './storage';
const captions = { const captions = {
// Setup captions // Setup captions
@ -16,18 +15,24 @@ const captions = {
} }
// Set default language if not set // Set default language if not set
if (!utils.is.empty(storage.get.call(this).language)) { const stored = this.storage.get('language');
this.captions.language = storage.get.call(this).language;
} else if (utils.is.empty(this.captions.language)) { if (!utils.is.empty(stored)) {
this.captions.language = stored;
}
if (utils.is.empty(this.captions.language)) {
this.captions.language = this.config.captions.language.toLowerCase(); this.captions.language = this.config.captions.language.toLowerCase();
} }
// Set captions enabled state if not set // Set captions enabled state if not set
if (!utils.is.boolean(this.captions.enabled)) { if (!utils.is.boolean(this.captions.active)) {
if (!utils.is.empty(storage.get.call(this).language)) { const active = this.storage.get('captions');
this.captions.enabled = storage.get.call(this).captions;
if (utils.is.boolean(active)) {
this.captions.active = active;
} else { } else {
this.captions.enabled = this.config.captions.active; this.captions.active = this.config.captions.active;
} }
} }
@ -42,7 +47,7 @@ const captions = {
} }
// Inject the container // Inject the container
if (!utils.is.htmlElement(this.elements.captions)) { if (!utils.is.element(this.elements.captions)) {
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions)); this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
utils.insertAfter(this.elements.captions, this.elements.wrapper); utils.insertAfter(this.elements.captions, this.elements.wrapper);
@ -141,7 +146,7 @@ const captions = {
return; return;
} }
if (utils.is.htmlElement(this.elements.captions)) { if (utils.is.element(this.elements.captions)) {
const content = utils.createElement('span'); const content = utils.createElement('span');
// Empty the container // Empty the container
@ -160,19 +165,19 @@ const captions = {
// Set new caption text // Set new caption text
this.elements.captions.appendChild(content); this.elements.captions.appendChild(content);
} else { } else {
this.console.warn('No captions element to render to'); this.debug.warn('No captions element to render to');
} }
}, },
// Display captions container and button (for initialization) // Display captions container and button (for initialization)
show() { show() {
// If there's no caption toggle, bail // If there's no caption toggle, bail
if (!utils.is.htmlElement(this.elements.buttons.captions)) { if (!utils.is.element(this.elements.buttons.captions)) {
return; return;
} }
// Try to load the value from storage // Try to load the value from storage
let active = storage.get.call(this).captions; let active = this.storage.get('captions');
// Otherwise fall back to the default config // Otherwise fall back to the default config
if (!utils.is.boolean(active)) { if (!utils.is.boolean(active)) {

28
src/js/console.js Normal file
View File

@ -0,0 +1,28 @@
// ==========================================================================
// Console wrapper
// ==========================================================================
const noop = () => {};
export default class Console {
constructor(player) {
this.enabled = window.console && player.config.debug;
if (this.enabled) {
this.log('Debugging enabled');
}
}
get log() {
// eslint-disable-next-line no-console
return this.enabled ? Function.prototype.bind.call(console.log, console) : noop;
}
get warn() {
// eslint-disable-next-line no-console
return this.enabled ? Function.prototype.bind.call(console.warn, console) : noop;
}
get error() {
// eslint-disable-next-line no-console
return this.enabled ? Function.prototype.bind.call(console.error, console) : noop;
}
}

49
src/js/controls.js vendored
View File

@ -22,12 +22,12 @@ const controls = {
const range = utils.is.event(target) ? target.target : target; const range = utils.is.event(target) ? target.target : target;
// Needs to be a valid <input type='range'> // Needs to be a valid <input type='range'>
if (!utils.is.htmlElement(range) || range.getAttribute('type') !== 'range') { if (!utils.is.element(range) || range.getAttribute('type') !== 'range') {
return; return;
} }
// Inject the stylesheet if needed // Inject the stylesheet if needed
if (!utils.is.htmlElement(this.elements.styleSheet)) { if (!utils.is.element(this.elements.styleSheet)) {
this.elements.styleSheet = utils.createElement('style'); this.elements.styleSheet = utils.createElement('style');
this.elements.container.appendChild(this.elements.styleSheet); this.elements.container.appendChild(this.elements.styleSheet);
} }
@ -322,7 +322,7 @@ const controls = {
// Create time display // Create time display
createTime(type) { createTime(type) {
const container = utils.createElement('span', { const container = utils.createElement('div', {
class: 'plyr__time', class: 'plyr__time',
}); });
@ -368,7 +368,7 @@ const controls = {
label.appendChild(faux); label.appendChild(faux);
label.insertAdjacentHTML('beforeend', title); label.insertAdjacentHTML('beforeend', title);
if (utils.is.htmlElement(badge)) { if (utils.is.element(badge)) {
label.appendChild(badge); label.appendChild(badge);
} }
@ -381,8 +381,8 @@ const controls = {
// Bail if setting not true // Bail if setting not true
if ( if (
!this.config.tooltips.seek || !this.config.tooltips.seek ||
!utils.is.htmlElement(this.elements.inputs.seek) || !utils.is.element(this.elements.inputs.seek) ||
!utils.is.htmlElement(this.elements.display.seekTooltip) || !utils.is.element(this.elements.display.seekTooltip) ||
this.duration === 0 this.duration === 0
) { ) {
return; return;
@ -542,7 +542,7 @@ const controls = {
switch (setting) { switch (setting) {
case 'captions': case 'captions':
value = this.captions.enabled ? this.captions.language : ''; value = this.captions.active ? this.captions.language : '';
break; break;
default: default:
@ -555,13 +555,13 @@ const controls = {
// Unsupported value // Unsupported value
if (!this.options[setting].includes(value)) { if (!this.options[setting].includes(value)) {
this.console.warn(`Unsupported value of '${value}' for ${setting}`); this.debug.warn(`Unsupported value of '${value}' for ${setting}`);
return; return;
} }
// Disabled value // Disabled value
if (!this.config[setting].options.includes(value)) { if (!this.config[setting].options.includes(value)) {
this.console.warn(`Disabled value of '${value}' for ${setting}`); this.debug.warn(`Disabled value of '${value}' for ${setting}`);
return; return;
} }
@ -569,7 +569,7 @@ const controls = {
} }
// Get the list if we need to // Get the list if we need to
if (!utils.is.htmlElement(list)) { if (!utils.is.element(list)) {
list = pane && pane.querySelector('ul'); list = pane && pane.querySelector('ul');
} }
@ -582,7 +582,7 @@ const controls = {
// Find the radio option // Find the radio option
const target = list && list.querySelector(`input[value="${value}"]`); const target = list && list.querySelector(`input[value="${value}"]`);
if (utils.is.htmlElement(target)) { if (utils.is.element(target)) {
// Check it // Check it
target.checked = true; target.checked = true;
} }
@ -638,7 +638,7 @@ const controls = {
return this.config.i18n.none; return this.config.i18n.none;
} }
if (this.captions.enabled) { if (this.captions.active) {
const currentTrack = captions.getCurrentTrack.call(this); const currentTrack = captions.getCurrentTrack.call(this);
if (utils.is.track(currentTrack)) { if (utils.is.track(currentTrack)) {
@ -736,10 +736,10 @@ const controls = {
toggleMenu(event) { toggleMenu(event) {
const { form } = this.elements.settings; const { form } = this.elements.settings;
const button = this.elements.buttons.settings; const button = this.elements.buttons.settings;
const show = utils.is.boolean(event) ? event : utils.is.htmlElement(form) && form.getAttribute('aria-hidden') === 'true'; const show = utils.is.boolean(event) ? event : utils.is.element(form) && form.getAttribute('aria-hidden') === 'true';
if (utils.is.event(event)) { if (utils.is.event(event)) {
const isMenuItem = utils.is.htmlElement(form) && form.contains(event.target); const isMenuItem = utils.is.element(form) && form.contains(event.target);
const isButton = event.target === this.elements.buttons.settings; const isButton = event.target === this.elements.buttons.settings;
// If the click was inside the form or if the click // If the click was inside the form or if the click
@ -756,11 +756,11 @@ const controls = {
} }
// Set form and button attributes // Set form and button attributes
if (utils.is.htmlElement(button)) { if (utils.is.element(button)) {
button.setAttribute('aria-expanded', show); button.setAttribute('aria-expanded', show);
} }
if (utils.is.htmlElement(form)) { if (utils.is.element(form)) {
form.setAttribute('aria-hidden', !show); form.setAttribute('aria-hidden', !show);
utils.toggleClass(this.elements.container, this.config.classNames.menu.open, show); utils.toggleClass(this.elements.container, this.config.classNames.menu.open, show);
@ -809,7 +809,7 @@ const controls = {
const pane = document.getElementById(tab.getAttribute('aria-controls')); const pane = document.getElementById(tab.getAttribute('aria-controls'));
// Nothing to show, bail // Nothing to show, bail
if (!utils.is.htmlElement(pane)) { if (!utils.is.element(pane)) {
return; return;
} }
@ -908,7 +908,7 @@ const controls = {
// Progress // Progress
if (this.config.controls.includes('progress')) { if (this.config.controls.includes('progress')) {
const progress = utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.progress)); const progress = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.progress));
// Seek range slider // Seek range slider
const seek = controls.createRange.call(this, 'seek', { const seek = controls.createRange.call(this, 'seek', {
@ -958,7 +958,7 @@ const controls = {
// Volume range control // Volume range control
if (this.config.controls.includes('volume')) { if (this.config.controls.includes('volume')) {
const volume = utils.createElement('span', { const volume = utils.createElement('div', {
class: 'plyr__volume', class: 'plyr__volume',
}); });
@ -1186,22 +1186,27 @@ const controls = {
} }
// Inject into the container by default // Inject into the container by default
if (!utils.is.htmlElement(target)) { if (!utils.is.element(target)) {
target = this.elements.container; target = this.elements.container;
} }
// Inject controls HTML // Inject controls HTML
if (utils.is.htmlElement(container)) { if (utils.is.element(container)) {
target.appendChild(container); target.appendChild(container);
} else { } else {
target.insertAdjacentHTML('beforeend', container); target.insertAdjacentHTML('beforeend', container);
} }
// Find the elements if need be // Find the elements if need be
if (utils.is.htmlElement(this.elements.controls)) { if (utils.is.element(this.elements.controls)) {
utils.findElements.call(this); utils.findElements.call(this);
} }
// Edge sometimes doesn't finish the paint so force a redraw
if (window.navigator.userAgent.includes('Edge')) {
utils.repaint(target);
}
// Setup tooltips // Setup tooltips
if (this.config.tooltips.controls) { if (this.config.tooltips.controls) {
const labels = utils.getElements.call( const labels = utils.getElements.call(

View File

@ -100,12 +100,12 @@ const fullscreen = {
const nativeSupport = fullscreen.enabled; const nativeSupport = fullscreen.enabled;
if (nativeSupport || (this.config.fullscreen.fallback && !utils.inFrame())) { if (nativeSupport || (this.config.fullscreen.fallback && !utils.inFrame())) {
this.console.log(`${nativeSupport ? 'Native' : 'Fallback'} fullscreen enabled`); this.debug.log(`${nativeSupport ? 'Native' : 'Fallback'} fullscreen enabled`);
// Add styling hook to show button // Add styling hook to show button
utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.enabled, true); utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.enabled, true);
} else { } else {
this.console.log('Fullscreen not supported and fallback disabled'); this.debug.log('Fullscreen not supported and fallback disabled');
} }
// Toggle state // Toggle state

View File

@ -6,7 +6,6 @@ import support from './support';
import utils from './utils'; import utils from './utils';
import controls from './controls'; import controls from './controls';
import fullscreen from './fullscreen'; import fullscreen from './fullscreen';
import storage from './storage';
import ui from './ui'; import ui from './ui';
// Sniff out the browser // Sniff out the browser
@ -53,7 +52,7 @@ const listeners = {
// and if the focused element is not editable (e.g. text input) // and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/ // and any that accept key input http://webaim.org/techniques/keyboard/
const focused = utils.getFocusElement(); const focused = utils.getFocusElement();
if (utils.is.htmlElement(focused) && utils.matches(focused, this.config.selectors.editable)) { if (utils.is.element(focused) && utils.matches(focused, this.config.selectors.editable)) {
return; return;
} }
@ -248,7 +247,7 @@ const listeners = {
const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`); const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`);
// Bail if there's no wrapper (this should never happen) // Bail if there's no wrapper (this should never happen)
if (!utils.is.htmlElement(wrapper)) { if (!utils.is.element(wrapper)) {
return; return;
} }
@ -285,7 +284,7 @@ const listeners = {
// Volume change // Volume change
utils.on(this.media, 'volumechange', () => { utils.on(this.media, 'volumechange', () => {
// Save to storage // Save to storage
storage.set.call(this, { volume: this.volume, muted: this.muted }); this.storage.set({ volume: this.volume, muted: this.muted });
}); });
// Speed change // Speed change
@ -294,7 +293,7 @@ const listeners = {
controls.updateSetting.call(this, 'speed'); controls.updateSetting.call(this, 'speed');
// Save to storage // Save to storage
storage.set.call(this, { speed: this.speed }); this.storage.set({ speed: this.speed });
}); });
// Quality change // Quality change
@ -303,7 +302,7 @@ const listeners = {
controls.updateSetting.call(this, 'quality'); controls.updateSetting.call(this, 'quality');
// Save to storage // Save to storage
storage.set.call(this, { quality: this.quality }); this.storage.set({ quality: this.quality });
}); });
// Caption language change // Caption language change
@ -312,7 +311,7 @@ const listeners = {
controls.updateSetting.call(this, 'captions'); controls.updateSetting.call(this, 'captions');
// Save to storage // Save to storage
storage.set.call(this, { language: this.language }); this.storage.set({ language: this.language });
}); });
// Captions toggle // Captions toggle
@ -321,7 +320,7 @@ const listeners = {
controls.updateSetting.call(this, 'captions'); controls.updateSetting.call(this, 'captions');
// Save to storage // Save to storage
storage.set.call(this, { captions: this.captions.enabled }); this.storage.set({ captions: this.captions.active });
}); });
// Proxy events to container // Proxy events to container
@ -462,7 +461,7 @@ const listeners = {
// Current time invert // Current time invert
// Only if one time element is used for both currentTime and duration // Only if one time element is used for both currentTime and duration
if (this.config.toggleInvert && !utils.is.htmlElement(this.elements.display.duration)) { if (this.config.toggleInvert && !utils.is.element(this.elements.display.duration)) {
utils.on(this.elements.display.currentTime, 'click', () => { utils.on(this.elements.display.currentTime, 'click', () => {
// Do nothing if we're at the start // Do nothing if we're at the start
if (this.currentTime === 0) { if (this.currentTime === 0) {

View File

@ -16,7 +16,7 @@ const media = {
setup() { setup() {
// If there's no media, bail // If there's no media, bail
if (!this.media) { if (!this.media) {
this.console.warn('No media element found!'); this.debug.warn('No media element found!');
return; return;
} }
@ -99,7 +99,7 @@ const media = {
this.media.load(); this.media.load();
// Debugging // Debugging
this.console.log('Cancelled network requests'); this.debug.log('Cancelled network requests');
}, },
}; };

View File

@ -242,7 +242,7 @@ const vimeo = {
}); });
player.embed.on('loaded', () => { player.embed.on('loaded', () => {
if (utils.is.htmlElement(player.embed.element) && player.supported.ui) { if (utils.is.element(player.embed.element) && player.supported.ui) {
const frame = player.embed.element; const frame = player.embed.element;
// Fix keyboard focus issues // Fix keyboard focus issues

View File

@ -10,15 +10,21 @@ import defaults from './defaults';
import support from './support'; import support from './support';
import utils from './utils'; import utils from './utils';
import Console from './console';
import Storage from './storage';
import captions from './captions'; import captions from './captions';
import controls from './controls'; import controls from './controls';
import fullscreen from './fullscreen'; import fullscreen from './fullscreen';
import listeners from './listeners'; import listeners from './listeners';
import media from './media'; import media from './media';
import storage from './storage';
import source from './source'; import source from './source';
import ui from './ui'; import ui from './ui';
// Private properties
// TODO: Use a WeakMap for private globals
// const globals = new WeakMap();
// Globals // Globals
let scrollPosition = { let scrollPosition = {
x: 0, x: 0,
@ -54,7 +60,7 @@ class Plyr {
try { try {
return JSON.parse(this.media.getAttribute('data-plyr-config')); return JSON.parse(this.media.getAttribute('data-plyr-config'));
} catch (e) { } catch (e) {
return null; return {};
} }
})() })()
); );
@ -76,7 +82,7 @@ class Plyr {
// Captions // Captions
this.captions = { this.captions = {
enabled: null, active: null,
currentTrack: null, currentTrack: null,
}; };
@ -92,46 +98,35 @@ class Plyr {
}; };
// Debugging // Debugging
this.console = { // TODO: move to globals
log() {}, this.debug = new Console(this);
warn() {},
error() {},
};
if (this.config.debug && 'console' in window) {
this.console = {
log: console.log, // eslint-disable-line
warn: console.warn, // eslint-disable-line
error: console.error, // eslint-disable-line
};
this.console.log('Debugging enabled');
}
// Log config options and support // Log config options and support
this.console.log('Config', this.config); this.debug.log('Config', this.config);
this.console.log('Support', support); this.debug.log('Support', support);
// We need an element to setup // We need an element to setup
if (utils.is.nullOrUndefined(this.media) || !utils.is.htmlElement(this.media)) { if (utils.is.nullOrUndefined(this.media) || !utils.is.element(this.media)) {
this.console.error('Setup failed: no suitable element passed'); this.debug.error('Setup failed: no suitable element passed');
return; return;
} }
// Bail if the element is initialized // Bail if the element is initialized
if (this.media.plyr) { if (this.media.plyr) {
this.console.warn('Target already setup'); this.debug.warn('Target already setup');
return; return;
} }
// Bail if not enabled // Bail if not enabled
if (!this.config.enabled) { if (!this.config.enabled) {
this.console.error('Setup failed: disabled by config'); this.debug.error('Setup failed: disabled by config');
return; return;
} }
// Bail if disabled or no basic support // Bail if disabled or no basic support
// You may want to disable certain UAs etc // You may want to disable certain UAs etc
if (!support.check().api) { if (!support.check().api) {
this.console.error('Setup failed: no support'); this.debug.error('Setup failed: no support');
return; return;
} }
@ -158,13 +153,13 @@ class Plyr {
this.embedId = this.media.getAttribute(attributes.id); this.embedId = this.media.getAttribute(attributes.id);
if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) { if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) {
this.console.error('Setup failed: Invalid provider'); this.debug.error('Setup failed: Invalid provider');
return; return;
} }
// Try and get the embed id // Try and get the embed id
if (utils.is.empty(this.embedId)) { if (utils.is.empty(this.embedId)) {
this.console.error('Setup failed: Embed ID or URL missing'); this.debug.error('Setup failed: Embed ID or URL missing');
return; return;
} }
@ -202,19 +197,19 @@ class Plyr {
break; break;
default: default:
this.console.error('Setup failed: unsupported type'); this.debug.error('Setup failed: unsupported type');
return; return;
} }
// Setup local storage for user settings // Setup local storage for user settings
storage.setup.call(this); this.storage = new Storage(this);
// Check for support again but with type // Check for support again but with type
this.supported = support.check(this.type, this.provider, this.config.inline); this.supported = support.check(this.type, this.provider, this.config.inline);
// If no support for even API, bail // If no support for even API, bail
if (!this.supported.api) { if (!this.supported.api) {
this.console.error('Setup failed: no support'); this.debug.error('Setup failed: no support');
return; return;
} }
@ -240,7 +235,7 @@ class Plyr {
// Listen for events if debugging // Listen for events if debugging
if (this.config.debug) { if (this.config.debug) {
utils.on(this.elements.container, this.config.events.join(' '), event => { utils.on(this.elements.container, this.config.events.join(' '), event => {
this.console.log(`event: ${event.type}`); this.debug.log(`event: ${event.type}`);
}); });
} }
@ -386,7 +381,7 @@ class Plyr {
this.media.currentTime = targetTime.toFixed(4); this.media.currentTime = targetTime.toFixed(4);
// Logging // Logging
this.console.log(`Seeking to ${this.currentTime} seconds`); this.debug.log(`Seeking to ${this.currentTime} seconds`);
} }
/** /**
@ -432,7 +427,7 @@ class Plyr {
// Load volume from storage if no value specified // Load volume from storage if no value specified
if (!utils.is.number(volume)) { if (!utils.is.number(volume)) {
({ volume } = storage.get.call(this)); volume = this.storage.get('volume');
} }
// Use config if all else fails // Use config if all else fails
@ -497,7 +492,7 @@ class Plyr {
// Load muted state from storage // Load muted state from storage
if (!utils.is.boolean(toggle)) { if (!utils.is.boolean(toggle)) {
toggle = storage.get.call(this).muted; toggle = this.storage.get('muted');
} }
// Use config if all else fails // Use config if all else fails
@ -541,9 +536,13 @@ class Plyr {
if (utils.is.number(input)) { if (utils.is.number(input)) {
speed = input; speed = input;
} else if (utils.is.number(storage.get.call(this).speed)) { }
({ speed } = storage.get.call(this));
} else { if (!utils.is.number(speed)) {
speed = this.storage.get('speed');
}
if (!utils.is.number(speed)) {
speed = this.config.speed.selected; speed = this.config.speed.selected;
} }
@ -556,7 +555,7 @@ class Plyr {
} }
if (!this.config.speed.options.includes(speed)) { if (!this.config.speed.options.includes(speed)) {
this.console.warn(`Unsupported speed (${speed})`); this.debug.warn(`Unsupported speed (${speed})`);
return; return;
} }
@ -584,14 +583,18 @@ class Plyr {
if (utils.is.string(input)) { if (utils.is.string(input)) {
quality = input; quality = input;
} else if (utils.is.number(storage.get.call(this).quality)) { }
({ quality } = storage.get.call(this));
} else { if (!utils.is.string(quality)) {
quality = this.storage.get('quality');
}
if (!utils.is.string(quality)) {
quality = this.config.quality.selected; quality = this.config.quality.selected;
} }
if (!this.options.quality.includes(quality)) { if (!this.options.quality.includes(quality)) {
this.console.warn(`Unsupported quality option (${quality})`); this.debug.warn(`Unsupported quality option (${quality})`);
return; return;
} }
@ -691,7 +694,7 @@ class Plyr {
*/ */
set poster(input) { set poster(input) {
if (!this.isHTML5 || !this.isVideo) { if (!this.isHTML5 || !this.isVideo) {
this.console.warn('Poster can only be set on HTML5 video'); this.debug.warn('Poster can only be set on HTML5 video');
return; return;
} }
@ -733,7 +736,7 @@ class Plyr {
*/ */
toggleCaptions(input) { toggleCaptions(input) {
// If there's no full support, or there's no caption toggle // If there's no full support, or there's no caption toggle
if (!this.supported.ui || !utils.is.htmlElement(this.elements.buttons.captions)) { if (!this.supported.ui || !utils.is.element(this.elements.buttons.captions)) {
return this; return this;
} }
@ -741,21 +744,21 @@ class Plyr {
const show = utils.is.boolean(input) ? input : this.elements.container.className.indexOf(this.config.classNames.captions.active) === -1; const show = utils.is.boolean(input) ? input : this.elements.container.className.indexOf(this.config.classNames.captions.active) === -1;
// Nothing to change... // Nothing to change...
if (this.captions.enabled === show) { if (this.captions.active === show) {
return this; return this;
} }
// Set global // Set global
this.captions.enabled = show; this.captions.active = show;
// Toggle state // Toggle state
utils.toggleState(this.elements.buttons.captions, this.captions.enabled); utils.toggleState(this.elements.buttons.captions, this.captions.active);
// Add class hook // Add class hook
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.captions.enabled); utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.captions.active);
// Trigger an event // Trigger an event
utils.dispatchEvent.call(this, this.media, this.captions.enabled ? 'captionsenabled' : 'captionsdisabled'); utils.dispatchEvent.call(this, this.media, this.captions.active ? 'captionsenabled' : 'captionsdisabled');
// Allow chaining // Allow chaining
return this; return this;
@ -850,7 +853,7 @@ class Plyr {
} }
// Set button state // Set button state
if (utils.is.htmlElement(this.elements.buttons.fullscreen)) { if (utils.is.element(this.elements.buttons.fullscreen)) {
utils.toggleState(this.elements.buttons.fullscreen, this.fullscreen.active); utils.toggleState(this.elements.buttons.fullscreen, this.fullscreen.active);
} }
@ -916,7 +919,7 @@ class Plyr {
*/ */
toggleControls(toggle) { toggleControls(toggle) {
// We need controls of course... // We need controls of course...
if (!utils.is.htmlElement(this.elements.controls)) { if (!utils.is.element(this.elements.controls)) {
return this; return this;
} }
@ -981,7 +984,7 @@ 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 = window.setTimeout(() => { this.timers.controls = window.setTimeout(() => {
/* this.console.warn({ /* this.debug.warn({
pressed: this.elements.controls.pressed, pressed: this.elements.controls.pressed,
hover: this.elements.controls.pressed, hover: this.elements.controls.pressed,
playing: this.playing, playing: this.playing,
@ -1088,7 +1091,7 @@ class Plyr {
// Replace the container with the original element provided // Replace the container with the original element provided
const parent = this.elements.container.parentNode; const parent = this.elements.container.parentNode;
if (utils.is.htmlElement(parent)) { if (utils.is.element(parent)) {
parent.replaceChild(this.elements.original, this.elements.container); parent.replaceChild(this.elements.original, this.elements.container);
} }

View File

@ -26,7 +26,7 @@ const source = {
// Sources are not checked for support so be careful // Sources are not checked for support so be careful
change(input) { change(input) {
if (!utils.is.object(input) || !('sources' in input) || !input.sources.length) { if (!utils.is.object(input) || !('sources' in input) || !input.sources.length) {
this.console.warn('Invalid source format'); this.debug.warn('Invalid source format');
return; return;
} }
@ -44,7 +44,7 @@ const source = {
this.media = null; this.media = null;
// Reset class name // Reset class name
if (utils.is.htmlElement(this.elements.container)) { if (utils.is.element(this.elements.container)) {
this.elements.container.removeAttribute('class'); this.elements.container.removeAttribute('class');
} }
@ -105,8 +105,7 @@ const source = {
} }
} }
// Restore class hooks // Restore class hook
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.supported.ui && this.captions.enabled);
ui.addStyleHook.call(this); ui.addStyleHook.call(this);
// Set new sources for html5 // Set new sources for html5

View File

@ -2,24 +2,48 @@
// Plyr storage // Plyr storage
// ========================================================================== // ==========================================================================
import support from './support';
import utils from './utils'; import utils from './utils';
// Get contents of local storage class Storage {
function get() { constructor(player) {
const store = window.localStorage.getItem(this.config.storage.key); this.enabled = player.config.storage.enabled;
this.key = player.config.storage.key;
if (utils.is.empty(store)) {
return {};
} }
return JSON.parse(store); // Check for actual support (see if we can use it)
} static get supported() {
if (!('localStorage' in window)) {
return false;
}
// Save a value back to local storage const test = '___test';
function set(object) {
// Try to use it (it might be disabled, e.g. user is in private mode)
// see: https://github.com/sampotts/plyr/issues/131
try {
window.localStorage.setItem(test, test);
window.localStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
get(key) {
const store = window.localStorage.getItem(this.key);
if (!Storage.supported || utils.is.empty(store)) {
return null;
}
const json = JSON.parse(store);
return utils.is.string(key) && key.length ? json[key] : json;
}
set(object) {
// Bail if we don't have localStorage support or it's disabled // Bail if we don't have localStorage support or it's disabled
if (!support.storage || !this.config.storage.enabled) { if (!Storage.supported || !this.enabled) {
return; return;
} }
@ -29,47 +53,14 @@ function set(object) {
} }
// Get current storage // Get current storage
const storage = get.call(this); const storage = this.get();
// Update the working copy of the values // Update the working copy of the values
utils.extend(storage, object); utils.extend(storage, object);
// Update storage // Update storage
window.localStorage.setItem(this.config.storage.key, JSON.stringify(storage)); window.localStorage.setItem(this.key, JSON.stringify(storage));
}
} }
// Setup localStorage export default Storage;
function setup() {
let value = null;
let storage = {};
// Bail if we don't have localStorage support or it's disabled
if (!support.storage || !this.config.storage.enabled) {
return storage;
}
// Clean up old volume
// https://github.com/sampotts/plyr/issues/171
window.localStorage.removeItem('plyr-volume');
// load value from the current key
value = window.localStorage.getItem(this.config.storage.key);
if (!value) {
// Key wasn't set (or had been cleared), move along
} else if (/^\d+(\.\d+)?$/.test(value)) {
// If value is a number, it's probably volume from an older
// version of this. See: https://github.com/sampotts/plyr/pull/313
// Update the key to be JSON
set({
volume: parseFloat(value),
});
} else {
// Assume it's JSON from this or a later version of plyr
storage = JSON.parse(value);
}
return storage;
}
export default { setup, set, get };

View File

@ -50,25 +50,6 @@ const support = {
}; };
}, },
// Local storage
// We can't assume if local storage is present that we can use it
storage: (() => {
if (!('localStorage' in window)) {
return false;
}
// Try to use it (it might be disabled, e.g. user is in private/porn mode)
// see: https://github.com/sampotts/plyr/issues/131
const test = '___test';
try {
window.localStorage.setItem(test, test);
window.localStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
})(),
// Picture-in-picture support // Picture-in-picture support
// Safari only currently // Safari only currently
pip: (() => { pip: (() => {

View File

@ -31,7 +31,7 @@ const ui = {
// Don't setup interface if no support // Don't setup interface if no support
if (!this.supported.ui) { if (!this.supported.ui) {
this.console.warn(`Basic support only for ${this.provider} ${this.type}`); this.debug.warn(`Basic support only for ${this.provider} ${this.type}`);
// Remove controls // Remove controls
utils.removeElement.call(this, 'controls'); utils.removeElement.call(this, 'controls');
@ -47,7 +47,7 @@ const ui = {
} }
// Inject custom controls if not present // Inject custom controls if not present
if (!utils.is.htmlElement(this.elements.controls)) { if (!utils.is.element(this.elements.controls)) {
// Inject custom controls // Inject custom controls
controls.inject.call(this); controls.inject.call(this);
@ -56,7 +56,7 @@ const ui = {
} }
// If there's no controls, bail // If there's no controls, bail
if (!utils.is.htmlElement(this.elements.controls)) { if (!utils.is.element(this.elements.controls)) {
return; return;
} }
@ -125,7 +125,7 @@ const ui = {
if (this.isEmbed) { if (this.isEmbed) {
const iframe = utils.getElement.call(this, 'iframe'); const iframe = utils.getElement.call(this, 'iframe');
if (!utils.is.htmlElement(iframe)) { if (!utils.is.element(iframe)) {
return; return;
} }
@ -175,19 +175,19 @@ const ui = {
} }
// Update range // Update range
if (utils.is.htmlElement(this.elements.inputs.volume)) { if (utils.is.element(this.elements.inputs.volume)) {
ui.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume); ui.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume);
} }
// Update mute state // Update mute state
if (utils.is.htmlElement(this.elements.buttons.mute)) { if (utils.is.element(this.elements.buttons.mute)) {
utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0); utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0);
} }
}, },
// Update seek value and lower fill // Update seek value and lower fill
setRange(target, value = 0) { setRange(target, value = 0) {
if (!utils.is.htmlElement(target)) { if (!utils.is.element(target)) {
return; return;
} }
@ -201,15 +201,15 @@ const ui = {
// Set <progress> value // Set <progress> value
setProgress(target, input) { setProgress(target, input) {
const value = utils.is.number(input) ? input : 0; const value = utils.is.number(input) ? input : 0;
const progress = utils.is.htmlElement(target) ? target : this.elements.display.buffer; const progress = utils.is.element(target) ? target : this.elements.display.buffer;
// Update value and label // Update value and label
if (utils.is.htmlElement(progress)) { if (utils.is.element(progress)) {
progress.value = value; progress.value = value;
// Update text label inside // Update text label inside
const label = progress.getElementsByTagName('span')[0]; const label = progress.getElementsByTagName('span')[0];
if (utils.is.htmlElement(label)) { if (utils.is.element(label)) {
label.childNodes[0].nodeValue = value; label.childNodes[0].nodeValue = value;
} }
} }
@ -267,7 +267,7 @@ const ui = {
// Update the displayed time // Update the displayed time
updateTimeDisplay(target = null, time = 0, inverted = false) { updateTimeDisplay(target = null, time = 0, inverted = false) {
// Bail if there's no element to display or the value isn't a number // Bail if there's no element to display or the value isn't a number
if (!utils.is.htmlElement(target) || !utils.is.number(time)) { if (!utils.is.element(target) || !utils.is.number(time)) {
return; return;
} }
@ -299,7 +299,7 @@ const ui = {
// Handle time change event // Handle time change event
timeUpdate(event) { timeUpdate(event) {
// Only invert if only one time element is displayed and used for both duration and currentTime // Only invert if only one time element is displayed and used for both duration and currentTime
const invert = !utils.is.htmlElement(this.elements.display.duration) && this.config.invertTime; const invert = !utils.is.element(this.elements.display.duration) && this.config.invertTime;
// Duration // Duration
ui.updateTimeDisplay.call(this, this.elements.display.currentTime, invert ? this.duration - this.currentTime : this.currentTime, invert); ui.updateTimeDisplay.call(this, this.elements.display.currentTime, invert ? this.duration - this.currentTime : this.currentTime, invert);
@ -320,12 +320,12 @@ const ui = {
} }
// If there's only one time display, display duration there // If there's only one time display, display duration there
if (!utils.is.htmlElement(this.elements.display.duration) && this.config.displayDuration && this.paused) { if (!utils.is.element(this.elements.display.duration) && this.config.displayDuration && this.paused) {
ui.updateTimeDisplay.call(this, this.elements.display.currentTime, this.duration); ui.updateTimeDisplay.call(this, this.elements.display.currentTime, this.duration);
} }
// If there's a duration element, update content // If there's a duration element, update content
if (utils.is.htmlElement(this.elements.display.duration)) { if (utils.is.element(this.elements.display.duration)) {
ui.updateTimeDisplay.call(this, this.elements.display.duration, this.duration); ui.updateTimeDisplay.call(this, this.elements.display.duration, this.duration);
} }

View File

@ -7,6 +7,9 @@ import support from './support';
const utils = { const utils = {
// Check variable types // Check variable types
is: { is: {
plyr(input) {
return this.instanceof(input, Plyr);
},
object(input) { object(input) {
return this.getConstructor(input) === Object; return this.getConstructor(input) === Object;
}, },
@ -25,23 +28,26 @@ const utils = {
array(input) { array(input) {
return !this.nullOrUndefined(input) && Array.isArray(input); return !this.nullOrUndefined(input) && Array.isArray(input);
}, },
nodeList(input) { weakMap(input) {
return this.instanceof(input, window.NodeList); return this.instanceof(input, WeakMap);
}, },
htmlElement(input) { nodeList(input) {
return this.instanceof(input, window.HTMLElement); return this.instanceof(input, NodeList);
},
element(input) {
return this.instanceof(input, Element);
}, },
textNode(input) { textNode(input) {
return this.getConstructor(input) === Text; return this.getConstructor(input) === Text;
}, },
event(input) { event(input) {
return this.instanceof(input, window.Event); return this.instanceof(input, Event);
}, },
cue(input) { cue(input) {
return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue); return this.instanceof(input, TextTrackCue) || this.instanceof(input, VTTCue);
}, },
track(input) { track(input) {
return this.instanceof(input, window.TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind)); return this.instanceof(input, TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind));
}, },
nullOrUndefined(input) { nullOrUndefined(input) {
return input === null || typeof input === 'undefined'; return input === null || typeof input === 'undefined';
@ -249,7 +255,7 @@ const utils = {
// Remove an element // Remove an element
removeElement(element) { removeElement(element) {
if (!utils.is.htmlElement(element) || !utils.is.htmlElement(element.parentNode)) { if (!utils.is.element(element) || !utils.is.element(element.parentNode)) {
return null; return null;
} }
@ -270,6 +276,10 @@ const utils = {
// Set attributes // Set attributes
setAttributes(element, attributes) { setAttributes(element, attributes) {
if (!utils.is.element(element) || utils.is.empty(attributes)) {
return;
}
Object.keys(attributes).forEach(key => { Object.keys(attributes).forEach(key => {
element.setAttribute(key, attributes[key]); element.setAttribute(key, attributes[key]);
}); });
@ -334,7 +344,7 @@ const utils = {
// Toggle class on an element // Toggle class on an element
toggleClass(element, className, toggle) { toggleClass(element, className, toggle) {
if (utils.is.htmlElement(element)) { if (utils.is.element(element)) {
const contains = element.classList.contains(className); const contains = element.classList.contains(className);
element.classList[toggle ? 'add' : 'remove'](className); element.classList[toggle ? 'add' : 'remove'](className);
@ -347,12 +357,12 @@ const utils = {
// Has class name // Has class name
hasClass(element, className) { hasClass(element, className) {
return utils.is.htmlElement(element) && element.classList.contains(className); return utils.is.element(element) && element.classList.contains(className);
}, },
// Toggle hidden attribute on an element // Toggle hidden attribute on an element
toggleHidden(element, toggle) { toggleHidden(element, toggle) {
if (!utils.is.htmlElement(element)) { if (!utils.is.element(element)) {
return; return;
} }
@ -424,14 +434,14 @@ const utils = {
}; };
// Seek tooltip // Seek tooltip
if (utils.is.htmlElement(this.elements.progress)) { if (utils.is.element(this.elements.progress)) {
this.elements.display.seekTooltip = this.elements.progress.querySelector(`.${this.config.classNames.tooltip}`); this.elements.display.seekTooltip = this.elements.progress.querySelector(`.${this.config.classNames.tooltip}`);
} }
return true; return true;
} catch (error) { } catch (error) {
// Log it // Log it
this.console.warn('It looks like there is a problem with your custom controls HTML', error); this.debug.warn('It looks like there is a problem with your custom controls HTML', error);
// Restore native video controls // Restore native video controls
this.toggleNativeControls(true); this.toggleNativeControls(true);
@ -560,7 +570,7 @@ const utils = {
// http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles // http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles
toggleState(element, input) { toggleState(element, input) {
// Bail if no target // Bail if no target
if (!utils.is.htmlElement(element)) { if (!utils.is.element(element)) {
return; return;
} }
@ -580,45 +590,31 @@ const utils = {
return (current / max * 100).toFixed(2); return (current / max * 100).toFixed(2);
}, },
// Deep extend/merge destination object with N more objects // Deep extend destination object with N more objects
// http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/ extend(target = {}, ...sources) {
// Removed call to arguments.callee (used explicit function name instead) if (!sources.length) {
extend(...objects) { return target;
const { length } = objects;
// Bail if nothing to merge
if (!length) {
return null;
} }
// Return first if specified but nothing to merge const source = sources.shift();
if (length === 1) {
return objects[0];
}
// First object is the destination
let destination = Array.prototype.shift.call(objects);
if (!utils.is.object(destination)) {
destination = {};
}
// Loop through all objects to merge
objects.forEach(source => {
if (!utils.is.object(source)) { if (!utils.is.object(source)) {
return; return target;
} }
Object.keys(source).forEach(property => { Object.keys(source).forEach(key => {
if (source[property] && source[property].constructor && source[property].constructor === Object) { if (utils.is.object(source[key])) {
destination[property] = destination[property] || {}; if (!Object.keys(target).includes(key)) {
utils.extend(destination[property], source[property]); Object.assign(target, { [key]: {} });
}
utils.extend(target[key], source[key]);
} else { } else {
destination[property] = source[property]; Object.assign(target, { [key]: source[key] });
} }
}); });
});
return destination; return utils.extend(target, ...sources);
}, },
// Parse YouTube ID from URL // Parse YouTube ID from URL
@ -679,6 +675,15 @@ const utils = {
return typeof type === 'string' ? type : false; return typeof type === 'string' ? type : false;
})(), })(),
// Force repaint of element
repaint(element) {
window.setTimeout(() => {
element.setAttribute('hidden', '');
element.offsetHeight; // eslint-disable-line
element.removeAttribute('hidden');
}, 0);
},
}; };
export default utils; export default utils;

View File

@ -13,8 +13,8 @@
@import 'base'; @import 'base';
@import 'components/badges'; @import 'components/badges';
@import 'components/buttons';
@import 'components/captions'; @import 'components/captions';
@import 'components/control';
@import 'components/controls'; @import 'components/controls';
@import 'components/embed'; @import 'components/embed';
@import 'components/menus'; @import 'components/menus';

View File

@ -39,12 +39,12 @@
display: none; display: none;
} }
@media (min-width: @plyr-bp-screen-sm) { @media @plyr-mq-sm {
padding: (@plyr-control-spacing * 2); padding: (@plyr-control-spacing * 2);
font-size: @plyr-font-size-captions-base; font-size: @plyr-font-size-captions-base;
} }
@media (min-width: @plyr-bp-screen-md) { @media @plyr-mq-md {
font-size: @plyr-font-size-captions-medium; font-size: @plyr-font-size-captions-medium;
} }
} }

View File

@ -1,13 +1,15 @@
// --------------------------------------------------------------
// Control buttons
// --------------------------------------------------------------
.plyr__control { .plyr__control {
position: relative; position: relative;
display: inline-block;
flex-shrink: 0; flex-shrink: 0;
overflow: visible; // IE11 overflow: visible; // IE11
vertical-align: middle;
padding: @plyr-control-padding; padding: @plyr-control-padding;
border: 0; border: 0;
background: transparent; background: transparent;
border-radius: 3px; border-radius: @plyr-control-radius;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
color: inherit; color: inherit;
@ -66,7 +68,7 @@
svg { svg {
position: relative; position: relative;
left: 2px; left: 2px; // Offset to make the play button look right
width: @plyr-control-icon-size-large; width: @plyr-control-icon-size-large;
height: @plyr-control-icon-size-large; height: @plyr-control-icon-size-large;
} }
@ -78,7 +80,7 @@
} }
.plyr--full-ui.plyr--video .plyr__control--overlaid { .plyr--full-ui.plyr--video .plyr__control--overlaid {
display: inline-block; display: block;
} }
.plyr--playing .plyr__control--overlaid { .plyr--playing .plyr__control--overlaid {

View File

@ -30,7 +30,7 @@
margin-left: (@plyr-control-spacing / 2); margin-left: (@plyr-control-spacing / 2);
} }
@media (min-width: @plyr-bp-screen-sm) { @media @plyr-mq-sm {
> .plyr__control, > .plyr__control,
.plyr__progress, .plyr__progress,
.plyr__time, .plyr__time,

View File

@ -3,24 +3,20 @@
// -------------------------------------------------------------- // --------------------------------------------------------------
.plyr__time { .plyr__time {
display: inline-block;
vertical-align: middle;
font-size: @plyr-font-size-time; font-size: @plyr-font-size-time;
} }
// Media duration hidden on small screens // Media duration hidden on small screens
.plyr__time + .plyr__time { .plyr__time + .plyr__time {
display: none;
@media (min-width: @plyr-bp-screen-md) {
display: inline-block;
}
// Add a slash in before // Add a slash in before
&::before { &::before {
content: '\2044'; content: '\2044';
margin-right: @plyr-control-spacing; margin-right: @plyr-control-spacing;
} }
@media @plyr-mq-sm-max {
display: none;
}
} }
.plyr--video .plyr__time { .plyr--video .plyr__time {

View File

@ -11,12 +11,11 @@
z-index: 2; z-index: 2;
} }
@media (min-width: @plyr-bp-screen-sm) { @media @plyr-mq-sm {
display: block;
max-width: 50px; max-width: 50px;
} }
@media (min-width: @plyr-bp-screen-md) { @media @plyr-mq-md {
max-width: 80px; max-width: 80px;
} }
} }

View File

@ -90,7 +90,7 @@
} }
// Large captions in full screen on larger screens // Large captions in full screen on larger screens
@media (min-width: @plyr-bp-screen-lg) { @media @plyr-mq-lg {
.plyr__captions { .plyr__captions {
font-size: @plyr-font-size-captions-large; font-size: @plyr-font-size-captions-large;
} }

View File

@ -42,10 +42,13 @@
@plyr-control-icon-size-large: 20px; @plyr-control-icon-size-large: 20px;
@plyr-control-spacing: 10px; @plyr-control-spacing: 10px;
@plyr-control-padding: (@plyr-control-spacing * 0.7); @plyr-control-padding: (@plyr-control-spacing * 0.7);
@plyr-control-radius: 3px;
@plyr-video-controls-bg: #000; @plyr-video-controls-bg: #000;
@plyr-video-control-color: #fff; @plyr-video-control-color: #fff;
@plyr-video-control-color-hover: #fff; @plyr-video-control-color-hover: #fff;
@plyr-video-control-bg-hover: @plyr-color-main; @plyr-video-control-bg-hover: @plyr-color-main;
@plyr-audio-controls-bg: #fff; @plyr-audio-controls-bg: #fff;
@plyr-audio-control-color: @plyr-color-fiord; @plyr-audio-control-color: @plyr-color-fiord;
@plyr-audio-control-color-hover: #fff; @plyr-audio-control-color-hover: #fff;
@ -74,20 +77,35 @@
@plyr-audio-progress-buffered-bg: fade(@plyr-color-heather, 66%); @plyr-audio-progress-buffered-bg: fade(@plyr-color-heather, 66%);
// Range sliders // Range sliders
@plyr-range-track-height: 8px; @plyr-range-track-height: 6px;
@plyr-range-thumb-height: floor(@plyr-range-track-height * 2); @plyr-range-thumb-height: ceil(@plyr-range-track-height * 2.3);
@plyr-range-thumb-width: floor(@plyr-range-track-height * 2); @plyr-range-thumb-width: ceil(@plyr-range-track-height * 2.3);
@plyr-range-thumb-bg: #fff; @plyr-range-thumb-bg: #fff;
@plyr-range-thumb-border: 2px solid transparent; @plyr-range-thumb-border: 2px solid transparent;
@plyr-range-thumb-shadow: 0 1px 1px fade(@plyr-video-controls-bg, 15%), 0 0 0 1px fade(@plyr-color-gunmetal, 20%); @plyr-range-thumb-shadow: 0 1px 1px fade(@plyr-video-controls-bg, 15%), 0 0 0 1px fade(@plyr-color-gunmetal, 20%);
@plyr-range-thumb-active-border-color: #fff; @plyr-range-thumb-active-border-color: #fff;
@plyr-range-thumb-active-bg: @plyr-video-control-bg-hover; @plyr-range-thumb-active-bg: @plyr-video-control-bg-hover;
@plyr-range-thumb-active-scale: 1.25; @plyr-range-thumb-active-scale: 1.5;
@plyr-video-range-track-bg: @plyr-video-progress-buffered-bg; @plyr-video-range-track-bg: @plyr-video-progress-buffered-bg;
@plyr-audio-range-track-bg: @plyr-audio-progress-buffered-bg; @plyr-audio-range-track-bg: @plyr-audio-progress-buffered-bg;
@plyr-range-selected-bg: @plyr-color-main; @plyr-range-selected-bg: @plyr-color-main;
// Breakpoints // Breakpoints
@plyr-bp-screen-sm: 480px; @plyr-bp-sm: 480px;
@plyr-bp-screen-md: 768px; @plyr-bp-md: 768px;
@plyr-bp-screen-lg: 1024px; @plyr-bp-lg: 1024px;
// Max-width media queries
@plyr-bp-xs-max: (@plyr-bp-sm - 1);
@plyr-bp-sm-max: (@plyr-bp-md - 1);
@plyr-bp-md-max: (@plyr-bp-lg - 1);
// Mobile first
@plyr-mq-sm: ~'only screen and (min-width: @{plyr-bp-sm}) ';
@plyr-mq-md: ~'only screen and (min-width: @{plyr-bp-md}) ';
@plyr-mq-lg: ~'only screen and (min-width: @{plyr-bp-lg}) ';
// Mobile last
@plyr-mq-xs-max: ~'only screen and (max-width: @{plyr-bp-xs-max}) ';
@plyr-mq-sm-max: ~'only screen and (max-width: @{plyr-bp-sm-max}) ';
@plyr-mq-md-max: ~'only screen and (max-width: @{plyr-bp-md-max}) ';