Compare commits

...

38 Commits

Author SHA1 Message Date
Sam Potts 0d0ece94d3 Fix regression 2018-03-29 20:20:37 +11:00
Sam Potts 1c06f6d06d Vimeo hotfix 2018-03-29 19:35:02 +11:00
Sam Potts dda8e30b92 Merge branch 'master' of github.com:sampotts/plyr 2018-03-28 22:45:18 +11:00
Sam Potts c4e2e24643 Bug fixes 2018-03-28 22:45:11 +11:00
Sam Potts e020a105a3 Update readme.md 2018-03-28 00:55:55 +11:00
Sam Potts 2b7fe9a4f9 v3.0.6 2018-03-28 00:17:15 +11:00
Sam Potts 951df64b7f v3.0.5 2018-03-27 23:52:26 +11:00
Sam Potts 0976afe282 v3.0.4 2018-03-27 23:47:58 +11:00
Sam Potts 7b1e4abda7 Controls fixes 2018-03-27 23:43:38 +11:00
Sam Potts 0cf75eed3f Revert API method change 2018-03-27 21:15:11 +11:00
Sam Potts d96957d086 Allow fullscreen in iframe 2018-03-27 21:13:22 +11:00
Sam Potts 1a032ea498 Fix for seeking issue 2018-03-27 21:10:06 +11:00
Sam Potts 5d079da1b8 Use object.entries 2018-03-27 10:41:06 +11:00
Sam Potts 9c1bc6ab08 Fixes for fast forward and issues with event.preventDefault() 2018-03-27 10:36:08 +11:00
Sam Potts 3d2ba8c009 Update readme.md 2018-03-22 09:11:42 +11:00
Sam Potts e872ce3f77 Update readme.md 2018-03-22 09:10:50 +11:00
Sam Potts b77756da04 Typo 2018-03-22 01:15:10 +11:00
Sam Potts 9b23e13ce8 v3.0.3 2018-03-22 01:13:37 +11:00
Sam Potts 5eafe9baff Vimeo offset tweak (fixes #826) 2018-03-22 01:08:08 +11:00
Sam Potts c251c94131 Fix for .stop() method (fixes #819) 2018-03-22 01:02:38 +11:00
Sam Potts 17041efc71 Check for array for speed options (fixes #252) 2018-03-22 00:33:14 +11:00
Sam Potts 05b8e8a6e0 Restore as float (fixes #828) 2018-03-22 00:28:42 +11:00
Sam Potts f998b996fa Fix for Firefox fullscreen oddness (Fixes #821) 2018-03-22 00:26:01 +11:00
Sam Potts 958b47c435 Merge branch 'master' of github.com:sampotts/plyr 2018-03-22 00:06:26 +11:00
Sam Potts a27248d3b6 Merge pull request #820 from saadshahd/patch-1
Fix fast-forward control
2018-03-22 00:05:24 +11:00
Sam Potts 1b1f7be7ff Merge branch 'master' of github.com:sampotts/plyr 2018-03-22 00:04:34 +11:00
Sam Potts 59d4a27240 Improve Sprite checking (fixes #827) 2018-03-22 00:04:28 +11:00
Saad Shahd 75e9f3c2e3 Fix fast-forward control
fast-forward control doesn't work.
2018-03-21 12:15:57 +02:00
Sam Potts 7132eccf50 Merge pull request #822 from DanielRuf/patch/fix-options-link
fix the options link in the readme
2018-03-21 09:12:06 +11:00
Daniel Ruf e953c6398c fix the options link in the readme 2018-03-20 15:05:51 +01:00
Sam Potts bb7eea27e5 v3.0.2 2018-03-18 22:46:36 +11:00
Sam Potts 595c5e95bc Fix for Safari with adblockers 2018-03-18 22:37:06 +11:00
Sam Potts 43e6dcd41d Fix for local storage issue 2018-03-18 01:37:24 +11:00
Sam Potts b06c8ae43f Changelog updated 2018-03-18 01:14:18 +11:00
Sam Potts c7ea13c0c7 Sentry in live only 2018-03-18 01:08:05 +11:00
Sam Potts 0f8c6e147b Added Sentry 2018-03-18 00:21:23 +11:00
Sam Potts e566365288 Typo 2018-03-17 23:44:40 +11:00
Sam Potts a06e0f5890 Updated screenshot 2018-03-17 23:40:28 +11:00
38 changed files with 7017 additions and 1607 deletions
+362 -315
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+3988 -198
View File
File diff suppressed because it is too large Load Diff
+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
+6 -6
View File
@@ -163,25 +163,25 @@
c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"></path> c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"></path>
</svg> </svg>
<p>If you think Plyr's good, <p>If you think Plyr's good,
<a href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts" <a href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&amp;url=http%3A%2F%2Fplyr.io&amp;via=Sam_Potts"
target="_blank" data-shr-network="twitter">tweet it</a> target="_blank" data-shr-network="twitter">tweet it</a>
</p> </p>
</aside> </aside>
<!-- Polyfills --> <!-- Polyfills -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent"></script> <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent" crossorigin="anonymous"></script>
<!-- Plyr core script --> <!-- Plyr core script -->
<script src="../dist/plyr.js"></script> <script src="../dist/plyr.js" crossorigin="anonymous"></script>
<!-- Sharing libary (https://shr.one) --> <!-- Sharing libary (https://shr.one) -->
<script src="https://cdn.shr.one/1.0.1/shr.js"></script> <script src="https://cdn.shr.one/1.0.1/shr.js" crossorigin="anonymous"></script>
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) --> <!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
<script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async></script> <script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async crossorigin="anonymous"></script>
<!-- Docs script --> <!-- Docs script -->
<script src="dist/demo.js"></script> <script src="dist/demo.js" crossorigin="anonymous"></script>
</body> </body>
</html> </html>
+238 -208
View File
@@ -4,243 +4,273 @@
// Please see readme.md in the root or github.com/sampotts/plyr // Please see readme.md in the root or github.com/sampotts/plyr
// ========================================================================== // ==========================================================================
document.addEventListener('DOMContentLoaded', () => { import Raven from 'raven-js';
if (window.shr) {
window.shr.setup({ (() => {
count: { const isLive = window.location.host === 'plyr.io';
classname: 'button__count',
}, // Raven / Sentry
}); // For demo site (https://plyr.io) only
if (isLive) {
Raven.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
} }
// Setup tab focus document.addEventListener('DOMContentLoaded', () => {
const tabClassName = 'tab-focus'; Raven.context(() => {
if (window.shr) {
window.shr.setup({
count: {
classname: 'button__count',
},
});
}
// Remove class on blur // Setup tab focus
document.addEventListener('focusout', event => { const tabClassName = 'tab-focus';
event.target.classList.remove(tabClassName);
});
// Add classname to tabbed elements // Remove class on blur
document.addEventListener('keydown', event => { document.addEventListener('focusout', event => {
if (event.keyCode !== 9) { event.target.classList.remove(tabClassName);
return; });
}
// Delay the adding of classname until the focus has changed // Add classname to tabbed elements
// This event fires before the focusin event document.addEventListener('keydown', event => {
setTimeout(() => { if (event.keyCode !== 9) {
document.activeElement.classList.add(tabClassName); return;
}, 0); }
});
// Setup the player // Delay the adding of classname until the focus has changed
const player = new Plyr('#player', { // This event fires before the focusin event
debug: true, setTimeout(() => {
title: 'View From A Blue Moon', document.activeElement.classList.add(tabClassName);
iconUrl: '../dist/plyr.svg', }, 0);
keyboard: { });
global: true,
},
tooltips: {
controls: true,
},
captions: {
active: true,
},
keys: {
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
},
ads: {
enabled: true,
publisherId: '918848828995742',
},
});
// Expose for tinkering in the console // Setup the player
window.player = player; const player = new Plyr('#player', {
debug: true,
title: 'View From A Blue Moon',
iconUrl: '../dist/plyr.svg',
keyboard: {
global: true,
},
tooltips: {
controls: true,
},
/* controls: [
'play-large',
'restart',
'rewind',
'play',
'fast-forward',
'progress',
'current-time',
'mute',
'volume',
'captions',
'settings',
'pip',
'airplay',
'fullscreen',
], */
captions: {
active: true,
},
keys: {
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
},
ads: {
enabled: true,
publisherId: '918848828995742',
},
});
// Setup type toggle // Expose for tinkering in the console
const buttons = document.querySelectorAll('[data-source]'); window.player = player;
const types = {
video: 'video',
audio: 'audio',
youtube: 'youtube',
vimeo: 'vimeo',
};
let currentType = window.location.hash.replace('#', '');
const historySupport = window.history && window.history.pushState;
// Toggle class on an element // Setup type toggle
function toggleClass(element, className, state) { const buttons = document.querySelectorAll('[data-source]');
if (element) { const types = {
element.classList[state ? 'add' : 'remove'](className); video: 'video',
} audio: 'audio',
} youtube: 'youtube',
vimeo: 'vimeo',
};
let currentType = window.location.hash.replace('#', '');
const historySupport = window.history && window.history.pushState;
// Set a new source // Toggle class on an element
function newSource(type, init) { function toggleClass(element, className, state) {
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video if (element) {
if (!(type in types) || (!init && type === currentType) || (!currentType.length && type === types.video)) { element.classList[state ? 'add' : 'remove'](className);
return; }
} }
switch (type) { // Set a new source
case types.video: function newSource(type, init) {
player.source = { // Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
type: 'video', if (!(type in types) || (!init && type === currentType) || (!currentType.length && type === types.video)) {
title: 'View From A Blue Moon', return;
sources: [{ }
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4',
type: 'video/mp4',
}],
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
tracks: [
{
kind: 'captions',
label: 'English',
srclang: 'en',
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
default: true,
},
{
kind: 'captions',
label: 'French',
srclang: 'fr',
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
},
],
};
break; switch (type) {
case types.video:
player.source = {
type: 'video',
title: 'View From A Blue Moon',
sources: [{
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4',
type: 'video/mp4',
}],
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
tracks: [
{
kind: 'captions',
label: 'English',
srclang: 'en',
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
default: true,
},
{
kind: 'captions',
label: 'French',
srclang: 'fr',
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
},
],
};
case types.audio: break;
player.source = {
type: 'audio',
title: 'Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;',
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 types.audio:
player.source = {
type: 'audio',
title: 'Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;',
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',
},
],
};
case types.youtube: break;
player.source = {
type: 'video',
title: 'View From A Blue Moon',
sources: [{
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
provider: 'youtube',
}],
};
break; case types.youtube:
player.source = {
type: 'video',
title: 'View From A Blue Moon',
sources: [{
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
provider: 'youtube',
}],
};
case types.vimeo: break;
player.source = {
type: 'video',
sources: [{
src: 'https://vimeo.com/76979871',
provider: 'vimeo',
}],
};
break; case types.vimeo:
player.source = {
type: 'video',
sources: [{
src: 'https://vimeo.com/76979871',
provider: 'vimeo',
}],
};
default: break;
break;
}
// Set the current type for next time default:
currentType = type; break;
}
// Remove active classes // Set the current type for next time
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false)); currentType = type;
// Set active on parent // Remove active classes
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true); Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
// Show cite // Set active on parent
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => { toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
cite.setAttribute('hidden', '');
});
document.querySelector(`.plyr__cite--${type}`).removeAttribute('hidden');
}
// Bind to each button // Show cite
Array.from(buttons).forEach(button => { Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
button.addEventListener('click', () => { cite.setAttribute('hidden', '');
const type = button.getAttribute('data-source'); });
document.querySelector(`.plyr__cite--${type}`).removeAttribute('hidden');
}
newSource(type); // Bind to each button
Array.from(buttons).forEach(button => {
button.addEventListener('click', () => {
const type = button.getAttribute('data-source');
newSource(type);
if (historySupport) {
window.history.pushState({ type }, '', `#${type}`);
}
});
});
// List for backwards/forwards
window.addEventListener('popstate', event => {
if (event.state && 'type' in event.state) {
newSource(event.state.type);
}
});
// On load
if (historySupport) { if (historySupport) {
window.history.pushState({ type }, '', `#${type}`); const video = !currentType.length;
// If there's no current type set, assume video
if (video) {
currentType = types.video;
}
// Replace current history state
if (currentType in types) {
window.history.replaceState(
{
type: currentType,
},
'',
video ? '' : `#${currentType}`,
);
}
// If it's not video, load the source
if (currentType !== types.video) {
newSource(currentType, true);
}
} }
}); });
}); });
// List for backwards/forwards // Google analytics
window.addEventListener('popstate', event => { // For demo site (https://plyr.io) only
if (event.state && 'type' in event.state) { /* eslint-disable */
newSource(event.state.type); if (isLive) {
} (function(i, s, o, g, r, a, m) {
}); i.GoogleAnalyticsObject = r;
i[r] =
// On load i[r] ||
if (historySupport) { function() {
const video = !currentType.length; (i[r].q = i[r].q || []).push(arguments);
};
// If there's no current type set, assume video i[r].l = 1 * new Date();
if (video) { a = s.createElement(o);
currentType = types.video; m = s.getElementsByTagName(o)[0];
} a.async = 1;
a.src = g;
// Replace current history state m.parentNode.insertBefore(a, m);
if (currentType in types) { })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
window.history.replaceState( window.ga('create', 'UA-40881672-11', 'auto');
{ window.ga('send', 'pageview');
type: currentType,
},
'',
video ? '' : `#${currentType}`,
);
}
// If it's not video, load the source
if (currentType !== types.video) {
newSource(currentType, true);
}
} }
}); /* eslint-enable */
})();
// Google analytics
// For demo site (https://plyr.io) only
/* eslint-disable */
if (window.location.host === 'plyr.io') {
(function(i, s, o, g, r, a, m) {
i.GoogleAnalyticsObject = r;
i[r] =
i[r] ||
function() {
(i[r].q = i[r].q || []).push(arguments);
};
i[r].l = 1 * new Date();
a = s.createElement(o);
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
window.ga('create', 'UA-40881672-11', 'auto');
window.ga('send', 'pageview');
}
/* eslint-enable */
+1 -1
View File
File diff suppressed because one or more lines are too long
+702 -272
View File
File diff suppressed because it is too large Load Diff
+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
+693 -273
View File
File diff suppressed because it is too large Load Diff
+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
+23 -18
View File
@@ -70,10 +70,11 @@ const paths = {
root: path.join(root, 'demo/'), root: path.join(root, 'demo/'),
}, },
upload: [ upload: [
path.join(root, `dist/*${minSuffix}.js`), path.join(root, `dist/*${minSuffix}.*`),
path.join(root, 'dist/*.css'), path.join(root, 'dist/*.css'),
path.join(root, 'dist/*.svg'), path.join(root, 'dist/*.svg'),
path.join(root, 'demo/dist/**'), path.join(root, `demo/dist/*${minSuffix}.*`),
path.join(root, 'demo/dist/*.css'),
], ],
}; };
@@ -303,22 +304,26 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
console.log(`Uploading '${version}' to ${aws.cdn.domain}...`); console.log(`Uploading '${version}' to ${aws.cdn.domain}...`);
// Upload to CDN // Upload to CDN
return gulp return (
.src(paths.upload) gulp
.pipe( .src(paths.upload)
rename(p => { .pipe(
p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line rename(p => {
p.dirname = p.dirname.replace('.', version); // eslint-disable-line p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line
}), p.dirname = p.dirname.replace('.', version); // eslint-disable-line
) }),
.pipe( )
size({ // Remove min suffix from source map URL
showFiles: true, .pipe(replace(/sourceMappingURL=([\w-?.]+)/, (match, p1) => `sourceMappingURL=${p1.replace(minSuffix, '')}`))
gzip: true, .pipe(
}), size({
) showFiles: true,
.pipe(replace(localPath, versionPath)) gzip: true,
.pipe(s3(aws.cdn, options.cdn)); }),
)
.pipe(replace(localPath, versionPath))
.pipe(s3(aws.cdn, options.cdn))
);
}); });
// Publish to demo bucket // Publish to demo bucket
+7 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "plyr", "name": "plyr",
"version": "3.0.0", "version": "3.0.9",
"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",
@@ -13,7 +13,7 @@
"babel-plugin-external-helpers": "^6.22.0", "babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"del": "^3.0.0", "del": "^3.0.0",
"eslint": "^4.18.2", "eslint": "^4.19.0",
"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.9.0",
@@ -35,6 +35,8 @@
"gulp-svgstore": "^6.1.1", "gulp-svgstore": "^6.1.1",
"gulp-uglify-es": "^1.0.1", "gulp-uglify-es": "^1.0.1",
"gulp-util": "^3.0.8", "gulp-util": "^3.0.8",
"prettier-eslint": "^8.8.1",
"prettier-stylelint": "^0.4.2",
"rollup-plugin-babel": "^3.0.3", "rollup-plugin-babel": "^3.0.3",
"rollup-plugin-commonjs": "^8.4.1", "rollup-plugin-commonjs": "^8.4.1",
"rollup-plugin-node-resolve": "^3.2.0", "rollup-plugin-node-resolve": "^3.2.0",
@@ -65,6 +67,8 @@
"author": "Sam Potts <sam@potts.es>", "author": "Sam Potts <sam@potts.es>",
"dependencies": { "dependencies": {
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"custom-event-polyfill": "^0.3.0" "custom-event-polyfill": "^0.3.0",
"loadjs": "^3.5.2",
"raven-js": "^3.23.3"
} }
} }
+13 -9
View File
@@ -2,15 +2,15 @@
A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers. A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers.
[Checkout the demo](https://plyr.io) - [Donate to support Plyr](#donate) - [Chat on Slack](https://bit.ly/plyr-slack) [Checkout the demo](https://plyr.io) - [Donate to support Plyr](#donate) - [Chat on Slack](https://bit.ly/plyr-chat)
[![Image of Plyr](https://cdn.plyr.io/static/demo/screenshot.png)](https://plyr.io) [![Image of Plyr](https://cdn.plyr.io/static/demo/screenshot.png?v=3)](https://plyr.io)
## Features ## Features
* **Accessible** - full support for VTT captions and screen readers * **Accessible** - full support for VTT captions and screen readers
* **[Customisable](#html)** - make the player look how you want with the markup you want * **[Customisable](#html)** - make the player look how you want with the markup you want
* **Semantic** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no * **Good HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
`<span>` or `<a href="#">` button hacks `<span>` or `<a href="#">` button hacks
* **Responsive** - works with any screen size * **Responsive** - works with any screen size
* **HTML Video & Audio** - support for both formats * **HTML Video & Audio** - support for both formats
@@ -21,6 +21,10 @@ A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo medi
* **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats * **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
* **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes * **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes
* **[Shortcuts](#shortcuts)** - supports keyboard shortcuts * **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
* **Picture-in-Picture** - supports Safari's picture-in-picture mode
* **Playsinline** - supports the `playsinline` attribute
* **Speed controls** - adjust speed on the fly
* **Multiple captions** - support for multiple caption tracks
* **i18n support** - support for internationalization of controls * **i18n support** - support for internationalization of controls
* **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required * **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
* **SASS** - to include in your build processes * **SASS** - to include in your build processes
@@ -124,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.0/plyr.js"></script> <script src="https://cdn.plyr.io/3.0.9/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
@@ -140,17 +144,17 @@ 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.0/plyr.css"> <link rel="stylesheet" href="https://cdn.plyr.io/3.0.9/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.0/plyr.svg`. reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.9/plyr.svg`.
## Ads ## Ads
Plyr has partnered up with [ai.vi](http://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy: Plyr has partnered up with [vi.ai](http://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy:
* [Sign up for a vi.ai account](http://vi.ai/publisher-video-monetization/?aid=plyrio) * [Sign up for a vi.ai account](http://vi.ai/publisher-video-monetization/?aid=plyrio)
* Grab your publisher ID from the code snippet * Grab your publisher ID from the code snippet
@@ -236,7 +240,7 @@ The NodeList, HTMLElement or string selector can be the target `<video>`, `<audi
const players = Array.from(document.querySelectorAll('.js-player')).map(player => new Plyr(player)); const players = Array.from(document.querySelectorAll('.js-player')).map(player => new Plyr(player));
``` ```
The second argument for the constructor is the [#options](options) object: The second argument for the constructor is the [options](#options) object:
```javascript ```javascript
const player = new Plyr('#player', { const player = new Plyr('#player', {
@@ -703,7 +707,7 @@ Credit to the PayPal HTML5 Video player from which Plyr's caption functionality
## Thanks ## Thanks
[![Fastly](https://www.fastly.com/sites/all/themes/custom/fastly2016/logo.png)](https://www.fastly.com/) [![Fastly](https://cdn.plyr.io/static/demo/fastly-logo.png)](https://www.fastly.com/)
Massive thanks to [Fastly](https://www.fastly.com/) for providing the CDN services. Massive thanks to [Fastly](https://www.fastly.com/) for providing the CDN services.
+51 -28
View File
@@ -5,6 +5,7 @@
import support from './support'; import support from './support';
import utils from './utils'; import utils from './utils';
import ui from './ui'; import ui from './ui';
import i18n from './i18n';
import captions from './captions'; import captions from './captions';
// Sniff out the browser // Sniff out the browser
@@ -74,7 +75,7 @@ const controls = {
// Create hidden text label // Create hidden text label
createLabel(type, attr) { createLabel(type, attr) {
let text = this.config.i18n[type]; let text = i18n.get(type, this.config);
const attributes = Object.assign({}, attr); const attributes = Object.assign({}, attr);
switch (type) { switch (type) {
@@ -126,7 +127,7 @@ const controls = {
createButton(buttonType, attr) { createButton(buttonType, attr) {
const button = utils.createElement('button'); const button = utils.createElement('button');
const attributes = Object.assign({}, attr); const attributes = Object.assign({}, attr);
let type = buttonType; let type = utils.toCamelCase(buttonType);
let toggle = false; let toggle = false;
let label; let label;
@@ -147,7 +148,7 @@ const controls = {
} }
// Large play button // Large play button
switch (type) { switch (buttonType) {
case 'play': case 'play':
toggle = true; toggle = true;
label = 'play'; label = 'play';
@@ -189,7 +190,7 @@ const controls = {
default: default:
label = type; label = type;
icon = type; icon = buttonType;
} }
// Setup toggle icon and labels // Setup toggle icon and labels
@@ -204,7 +205,7 @@ const controls = {
// Add aria attributes // Add aria attributes
attributes['aria-pressed'] = false; attributes['aria-pressed'] = false;
attributes['aria-label'] = this.config.i18n[label]; attributes['aria-label'] = i18n.get(label, this.config);
} else { } else {
button.appendChild(controls.createIcon.call(this, icon)); button.appendChild(controls.createIcon.call(this, icon));
button.appendChild(controls.createLabel.call(this, label)); button.appendChild(controls.createLabel.call(this, label));
@@ -238,7 +239,7 @@ const controls = {
for: attributes.id, for: attributes.id,
class: this.config.classNames.hidden, class: this.config.classNames.hidden,
}, },
this.config.i18n[type], i18n.get(type, this.config),
); );
// Seek input // Seek input
@@ -291,11 +292,11 @@ const controls = {
let suffix = ''; let suffix = '';
switch (type) { switch (type) {
case 'played': case 'played':
suffix = this.config.i18n.played; suffix = i18n.get('played', this.config);
break; break;
case 'buffer': case 'buffer':
suffix = this.config.i18n.buffered; suffix = i18n.get('buffered', this.config);
break; break;
default: default:
@@ -322,7 +323,7 @@ const controls = {
{ {
class: this.config.classNames.hidden, class: this.config.classNames.hidden,
}, },
this.config.i18n[type], i18n.get(type, this.config),
), ),
); );
@@ -383,6 +384,16 @@ const controls = {
const clientRect = this.elements.inputs.seek.getBoundingClientRect(); const clientRect = this.elements.inputs.seek.getBoundingClientRect();
const visible = `${this.config.classNames.tooltip}--visible`; const visible = `${this.config.classNames.tooltip}--visible`;
const toggle = toggle => {
utils.toggleClass(this.elements.display.seekTooltip, visible, toggle);
};
// Hide on touch
if (this.touch) {
toggle(false);
return;
}
// Determine percentage, if already visible // Determine percentage, if already visible
if (utils.is.event(event)) { if (utils.is.event(event)) {
percent = 100 / clientRect.width * (event.pageX - clientRect.left); percent = 100 / clientRect.width * (event.pageX - clientRect.left);
@@ -411,7 +422,7 @@ const controls = {
'mouseenter', 'mouseenter',
'mouseleave', 'mouseleave',
].includes(event.type)) { ].includes(event.type)) {
utils.toggleClass(this.elements.display.seekTooltip, visible, event.type === 'mouseenter'); toggle(event.type === 'mouseenter');
} }
}, },
@@ -540,7 +551,7 @@ const controls = {
switch (setting) { switch (setting) {
case 'captions': case 'captions':
value = this.captions.active ? this.captions.language : ''; value = this.captions.active ? this.captions.language : i18n.get('disabled', this.config);
break; break;
default: default:
@@ -617,7 +628,7 @@ const controls = {
class: this.config.classNames.control, class: this.config.classNames.control,
'data-plyr-loop-action': option, 'data-plyr-loop-action': option,
}), }),
this.config.i18n[option] i18n.get(option, this.config)
); );
if (['start', 'end'].includes(option)) { if (['start', 'end'].includes(option)) {
@@ -637,11 +648,7 @@ const controls = {
return null; return null;
} }
if (!support.textTracks || !captions.getTracks.call(this).length) { if (support.textTracks && captions.getTracks.call(this).length && this.captions.active) {
return this.config.i18n.none;
}
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)) {
@@ -649,7 +656,7 @@ const controls = {
} }
} }
return this.config.i18n.disabled; return i18n.get('disabled', this.config);
}, },
// Set a list of available captions languages // Set a list of available captions languages
@@ -676,10 +683,10 @@ const controls = {
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(), label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
})); }));
// Add the "None" option to turn off captions // Add the "Disabled" option to turn off captions
tracks.unshift({ tracks.unshift({
language: '', language: '',
label: this.config.i18n.none, label: i18n.get('disabled', this.config),
}); });
// Generate options // Generate options
@@ -699,7 +706,12 @@ const controls = {
}, },
// Set a list of available captions languages // Set a list of available captions languages
setSpeedMenu() { setSpeedMenu(options) {
// Do nothing if not selected
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
return;
}
// Menu required // Menu required
if (!utils.is.element(this.elements.settings.panes.speed)) { if (!utils.is.element(this.elements.settings.panes.speed)) {
return; return;
@@ -707,8 +719,8 @@ const controls = {
const type = 'speed'; const type = 'speed';
// Set the default speeds // Set the speed options
if (!utils.is.object(this.options.speed) || !Object.keys(this.options.speed).length) { if (!utils.is.array(options)) {
this.options.speed = [ this.options.speed = [
0.5, 0.5,
0.75, 0.75,
@@ -718,6 +730,8 @@ 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
@@ -727,6 +741,9 @@ const controls = {
const toggle = !utils.is.empty(this.options.speed); const toggle = !utils.is.empty(this.options.speed);
controls.toggleTab.call(this, type, toggle); controls.toggleTab.call(this, type, toggle);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If we're hiding, nothing more to do // If we're hiding, nothing more to do
if (!toggle) { if (!toggle) {
return; return;
@@ -748,6 +765,14 @@ const controls = {
controls.updateSetting.call(this, type, list); controls.updateSetting.call(this, type, list);
}, },
// Check if we need to hide/show the settings menu
checkMenu() {
const speedHidden = this.elements.settings.tabs.speed.getAttribute('hidden') !== null;
const languageHidden = this.elements.settings.tabs.captions.getAttribute('hidden') !== null;
utils.toggleHidden(this.elements.settings.menu, speedHidden && languageHidden);
},
// Show/hide menu // Show/hide menu
toggleMenu(event) { toggleMenu(event) {
const { form } = this.elements.settings; const { form } = this.elements.settings;
@@ -1069,7 +1094,7 @@ const controls = {
'aria-controls': `plyr-settings-${data.id}-${type}`, 'aria-controls': `plyr-settings-${data.id}-${type}`,
'aria-expanded': false, 'aria-expanded': false,
}), }),
this.config.i18n[type], i18n.get(type, this.config),
); );
const value = utils.createElement('span', { const value = utils.createElement('span', {
@@ -1109,7 +1134,7 @@ const controls = {
'aria-controls': `plyr-settings-${data.id}-home`, 'aria-controls': `plyr-settings-${data.id}-home`,
'aria-expanded': false, 'aria-expanded': false,
}, },
this.config.i18n[type], i18n.get(type, this.config),
); );
pane.appendChild(back); pane.appendChild(back);
@@ -1152,9 +1177,7 @@ const controls = {
this.elements.controls = container; this.elements.controls = container;
if (this.config.controls.includes('settings') && this.config.settings.includes('speed')) { controls.setSpeedMenu.call(this);
controls.setSpeedMenu.call(this);
}
return container; return container;
}, },
+7 -5
View File
@@ -56,7 +56,7 @@ const defaults = {
// Sprite (for icons) // Sprite (for icons)
loadSprite: true, loadSprite: true,
iconPrefix: 'plyr', iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.0.0/plyr.svg', iconUrl: 'https://cdn.plyr.io/3.0.9/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',
@@ -132,7 +132,10 @@ const defaults = {
// Default controls // Default controls
controls: [ controls: [
'play-large', 'play-large',
// 'restart',
// 'rewind',
'play', 'play',
// 'fast-forward',
'progress', 'progress',
'current-time', 'current-time',
'mute', 'mute',
@@ -155,7 +158,7 @@ const defaults = {
rewind: 'Rewind {seektime} secs', rewind: 'Rewind {seektime} secs',
play: 'Play', play: 'Play',
pause: 'Pause', pause: 'Pause',
forward: 'Forward {seektime} secs', fastForward: 'Forward {seektime} secs',
seek: 'Seek', seek: 'Seek',
played: 'Played', played: 'Played',
buffered: 'Buffered', buffered: 'Buffered',
@@ -178,7 +181,6 @@ const defaults = {
end: 'End', end: 'End',
all: 'All', all: 'All',
reset: 'Reset', reset: 'Reset',
none: 'None',
disabled: 'Disabled', disabled: 'Disabled',
advertisement: 'Ad', advertisement: 'Ad',
}, },
@@ -203,7 +205,7 @@ const defaults = {
pause: null, pause: null,
restart: null, restart: null,
rewind: null, rewind: null,
forward: null, fastForward: null,
mute: null, mute: null,
volume: null, volume: null,
captions: null, captions: null,
@@ -283,7 +285,7 @@ const defaults = {
pause: '[data-plyr="pause"]', pause: '[data-plyr="pause"]',
restart: '[data-plyr="restart"]', restart: '[data-plyr="restart"]',
rewind: '[data-plyr="rewind"]', rewind: '[data-plyr="rewind"]',
forward: '[data-plyr="fast-forward"]', fastForward: '[data-plyr="fast-forward"]',
mute: '[data-plyr="mute"]', mute: '[data-plyr="mute"]',
captions: '[data-plyr="captions"]', captions: '[data-plyr="captions"]',
fullscreen: '[data-plyr="fullscreen"]', fullscreen: '[data-plyr="fullscreen"]',
+19 -12
View File
@@ -1,5 +1,6 @@
// ========================================================================== // ==========================================================================
// Fullscreen wrapper // Fullscreen wrapper
// https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API#prefixing
// ========================================================================== // ==========================================================================
import utils from './utils'; import utils from './utils';
@@ -54,6 +55,7 @@ class Fullscreen {
// Get prefix // Get prefix
this.prefix = Fullscreen.prefix; this.prefix = Fullscreen.prefix;
this.name = Fullscreen.name;
// Scroll position // Scroll position
this.scrollPosition = { x: 0, y: 0 }; this.scrollPosition = { x: 0, y: 0 };
@@ -85,7 +87,7 @@ class Fullscreen {
// Get the prefix for handlers // Get the prefix for handlers
static get prefix() { static get prefix() {
// No prefix // No prefix
if (utils.is.function(document.cancelFullScreen)) { if (utils.is.function(document.exitFullscreen)) {
return false; return false;
} }
@@ -98,12 +100,9 @@ class Fullscreen {
]; ];
prefixes.some(pre => { prefixes.some(pre => {
if (utils.is.function(document[`${pre}CancelFullScreen`])) { if (utils.is.function(document[`${pre}ExitFullscreen`]) || utils.is.function(document[`${pre}CancelFullScreen`])) {
value = pre; value = pre;
return true; return true;
} else if (utils.is.function(document.msExitFullscreen)) {
value = 'ms';
return true;
} }
return false; return false;
@@ -112,11 +111,18 @@ class Fullscreen {
return value; return value;
} }
static get name() {
return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
}
// Determine if fullscreen is enabled // Determine if fullscreen is enabled
get enabled() { get enabled() {
const fallback = this.player.config.fullscreen.fallback && !utils.inFrame(); return (
(Fullscreen.native || this.player.config.fullscreen.fallback) &&
return (Fullscreen.native || fallback) && this.player.config.fullscreen.enabled && this.player.supported.ui && this.player.isVideo; this.player.config.fullscreen.enabled &&
this.player.supported.ui &&
this.player.isVideo
);
} }
// Get active state // Get active state
@@ -130,7 +136,7 @@ class Fullscreen {
return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback); return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
} }
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}FullscreenElement`]; const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.name}Element`];
return element === this.target; return element === this.target;
} }
@@ -166,9 +172,9 @@ class Fullscreen {
} else if (!Fullscreen.native) { } else if (!Fullscreen.native) {
toggleFallback.call(this, true); toggleFallback.call(this, true);
} else if (!this.prefix) { } else if (!this.prefix) {
this.target.requestFullScreen(); this.target.requestFullscreen();
} else if (!utils.is.empty(this.prefix)) { } else if (!utils.is.empty(this.prefix)) {
this.target[`${this.prefix}${this.prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen'}`](); this.target[`${this.prefix}Request${this.name}`]();
} }
} }
@@ -187,7 +193,8 @@ class Fullscreen {
} else if (!this.prefix) { } else if (!this.prefix) {
document.cancelFullScreen(); document.cancelFullScreen();
} else if (!utils.is.empty(this.prefix)) { } else if (!utils.is.empty(this.prefix)) {
document[`${this.prefix}${this.prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen'}`](); const action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
document[`${this.prefix}${action}${this.name}`]();
} }
} }
+31
View File
@@ -0,0 +1,31 @@
// ==========================================================================
// Plyr internationalization
// ==========================================================================
import utils from './utils';
const i18n = {
get(key = '', config = {}) {
if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
return '';
}
let string = config.i18n[key];
const replace = {
'{seektime}': config.seekTime,
'{title}': config.title,
};
Object.entries(replace).forEach(([
key,
value,
]) => {
string = utils.replaceAll(string, key, value);
});
return string;
},
};
export default i18n;
+130 -102
View File
@@ -17,6 +17,7 @@ class Listeners {
this.handleKey = this.handleKey.bind(this); this.handleKey = this.handleKey.bind(this);
this.toggleMenu = this.toggleMenu.bind(this); this.toggleMenu = this.toggleMenu.bind(this);
this.firstTouch = this.firstTouch.bind(this);
} }
// Handle key presses // Handle key presses
@@ -187,6 +188,17 @@ class Listeners {
controls.toggleMenu.call(this.player, event); controls.toggleMenu.call(this.player, event);
} }
// Device is touch enabled
firstTouch() {
this.player.touch = true;
// Add touch class
utils.toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true);
// Clean up
utils.off(document.body, 'touchstart', this.firstTouch);
}
// Global window & document listeners // Global window & document listeners
global(toggle = true) { global(toggle = true) {
// Keyboard shortcuts // Keyboard shortcuts
@@ -196,6 +208,9 @@ class Listeners {
// Click anywhere closes menu // Click anywhere closes menu
utils.toggleListener(document.body, 'click', this.toggleMenu, toggle); utils.toggleListener(document.body, 'click', this.toggleMenu, toggle);
// Detect touch by events
utils.on(document.body, 'touchstart', this.firstTouch);
} }
// Container listeners // Container listeners
@@ -267,7 +282,7 @@ class Listeners {
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 native play/pause
utils.on(this.player.media, 'playing play pause ended', 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
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));
@@ -288,7 +303,7 @@ class Listeners {
// On click play, pause ore restart // On click play, pause ore restart
utils.on(wrapper, 'click', () => { utils.on(wrapper, 'click', () => {
// Touch devices will just show controls (if we're hiding controls) // Touch devices will just show controls (if we're hiding controls)
if (this.player.config.hideControls && support.touch && !this.player.paused) { if (this.player.config.hideControls && this.player.touch && !this.player.paused) {
return; return;
} }
@@ -379,122 +394,132 @@ class Listeners {
// IE doesn't support input event, so we fallback to change // IE doesn't support input event, so we fallback to change
const inputEvent = browser.isIE ? 'change' : 'input'; const inputEvent = browser.isIE ? 'change' : 'input';
// Trigger custom and default handlers // Run default and custom handlers
const proxy = (event, handlerKey, defaultHandler) => { const proxy = (event, defaultHandler, customHandlerKey) => {
const customHandler = this.player.config.listeners[handlerKey]; const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = utils.is.function(customHandler);
let returned = true;
// Execute custom handler // Execute custom handler
if (utils.is.function(customHandler)) { if (hasCustomHandler) {
customHandler.call(this.player, event); returned = customHandler.call(this.player, event);
} }
// Only call default handler if not prevented in custom handler // Only call default handler if not prevented in custom handler
if (!event.defaultPrevented && utils.is.function(defaultHandler)) { if (returned && utils.is.function(defaultHandler)) {
defaultHandler.call(this.player, event); defaultHandler.call(this.player, event);
} }
}; };
// Trigger custom and default handlers
const on = (element, type, defaultHandler, customHandlerKey, passive = true) => {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = utils.is.function(customHandler);
utils.on(element, type, event => proxy(event, defaultHandler, customHandlerKey), passive && !hasCustomHandler);
};
// Play/pause toggle // Play/pause toggle
utils.on(this.player.elements.buttons.play, 'click', event => on(this.player.elements.buttons.play, 'click', this.player.togglePlay, 'play');
proxy(event, 'play', () => {
this.player.togglePlay();
}),
);
// Pause // Pause
utils.on(this.player.elements.buttons.restart, 'click', event => on(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
proxy(event, 'restart', () => {
this.player.restart();
}),
);
// Rewind // Rewind
utils.on(this.player.elements.buttons.rewind, 'click', event => on(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
proxy(event, 'rewind', () => {
this.player.rewind();
}),
);
// Rewind // Rewind
utils.on(this.player.elements.buttons.forward, 'click', event => on(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
proxy(event, 'forward', () => {
this.player.forward();
}),
);
// Mute toggle // Mute toggle
utils.on(this.player.elements.buttons.mute, 'click', event => on(
proxy(event, 'mute', () => { this.player.elements.buttons.mute,
'click',
() => {
this.player.muted = !this.player.muted; this.player.muted = !this.player.muted;
}), },
'mute',
); );
// Captions toggle // Captions toggle
utils.on(this.player.elements.buttons.captions, 'click', event => on(this.player.elements.buttons.captions, 'click', this.player.toggleCaptions);
proxy(event, 'captions', () => {
this.player.toggleCaptions();
}),
);
// Fullscreen toggle // Fullscreen toggle
utils.on(this.player.elements.buttons.fullscreen, 'click', event => on(
proxy(event, 'fullscreen', () => { this.player.elements.buttons.fullscreen,
'click',
() => {
this.player.fullscreen.toggle(); this.player.fullscreen.toggle();
}), },
'fullscreen',
); );
// Picture-in-Picture // Picture-in-Picture
utils.on(this.player.elements.buttons.pip, 'click', event => on(
proxy(event, 'pip', () => { this.player.elements.buttons.pip,
'click',
() => {
this.player.pip = 'toggle'; this.player.pip = 'toggle';
}), },
'pip',
); );
// Airplay // Airplay
utils.on(this.player.elements.buttons.airplay, 'click', event => on(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
proxy(event, 'airplay', () => {
this.player.airplay();
}),
);
// Settings menu // Settings menu
utils.on(this.player.elements.buttons.settings, 'click', event => { on(this.player.elements.buttons.settings, 'click', event => {
controls.toggleMenu.call(this.player, event); controls.toggleMenu.call(this.player, event);
}); });
// Settings menu // Settings menu
utils.on(this.player.elements.settings.form, 'click', event => { on(this.player.elements.settings.form, 'click', event => {
event.stopPropagation(); event.stopPropagation();
// Settings menu items - use event delegation as items are added/removed // Settings menu items - use event delegation as items are added/removed
if (utils.matches(event.target, this.player.config.selectors.inputs.language)) { if (utils.matches(event.target, this.player.config.selectors.inputs.language)) {
proxy(event, 'language', () => { proxy(
this.player.language = event.target.value; event,
}); () => {
this.player.language = event.target.value;
},
'language',
);
} else if (utils.matches(event.target, this.player.config.selectors.inputs.quality)) { } else if (utils.matches(event.target, this.player.config.selectors.inputs.quality)) {
proxy(event, 'quality', () => { proxy(
this.player.quality = event.target.value; event,
}); () => {
this.player.quality = event.target.value;
},
'quality',
);
} else if (utils.matches(event.target, this.player.config.selectors.inputs.speed)) { } else if (utils.matches(event.target, this.player.config.selectors.inputs.speed)) {
proxy(event, 'speed', () => { proxy(
this.player.speed = parseFloat(event.target.value); event,
}); () => {
this.player.speed = parseFloat(event.target.value);
},
'speed',
);
} else { } else {
controls.showTab.call(this.player, event); controls.showTab.call(this.player, event);
} }
}); });
// Seek // Seek
utils.on(this.player.elements.inputs.seek, inputEvent, event => on(
proxy(event, 'seek', () => { this.player.elements.inputs.seek,
inputEvent,
event => {
this.player.currentTime = event.target.value / event.target.max * this.player.duration; this.player.currentTime = event.target.value / event.target.max * this.player.duration;
}), },
'seek',
); );
// 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.player.config.toggleInvert && !utils.is.element(this.player.elements.display.duration)) { if (this.player.config.toggleInvert && !utils.is.element(this.player.elements.display.duration)) {
utils.on(this.player.elements.display.currentTime, 'click', () => { on(this.player.elements.display.currentTime, 'click', () => {
// Do nothing if we're at the start // Do nothing if we're at the start
if (this.player.currentTime === 0) { if (this.player.currentTime === 0) {
return; return;
@@ -506,31 +531,34 @@ class Listeners {
} }
// Volume // Volume
utils.on(this.player.elements.inputs.volume, inputEvent, event => on(
proxy(event, 'volume', () => { this.player.elements.inputs.volume,
inputEvent,
event => {
this.player.volume = event.target.value; this.player.volume = event.target.value;
}), },
'volume',
); );
// Polyfill for lower fill in <input type="range"> for webkit // Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) { if (browser.isWebkit) {
utils.on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => { on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => {
controls.updateRangeFill.call(this.player, event.target); controls.updateRangeFill.call(this.player, event.target);
}); });
} }
// Seek tooltip // Seek tooltip
utils.on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event)); on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event));
// Toggle controls visibility based on mouse movement // Toggle controls visibility based on mouse movement
if (this.player.config.hideControls) { if (this.player.config.hideControls) {
// Watch for cursor over controls so they don't hide when trying to interact // Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.player.elements.controls, 'mouseenter mouseleave', event => { on(this.player.elements.controls, 'mouseenter mouseleave', event => {
this.player.elements.controls.hover = event.type === 'mouseenter'; this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
}); });
// Watch for cursor over controls so they don't hide when trying to interact // Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => { on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
this.player.elements.controls.pressed = [ this.player.elements.controls.pressed = [
'mousedown', 'mousedown',
'touchstart', 'touchstart',
@@ -538,50 +566,50 @@ class Listeners {
}); });
// Focus in/out on controls // Focus in/out on controls
utils.on(this.player.elements.controls, 'focusin focusout', event => { on(this.player.elements.controls, 'focusin focusout', event => {
this.player.toggleControls(event); this.player.toggleControls(event);
}); });
} }
// Mouse wheel for volume // Mouse wheel for volume
utils.on( on(
this.player.elements.inputs.volume, this.player.elements.inputs.volume,
'wheel', 'wheel',
event => event => {
proxy(event, 'volume', () => { // Detect "natural" scroll - suppored on OS X Safari only
// Detect "natural" scroll - suppored on OS X Safari only // Other browsers on OS X will be inverted until support improves
// Other browsers on OS X will be inverted until support improves const inverted = event.webkitDirectionInvertedFromDevice;
const inverted = event.webkitDirectionInvertedFromDevice; const step = 1 / 50;
const step = 1 / 50; let direction = 0;
let direction = 0;
// Scroll down (or up on natural) to decrease // Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) { if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) { if (inverted) {
this.player.decreaseVolume(step); this.player.decreaseVolume(step);
direction = -1; direction = -1;
} else { } else {
this.player.increaseVolume(step); this.player.increaseVolume(step);
direction = 1; direction = 1;
}
} }
}
// Scroll up (or down on natural) to increase // Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) { if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) { if (inverted) {
this.player.increaseVolume(step); this.player.increaseVolume(step);
direction = 1; direction = 1;
} else { } else {
this.player.decreaseVolume(step); this.player.decreaseVolume(step);
direction = -1; direction = -1;
}
} }
}
// Don't break page scrolling at max and min // Don't break page scrolling at max and min
if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) { if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) {
event.preventDefault(); event.preventDefault();
} }
}), },
'volume',
false, false,
); );
} }
+1 -1
View File
@@ -46,7 +46,7 @@ const media = {
utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos); utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
// Add touch class // Add touch class
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, support.touch); utils.toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
} }
// Inject the player wrapper // Inject the player wrapper
+2 -1
View File
@@ -7,6 +7,7 @@
/* global google */ /* global google */
import utils from '../utils'; import utils from '../utils';
import i18n from '../i18n';
class Ads { class Ads {
/** /**
@@ -178,7 +179,7 @@ class Ads {
const update = () => { const update = () => {
const time = utils.formatTime(Math.max(this.manager.getRemainingTime(), 0)); const time = utils.formatTime(Math.max(this.manager.getRemainingTime(), 0));
const label = `${this.player.config.i18n.advertisement} - ${time}`; const label = `${i18n.get('advertisement', this.player.config)} - ${time}`;
this.elements.container.setAttribute('data-badge-text', label); this.elements.container.setAttribute('data-badge-text', label);
}; };
+25 -12
View File
@@ -4,6 +4,7 @@
import utils from './../utils'; import utils from './../utils';
import captions from './../captions'; import captions from './../captions';
import controls from './../controls';
import ui from './../ui'; import ui from './../ui';
const vimeo = { const vimeo = {
@@ -34,7 +35,7 @@ const vimeo = {
setAspectRatio(input) { setAspectRatio(input) {
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':'); const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
const padding = 100 / ratio[0] * ratio[1]; const padding = 100 / ratio[0] * ratio[1];
const height = 200; const height = 240;
const offset = (height - padding) / (height / 50); const offset = (height - padding) / (height / 50);
this.elements.wrapper.style.paddingBottom = `${padding}%`; this.elements.wrapper.style.paddingBottom = `${padding}%`;
this.media.style.transform = `translateY(-${offset}%)`; this.media.style.transform = `translateY(-${offset}%)`;
@@ -101,10 +102,8 @@ const vimeo = {
}; };
player.media.stop = () => { player.media.stop = () => {
player.embed.stop().then(() => { player.pause();
player.media.paused = true; player.currentTime = 0;
player.currentTime = 0;
});
}; };
// Seeking // Seeking
@@ -141,10 +140,18 @@ const vimeo = {
return speed; return speed;
}, },
set(input) { set(input) {
player.embed.setPlaybackRate(input).then(() => { player.embed
speed = input; .setPlaybackRate(input)
utils.dispatchEvent.call(player, player.media, 'ratechange'); .then(() => {
}); speed = input;
utils.dispatchEvent.call(player, player.media, 'ratechange');
})
.catch(error => {
// Hide menu item (and menu if empty)
if (error.name === 'Error') {
controls.setSpeedMenu.call(player, []);
}
});
}, },
}); });
@@ -195,9 +202,15 @@ const vimeo = {
// Source // Source
let currentSrc; let currentSrc;
player.embed.getVideoUrl().then(value => { player.embed
currentSrc = value; .getVideoUrl()
}); .then(value => {
currentSrc = value;
})
.catch(error => {
this.debug.warn(error);
});
Object.defineProperty(player.media, 'currentSrc', { Object.defineProperty(player.media, 'currentSrc', {
get() { get() {
return currentSrc; return currentSrc;
+12 -1
View File
@@ -294,7 +294,8 @@ const youtube = {
}); });
// Get available speeds // Get available speeds
player.options.speed = instance.getAvailablePlaybackRates(); const options = 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) {
@@ -347,6 +348,16 @@ const youtube = {
// 3 Buffering // 3 Buffering
// 5 Video cued // 5 Video cued
switch (event.data) { switch (event.data) {
case -1:
// Update scrubber
utils.dispatchEvent.call(player, player.media, 'timeupdate');
// Get loaded % from YouTube
player.media.buffered = instance.getVideoLoadedFraction();
utils.dispatchEvent.call(player, player.media, 'progress');
break;
case 0: case 0:
player.media.paused = true; player.media.paused = true;
+35 -19
View File
@@ -1,6 +1,6 @@
// ========================================================================== // ==========================================================================
// Plyr // Plyr
// plyr.js v3.0.0 // plyr.js v3.0.9
// https://github.com/sampotts/plyr // https://github.com/sampotts/plyr
// License: The MIT License (MIT) // License: The MIT License (MIT)
// ========================================================================== // ==========================================================================
@@ -36,6 +36,9 @@ class Plyr {
this.loading = false; this.loading = false;
this.failed = false; this.failed = false;
// Touch device
this.touch = support.touch;
// Set the media element // Set the media element
this.media = target; this.media = target;
@@ -315,6 +318,10 @@ class Plyr {
* Play the media, or play the advertisement (if they are not blocked) * Play the media, or play the advertisement (if they are not blocked)
*/ */
play() { play() {
if (!utils.is.function(this.media.play)) {
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(() => this.ads.play()).catch(() => this.media.play()); return this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play());
@@ -328,7 +335,7 @@ class Plyr {
* Pause the media * Pause the media
*/ */
pause() { pause() {
if (!this.playing) { if (!this.playing || !utils.is.function(this.media.pause)) {
return; return;
} }
@@ -375,8 +382,11 @@ class Plyr {
* Stop playback * Stop playback
*/ */
stop() { stop() {
this.restart(); if (this.isHTML5) {
this.pause(); this.media.load();
} else {
this.media.stop();
}
} }
/** /**
@@ -421,7 +431,7 @@ class Plyr {
} }
// Set // Set
this.media.currentTime = targetTime.toFixed(4); this.media.currentTime = parseFloat(targetTime.toFixed(4));
// Logging // Logging
this.debug.log(`Seeking to ${this.currentTime} seconds`); this.debug.log(`Seeking to ${this.currentTime} seconds`);
@@ -470,7 +480,7 @@ class Plyr {
const fauxDuration = parseInt(this.config.duration, 10); const fauxDuration = parseInt(this.config.duration, 10);
// True duration // True duration
const realDuration = Number(this.media.duration); const realDuration = this.media ? Number(this.media.duration) : 0;
// If custom duration is funky, use regular duration // If custom duration is funky, use regular duration
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration; return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
@@ -947,26 +957,32 @@ class Plyr {
// Is the enter fullscreen event // Is the enter fullscreen event
isEnterFullscreen = toggle.type === 'enterfullscreen'; isEnterFullscreen = toggle.type === 'enterfullscreen';
// Whether to show controls // Events that show the controls
show = [ const showEvents = [
'mouseenter',
'mousemove',
'touchstart', 'touchstart',
'touchmove', 'touchmove',
'focusin', 'mouseenter',
].includes(toggle.type);
// Delay hiding on move events
if ([
'mousemove', 'mousemove',
'focusin',
];
// Events that delay hiding
const delayEvents = [
'touchmove', 'touchmove',
'touchend', 'touchend',
].includes(toggle.type)) { 'mousemove',
];
// Whether to show controls
show = showEvents.includes(toggle.type);
// Delay hiding on move events
if (delayEvents.includes(toggle.type)) {
delay = 2000; delay = 2000;
} }
// Delay a little more for keyboard users // Delay a little more for keyboard users
if (toggle.type === 'focusin') { if (!this.touch && toggle.type === 'focusin') {
delay = 3000; delay = 3000;
utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true); utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true);
} }
@@ -994,7 +1010,7 @@ class Plyr {
} }
// Delay for hiding on touch // Delay for hiding on touch
if (support.touch) { if (this.touch) {
delay = 3000; delay = 3000;
} }
} }
@@ -1135,7 +1151,7 @@ class Plyr {
clearInterval(this.timers.playing); clearInterval(this.timers.playing);
// Destroy YouTube API // Destroy YouTube API
if (this.embed !== null) { if (this.embed !== null && utils.is.function(this.embed.destroy)) {
this.embed.destroy(); this.embed.destroy();
} }
+1 -1
View File
@@ -1,6 +1,6 @@
// ========================================================================== // ==========================================================================
// Plyr Polyfilled Build // Plyr Polyfilled Build
// plyr.js v3.0.0 // plyr.js v3.0.9
// https://github.com/sampotts/plyr // https://github.com/sampotts/plyr
// License: The MIT License (MIT) // License: The MIT License (MIT)
// ========================================================================== // ==========================================================================
+14 -9
View File
@@ -12,17 +12,18 @@ class Storage {
// Check for actual support (see if we can use it) // Check for actual support (see if we can use it)
static get supported() { static get supported() {
if (!('localStorage' in window)) {
return false;
}
const test = '___test';
// Try to use it (it might be disabled, e.g. user is in private mode)
// see: https://github.com/sampotts/plyr/issues/131
try { try {
if (!('localStorage' in window)) {
return false;
}
const test = '___test';
// Try to use it (it might be disabled, e.g. user is in private mode)
// see: https://github.com/sampotts/plyr/issues/131
window.localStorage.setItem(test, test); window.localStorage.setItem(test, test);
window.localStorage.removeItem(test); window.localStorage.removeItem(test);
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
@@ -30,9 +31,13 @@ class Storage {
} }
get(key) { get(key) {
if (!Storage.supported) {
return null;
}
const store = window.localStorage.getItem(this.key); const store = window.localStorage.getItem(this.key);
if (!Storage.supported || utils.is.empty(store)) { if (utils.is.empty(store)) {
return null; return null;
} }
+1 -1
View File
@@ -143,7 +143,7 @@ const support = {
})(), })(),
// Touch // Touch
// Remember a device can be moust + touch enabled // NOTE: Remember a device can be mouse + touch enabled so we check on first touch event
touch: 'ontouchstart' in document.documentElement, touch: 'ontouchstart' in document.documentElement,
// Detect transitions support // Detect transitions support
+3 -2
View File
@@ -5,6 +5,7 @@
import utils from './utils'; import utils from './utils';
import captions from './captions'; import captions from './captions';
import controls from './controls'; import controls from './controls';
import i18n from './i18n';
const ui = { const ui = {
addStyleHook() { addStyleHook() {
@@ -94,7 +95,7 @@ const ui = {
// Setup aria attribute for play and iframe title // Setup aria attribute for play and iframe title
setTitle() { setTitle() {
// Find the current text // Find the current text
let label = this.config.i18n.play; let label = i18n.get('play', this.config);
// If there's a media title set, use that for the label // If there's a media title set, use that for the label
if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) { if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) {
@@ -123,7 +124,7 @@ const ui = {
// Default to media type // Default to media type
const title = !utils.is.empty(this.config.title) ? this.config.title : 'video'; const title = !utils.is.empty(this.config.title) ? this.config.title : 'video';
iframe.setAttribute('title', this.config.i18n.frameTitle.replace('{title}', title)); iframe.setAttribute('title', i18n.get('frameTitle', this.config));
} }
}, },
+72 -74
View File
@@ -2,6 +2,8 @@
// Plyr utils // Plyr utils
// ========================================================================== // ==========================================================================
import loadjs from 'loadjs';
import support from './support'; import support from './support';
import { providers } from './types'; import { providers } from './types';
@@ -97,11 +99,10 @@ const utils = {
if (responseType === 'text') { if (responseType === 'text') {
try { try {
resolve(JSON.parse(request.responseText)); resolve(JSON.parse(request.responseText));
} catch(e) { } catch (e) {
resolve(request.responseText); resolve(request.responseText);
} }
} } else {
else {
resolve(request.response); resolve(request.response);
} }
}); });
@@ -125,52 +126,10 @@ const utils = {
// Load an external script // Load an external script
loadScript(url) { loadScript(url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const current = document.querySelector(`script[src="${url}"]`); loadjs(url, {
success: resolve,
// Check script is not already referenced, if so wait for load error: reject,
if (current !== null) { });
current.callbacks = current.callbacks || [];
current.callbacks.push(resolve);
return;
}
// Build the element
const element = document.createElement('script');
// Callback queue
element.callbacks = element.callbacks || [];
element.callbacks.push(resolve);
// Error queue
element.errors = element.errors || [];
element.errors.push(reject);
// Bind callback
element.addEventListener(
'load',
event => {
element.callbacks.forEach(cb => cb.call(null, event));
element.callbacks = null;
},
false,
);
// Bind error handling
element.addEventListener(
'error',
event => {
element.errors.forEach(err => err.call(null, event));
element.errors = null;
},
false,
);
// Set the URL after binding callback
element.src = url;
// Inject
const first = document.getElementsByTagName('script')[0];
first.parentNode.insertBefore(element, first);
}); });
}, },
@@ -184,7 +143,14 @@ const utils = {
const hasId = utils.is.string(id); const hasId = utils.is.string(id);
let isCached = false; let isCached = false;
function updateSprite(data) { const exists = () => document.querySelectorAll(`#${id}`).length;
function injectSprite(data) {
// Check again incase of race condition
if (hasId && exists()) {
return;
}
// Inject content // Inject content
this.innerHTML = data; this.innerHTML = data;
@@ -192,8 +158,8 @@ const utils = {
document.body.insertBefore(this, document.body.childNodes[0]); document.body.insertBefore(this, document.body.childNodes[0]);
} }
// Only load once // Only load once if ID set
if (!hasId || !document.querySelectorAll(`#${id}`).length) { if (!hasId || !exists()) {
// Create container // Create container
const container = document.createElement('div'); const container = document.createElement('div');
utils.toggleHidden(container, true); utils.toggleHidden(container, true);
@@ -209,7 +175,7 @@ const utils = {
if (isCached) { if (isCached) {
const data = JSON.parse(cached); const data = JSON.parse(cached);
updateSprite.call(container, data.content); injectSprite.call(container, data.content);
return; return;
} }
} }
@@ -231,7 +197,7 @@ const utils = {
); );
} }
updateSprite.call(container, result); injectSprite.call(container, result);
}) })
.catch(() => {}); .catch(() => {});
} }
@@ -242,15 +208,6 @@ const utils = {
return `${prefix}-${Math.floor(Math.random() * 10000)}`; return `${prefix}-${Math.floor(Math.random() * 10000)}`;
}, },
// Determine if we're in an iframe
inFrame() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
},
// Wrap an element // Wrap an element
wrap(elements, wrapper) { wrap(elements, wrapper) {
// Convert `elements` to an array, if necessary. // Convert `elements` to an array, if necessary.
@@ -353,8 +310,11 @@ const utils = {
return; return;
} }
Object.keys(attributes).forEach(key => { Object.entries(attributes).forEach(([
element.setAttribute(key, attributes[key]); key,
value,
]) => {
element.setAttribute(key, value);
}); });
}, },
@@ -481,7 +441,7 @@ const utils = {
pause: utils.getElement.call(this, this.config.selectors.buttons.pause), pause: utils.getElement.call(this, this.config.selectors.buttons.pause),
restart: utils.getElement.call(this, this.config.selectors.buttons.restart), restart: utils.getElement.call(this, this.config.selectors.buttons.restart),
rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind), rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind),
forward: utils.getElement.call(this, this.config.selectors.buttons.forward), fastForward: utils.getElement.call(this, this.config.selectors.buttons.fastForward),
mute: utils.getElement.call(this, this.config.selectors.buttons.mute), mute: utils.getElement.call(this, this.config.selectors.buttons.mute),
pip: utils.getElement.call(this, this.config.selectors.buttons.pip), pip: utils.getElement.call(this, this.config.selectors.buttons.pip),
airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay), airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay),
@@ -574,9 +534,9 @@ const utils = {
}, },
// Toggle event listener // Toggle event listener
toggleListener(elements, event, callback, toggle, passive, capture) { toggleListener(elements, event, callback, toggle = false, passive = true, capture = false) {
// Bail if no elemetns, event, or callback // Bail if no elemetns, event, or callback
if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) { if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) {
return; return;
} }
@@ -596,16 +556,16 @@ const utils = {
const events = event.split(' '); const events = event.split(' ');
// Build options // Build options
// Default to just capture boolean // Default to just the capture boolean for browsers with no passive listener support
let options = utils.is.boolean(capture) ? capture : false; let options = capture;
// If passive events listeners are supported // If passive events listeners are supported
if (support.passiveListeners) { if (support.passiveListeners) {
options = { options = {
// Whether the listener can be passive (i.e. default never prevented) // Whether the listener can be passive (i.e. default never prevented)
passive: utils.is.boolean(passive) ? passive : true, passive,
// Whether the listener is a capturing listener or not // Whether the listener is a capturing listener or not
capture: utils.is.boolean(capture) ? capture : false, capture,
}; };
} }
@@ -616,12 +576,12 @@ const utils = {
}, },
// Bind event handler // Bind event handler
on(element, events, callback, passive, capture) { on(element, events = '', callback, passive = true, capture = false) {
utils.toggleListener(element, events, callback, true, passive, capture); utils.toggleListener(element, events, callback, true, passive, capture);
}, },
// Unbind event handler // Unbind event handler
off(element, events, callback, passive, capture) { off(element, events = '', callback, passive = true, capture = false) {
utils.toggleListener(element, events, callback, false, passive, capture); utils.toggleListener(element, events, callback, false, passive, capture);
}, },
@@ -712,6 +672,44 @@ const utils = {
return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`; return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
}, },
// Replace all occurances of a string in a string
replaceAll(input = '', find = '', replace = '') {
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
},
// Convert to title case
toTitleCase(input = '') {
return input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase());
},
// Convert string to pascalCase
toPascalCase(input = '') {
let string = input.toString();
// Convert kebab case
string = utils.replaceAll(string, '-', ' ');
// Convert snake case
string = utils.replaceAll(string, '_', ' ');
// Convert to title case
string = utils.toTitleCase(string);
// Convert to pascal case
return utils.replaceAll(string, ' ', '');
},
// Convert string to pascalCase
toCamelCase(input = '') {
let string = input.toString();
// Convert to pascal case
string = utils.toPascalCase(string);
// Convert first character to lowercase
return string.charAt(0).toLowerCase() + string.slice(1);
},
// Deep extend destination object with N more objects // Deep extend destination object with N more objects
extend(target = {}, ...sources) { extend(target = {}, ...sources) {
if (!sources.length) { if (!sources.length) {
+1 -1
View File
@@ -6,7 +6,7 @@
.plyr__video-embed { .plyr__video-embed {
// Default to 16:9 ratio but this is set by JavaScript based on config // Default to 16:9 ratio but this is set by JavaScript based on config
$padding: ((100 / 16) * 9); $padding: ((100 / 16) * 9);
$height: 200; $height: 240;
$offset: to-percentage(($height - $padding) / ($height / 50)); $offset: to-percentage(($height - $padding) / ($height / 50));
height: 0; height: 0;
-2
View File
@@ -84,7 +84,6 @@
position: absolute; position: absolute;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
transition: border-color 0.2s ease;
} }
&--forward { &--forward {
@@ -108,7 +107,6 @@
margin-bottom: floor($plyr-control-padding / 2); margin-bottom: floor($plyr-control-padding / 2);
padding-left: ceil($plyr-control-padding * 4); padding-left: ceil($plyr-control-padding * 4);
position: relative; position: relative;
width: calc(100% - #{$horizontal-padding}); width: calc(100% - #{$horizontal-padding});
&::after { &::after {
+569 -23
View File
File diff suppressed because it is too large Load Diff