Compare commits
77 Commits
v3.0.0-bet
...
v3.0.7
Author | SHA1 | Date | |
---|---|---|---|
dda8e30b92 | |||
c4e2e24643 | |||
e020a105a3 | |||
2b7fe9a4f9 | |||
951df64b7f | |||
0976afe282 | |||
7b1e4abda7 | |||
0cf75eed3f | |||
d96957d086 | |||
1a032ea498 | |||
5d079da1b8 | |||
9c1bc6ab08 | |||
3d2ba8c009 | |||
e872ce3f77 | |||
b77756da04 | |||
9b23e13ce8 | |||
5eafe9baff | |||
c251c94131 | |||
17041efc71 | |||
05b8e8a6e0 | |||
f998b996fa | |||
958b47c435 | |||
a27248d3b6 | |||
1b1f7be7ff | |||
59d4a27240 | |||
75e9f3c2e3 | |||
7132eccf50 | |||
e953c6398c | |||
bb7eea27e5 | |||
595c5e95bc | |||
43e6dcd41d | |||
b06c8ae43f | |||
c7ea13c0c7 | |||
0f8c6e147b | |||
e566365288 | |||
a06e0f5890 | |||
3bccc0da01 | |||
a0173d991e | |||
600f0eb8a3 | |||
5db73b1327 | |||
5cb1628cd8 | |||
c74b75e8e1 | |||
0538476d6f | |||
5ebe18d081 | |||
207adde36d | |||
1b13ddaa54 | |||
9981c349be | |||
b3365a7373 | |||
9a0c1c830d | |||
ef27ba16f4 | |||
e206edc1f6 | |||
c734bc4957 | |||
572b8a7aca | |||
eebae4a227 | |||
e0562752ea | |||
6a2ca534d2 | |||
7adc2bc6c8 | |||
ba8d7831a7 | |||
69ffcbad27 | |||
819f7d1080 | |||
409b588458 | |||
e90a603d57 | |||
6f061621ad | |||
0300610108 | |||
2fba5f152c | |||
317b08c703 | |||
e6db374a72 | |||
bfb550b8d0 | |||
174234c166 | |||
24b4220de5 | |||
f1895a4cce | |||
c2a6306d46 | |||
7ac732f45b | |||
a59dcb2f53 | |||
ab7f277a1b | |||
ce1d5a60d6 | |||
d5a1a7ca1c |
@ -8,6 +8,7 @@
|
|||||||
"globals": { "Plyr": false, "jQuery": false },
|
"globals": { "Plyr": false, "jQuery": false },
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-const-assign": 1,
|
"no-const-assign": 1,
|
||||||
|
"no-shadow": 0,
|
||||||
"no-this-before-super": 1,
|
"no-this-before-super": 1,
|
||||||
"no-undef": 1,
|
"no-undef": 1,
|
||||||
"no-unreachable": 1,
|
"no-unreachable": 1,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"plugins": ["stylelint-selector-bem-pattern", "stylelint-scss"],
|
"plugins": ["stylelint-selector-bem-pattern", "stylelint-scss"],
|
||||||
"extends": ["stylelint-config-sass-guidelines", "stylelint-config-prettier"],
|
"extends": ["stylelint-config-recommended", "stylelint-config-sass-guidelines", "stylelint-config-prettier"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"selector-class-pattern": null,
|
"selector-class-pattern": null,
|
||||||
"selector-no-qualifying-type": [
|
"selector-no-qualifying-type": [
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
"plyr.css": "src/sass/plyr.scss"
|
"plyr.css": "src/sass/plyr.scss"
|
||||||
},
|
},
|
||||||
"js": {
|
"js": {
|
||||||
"plyr.js": "src/js/plyr.js"
|
"plyr.js": "src/js/plyr.js",
|
||||||
|
"plyr.polyfilled.js": "src/js/plyr.polyfilled.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"demo": {
|
"demo": {
|
||||||
|
40
changelog.md
40
changelog.md
@ -1,3 +1,41 @@
|
|||||||
|
## v3.0.7
|
||||||
|
|
||||||
|
* Fix for keyboard shortcut error with fast forward
|
||||||
|
* Fix for Vimeo trying to set playback rate when not allowed
|
||||||
|
|
||||||
|
## v3.0.6
|
||||||
|
|
||||||
|
* Improved the logic for the custom handlers preventing default handlers
|
||||||
|
|
||||||
|
## v3.0.5
|
||||||
|
|
||||||
|
* Removed console messages
|
||||||
|
|
||||||
|
## v3.0.4
|
||||||
|
|
||||||
|
* Fixes for fullscreen not working inside iframes
|
||||||
|
* Fixes for custom handlers being able to prevent default
|
||||||
|
* Fixes for controls not hiding/showing correctly on Mobile Safari
|
||||||
|
|
||||||
|
## v3.0.3
|
||||||
|
|
||||||
|
* Vimeo offset tweak (fixes #826)
|
||||||
|
* Fix for .stop() method (fixes #819)
|
||||||
|
* Check for array for speed options (fixes #817)
|
||||||
|
* Restore as float (fixes #828)
|
||||||
|
* Fix for Firefox fullscreen oddness (Fixes #821)
|
||||||
|
* Improve Sprite checking (fixes #827)
|
||||||
|
* Fix fast-forward control (thanks @saadshahd)
|
||||||
|
* Fix the options link in the readme (thanks @DanielRuf)
|
||||||
|
|
||||||
|
## v3.0.2
|
||||||
|
|
||||||
|
* Fix for Safari not firing error events when trying to load blocked scripts
|
||||||
|
|
||||||
|
## v3.0.1
|
||||||
|
|
||||||
|
* Fix for trying to accessing local storage when it's blocked
|
||||||
|
|
||||||
# v3.0.0
|
# v3.0.0
|
||||||
|
|
||||||
This is a massive release. A _mostly_ complete rewrite in ES6. What started out as a few changes quickly snowballed. There's many breaking changes so be careful upgrading.
|
This is a massive release. A _mostly_ complete rewrite in ES6. What started out as a few changes quickly snowballed. There's many breaking changes so be careful upgrading.
|
||||||
@ -67,7 +105,7 @@ You gotta break eggs to make an omelette. Sadly, there's quite a few breaking ch
|
|||||||
|
|
||||||
### Polyfilling
|
### Polyfilling
|
||||||
|
|
||||||
Because we're using the fancy new ES6 syntax, you will need to polyfill for vintage browsers if you want to use Plyr and still support them. Luckily there's a decent service for this that makes it painless, [polyfill.io](https://polyfill.io).
|
Because we're using the fancy new ES6 syntax, you will need to polyfill for vintage browsers if you want to use Plyr and still support them. Luckily there's a decent service for this that makes it painless, [polyfill.io](https://polyfill.io). Alternatively, you can use the prebuilt polyfilled build but bear in mind this is 20kb larger. I'd suggest working our your own polyfill strategy.
|
||||||
|
|
||||||
## v2.0.18
|
## v2.0.18
|
||||||
|
|
||||||
|
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
4035
demo/dist/demo.js
vendored
4035
demo/dist/demo.js
vendored
File diff suppressed because it is too large
Load Diff
2
demo/dist/demo.js.map
vendored
2
demo/dist/demo.js.map
vendored
File diff suppressed because one or more lines are too long
2
demo/dist/demo.min.js
vendored
Normal file
2
demo/dist/demo.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
demo/dist/demo.min.js.map
vendored
Normal file
1
demo/dist/demo.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -66,7 +66,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>Premium video monitization from
|
<p>Premium video monitization from
|
||||||
<a href="https://vi.ai/publisher-video-monetization/" target="_blank" class="no-border">
|
<a href="https://vi.ai/publisher-video-monetization/?aid=plyrio" target="_blank" class="no-border">
|
||||||
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi">
|
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi">
|
||||||
<span class="sr-only">ai.vi</span>
|
<span class="sr-only">ai.vi</span>
|
||||||
</a>
|
</a>
|
||||||
@ -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.&url=http%3A%2F%2Fplyr.io&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>
|
@ -4,7 +4,19 @@
|
|||||||
// Please see readme.md in the root or github.com/sampotts/plyr
|
// Please see readme.md in the root or github.com/sampotts/plyr
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
import Raven from 'raven-js';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const isLive = window.location.host === 'plyr.io';
|
||||||
|
|
||||||
|
// Raven / Sentry
|
||||||
|
// For demo site (https://plyr.io) only
|
||||||
|
if (isLive) {
|
||||||
|
Raven.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
Raven.context(() => {
|
||||||
if (window.shr) {
|
if (window.shr) {
|
||||||
window.shr.setup({
|
window.shr.setup({
|
||||||
count: {
|
count: {
|
||||||
@ -29,7 +41,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// Delay the adding of classname until the focus has changed
|
// Delay the adding of classname until the focus has changed
|
||||||
// This event fires before the focusin event
|
// This event fires before the focusin event
|
||||||
window.setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.activeElement.classList.add(tabClassName);
|
document.activeElement.classList.add(tabClassName);
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
@ -38,13 +50,29 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const player = new Plyr('#player', {
|
const player = new Plyr('#player', {
|
||||||
debug: true,
|
debug: true,
|
||||||
title: 'View From A Blue Moon',
|
title: 'View From A Blue Moon',
|
||||||
// iconUrl: '../dist/plyr.svg',
|
iconUrl: '../dist/plyr.svg',
|
||||||
keyboard: {
|
keyboard: {
|
||||||
global: true,
|
global: true,
|
||||||
},
|
},
|
||||||
tooltips: {
|
tooltips: {
|
||||||
controls: true,
|
controls: true,
|
||||||
},
|
},
|
||||||
|
/* controls: [
|
||||||
|
'play-large',
|
||||||
|
'restart',
|
||||||
|
'rewind',
|
||||||
|
'play',
|
||||||
|
'fast-forward',
|
||||||
|
'progress',
|
||||||
|
'current-time',
|
||||||
|
'mute',
|
||||||
|
'volume',
|
||||||
|
'captions',
|
||||||
|
'settings',
|
||||||
|
'pip',
|
||||||
|
'airplay',
|
||||||
|
'fullscreen',
|
||||||
|
], */
|
||||||
captions: {
|
captions: {
|
||||||
active: true,
|
active: true,
|
||||||
},
|
},
|
||||||
@ -53,10 +81,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
},
|
},
|
||||||
ads: {
|
ads: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
publisherId: '918848828995742',
|
||||||
},
|
},
|
||||||
|
/* listeners: {
|
||||||
|
seek: () => false,
|
||||||
|
}, */
|
||||||
});
|
});
|
||||||
|
|
||||||
// Expose for testing
|
// Expose for tinkering in the console
|
||||||
window.player = player;
|
window.player = player;
|
||||||
|
|
||||||
// Setup type toggle
|
// Setup type toggle
|
||||||
@ -147,7 +179,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
player.source = {
|
player.source = {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
sources: [{
|
sources: [{
|
||||||
src: 'https://vimeo.com/76979871',
|
src: 'https://vimeo.com/25345658',
|
||||||
provider: 'vimeo',
|
provider: 'vimeo',
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
@ -220,11 +252,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Google analytics
|
// Google analytics
|
||||||
// For demo site (https://plyr.io) only
|
// For demo site (https://plyr.io) only
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
if (window.location.host === 'plyr.io') {
|
if (isLive) {
|
||||||
(function(i, s, o, g, r, a, m) {
|
(function(i, s, o, g, r, a, m) {
|
||||||
i.GoogleAnalyticsObject = r;
|
i.GoogleAnalyticsObject = r;
|
||||||
i[r] =
|
i[r] =
|
||||||
@ -238,8 +271,9 @@ if (window.location.host === 'plyr.io') {
|
|||||||
a.async = 1;
|
a.async = 1;
|
||||||
a.src = g;
|
a.src = g;
|
||||||
m.parentNode.insertBefore(a, m);
|
m.parentNode.insertBefore(a, m);
|
||||||
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
|
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||||
window.ga('create', 'UA-40881672-11', 'auto');
|
window.ga('create', 'UA-40881672-11', 'auto');
|
||||||
window.ga('send', 'pageview');
|
window.ga('send', 'pageview');
|
||||||
}
|
}
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
})();
|
||||||
|
2
dist/plyr.css
vendored
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
7754
dist/plyr.js
vendored
7754
dist/plyr.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.js.map
vendored
2
dist/plyr.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.js
vendored
Normal file
2
dist/plyr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/plyr.min.js.map
vendored
Normal file
1
dist/plyr.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
12943
dist/plyr.polyfilled.js
vendored
Normal file
12943
dist/plyr.polyfilled.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/plyr.polyfilled.js.map
vendored
Normal file
1
dist/plyr.polyfilled.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.js
vendored
Normal file
2
dist/plyr.polyfilled.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/plyr.polyfilled.min.js.map
vendored
Normal file
1
dist/plyr.polyfilled.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
76
gulpfile.js
76
gulpfile.js
@ -9,6 +9,7 @@ const path = require('path');
|
|||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
const gutil = require('gulp-util');
|
const gutil = require('gulp-util');
|
||||||
const concat = require('gulp-concat');
|
const concat = require('gulp-concat');
|
||||||
|
const filter = require('gulp-filter');
|
||||||
const sass = require('gulp-sass');
|
const sass = require('gulp-sass');
|
||||||
const cleancss = require('gulp-clean-css');
|
const cleancss = require('gulp-clean-css');
|
||||||
const run = require('run-sequence');
|
const run = require('run-sequence');
|
||||||
@ -24,8 +25,7 @@ const size = require('gulp-size');
|
|||||||
const rollup = require('gulp-better-rollup');
|
const rollup = require('gulp-better-rollup');
|
||||||
const babel = require('rollup-plugin-babel');
|
const babel = require('rollup-plugin-babel');
|
||||||
const sourcemaps = require('gulp-sourcemaps');
|
const sourcemaps = require('gulp-sourcemaps');
|
||||||
const uglify = require('rollup-plugin-uglify');
|
const uglify = require('gulp-uglify-es').default;
|
||||||
const { minify } = require('uglify-es');
|
|
||||||
const commonjs = require('rollup-plugin-commonjs');
|
const commonjs = require('rollup-plugin-commonjs');
|
||||||
const resolve = require('rollup-plugin-node-resolve');
|
const resolve = require('rollup-plugin-node-resolve');
|
||||||
|
|
||||||
@ -40,6 +40,8 @@ try {
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const minSuffix = '.min';
|
||||||
|
|
||||||
// Paths
|
// Paths
|
||||||
const root = __dirname;
|
const root = __dirname;
|
||||||
const paths = {
|
const paths = {
|
||||||
@ -68,8 +70,11 @@ const paths = {
|
|||||||
root: path.join(root, 'demo/'),
|
root: path.join(root, 'demo/'),
|
||||||
},
|
},
|
||||||
upload: [
|
upload: [
|
||||||
path.join(root, 'dist/**'),
|
path.join(root, `dist/*${minSuffix}.*`),
|
||||||
path.join(root, 'demo/dist/**'),
|
path.join(root, 'dist/*.css'),
|
||||||
|
path.join(root, 'dist/*.svg'),
|
||||||
|
path.join(root, `demo/dist/*${minSuffix}.*`),
|
||||||
|
path.join(root, 'demo/dist/*.css'),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -122,6 +127,7 @@ const build = {
|
|||||||
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);
|
||||||
|
const { output } = paths[bundle];
|
||||||
|
|
||||||
gulp.task(name, () =>
|
gulp.task(name, () =>
|
||||||
gulp
|
gulp
|
||||||
@ -135,15 +141,19 @@ const build = {
|
|||||||
resolve(),
|
resolve(),
|
||||||
commonjs(),
|
commonjs(),
|
||||||
babel(babelrc),
|
babel(babelrc),
|
||||||
uglify({}, minify),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
options,
|
options,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.pipe(size(sizeOptions))
|
|
||||||
.pipe(sourcemaps.write(''))
|
.pipe(sourcemaps.write(''))
|
||||||
.pipe(gulp.dest(paths[bundle].output)),
|
.pipe(gulp.dest(output))
|
||||||
|
.pipe(filter('**/*.js'))
|
||||||
|
.pipe(uglify())
|
||||||
|
.pipe(size(sizeOptions))
|
||||||
|
.pipe(rename({ suffix: minSuffix }))
|
||||||
|
.pipe(sourcemaps.write(''))
|
||||||
|
.pipe(gulp.dest(output)),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -266,6 +276,23 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
|||||||
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');
|
||||||
const versionPath = `https://${aws.cdn.domain}/${version}`;
|
const versionPath = `https://${aws.cdn.domain}/${version}`;
|
||||||
|
const cdnpath = new RegExp(`${aws.cdn.domain}/${regex}/`, 'gi');
|
||||||
|
|
||||||
|
gulp.task('version', () => {
|
||||||
|
console.log(`Updating versions to '${version}'...`);
|
||||||
|
|
||||||
|
// Replace versioned URLs in source
|
||||||
|
const files = [
|
||||||
|
'plyr.js',
|
||||||
|
'plyr.polyfilled.js',
|
||||||
|
'defaults.js',
|
||||||
|
];
|
||||||
|
gulp
|
||||||
|
.src(files.map(file => path.join(root, `src/js/${file}`)))
|
||||||
|
.pipe(replace(semver, `v${version}`))
|
||||||
|
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
||||||
|
.pipe(gulp.dest(path.join(root, 'src/js/')));
|
||||||
|
});
|
||||||
|
|
||||||
// Publish version to CDN bucket
|
// Publish version to CDN bucket
|
||||||
gulp.task('cdn', () => {
|
gulp.task('cdn', () => {
|
||||||
@ -277,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 (
|
||||||
|
gulp
|
||||||
.src(paths.upload)
|
.src(paths.upload)
|
||||||
|
.pipe(
|
||||||
|
rename(p => {
|
||||||
|
p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line
|
||||||
|
p.dirname = p.dirname.replace('.', version); // eslint-disable-line
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
// Remove min suffix from source map URL
|
||||||
|
.pipe(replace(/sourceMappingURL=([\w-?.]+)/, (match, p1) => `sourceMappingURL=${p1.replace(minSuffix, '')}`))
|
||||||
.pipe(
|
.pipe(
|
||||||
size({
|
size({
|
||||||
showFiles: true,
|
showFiles: true,
|
||||||
gzip: true,
|
gzip: true,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.pipe(
|
|
||||||
rename(p => {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
p.dirname = p.dirname.replace('.', version);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.pipe(replace(localPath, versionPath))
|
.pipe(replace(localPath, versionPath))
|
||||||
.pipe(s3(aws.cdn, options.cdn));
|
.pipe(s3(aws.cdn, options.cdn))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Publish to demo bucket
|
// Publish to demo bucket
|
||||||
@ -304,25 +335,12 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
|||||||
|
|
||||||
console.log(`Uploading '${version}' demo to ${aws.demo.domain}...`);
|
console.log(`Uploading '${version}' demo to ${aws.demo.domain}...`);
|
||||||
|
|
||||||
const cdnpath = new RegExp(`${aws.cdn.domain}/${regex}/`, 'gi');
|
|
||||||
|
|
||||||
// Replace versioned files in readme.md
|
// Replace versioned files in readme.md
|
||||||
gulp
|
gulp
|
||||||
.src([`${root}/readme.md`])
|
.src([`${root}/readme.md`])
|
||||||
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
||||||
.pipe(gulp.dest(root));
|
.pipe(gulp.dest(root));
|
||||||
|
|
||||||
// Replace versioned URLs in source
|
|
||||||
const files = [
|
|
||||||
'plyr.js',
|
|
||||||
'defaults.js',
|
|
||||||
];
|
|
||||||
gulp
|
|
||||||
.src(files.map(file => path.join(root, `src/js/${file}`)))
|
|
||||||
.pipe(replace(semver, `v${version}`))
|
|
||||||
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
|
||||||
.pipe(gulp.dest(path.join(root, 'src/js/')));
|
|
||||||
|
|
||||||
// Replace local file paths with remote paths in demo HTML
|
// Replace local file paths with remote paths in demo HTML
|
||||||
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
|
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
|
||||||
const index = `${paths.demo.root}index.html`;
|
const index = `${paths.demo.root}index.html`;
|
||||||
@ -389,6 +407,6 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
|||||||
|
|
||||||
// Do everything
|
// Do everything
|
||||||
gulp.task('publish', () => {
|
gulp.task('publish', () => {
|
||||||
run(tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'cdn', 'demo');
|
run('version', tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'cdn', 'demo');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
48
package.json
48
package.json
@ -1,28 +1,30 @@
|
|||||||
{
|
{
|
||||||
"name": "plyr",
|
"name": "plyr",
|
||||||
"version": "3.0.0-beta.13",
|
"version": "3.0.7",
|
||||||
"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",
|
||||||
|
"browser": "./dist/plyr.min.js",
|
||||||
"sass": "./src/sass/plyr.scss",
|
"sass": "./src/sass/plyr.scss",
|
||||||
"style": "./dist/plyr.css",
|
"style": "./dist/plyr.css",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-eslint": "^8.2.1",
|
"babel-eslint": "^8.2.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",
|
||||||
"del": "^3.0.0",
|
"del": "^3.0.0",
|
||||||
"eslint": "^4.17.0",
|
"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.8.0",
|
"eslint-plugin-import": "^2.9.0",
|
||||||
"git-branch": "^1.0.0",
|
"git-branch": "^2.0.1",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"gulp-autoprefixer": "^4.1.0",
|
"gulp-autoprefixer": "^5.0.0",
|
||||||
"gulp-better-rollup": "^3.0.0",
|
"gulp-better-rollup": "^3.0.0",
|
||||||
"gulp-clean-css": "^3.9.2",
|
"gulp-clean-css": "^3.9.3",
|
||||||
"gulp-concat": "^2.6.1",
|
"gulp-concat": "^2.6.1",
|
||||||
"gulp-open": "^2.1.0",
|
"gulp-filter": "^5.1.0",
|
||||||
|
"gulp-open": "^3.0.0",
|
||||||
"gulp-rename": "^1.2.2",
|
"gulp-rename": "^1.2.2",
|
||||||
"gulp-replace": "^0.6.1",
|
"gulp-replace": "^0.6.1",
|
||||||
"gulp-s3": "^0.11.0",
|
"gulp-s3": "^0.11.0",
|
||||||
@ -31,20 +33,21 @@
|
|||||||
"gulp-sourcemaps": "^2.6.4",
|
"gulp-sourcemaps": "^2.6.4",
|
||||||
"gulp-svgmin": "^1.2.4",
|
"gulp-svgmin": "^1.2.4",
|
||||||
"gulp-svgstore": "^6.1.1",
|
"gulp-svgstore": "^6.1.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.3.0",
|
"rollup-plugin-commonjs": "^8.4.1",
|
||||||
"rollup-plugin-node-resolve": "^3.0.2",
|
"rollup-plugin-node-resolve": "^3.2.0",
|
||||||
"rollup-plugin-uglify": "^3.0.0",
|
|
||||||
"run-sequence": "^2.2.1",
|
"run-sequence": "^2.2.1",
|
||||||
"stylelint": "^8.4.0",
|
"stylelint": "^9.1.3",
|
||||||
"stylelint-config-prettier": "^2.0.0",
|
"stylelint-config-prettier": "^3.0.4",
|
||||||
"stylelint-config-sass-guidelines": "^4.1.0",
|
"stylelint-config-recommended": "^2.1.0",
|
||||||
"stylelint-config-standard": "^18.0.0",
|
"stylelint-config-sass-guidelines": "^5.0.0",
|
||||||
"stylelint-order": "^0.8.0",
|
"stylelint-order": "^0.8.1",
|
||||||
"stylelint-scss": "^2.3.0",
|
"stylelint-scss": "^2.5.0",
|
||||||
"stylelint-selector-bem-pattern": "^2.0.0",
|
"stylelint-selector-bem-pattern": "^2.0.0"
|
||||||
"uglify-es": "^3.3.10"
|
|
||||||
},
|
},
|
||||||
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
|
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -62,5 +65,10 @@
|
|||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "Sam Potts <sam@potts.es>",
|
"author": "Sam Potts <sam@potts.es>",
|
||||||
"dependencies": {}
|
"dependencies": {
|
||||||
|
"babel-polyfill": "^6.26.0",
|
||||||
|
"custom-event-polyfill": "^0.3.0",
|
||||||
|
"loadjs": "^3.5.2",
|
||||||
|
"raven-js": "^3.23.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
63
readme.md
63
readme.md
@ -1,30 +1,30 @@
|
|||||||
---
|
|
||||||
Beware: This version is currently in beta and not production-ready
|
|
||||||
---
|
|
||||||
|
|
||||||
# Plyr
|
# Plyr
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
[](https://plyr.io)
|
[](https://plyr.io)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* **Accessible** - full support for VTT captions and screen readers
|
* **Accessible** - full support for VTT captions and screen readers
|
||||||
* **Lightweight** - just 18KB minified and gzipped
|
|
||||||
* **[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
|
||||||
* **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
|
* **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
|
||||||
|
* **[Monetization](#ads)** - make money from your videos
|
||||||
* **[Streaming](#streaming)** - support for hls.js, Shaka and dash.js streaming playback
|
* **[Streaming](#streaming)** - support for hls.js, Shaka and dash.js streaming playback
|
||||||
* **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
* **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
||||||
* **[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
|
||||||
@ -53,8 +53,7 @@ Here's a quick run through on getting up and running. There's also a [demo on Co
|
|||||||
|
|
||||||
### HTML
|
### HTML
|
||||||
|
|
||||||
Plyr extends upon the standard HTML5 markup so that's all you need for those types. More info on advanced HTML markup can be found under
|
Plyr extends upon the standard [HTML5 media element](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement) markup so that's all you need for those types.
|
||||||
[initialising](#initialising).
|
|
||||||
|
|
||||||
#### HTML5 Video
|
#### HTML5 Video
|
||||||
|
|
||||||
@ -117,18 +116,19 @@ Or the `<div>` non progressively enhanced method:
|
|||||||
|
|
||||||
### JavaScript
|
### JavaScript
|
||||||
|
|
||||||
Include the `plyr.js` script before the closing `</body>` tag and then call `plyr.setup()`. More info on `setup()` can be found under
|
Include the `plyr.js` script before the closing `</body>` tag and then in your JS create a new instance of Plyr as below.
|
||||||
[initialising](#initialising).
|
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="path/to/plyr.js"></script>
|
<script src="path/to/plyr.js"></script>
|
||||||
<script>const player = new Plyr('#player');</script>
|
<script>const player = new Plyr('#player');</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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-beta.13/plyr.js"></script>
|
<script src="https://cdn.plyr.io/3.0.7/plyr.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
_Note_: Be sure to read the [polyfills](#polyfills) section below about browser compatibility
|
_Note_: Be sure to read the [polyfills](#polyfills) section below about browser compatibility
|
||||||
@ -144,13 +144,23 @@ 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-beta.13/plyr.css">
|
<link rel="stylesheet" href="https://cdn.plyr.io/3.0.7/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-beta.13/plyr.svg`.
|
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.7/plyr.svg`.
|
||||||
|
|
||||||
|
## Ads
|
||||||
|
|
||||||
|
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)
|
||||||
|
* Grab your publisher ID from the code snippet
|
||||||
|
* Enable ads in the [config options](#options) and enter your publisher ID
|
||||||
|
|
||||||
|
Any questions regarding the ads can be sent straight to vi.ai and any issues with rendering raised through GitHub issues.
|
||||||
|
|
||||||
## Advanced
|
## Advanced
|
||||||
|
|
||||||
@ -196,10 +206,11 @@ WebVTT captions are supported. To add a caption track, check the HTML example ab
|
|||||||
You can specify a range of arguments for the constructor to use:
|
You can specify a range of arguments for the constructor to use:
|
||||||
|
|
||||||
* A CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
|
* A CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
|
||||||
* A [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
|
* A [`HTMLElement`](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
|
||||||
* A [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList) or Array of [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement) -
|
* A [`NodeList]`(https://developer.mozilla.org/en-US/docs/Web/API/NodeList)
|
||||||
the first element will be used
|
* A [jQuery](https://jquery.com) object
|
||||||
* A [jQuery](https://jquery.com) object - if multiple are passed, the first element will be used
|
|
||||||
|
_Note_: If a `NodeList`, `Array`, or jQuery object are passed, the first element will be used for setup.
|
||||||
|
|
||||||
Here's some examples
|
Here's some examples
|
||||||
|
|
||||||
@ -223,7 +234,13 @@ const player = new Plyr(document.querySelectorAll('.js-player'));
|
|||||||
|
|
||||||
The NodeList, HTMLElement or string selector can be the target `<video>`, `<audio>`, or `<div>` wrapper for embeds
|
The NodeList, HTMLElement or string selector can be the target `<video>`, `<audio>`, or `<div>` wrapper for embeds
|
||||||
|
|
||||||
The second argument for the constructor is the [#options](options) object:
|
##### Setting up multiple players
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const players = Array.from(document.querySelectorAll('.js-player')).map(player => new Plyr(player));
|
||||||
|
```
|
||||||
|
|
||||||
|
The second argument for the constructor is the [options](#options) object:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const player = new Plyr('#player', {
|
const player = new Plyr('#player', {
|
||||||
@ -271,12 +288,13 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
|
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
|
||||||
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
|
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
|
||||||
| `captions` | Object | `{ active: false, language: window.navigator.language.split('-')[0] }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). |
|
| `captions` | Object | `{ active: false, language: window.navigator.language.split('-')[0] }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). |
|
||||||
| `fullscreen` | Object | `{ enabled: true, fallback: true }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution. |
|
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution. `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) |
|
||||||
| `ratio` | String | `16:9` | The aspect ratio you want to use for embedded players. |
|
| `ratio` | String | `16:9` | The aspect ratio you want to use for embedded players. |
|
||||||
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
||||||
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }` | `selected`: The default speed for playback. `options`: Options to display in the menu. Most browsers will refuse to play slower than 0.5. |
|
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }` | `selected`: The default speed for playback. `options`: Options to display in the menu. Most browsers will refuse to play slower than 0.5. |
|
||||||
| `quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display. |
|
| `quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display. |
|
||||||
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
||||||
|
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable vi.ai ads. `publisherId`: Your unique vi.ai publisher ID. |
|
||||||
|
|
||||||
1. Vimeo only
|
1. Vimeo only
|
||||||
|
|
||||||
@ -359,6 +377,7 @@ player.fullscreen.active; // false;
|
|||||||
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
|
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
|
||||||
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
|
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
|
||||||
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
|
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
|
||||||
|
| `buffered` | ✓ | - | Returns a float between 0 and 1 indicating how much of the media is buffered |
|
||||||
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
|
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
|
||||||
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
|
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
|
||||||
| `duration` | ✓ | - | Returns the duration for the current media. |
|
| `duration` | ✓ | - | Returns the duration for the current media. |
|
||||||
@ -688,7 +707,7 @@ Credit to the PayPal HTML5 Video player from which Plyr's caption functionality
|
|||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
[](https://www.fastly.com/)
|
[](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.
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr Captions
|
// Plyr Captions
|
||||||
|
// TODO: Create as class
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import support from './support';
|
import support from './support';
|
||||||
@ -45,7 +46,6 @@ const captions = {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the container
|
// Inject the container
|
||||||
if (!utils.is.element(this.elements.captions)) {
|
if (!utils.is.element(this.elements.captions)) {
|
||||||
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
|
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
|
||||||
@ -56,11 +56,42 @@ const captions = {
|
|||||||
// Set the class hook
|
// Set the class hook
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
|
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
|
||||||
|
|
||||||
|
// Get tracks
|
||||||
|
const tracks = captions.getTracks.call(this);
|
||||||
|
|
||||||
// If no caption file exists, hide container for caption text
|
// If no caption file exists, hide container for caption text
|
||||||
if (utils.is.empty(captions.getTracks.call(this))) {
|
if (utils.is.empty(tracks)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get browser info
|
||||||
|
const browser = utils.getBrowser();
|
||||||
|
|
||||||
|
// Fix IE captions if CORS is used
|
||||||
|
// Fetch captions and inject as blobs instead (data URIs not supported!)
|
||||||
|
if (browser.isIE && window.URL) {
|
||||||
|
const elements = this.media.querySelectorAll('track');
|
||||||
|
|
||||||
|
Array.from(elements).forEach(track => {
|
||||||
|
const src = track.getAttribute('src');
|
||||||
|
const href = utils.parseUrl(src);
|
||||||
|
|
||||||
|
if (href.hostname !== window.location.href.hostname && [
|
||||||
|
'http:',
|
||||||
|
'https:',
|
||||||
|
].includes(href.protocol)) {
|
||||||
|
utils
|
||||||
|
.fetch(src, 'blob')
|
||||||
|
.then(blob => {
|
||||||
|
track.setAttribute('src', window.URL.createObjectURL(blob));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
utils.removeElement(track);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Set language
|
// Set language
|
||||||
captions.setLanguage.call(this);
|
captions.setLanguage.call(this);
|
||||||
|
|
||||||
|
128
src/js/controls.js
vendored
128
src/js/controls.js
vendored
@ -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
|
||||||
@ -50,7 +51,7 @@ const controls = {
|
|||||||
icon,
|
icon,
|
||||||
utils.extend(attributes, {
|
utils.extend(attributes, {
|
||||||
role: 'presentation',
|
role: 'presentation',
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create the <use> to reference sprite
|
// Create the <use> to reference sprite
|
||||||
@ -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) {
|
||||||
@ -115,8 +116,8 @@ const controls = {
|
|||||||
{
|
{
|
||||||
class: this.config.classNames.menu.badge,
|
class: this.config.classNames.menu.badge,
|
||||||
},
|
},
|
||||||
text
|
text,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return badge;
|
return badge;
|
||||||
@ -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
|
||||||
@ -254,8 +255,8 @@ const controls = {
|
|||||||
value: 0,
|
value: 0,
|
||||||
autocomplete: 'off',
|
autocomplete: 'off',
|
||||||
},
|
},
|
||||||
attributes
|
attributes,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.elements.inputs[type] = input;
|
this.elements.inputs[type] = input;
|
||||||
@ -280,8 +281,8 @@ const controls = {
|
|||||||
max: 100,
|
max: 100,
|
||||||
value: 0,
|
value: 0,
|
||||||
},
|
},
|
||||||
attributes
|
attributes,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create the label inside
|
// Create the label inside
|
||||||
@ -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,8 +323,8 @@ const controls = {
|
|||||||
{
|
{
|
||||||
class: this.config.classNames.hidden,
|
class: this.config.classNames.hidden,
|
||||||
},
|
},
|
||||||
this.config.i18n[type]
|
i18n.get(type, this.config),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
container.appendChild(utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.display[type]), '00:00'));
|
container.appendChild(utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.display[type]), '00:00'));
|
||||||
@ -349,7 +350,7 @@ const controls = {
|
|||||||
value,
|
value,
|
||||||
checked,
|
checked,
|
||||||
class: 'plyr__sr-only',
|
class: 'plyr__sr-only',
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const faux = utils.createElement('span', { 'aria-hidden': true });
|
const faux = utils.createElement('span', { 'aria-hidden': true });
|
||||||
@ -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');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -427,6 +438,11 @@ const controls = {
|
|||||||
// Set the YouTube quality menu
|
// Set the YouTube quality menu
|
||||||
// TODO: Support for HTML5
|
// TODO: Support for HTML5
|
||||||
setQualityMenu(options) {
|
setQualityMenu(options) {
|
||||||
|
// Menu required
|
||||||
|
if (!utils.is.element(this.elements.settings.panes.quality)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const type = 'quality';
|
const type = 'quality';
|
||||||
const list = this.elements.settings.panes.quality.querySelector('ul');
|
const list = this.elements.settings.panes.quality.querySelector('ul');
|
||||||
|
|
||||||
@ -482,7 +498,7 @@ const controls = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.options.quality.forEach(quality =>
|
this.options.quality.forEach(quality =>
|
||||||
controls.createMenuItem.call(this, quality, list, type, controls.getLabel.call(this, 'quality', quality), getBadge(quality))
|
controls.createMenuItem.call(this, quality, list, type, controls.getLabel.call(this, 'quality', quality), getBadge(quality)),
|
||||||
);
|
);
|
||||||
|
|
||||||
controls.updateSetting.call(this, type, list);
|
controls.updateSetting.call(this, type, list);
|
||||||
@ -535,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:
|
||||||
@ -583,6 +599,11 @@ const controls = {
|
|||||||
|
|
||||||
// Set the looping options
|
// Set the looping options
|
||||||
/* setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
|
// Menu required
|
||||||
|
if (!utils.is.element(this.elements.settings.panes.loop)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panes.loop.querySelector('ul');
|
const list = this.elements.settings.panes.loop.querySelector('ul');
|
||||||
|
|
||||||
@ -607,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)) {
|
||||||
@ -627,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)) {
|
||||||
@ -639,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
|
||||||
@ -666,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
|
||||||
@ -681,7 +698,7 @@ const controls = {
|
|||||||
'language',
|
'language',
|
||||||
track.label || track.language,
|
track.label || track.language,
|
||||||
controls.createBadge.call(this, track.language.toUpperCase()),
|
controls.createBadge.call(this, track.language.toUpperCase()),
|
||||||
track.language.toLowerCase() === this.captions.language.toLowerCase()
|
track.language.toLowerCase() === this.captions.language.toLowerCase(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -689,11 +706,21 @@ 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
|
||||||
|
if (!utils.is.element(this.elements.settings.panes.speed)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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,
|
||||||
@ -703,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
|
||||||
@ -712,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;
|
||||||
@ -733,10 +765,24 @@ 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;
|
||||||
const button = this.elements.buttons.settings;
|
const button = this.elements.buttons.settings;
|
||||||
|
|
||||||
|
// Menu and button are required
|
||||||
|
if (!utils.is.element(form) || !utils.is.element(button)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const show = utils.is.boolean(event) ? event : utils.is.element(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)) {
|
||||||
@ -933,7 +979,7 @@ const controls = {
|
|||||||
role: 'tooltip',
|
role: 'tooltip',
|
||||||
class: this.config.classNames.tooltip,
|
class: this.config.classNames.tooltip,
|
||||||
},
|
},
|
||||||
'00:00'
|
'00:00',
|
||||||
);
|
);
|
||||||
|
|
||||||
progress.appendChild(tooltip);
|
progress.appendChild(tooltip);
|
||||||
@ -978,7 +1024,7 @@ const controls = {
|
|||||||
'volume',
|
'volume',
|
||||||
utils.extend(attributes, {
|
utils.extend(attributes, {
|
||||||
id: `plyr-volume-${data.id}`,
|
id: `plyr-volume-${data.id}`,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
volume.appendChild(range.label);
|
volume.appendChild(range.label);
|
||||||
volume.appendChild(range.input);
|
volume.appendChild(range.input);
|
||||||
@ -1005,7 +1051,7 @@ const controls = {
|
|||||||
'aria-haspopup': true,
|
'aria-haspopup': true,
|
||||||
'aria-controls': `plyr-settings-${data.id}`,
|
'aria-controls': `plyr-settings-${data.id}`,
|
||||||
'aria-expanded': false,
|
'aria-expanded': false,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const form = utils.createElement('form', {
|
const form = utils.createElement('form', {
|
||||||
@ -1048,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', {
|
||||||
@ -1088,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);
|
||||||
@ -1131,7 +1177,7 @@ const controls = {
|
|||||||
|
|
||||||
this.elements.controls = container;
|
this.elements.controls = container;
|
||||||
|
|
||||||
if (this.config.controls.includes('settings') && this.config.settings.includes('speed')) {
|
if (this.isHTML5) {
|
||||||
controls.setSpeedMenu.call(this);
|
controls.setSpeedMenu.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1221,7 +1267,7 @@ const controls = {
|
|||||||
this.config.selectors.labels,
|
this.config.selectors.labels,
|
||||||
' .',
|
' .',
|
||||||
this.config.classNames.hidden,
|
this.config.classNames.hidden,
|
||||||
].join('')
|
].join(''),
|
||||||
);
|
);
|
||||||
|
|
||||||
Array.from(labels).forEach(label => {
|
Array.from(labels).forEach(label => {
|
||||||
|
@ -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-beta.13/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.0.7/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,9 +181,8 @@ const defaults = {
|
|||||||
end: 'End',
|
end: 'End',
|
||||||
all: 'All',
|
all: 'All',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
none: 'None',
|
|
||||||
disabled: 'Disabled',
|
disabled: 'Disabled',
|
||||||
advertisment: 'Ad',
|
advertisement: 'Ad',
|
||||||
},
|
},
|
||||||
|
|
||||||
// URLs
|
// URLs
|
||||||
@ -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,
|
||||||
@ -259,7 +261,7 @@ const defaults = {
|
|||||||
// Ads
|
// Ads
|
||||||
'adsloaded',
|
'adsloaded',
|
||||||
'adscontentpause',
|
'adscontentpause',
|
||||||
'adsconentresume',
|
'adscontentresume',
|
||||||
'adstarted',
|
'adstarted',
|
||||||
'adsmidpoint',
|
'adsmidpoint',
|
||||||
'adscomplete',
|
'adscomplete',
|
||||||
@ -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"]',
|
||||||
@ -373,9 +375,10 @@ const defaults = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Advertisements plugin
|
// Advertisements plugin
|
||||||
// Tag is not required as publisher is determined by vi.ai using the domain
|
// Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
|
||||||
ads: {
|
ads: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
publisherId: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
src/js/i18n.js
Normal file
31
src/js/i18n.js
Normal 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;
|
@ -10,19 +10,21 @@ import ui from './ui';
|
|||||||
// Sniff out the browser
|
// Sniff out the browser
|
||||||
const browser = utils.getBrowser();
|
const browser = utils.getBrowser();
|
||||||
|
|
||||||
const listeners = {
|
class Listeners {
|
||||||
// Global listeners
|
constructor(player) {
|
||||||
global() {
|
this.player = player;
|
||||||
let last = null;
|
this.lastKey = null;
|
||||||
|
|
||||||
// Get the key code for an event
|
this.handleKey = this.handleKey.bind(this);
|
||||||
const getKeyCode = event => (event.keyCode ? event.keyCode : event.which);
|
this.toggleMenu = this.toggleMenu.bind(this);
|
||||||
|
this.firstTouch = this.firstTouch.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle key press
|
// Handle key presses
|
||||||
const handleKey = event => {
|
handleKey(event) {
|
||||||
const code = getKeyCode(event);
|
const code = event.keyCode ? event.keyCode : event.which;
|
||||||
const pressed = event.type === 'keydown';
|
const pressed = event.type === 'keydown';
|
||||||
const repeat = pressed && code === last;
|
const repeat = pressed && code === this.lastKey;
|
||||||
|
|
||||||
// Bail if a modifier key is set
|
// Bail if a modifier key is set
|
||||||
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||||
@ -38,7 +40,7 @@ const listeners = {
|
|||||||
// Seek by the number keys
|
// Seek by the number keys
|
||||||
const seekByKey = () => {
|
const seekByKey = () => {
|
||||||
// Divide the max duration into 10th's and times by the number value
|
// Divide the max duration into 10th's and times by the number value
|
||||||
this.currentTime = this.duration / 10 * (code - 48);
|
this.player.currentTime = this.player.duration / 10 * (code - 48);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle the key on keydown
|
// Handle the key on keydown
|
||||||
@ -73,7 +75,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.element(focused) && utils.matches(focused, this.config.selectors.editable)) {
|
if (utils.is.element(focused) && utils.matches(focused, this.player.config.selectors.editable)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,52 +106,52 @@ const listeners = {
|
|||||||
case 75:
|
case 75:
|
||||||
// Space and K key
|
// Space and K key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
this.togglePlay();
|
this.player.togglePlay();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 38:
|
case 38:
|
||||||
// Arrow up
|
// Arrow up
|
||||||
this.increaseVolume(0.1);
|
this.player.increaseVolume(0.1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 40:
|
case 40:
|
||||||
// Arrow down
|
// Arrow down
|
||||||
this.decreaseVolume(0.1);
|
this.player.decreaseVolume(0.1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 77:
|
case 77:
|
||||||
// M key
|
// M key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
this.muted = !this.muted;
|
this.player.muted = !this.player.muted;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 39:
|
case 39:
|
||||||
// Arrow forward
|
// Arrow forward
|
||||||
this.forward();
|
this.player.forward();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 37:
|
case 37:
|
||||||
// Arrow back
|
// Arrow back
|
||||||
this.rewind();
|
this.player.rewind();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 70:
|
case 70:
|
||||||
// F key
|
// F key
|
||||||
this.fullscreen.toggle();
|
this.player.fullscreen.toggle();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 67:
|
case 67:
|
||||||
// C key
|
// C key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
this.toggleCaptions();
|
this.player.toggleCaptions();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 76:
|
case 76:
|
||||||
// L key
|
// L key
|
||||||
this.loop = !this.loop;
|
this.player.loop = !this.player.loop;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* case 73:
|
/* case 73:
|
||||||
@ -170,98 +172,128 @@ const listeners = {
|
|||||||
|
|
||||||
// Escape is handle natively when in full screen
|
// Escape is handle natively when in full screen
|
||||||
// So we only need to worry about non native
|
// So we only need to worry about non native
|
||||||
if (!this.fullscreen.enabled && this.fullscreen.active && code === 27) {
|
if (!this.player.fullscreen.enabled && this.player.fullscreen.active && code === 27) {
|
||||||
this.fullscreen.toggle();
|
this.player.fullscreen.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store last code for next cycle
|
// Store last code for next cycle
|
||||||
last = code;
|
this.lastKey = code;
|
||||||
} else {
|
} else {
|
||||||
last = null;
|
this.lastKey = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
// Toggle menu
|
||||||
|
toggleMenu(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(toggle = true) {
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
if (this.config.keyboard.global) {
|
if (this.player.config.keyboard.global) {
|
||||||
utils.on(window, 'keydown keyup', handleKey, false);
|
utils.toggleListener(window, 'keydown keyup', this.handleKey, toggle, false);
|
||||||
} else if (this.config.keyboard.focused) {
|
}
|
||||||
utils.on(this.elements.container, 'keydown keyup', handleKey, false);
|
|
||||||
|
// Click anywhere closes menu
|
||||||
|
utils.toggleListener(document.body, 'click', this.toggleMenu, toggle);
|
||||||
|
|
||||||
|
// Detect touch by events
|
||||||
|
utils.on(document.body, 'touchstart', this.firstTouch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container listeners
|
||||||
|
container() {
|
||||||
|
// Keyboard shortcuts
|
||||||
|
if (!this.player.config.keyboard.global && this.player.config.keyboard.focused) {
|
||||||
|
utils.on(this.player.elements.container, 'keydown keyup', this.handleKey, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect tab focus
|
// Detect tab focus
|
||||||
// Remove class on blur/focusout
|
// Remove class on blur/focusout
|
||||||
utils.on(this.elements.container, 'focusout', event => {
|
utils.on(this.player.elements.container, 'focusout', event => {
|
||||||
utils.toggleClass(event.target, this.config.classNames.tabFocus, false);
|
utils.toggleClass(event.target, this.player.config.classNames.tabFocus, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add classname to tabbed elements
|
// Add classname to tabbed elements
|
||||||
utils.on(this.elements.container, 'keydown', event => {
|
utils.on(this.player.elements.container, 'keydown', event => {
|
||||||
if (event.keyCode !== 9) {
|
if (event.keyCode !== 9) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay the adding of classname until the focus has changed
|
// Delay the adding of classname until the focus has changed
|
||||||
// This event fires before the focusin event
|
// This event fires before the focusin event
|
||||||
window.setTimeout(() => {
|
setTimeout(() => {
|
||||||
utils.toggleClass(utils.getFocusElement(), this.config.classNames.tabFocus, true);
|
utils.toggleClass(utils.getFocusElement(), this.player.config.classNames.tabFocus, true);
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggle controls visibility based on mouse movement
|
// Toggle controls visibility based on mouse movement
|
||||||
if (this.config.hideControls) {
|
if (this.player.config.hideControls) {
|
||||||
// Toggle controls on mouse events and entering fullscreen
|
// Toggle controls on mouse events and entering fullscreen
|
||||||
utils.on(this.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', event => {
|
utils.on(this.player.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', event => {
|
||||||
this.toggleControls(event);
|
this.player.toggleControls(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
// Listen for media events
|
// Listen for media events
|
||||||
media() {
|
media() {
|
||||||
// Time change on media
|
// Time change on media
|
||||||
utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event));
|
utils.on(this.player.media, 'timeupdate seeking', event => ui.timeUpdate.call(this.player, event));
|
||||||
|
|
||||||
// Display duration
|
// Display duration
|
||||||
utils.on(this.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this, event));
|
utils.on(this.player.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this.player, event));
|
||||||
|
|
||||||
// Check for audio tracks on load
|
// Check for audio tracks on load
|
||||||
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
||||||
utils.on(this.media, 'loadeddata', () => {
|
utils.on(this.player.media, 'loadeddata', () => {
|
||||||
utils.toggleHidden(this.elements.volume, !this.hasAudio);
|
utils.toggleHidden(this.player.elements.volume, !this.player.hasAudio);
|
||||||
utils.toggleHidden(this.elements.buttons.mute, !this.hasAudio);
|
utils.toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle the media finishing
|
// Handle the media finishing
|
||||||
utils.on(this.media, 'ended', () => {
|
utils.on(this.player.media, 'ended', () => {
|
||||||
// Show poster on end
|
// Show poster on end
|
||||||
if (this.isHTML5 && this.isVideo && this.config.showPosterOnEnd) {
|
if (this.player.isHTML5 && this.player.isVideo && this.player.config.showPosterOnEnd) {
|
||||||
// Restart
|
// Restart
|
||||||
this.restart();
|
this.player.restart();
|
||||||
|
|
||||||
// Re-load media
|
// Re-load media
|
||||||
this.media.load();
|
this.player.media.load();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for buffer progress
|
// Check for buffer progress
|
||||||
utils.on(this.media, 'progress playing', event => ui.updateProgress.call(this, event));
|
utils.on(this.player.media, 'progress playing', event => ui.updateProgress.call(this.player, event));
|
||||||
|
|
||||||
// Handle native mute
|
// Handle native mute
|
||||||
utils.on(this.media, 'volumechange', event => ui.updateVolume.call(this, 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.media, 'playing play pause ended', event => ui.checkPlaying.call(this, event));
|
utils.on(this.player.media, 'playing play pause ended emptied', event => ui.checkPlaying.call(this.player, event));
|
||||||
|
|
||||||
// Loading
|
// Loading
|
||||||
utils.on(this.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this, event));
|
utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event));
|
||||||
|
|
||||||
// Check if media failed to load
|
// Check if media failed to load
|
||||||
// utils.on(this.media, 'play', event => ui.checkFailed.call(this, event));
|
// utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event));
|
||||||
|
|
||||||
// Click video
|
// Click video
|
||||||
if (this.supported.ui && this.config.clickToPlay && !this.isAudio) {
|
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
|
||||||
// Re-fetch the wrapper
|
// Re-fetch the wrapper
|
||||||
const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`);
|
const wrapper = utils.getElement.call(this.player, `.${this.player.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.element(wrapper)) {
|
if (!utils.is.element(wrapper)) {
|
||||||
@ -271,25 +303,25 @@ const 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.config.hideControls && support.touch && !this.paused) {
|
if (this.player.config.hideControls && this.player.touch && !this.player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.paused) {
|
if (this.player.paused) {
|
||||||
this.play();
|
this.player.play();
|
||||||
} else if (this.ended) {
|
} else if (this.player.ended) {
|
||||||
this.restart();
|
this.player.restart();
|
||||||
this.play();
|
this.player.play();
|
||||||
} else {
|
} else {
|
||||||
this.pause();
|
this.player.pause();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable right click
|
// Disable right click
|
||||||
if (this.supported.ui && this.config.disableContextMenu) {
|
if (this.player.supported.ui && this.player.config.disableContextMenu) {
|
||||||
utils.on(
|
utils.on(
|
||||||
this.media,
|
this.player.media,
|
||||||
'contextmenu',
|
'contextmenu',
|
||||||
event => {
|
event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -299,50 +331,50 @@ const listeners = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Volume change
|
// Volume change
|
||||||
utils.on(this.media, 'volumechange', () => {
|
utils.on(this.player.media, 'volumechange', () => {
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.storage.set({ volume: this.volume, muted: this.muted });
|
this.player.storage.set({ volume: this.player.volume, muted: this.player.muted });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Speed change
|
// Speed change
|
||||||
utils.on(this.media, 'ratechange', () => {
|
utils.on(this.player.media, 'ratechange', () => {
|
||||||
// Update UI
|
// Update UI
|
||||||
controls.updateSetting.call(this, 'speed');
|
controls.updateSetting.call(this.player, 'speed');
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.storage.set({ speed: this.speed });
|
this.player.storage.set({ speed: this.player.speed });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quality change
|
// Quality change
|
||||||
utils.on(this.media, 'qualitychange', () => {
|
utils.on(this.player.media, 'qualitychange', () => {
|
||||||
// Update UI
|
// Update UI
|
||||||
controls.updateSetting.call(this, 'quality');
|
controls.updateSetting.call(this.player, 'quality');
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.storage.set({ quality: this.quality });
|
this.player.storage.set({ quality: this.player.quality });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Caption language change
|
// Caption language change
|
||||||
utils.on(this.media, 'languagechange', () => {
|
utils.on(this.player.media, 'languagechange', () => {
|
||||||
// Update UI
|
// Update UI
|
||||||
controls.updateSetting.call(this, 'captions');
|
controls.updateSetting.call(this.player, 'captions');
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.storage.set({ language: this.language });
|
this.player.storage.set({ language: this.player.language });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Captions toggle
|
// Captions toggle
|
||||||
utils.on(this.media, 'captionsenabled captionsdisabled', () => {
|
utils.on(this.player.media, 'captionsenabled captionsdisabled', () => {
|
||||||
// Update UI
|
// Update UI
|
||||||
controls.updateSetting.call(this, 'captions');
|
controls.updateSetting.call(this.player, 'captions');
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.storage.set({ captions: this.captions.active });
|
this.player.storage.set({ captions: this.player.captions.active });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Proxy events to container
|
// Proxy events to container
|
||||||
// Bubble up key events for Edge
|
// Bubble up key events for Edge
|
||||||
utils.on(this.media, this.config.events.concat([
|
utils.on(this.player.media, this.player.config.events.concat([
|
||||||
'keyup',
|
'keyup',
|
||||||
'keydown',
|
'keydown',
|
||||||
]).join(' '), event => {
|
]).join(' '), event => {
|
||||||
@ -350,193 +382,200 @@ const listeners = {
|
|||||||
|
|
||||||
// Get error details from media
|
// Get error details from media
|
||||||
if (event.type === 'error') {
|
if (event.type === 'error') {
|
||||||
detail = this.media.error;
|
detail = this.player.media.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.dispatchEvent.call(this, this.elements.container, event.type, true, detail);
|
utils.dispatchEvent.call(this.player, this.player.elements.container, event.type, true, detail);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
// Listen for control events
|
// Listen for control events
|
||||||
controls() {
|
controls() {
|
||||||
// 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.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, 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, 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.elements.buttons.play, 'click', event =>
|
on(this.player.elements.buttons.play, 'click', this.player.togglePlay, 'play');
|
||||||
proxy(event, 'play', () => {
|
|
||||||
this.togglePlay();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Pause
|
// Pause
|
||||||
utils.on(this.elements.buttons.restart, 'click', event =>
|
on(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
|
||||||
proxy(event, 'restart', () => {
|
|
||||||
this.restart();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Rewind
|
// Rewind
|
||||||
utils.on(this.elements.buttons.rewind, 'click', event =>
|
on(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
|
||||||
proxy(event, 'rewind', () => {
|
|
||||||
this.rewind();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Rewind
|
// Rewind
|
||||||
utils.on(this.elements.buttons.forward, 'click', event =>
|
on(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
|
||||||
proxy(event, 'forward', () => {
|
|
||||||
this.forward();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mute toggle
|
// Mute toggle
|
||||||
utils.on(this.elements.buttons.mute, 'click', event =>
|
on(
|
||||||
proxy(event, 'mute', () => {
|
this.player.elements.buttons.mute,
|
||||||
this.muted = !this.muted;
|
'click',
|
||||||
}),
|
() => {
|
||||||
|
this.player.muted = !this.player.muted;
|
||||||
|
},
|
||||||
|
'mute',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Captions toggle
|
// Captions toggle
|
||||||
utils.on(this.elements.buttons.captions, 'click', event =>
|
on(this.player.elements.buttons.captions, 'click', this.player.toggleCaptions);
|
||||||
proxy(event, 'captions', () => {
|
|
||||||
this.toggleCaptions();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fullscreen toggle
|
// Fullscreen toggle
|
||||||
utils.on(this.elements.buttons.fullscreen, 'click', event =>
|
on(
|
||||||
proxy(event, 'fullscreen', () => {
|
this.player.elements.buttons.fullscreen,
|
||||||
this.fullscreen.toggle();
|
'click',
|
||||||
}),
|
() => {
|
||||||
|
this.player.fullscreen.toggle();
|
||||||
|
},
|
||||||
|
'fullscreen',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Picture-in-Picture
|
// Picture-in-Picture
|
||||||
utils.on(this.elements.buttons.pip, 'click', event =>
|
on(
|
||||||
proxy(event, 'pip', () => {
|
this.player.elements.buttons.pip,
|
||||||
this.pip = 'toggle';
|
'click',
|
||||||
}),
|
() => {
|
||||||
|
this.player.pip = 'toggle';
|
||||||
|
},
|
||||||
|
'pip',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Airplay
|
// Airplay
|
||||||
utils.on(this.elements.buttons.airplay, 'click', event =>
|
on(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
|
||||||
proxy(event, 'airplay', () => {
|
|
||||||
this.airplay();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Settings menu
|
// Settings menu
|
||||||
utils.on(this.elements.buttons.settings, 'click', event => {
|
on(this.player.elements.buttons.settings, 'click', event => {
|
||||||
controls.toggleMenu.call(this, event);
|
controls.toggleMenu.call(this.player, event);
|
||||||
});
|
|
||||||
|
|
||||||
// Click anywhere closes menu
|
|
||||||
utils.on(document.documentElement, 'click', event => {
|
|
||||||
controls.toggleMenu.call(this, event);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Settings menu
|
// Settings menu
|
||||||
utils.on(this.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.config.selectors.inputs.language)) {
|
if (utils.matches(event.target, this.player.config.selectors.inputs.language)) {
|
||||||
proxy(event, 'language', () => {
|
proxy(
|
||||||
this.language = event.target.value;
|
event,
|
||||||
});
|
() => {
|
||||||
} else if (utils.matches(event.target, this.config.selectors.inputs.quality)) {
|
this.player.language = event.target.value;
|
||||||
proxy(event, 'quality', () => {
|
},
|
||||||
this.quality = event.target.value;
|
'language',
|
||||||
});
|
);
|
||||||
} else if (utils.matches(event.target, this.config.selectors.inputs.speed)) {
|
} else if (utils.matches(event.target, this.player.config.selectors.inputs.quality)) {
|
||||||
proxy(event, 'speed', () => {
|
proxy(
|
||||||
this.speed = parseFloat(event.target.value);
|
event,
|
||||||
});
|
() => {
|
||||||
|
this.player.quality = event.target.value;
|
||||||
|
},
|
||||||
|
'quality',
|
||||||
|
);
|
||||||
|
} else if (utils.matches(event.target, this.player.config.selectors.inputs.speed)) {
|
||||||
|
proxy(
|
||||||
|
event,
|
||||||
|
() => {
|
||||||
|
this.player.speed = parseFloat(event.target.value);
|
||||||
|
},
|
||||||
|
'speed',
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
controls.showTab.call(this, event);
|
controls.showTab.call(this.player, event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Seek
|
// Seek
|
||||||
utils.on(this.elements.inputs.seek, inputEvent, event =>
|
on(
|
||||||
proxy(event, 'seek', () => {
|
this.player.elements.inputs.seek,
|
||||||
this.currentTime = event.target.value / event.target.max * this.duration;
|
inputEvent,
|
||||||
}),
|
event => {
|
||||||
|
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.config.toggleInvert && !utils.is.element(this.elements.display.duration)) {
|
if (this.player.config.toggleInvert && !utils.is.element(this.player.elements.display.duration)) {
|
||||||
utils.on(this.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.currentTime === 0) {
|
if (this.player.currentTime === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.config.invertTime = !this.config.invertTime;
|
this.player.config.invertTime = !this.player.config.invertTime;
|
||||||
ui.timeUpdate.call(this);
|
ui.timeUpdate.call(this.player);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume
|
// Volume
|
||||||
utils.on(this.elements.inputs.volume, inputEvent, event =>
|
on(
|
||||||
proxy(event, 'volume', () => {
|
this.player.elements.inputs.volume,
|
||||||
this.volume = event.target.value;
|
inputEvent,
|
||||||
}),
|
event => {
|
||||||
|
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, 'input[type="range"]'), 'input', event => {
|
on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => {
|
||||||
controls.updateRangeFill.call(this, event.target);
|
controls.updateRangeFill.call(this.player, event.target);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek tooltip
|
// Seek tooltip
|
||||||
utils.on(this.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this, 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.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.elements.controls, 'mouseenter mouseleave', event => {
|
on(this.player.elements.controls, 'mouseenter mouseleave', event => {
|
||||||
this.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.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
|
on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
|
||||||
this.elements.controls.pressed = [
|
this.player.elements.controls.pressed = [
|
||||||
'mousedown',
|
'mousedown',
|
||||||
'touchstart',
|
'touchstart',
|
||||||
].includes(event.type);
|
].includes(event.type);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Focus in/out on controls
|
// Focus in/out on controls
|
||||||
utils.on(this.elements.controls, 'focusin focusout', event => {
|
on(this.player.elements.controls, 'focusin focusout', event => {
|
||||||
this.toggleControls(event);
|
this.player.toggleControls(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mouse wheel for volume
|
// Mouse wheel for volume
|
||||||
utils.on(
|
on(
|
||||||
this.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;
|
||||||
@ -546,10 +585,10 @@ const listeners = {
|
|||||||
// 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.decreaseVolume(step);
|
this.player.decreaseVolume(step);
|
||||||
direction = -1;
|
direction = -1;
|
||||||
} else {
|
} else {
|
||||||
this.increaseVolume(step);
|
this.player.increaseVolume(step);
|
||||||
direction = 1;
|
direction = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -557,22 +596,28 @@ const listeners = {
|
|||||||
// 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.increaseVolume(step);
|
this.player.increaseVolume(step);
|
||||||
direction = 1;
|
direction = 1;
|
||||||
} else {
|
} else {
|
||||||
this.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.media.volume < 1) || (direction === -1 && this.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,
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export default listeners;
|
// Reset on destroy
|
||||||
|
clear() {
|
||||||
|
this.global(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Listeners;
|
||||||
|
@ -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
|
||||||
|
@ -7,22 +7,7 @@
|
|||||||
/* global google */
|
/* global google */
|
||||||
|
|
||||||
import utils from '../utils';
|
import utils from '../utils';
|
||||||
|
import i18n from '../i18n';
|
||||||
// Build the default tag URL
|
|
||||||
const getTagUrl = () => {
|
|
||||||
const params = {
|
|
||||||
AV_PUBLISHERID: '58c25bb0073ef448b1087ad6',
|
|
||||||
AV_CHANNELID: '5a0458dc28a06145e4519d21',
|
|
||||||
AV_URL: '127.0.0.1:3000',
|
|
||||||
cb: 1,
|
|
||||||
AV_WIDTH: 640,
|
|
||||||
AV_HEIGHT: 480,
|
|
||||||
};
|
|
||||||
|
|
||||||
const base = 'https://go.aniview.com/api/adserver6/vast/';
|
|
||||||
|
|
||||||
return `${base}?${utils.buildUrlParams(params)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Ads {
|
class Ads {
|
||||||
/**
|
/**
|
||||||
@ -32,39 +17,10 @@ class Ads {
|
|||||||
*/
|
*/
|
||||||
constructor(player) {
|
constructor(player) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.enabled = player.config.ads.enabled;
|
this.publisherId = player.config.ads.publisherId;
|
||||||
|
this.enabled = player.isHTML5 && player.isVideo && player.config.ads.enabled && utils.is.string(this.publisherId) && this.publisherId.length;
|
||||||
this.playing = false;
|
this.playing = false;
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
this.blocked = false;
|
|
||||||
this.enabled = utils.is.url(player.config.ads.tag);
|
|
||||||
|
|
||||||
// Check if a tag URL is provided.
|
|
||||||
if (!this.enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the Google IMA3 SDK is loaded or load it ourselves
|
|
||||||
if (!utils.is.object(window.google)) {
|
|
||||||
utils.loadScript(
|
|
||||||
player.config.urls.googleIMA.api,
|
|
||||||
() => {
|
|
||||||
this.ready();
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
// Script failed to load or is blocked
|
|
||||||
this.blocked = true;
|
|
||||||
this.player.debug.log('Ads error: Google IMA SDK failed to load');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.ready();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the ads instance ready.
|
|
||||||
*/
|
|
||||||
ready() {
|
|
||||||
this.elements = {
|
this.elements = {
|
||||||
container: null,
|
container: null,
|
||||||
displayContainer: null,
|
displayContainer: null,
|
||||||
@ -76,32 +32,77 @@ class Ads {
|
|||||||
this.safetyTimer = null;
|
this.safetyTimer = null;
|
||||||
this.countdownTimer = null;
|
this.countdownTimer = null;
|
||||||
|
|
||||||
// Set listeners on the Plyr instance
|
// Setup a promise to resolve when the IMA manager is ready
|
||||||
this.listeners();
|
this.managerPromise = new Promise((resolve, reject) => {
|
||||||
|
// The ad is loaded and ready
|
||||||
|
this.on('loaded', resolve);
|
||||||
|
|
||||||
|
// Ads failed
|
||||||
|
this.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the IMA SDK
|
||||||
|
*/
|
||||||
|
load() {
|
||||||
|
if (this.enabled) {
|
||||||
|
// Check if the Google IMA3 SDK is loaded or load it ourselves
|
||||||
|
if (!utils.is.object(window.google) || !utils.is.object(window.google.ima)) {
|
||||||
|
utils
|
||||||
|
.loadScript(this.player.config.urls.googleIMA.api)
|
||||||
|
.then(() => {
|
||||||
|
this.ready();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Script failed to load or is blocked
|
||||||
|
this.trigger('error', new Error('Google IMA SDK failed to load'));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.ready();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ads instance ready
|
||||||
|
*/
|
||||||
|
ready() {
|
||||||
// Start ticking our safety timer. If the whole advertisement
|
// Start ticking our safety timer. If the whole advertisement
|
||||||
// thing doesn't resolve within our set time; we bail
|
// thing doesn't resolve within our set time; we bail
|
||||||
this.startSafetyTimer(12000, 'ready()');
|
this.startSafetyTimer(12000, 'ready()');
|
||||||
|
|
||||||
// Setup a simple promise to resolve if the IMA loader is ready
|
|
||||||
this.loaderPromise = new Promise(resolve => {
|
|
||||||
this.on('ADS_LOADER_LOADED', () => resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup a promise to resolve if the IMA manager is ready
|
|
||||||
this.managerPromise = new Promise(resolve => {
|
|
||||||
this.on('ADS_MANAGER_LOADED', () => resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear the safety timer
|
// Clear the safety timer
|
||||||
this.managerPromise.then(() => {
|
this.managerPromise.then(() => {
|
||||||
this.clearSafetyTimer('onAdsManagerLoaded()');
|
this.clearSafetyTimer('onAdsManagerLoaded()');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set listeners on the Plyr instance
|
||||||
|
this.listeners();
|
||||||
|
|
||||||
// Setup the IMA SDK
|
// Setup the IMA SDK
|
||||||
this.setupIMA();
|
this.setupIMA();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the default tag URL
|
||||||
|
get tagUrl() {
|
||||||
|
const params = {
|
||||||
|
AV_PUBLISHERID: '58c25bb0073ef448b1087ad6',
|
||||||
|
AV_CHANNELID: '5a0458dc28a06145e4519d21',
|
||||||
|
AV_URL: location.hostname,
|
||||||
|
cb: Date.now(),
|
||||||
|
AV_WIDTH: 640,
|
||||||
|
AV_HEIGHT: 480,
|
||||||
|
AV_CDIM2: this.publisherId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const base = 'https://go.aniview.com/api/adserver6/vast/';
|
||||||
|
|
||||||
|
return `${base}?${utils.buildUrlParams(params)}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In order for the SDK to display ads for our video, we need to tell it where to put them,
|
* In order for the SDK to display ads for our video, we need to tell it where to put them,
|
||||||
* so here we define our ad container. This div is set up to render on top of the video player.
|
* so here we define our ad container. This div is set up to render on top of the video player.
|
||||||
@ -114,7 +115,6 @@ class Ads {
|
|||||||
// Create the container for our advertisements
|
// Create the container for our advertisements
|
||||||
this.elements.container = utils.createElement('div', {
|
this.elements.container = utils.createElement('div', {
|
||||||
class: this.player.config.classNames.ads,
|
class: this.player.config.classNames.ads,
|
||||||
hidden: '',
|
|
||||||
});
|
});
|
||||||
this.player.elements.container.appendChild(this.elements.container);
|
this.player.elements.container.appendChild(this.elements.container);
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ class Ads {
|
|||||||
|
|
||||||
// Request video ads
|
// Request video ads
|
||||||
const request = new google.ima.AdsRequest();
|
const request = new google.ima.AdsRequest();
|
||||||
request.adTagUrl = getTagUrl();
|
request.adTagUrl = this.tagUrl;
|
||||||
|
|
||||||
// Specify the linear and nonlinear slot sizes. This helps the SDK
|
// Specify the linear and nonlinear slot sizes. This helps the SDK
|
||||||
// to select the correct creative if multiple are returned
|
// to select the correct creative if multiple are returned
|
||||||
@ -161,8 +161,6 @@ class Ads {
|
|||||||
request.forceNonLinearFullSlot = false;
|
request.forceNonLinearFullSlot = false;
|
||||||
|
|
||||||
this.loader.requestAds(request);
|
this.loader.requestAds(request);
|
||||||
|
|
||||||
this.handleEventListeners('ADS_LOADER_LOADED');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.onAdError(e);
|
this.onAdError(e);
|
||||||
}
|
}
|
||||||
@ -174,25 +172,25 @@ class Ads {
|
|||||||
*/
|
*/
|
||||||
pollCountdown(start = false) {
|
pollCountdown(start = false) {
|
||||||
if (!start) {
|
if (!start) {
|
||||||
window.clearInterval(this.countdownTimer);
|
clearInterval(this.countdownTimer);
|
||||||
this.elements.container.removeAttribute('data-badge-text');
|
this.elements.container.removeAttribute('data-badge-text');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
const time = utils.formatTime(this.manager.getRemainingTime());
|
const time = utils.formatTime(Math.max(this.manager.getRemainingTime(), 0));
|
||||||
const label = `${this.player.config.i18n.advertisment} - ${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);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.countdownTimer = window.setInterval(update, 100);
|
this.countdownTimer = setInterval(update, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called whenever the ads are ready inside the AdDisplayContainer
|
* This method is called whenever the ads are ready inside the AdDisplayContainer
|
||||||
* @param {Event} adsManagerLoadedEvent
|
* @param {Event} adsManagerLoadedEvent
|
||||||
*/
|
*/
|
||||||
onAdsManagerLoaded(adsManagerLoadedEvent) {
|
onAdsManagerLoaded(event) {
|
||||||
// Get the ads manager
|
// Get the ads manager
|
||||||
const settings = new google.ima.AdsRenderingSettings();
|
const settings = new google.ima.AdsRenderingSettings();
|
||||||
|
|
||||||
@ -202,14 +200,14 @@ class Ads {
|
|||||||
|
|
||||||
// The SDK is polling currentTime on the contentPlayback. And needs a duration
|
// The SDK is polling currentTime on the contentPlayback. And needs a duration
|
||||||
// so it can determine when to start the mid- and post-roll
|
// so it can determine when to start the mid- and post-roll
|
||||||
this.manager = adsManagerLoadedEvent.getAdsManager(this.player, settings);
|
this.manager = event.getAdsManager(this.player, settings);
|
||||||
|
|
||||||
// Get the cue points for any mid-rolls by filtering out the pre- and post-roll
|
// Get the cue points for any mid-rolls by filtering out the pre- and post-roll
|
||||||
this.cuePoints = this.manager.getCuePoints();
|
this.cuePoints = this.manager.getCuePoints();
|
||||||
|
|
||||||
// Add advertisement cue's within the time line if available
|
// Add advertisement cue's within the time line if available
|
||||||
this.cuePoints.forEach(cuePoint => {
|
this.cuePoints.forEach(cuePoint => {
|
||||||
if (cuePoint !== 0 && cuePoint !== -1) {
|
if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) {
|
||||||
const seekElement = this.player.elements.progress;
|
const seekElement = this.player.elements.progress;
|
||||||
|
|
||||||
if (seekElement) {
|
if (seekElement) {
|
||||||
@ -241,7 +239,7 @@ class Ads {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Resolve our adsManager
|
// Resolve our adsManager
|
||||||
this.handleEventListeners('ADS_MANAGER_LOADED');
|
this.trigger('loaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -259,17 +257,18 @@ class Ads {
|
|||||||
|
|
||||||
// Proxy event
|
// Proxy event
|
||||||
const dispatchEvent = type => {
|
const dispatchEvent = type => {
|
||||||
utils.dispatchEvent.call(this.player, this.player.media, `ads${type}`);
|
const event = `ads${type.replace(/_/g, '').toLowerCase()}`;
|
||||||
|
utils.dispatchEvent.call(this.player, this.player.media, event);
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case google.ima.AdEvent.Type.LOADED:
|
case google.ima.AdEvent.Type.LOADED:
|
||||||
// This is the first event sent for an ad - it is possible to determine whether the
|
// This is the first event sent for an ad - it is possible to determine whether the
|
||||||
// ad is a video ad or an overlay
|
// ad is a video ad or an overlay
|
||||||
this.handleEventListeners('LOADED');
|
this.trigger('loaded');
|
||||||
|
|
||||||
// Bubble event
|
// Bubble event
|
||||||
dispatchEvent('loaded');
|
dispatchEvent(event.type);
|
||||||
|
|
||||||
// Start countdown
|
// Start countdown
|
||||||
this.pollCountdown(true);
|
this.pollCountdown(true);
|
||||||
@ -287,10 +286,9 @@ class Ads {
|
|||||||
case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
|
case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
|
||||||
// All ads for the current videos are done. We can now request new advertisements
|
// All ads for the current videos are done. We can now request new advertisements
|
||||||
// in case the video is re-played
|
// in case the video is re-played
|
||||||
this.handleEventListeners('ALL_ADS_COMPLETED');
|
|
||||||
|
|
||||||
// Fire event
|
// Fire event
|
||||||
dispatchEvent('allcomplete');
|
dispatchEvent(event.type);
|
||||||
|
|
||||||
// TODO: Example for what happens when a next video in a playlist would be loaded.
|
// TODO: Example for what happens when a next video in a playlist would be loaded.
|
||||||
// So here we load a new video when all ads are done.
|
// So here we load a new video when all ads are done.
|
||||||
@ -322,9 +320,8 @@ class Ads {
|
|||||||
// This event indicates the ad has started - the video player can adjust the UI,
|
// This event indicates the ad has started - the video player can adjust the UI,
|
||||||
// for example display a pause button and remaining time. Fired when content should
|
// for example display a pause button and remaining time. Fired when content should
|
||||||
// be paused. This usually happens right before an ad is about to cover the content
|
// be paused. This usually happens right before an ad is about to cover the content
|
||||||
this.handleEventListeners('CONTENT_PAUSE_REQUESTED');
|
|
||||||
|
|
||||||
dispatchEvent('contentpause');
|
dispatchEvent(event.type);
|
||||||
|
|
||||||
this.pauseContent();
|
this.pauseContent();
|
||||||
|
|
||||||
@ -335,9 +332,8 @@ class Ads {
|
|||||||
// appropriate UI actions, such as removing the timer for remaining time detection.
|
// appropriate UI actions, such as removing the timer for remaining time detection.
|
||||||
// Fired when content should be resumed. This usually happens when an ad finishes
|
// Fired when content should be resumed. This usually happens when an ad finishes
|
||||||
// or collapses
|
// or collapses
|
||||||
this.handleEventListeners('CONTENT_RESUME_REQUESTED');
|
|
||||||
|
|
||||||
dispatchEvent('contentresume');
|
dispatchEvent(event.type);
|
||||||
|
|
||||||
this.pollCountdown();
|
this.pollCountdown();
|
||||||
|
|
||||||
@ -346,23 +342,11 @@ class Ads {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.STARTED:
|
case google.ima.AdEvent.Type.STARTED:
|
||||||
dispatchEvent('started');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.MIDPOINT:
|
case google.ima.AdEvent.Type.MIDPOINT:
|
||||||
dispatchEvent('midpoint');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.COMPLETE:
|
case google.ima.AdEvent.Type.COMPLETE:
|
||||||
dispatchEvent('complete');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.IMPRESSION:
|
case google.ima.AdEvent.Type.IMPRESSION:
|
||||||
dispatchEvent('impression');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.CLICK:
|
case google.ima.AdEvent.Type.CLICK:
|
||||||
dispatchEvent('click');
|
dispatchEvent(event.type);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -376,7 +360,7 @@ class Ads {
|
|||||||
*/
|
*/
|
||||||
onAdError(event) {
|
onAdError(event) {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
this.player.debug.log('Ads error', event);
|
this.player.debug.warn('Ads error', event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -423,11 +407,12 @@ class Ads {
|
|||||||
const { container } = this.player.elements;
|
const { container } = this.player.elements;
|
||||||
|
|
||||||
if (!this.managerPromise) {
|
if (!this.managerPromise) {
|
||||||
return;
|
this.resumeContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play the requested advertisement whenever the adsManager is ready
|
// Play the requested advertisement whenever the adsManager is ready
|
||||||
this.managerPromise.then(() => {
|
this.managerPromise
|
||||||
|
.then(() => {
|
||||||
// Initialize the container. Must be done via a user action on mobile devices
|
// Initialize the container. Must be done via a user action on mobile devices
|
||||||
this.elements.displayContainer.initialize();
|
this.elements.displayContainer.initialize();
|
||||||
|
|
||||||
@ -447,15 +432,16 @@ class Ads {
|
|||||||
// VAST response
|
// VAST response
|
||||||
this.onAdError(adError);
|
this.onAdError(adError);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resume our video.
|
* Resume our video
|
||||||
*/
|
*/
|
||||||
resumeContent() {
|
resumeContent() {
|
||||||
// Hide our ad container
|
// Hide the advertisement container
|
||||||
utils.toggleHidden(this.elements.container, true);
|
this.elements.container.style.zIndex = '';
|
||||||
|
|
||||||
// Ad is stopped
|
// Ad is stopped
|
||||||
this.playing = false;
|
this.playing = false;
|
||||||
@ -470,8 +456,8 @@ class Ads {
|
|||||||
* Pause our video
|
* Pause our video
|
||||||
*/
|
*/
|
||||||
pauseContent() {
|
pauseContent() {
|
||||||
// Show our ad container.
|
// Show the advertisement container
|
||||||
utils.toggleHidden(this.elements.container, false);
|
this.elements.container.style.zIndex = 3;
|
||||||
|
|
||||||
// Ad is playing.
|
// Ad is playing.
|
||||||
this.playing = true;
|
this.playing = true;
|
||||||
@ -493,7 +479,7 @@ class Ads {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tell our instance that we're done for now
|
// Tell our instance that we're done for now
|
||||||
this.handleEventListeners('ERROR');
|
this.trigger('error');
|
||||||
|
|
||||||
// Re-create our adsManager
|
// Re-create our adsManager
|
||||||
this.loadAds();
|
this.loadAds();
|
||||||
@ -504,7 +490,8 @@ class Ads {
|
|||||||
*/
|
*/
|
||||||
loadAds() {
|
loadAds() {
|
||||||
// Tell our adsManager to go bye bye
|
// Tell our adsManager to go bye bye
|
||||||
this.managerPromise.then(() => {
|
this.managerPromise
|
||||||
|
.then(() => {
|
||||||
// Destroy our adsManager
|
// Destroy our adsManager
|
||||||
if (this.manager) {
|
if (this.manager) {
|
||||||
this.manager.destroy();
|
this.manager.destroy();
|
||||||
@ -512,22 +499,29 @@ class Ads {
|
|||||||
|
|
||||||
// Re-set our adsManager promises
|
// Re-set our adsManager promises
|
||||||
this.managerPromise = new Promise(resolve => {
|
this.managerPromise = new Promise(resolve => {
|
||||||
this.on('ADS_MANAGER_LOADED', () => resolve());
|
this.on('loaded', resolve);
|
||||||
this.player.debug.log(this.manager);
|
this.player.debug.log(this.manager);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Now request some new advertisements
|
// Now request some new advertisements
|
||||||
this.requestAds();
|
this.requestAds();
|
||||||
});
|
})
|
||||||
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles callbacks after an ad event was invoked
|
* Handles callbacks after an ad event was invoked
|
||||||
* @param {string} event - Event type
|
* @param {string} event - Event type
|
||||||
*/
|
*/
|
||||||
handleEventListeners(event) {
|
trigger(event, ...args) {
|
||||||
if (utils.is.function(this.events[event])) {
|
const handlers = this.events[event];
|
||||||
this.events[event].call(this);
|
|
||||||
|
if (utils.is.array(handlers)) {
|
||||||
|
handlers.forEach(handler => {
|
||||||
|
if (utils.is.function(handler)) {
|
||||||
|
handler.apply(this, args);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,7 +532,12 @@ class Ads {
|
|||||||
* @return {Ads}
|
* @return {Ads}
|
||||||
*/
|
*/
|
||||||
on(event, callback) {
|
on(event, callback) {
|
||||||
this.events[event] = callback;
|
if (!utils.is.array(this.events[event])) {
|
||||||
|
this.events[event] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.events[event].push(callback);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,7 +552,7 @@ class Ads {
|
|||||||
startSafetyTimer(time, from) {
|
startSafetyTimer(time, from) {
|
||||||
this.player.debug.log(`Safety timer invoked from: ${from}`);
|
this.player.debug.log(`Safety timer invoked from: ${from}`);
|
||||||
|
|
||||||
this.safetyTimer = window.setTimeout(() => {
|
this.safetyTimer = setTimeout(() => {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
this.clearSafetyTimer('startSafetyTimer()');
|
this.clearSafetyTimer('startSafetyTimer()');
|
||||||
}, time);
|
}, time);
|
||||||
|
@ -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 = {
|
||||||
@ -16,8 +17,13 @@ const vimeo = {
|
|||||||
|
|
||||||
// Load the API if not already
|
// Load the API if not already
|
||||||
if (!utils.is.object(window.Vimeo)) {
|
if (!utils.is.object(window.Vimeo)) {
|
||||||
utils.loadScript(this.config.urls.vimeo.api, () => {
|
utils
|
||||||
|
.loadScript(this.config.urls.vimeo.api)
|
||||||
|
.then(() => {
|
||||||
vimeo.ready.call(this);
|
vimeo.ready.call(this);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.debug.warn('Vimeo API failed to load', error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
vimeo.ready.call(this);
|
vimeo.ready.call(this);
|
||||||
@ -29,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}%)`;
|
||||||
@ -96,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
|
||||||
@ -136,9 +140,17 @@ const vimeo = {
|
|||||||
return speed;
|
return speed;
|
||||||
},
|
},
|
||||||
set(input) {
|
set(input) {
|
||||||
player.embed.setPlaybackRate(input).then(() => {
|
player.embed
|
||||||
|
.setPlaybackRate(input)
|
||||||
|
.then(() => {
|
||||||
speed = input;
|
speed = input;
|
||||||
utils.dispatchEvent.call(player, player.media, 'ratechange');
|
utils.dispatchEvent.call(player, player.media, 'ratechange');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Hide menu item (and menu if empty)
|
||||||
|
if (error.name === 'Error') {
|
||||||
|
controls.setSpeedMenu.call(player, []);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -311,7 +323,7 @@ const vimeo = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Rebuild UI
|
// Rebuild UI
|
||||||
window.setTimeout(() => ui.build.call(player), 0);
|
setTimeout(() => ui.build.call(player), 0);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,7 +19,9 @@ const youtube = {
|
|||||||
youtube.ready.call(this);
|
youtube.ready.call(this);
|
||||||
} else {
|
} else {
|
||||||
// Load the API
|
// Load the API
|
||||||
utils.loadScript(this.config.urls.youtube.api);
|
utils.loadScript(this.config.urls.youtube.api).catch(error => {
|
||||||
|
this.debug.warn('YouTube API failed to load', error);
|
||||||
|
});
|
||||||
|
|
||||||
// Setup callback for the API
|
// Setup callback for the API
|
||||||
// YouTube has it's own system of course...
|
// YouTube has it's own system of course...
|
||||||
@ -194,17 +196,14 @@ const youtube = {
|
|||||||
// Create a faux HTML5 API using the YouTube API
|
// Create a faux HTML5 API using the YouTube API
|
||||||
player.media.play = () => {
|
player.media.play = () => {
|
||||||
instance.playVideo();
|
instance.playVideo();
|
||||||
player.media.paused = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
player.media.pause = () => {
|
player.media.pause = () => {
|
||||||
instance.pauseVideo();
|
instance.pauseVideo();
|
||||||
player.media.paused = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
player.media.stop = () => {
|
player.media.stop = () => {
|
||||||
instance.stopVideo();
|
instance.stopVideo();
|
||||||
player.media.paused = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
player.media.duration = instance.getDuration();
|
player.media.duration = instance.getDuration();
|
||||||
@ -295,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) {
|
||||||
@ -306,10 +306,10 @@ const youtube = {
|
|||||||
utils.dispatchEvent.call(player, player.media, 'durationchange');
|
utils.dispatchEvent.call(player, player.media, 'durationchange');
|
||||||
|
|
||||||
// Reset timer
|
// Reset timer
|
||||||
window.clearInterval(player.timers.buffering);
|
clearInterval(player.timers.buffering);
|
||||||
|
|
||||||
// Setup buffering
|
// Setup buffering
|
||||||
player.timers.buffering = window.setInterval(() => {
|
player.timers.buffering = setInterval(() => {
|
||||||
// Get loaded % from YouTube
|
// Get loaded % from YouTube
|
||||||
player.media.buffered = instance.getVideoLoadedFraction();
|
player.media.buffered = instance.getVideoLoadedFraction();
|
||||||
|
|
||||||
@ -323,7 +323,7 @@ const youtube = {
|
|||||||
|
|
||||||
// Bail if we're at 100%
|
// Bail if we're at 100%
|
||||||
if (player.media.buffered === 1) {
|
if (player.media.buffered === 1) {
|
||||||
window.clearInterval(player.timers.buffering);
|
clearInterval(player.timers.buffering);
|
||||||
|
|
||||||
// Trigger event
|
// Trigger event
|
||||||
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
|
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
|
||||||
@ -331,14 +331,14 @@ const youtube = {
|
|||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
// Rebuild UI
|
// Rebuild UI
|
||||||
window.setTimeout(() => ui.build.call(player), 50);
|
setTimeout(() => ui.build.call(player), 50);
|
||||||
},
|
},
|
||||||
onStateChange(event) {
|
onStateChange(event) {
|
||||||
// Get the instance
|
// Get the instance
|
||||||
const instance = event.target;
|
const instance = event.target;
|
||||||
|
|
||||||
// Reset timer
|
// Reset timer
|
||||||
window.clearInterval(player.timers.playing);
|
clearInterval(player.timers.playing);
|
||||||
|
|
||||||
// Handle events
|
// Handle events
|
||||||
// -1 Unstarted
|
// -1 Unstarted
|
||||||
@ -348,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;
|
||||||
|
|
||||||
@ -378,7 +388,7 @@ const youtube = {
|
|||||||
utils.dispatchEvent.call(player, player.media, 'playing');
|
utils.dispatchEvent.call(player, player.media, 'playing');
|
||||||
|
|
||||||
// Poll to get playback progress
|
// Poll to get playback progress
|
||||||
player.timers.playing = window.setInterval(() => {
|
player.timers.playing = setInterval(() => {
|
||||||
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
|
171
src/js/plyr.js
171
src/js/plyr.js
@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr
|
// Plyr
|
||||||
// plyr.js v3.0.0-beta.13
|
// plyr.js v3.0.7
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@ -12,12 +12,12 @@ import utils from './utils';
|
|||||||
|
|
||||||
import Console from './console';
|
import Console from './console';
|
||||||
import Fullscreen from './fullscreen';
|
import Fullscreen from './fullscreen';
|
||||||
|
import Listeners from './listeners';
|
||||||
import Storage from './storage';
|
import Storage from './storage';
|
||||||
import Ads from './plugins/ads';
|
import Ads from './plugins/ads';
|
||||||
|
|
||||||
import captions from './captions';
|
import captions from './captions';
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
import listeners from './listeners';
|
|
||||||
import media from './media';
|
import media from './media';
|
||||||
import source from './source';
|
import source from './source';
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
@ -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;
|
||||||
|
|
||||||
@ -235,6 +238,9 @@ class Plyr {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create listeners
|
||||||
|
this.listeners = new Listeners(this);
|
||||||
|
|
||||||
// Setup local storage for user settings
|
// Setup local storage for user settings
|
||||||
this.storage = new Storage(this);
|
this.storage = new Storage(this);
|
||||||
|
|
||||||
@ -250,9 +256,6 @@ class Plyr {
|
|||||||
// Allow focus to be captured
|
// Allow focus to be captured
|
||||||
this.elements.container.setAttribute('tabindex', 0);
|
this.elements.container.setAttribute('tabindex', 0);
|
||||||
|
|
||||||
// Global listeners
|
|
||||||
listeners.global.call(this);
|
|
||||||
|
|
||||||
// Add style hook
|
// Add style hook
|
||||||
ui.addStyleHook.call(this);
|
ui.addStyleHook.call(this);
|
||||||
|
|
||||||
@ -272,6 +275,12 @@ class Plyr {
|
|||||||
ui.build.call(this);
|
ui.build.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Container listeners
|
||||||
|
this.listeners.container();
|
||||||
|
|
||||||
|
// Global listeners
|
||||||
|
this.listeners.global();
|
||||||
|
|
||||||
// Setup fullscreen
|
// Setup fullscreen
|
||||||
this.fullscreen = new Fullscreen(this);
|
this.fullscreen = new Fullscreen(this);
|
||||||
|
|
||||||
@ -287,34 +296,37 @@ class Plyr {
|
|||||||
* Types and provider helpers
|
* Types and provider helpers
|
||||||
*/
|
*/
|
||||||
get isHTML5() {
|
get isHTML5() {
|
||||||
return this.provider === providers.html5;
|
return Boolean(this.provider === providers.html5);
|
||||||
}
|
}
|
||||||
get isEmbed() {
|
get isEmbed() {
|
||||||
return this.isYouTube || this.isVimeo;
|
return Boolean(this.isYouTube || this.isVimeo);
|
||||||
}
|
}
|
||||||
get isYouTube() {
|
get isYouTube() {
|
||||||
return this.provider === providers.youtube;
|
return Boolean(this.provider === providers.youtube);
|
||||||
}
|
}
|
||||||
get isVimeo() {
|
get isVimeo() {
|
||||||
return this.provider === providers.vimeo;
|
return Boolean(this.provider === providers.vimeo);
|
||||||
}
|
}
|
||||||
get isVideo() {
|
get isVideo() {
|
||||||
return this.type === types.video;
|
return Boolean(this.type === types.video);
|
||||||
}
|
}
|
||||||
get isAudio() {
|
get isAudio() {
|
||||||
return this.type === types.audio;
|
return Boolean(this.type === types.audio);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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() {
|
||||||
// TODO: Always return a promise?
|
if (!utils.is.function(this.media.play)) {
|
||||||
if (this.ads.enabled && !this.ads.initialized && !this.ads.blocked) {
|
|
||||||
this.ads.play();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If ads are enabled, wait for them first
|
||||||
|
if (this.ads.enabled && !this.ads.initialized) {
|
||||||
|
return this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play());
|
||||||
|
}
|
||||||
|
|
||||||
// Return the promise (for HTML5)
|
// Return the promise (for HTML5)
|
||||||
return this.media.play();
|
return this.media.play();
|
||||||
}
|
}
|
||||||
@ -323,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,21 +346,21 @@ class Plyr {
|
|||||||
* Get paused state
|
* Get paused state
|
||||||
*/
|
*/
|
||||||
get paused() {
|
get paused() {
|
||||||
return this.media.paused;
|
return Boolean(this.media.paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get playing state
|
* Get playing state
|
||||||
*/
|
*/
|
||||||
get playing() {
|
get playing() {
|
||||||
return !this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true);
|
return Boolean(!this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get ended state
|
* Get ended state
|
||||||
*/
|
*/
|
||||||
get ended() {
|
get ended() {
|
||||||
return this.media.ended;
|
return Boolean(this.media.ended);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -370,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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -416,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`);
|
||||||
@ -429,11 +444,32 @@ class Plyr {
|
|||||||
return Number(this.media.currentTime);
|
return Number(this.media.currentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get buffered
|
||||||
|
*/
|
||||||
|
get buffered() {
|
||||||
|
const { buffered } = this.media;
|
||||||
|
|
||||||
|
// YouTube / Vimeo return a float between 0-1
|
||||||
|
if (utils.is.number(buffered)) {
|
||||||
|
return buffered;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML5
|
||||||
|
// TODO: Handle buffered chunks of the media
|
||||||
|
// (i.e. seek to another section buffers only that section)
|
||||||
|
if (buffered && buffered.length && this.duration > 0) {
|
||||||
|
return buffered.end(0) / this.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get seeking status
|
* Get seeking status
|
||||||
*/
|
*/
|
||||||
get seeking() {
|
get seeking() {
|
||||||
return this.media.seeking;
|
return Boolean(this.media.seeking);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -444,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;
|
||||||
@ -498,7 +534,7 @@ class Plyr {
|
|||||||
* Get the current player volume
|
* Get the current player volume
|
||||||
*/
|
*/
|
||||||
get volume() {
|
get volume() {
|
||||||
return this.media.volume;
|
return Number(this.media.volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -547,7 +583,7 @@ class Plyr {
|
|||||||
* Get current muted state
|
* Get current muted state
|
||||||
*/
|
*/
|
||||||
get muted() {
|
get muted() {
|
||||||
return this.media.muted;
|
return Boolean(this.media.muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -564,12 +600,16 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get audio tracks
|
// Get audio tracks
|
||||||
return this.media.mozHasAudio || Boolean(this.media.webkitAudioDecodedByteCount) || Boolean(this.media.audioTracks && this.media.audioTracks.length);
|
return (
|
||||||
|
Boolean(this.media.mozHasAudio) ||
|
||||||
|
Boolean(this.media.webkitAudioDecodedByteCount) ||
|
||||||
|
Boolean(this.media.audioTracks && this.media.audioTracks.length)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set playback speed
|
* Set playback speed
|
||||||
* @param {decimal} speed - the speed of playback (0.5-2.0)
|
* @param {number} speed - the speed of playback (0.5-2.0)
|
||||||
*/
|
*/
|
||||||
set speed(input) {
|
set speed(input) {
|
||||||
let speed = null;
|
let speed = null;
|
||||||
@ -610,7 +650,7 @@ class Plyr {
|
|||||||
* Get current playback speed
|
* Get current playback speed
|
||||||
*/
|
*/
|
||||||
get speed() {
|
get speed() {
|
||||||
return this.media.playbackRate;
|
return Number(this.media.playbackRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -710,7 +750,7 @@ class Plyr {
|
|||||||
* Get current loop state
|
* Get current loop state
|
||||||
*/
|
*/
|
||||||
get loop() {
|
get loop() {
|
||||||
return this.media.loop;
|
return Boolean(this.media.loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -767,7 +807,7 @@ class Plyr {
|
|||||||
* Get the current autoplay state
|
* Get the current autoplay state
|
||||||
*/
|
*/
|
||||||
get autoplay() {
|
get autoplay() {
|
||||||
return this.config.autoplay;
|
return Boolean(this.config.autoplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -917,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);
|
||||||
}
|
}
|
||||||
@ -946,7 +992,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear timer on every call
|
// Clear timer on every call
|
||||||
window.clearTimeout(this.timers.controls);
|
clearTimeout(this.timers.controls);
|
||||||
|
|
||||||
// If the mouse is not over the controls, set a timeout to hide them
|
// If the mouse is not over the controls, set a timeout to hide them
|
||||||
if (show || this.paused || this.loading) {
|
if (show || this.paused || this.loading) {
|
||||||
@ -964,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -972,15 +1018,7 @@ class Plyr {
|
|||||||
// If toggle is false or if we're playing (regardless of toggle),
|
// If toggle is false or if we're playing (regardless of toggle),
|
||||||
// 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 = setTimeout(() => {
|
||||||
/* this.debug.warn({
|
|
||||||
pressed: this.elements.controls.pressed,
|
|
||||||
hover: this.elements.controls.pressed,
|
|
||||||
playing: this.playing,
|
|
||||||
paused: this.paused,
|
|
||||||
loading: this.loading,
|
|
||||||
}); */
|
|
||||||
|
|
||||||
// If the mouse is over the controls (and not entering fullscreen), bail
|
// If the mouse is over the controls (and not entering fullscreen), bail
|
||||||
if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
|
if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
|
||||||
return;
|
return;
|
||||||
@ -1032,6 +1070,10 @@ class Plyr {
|
|||||||
* @param {boolean} soft - Whether it's a soft destroy (for source changes etc)
|
* @param {boolean} soft - Whether it's a soft destroy (for source changes etc)
|
||||||
*/
|
*/
|
||||||
destroy(callback, soft = false) {
|
destroy(callback, soft = false) {
|
||||||
|
if (!this.ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const done = () => {
|
const done = () => {
|
||||||
// Reset overflow (incase destroyed while in fullscreen)
|
// Reset overflow (incase destroyed while in fullscreen)
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
@ -1060,6 +1102,9 @@ class Plyr {
|
|||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Unbind listeners
|
||||||
|
this.listeners.clear();
|
||||||
|
|
||||||
// Replace the container with the original element provided
|
// Replace the container with the original element provided
|
||||||
utils.replaceElement(this.elements.original, this.elements.container);
|
utils.replaceElement(this.elements.original, this.elements.container);
|
||||||
|
|
||||||
@ -1071,15 +1116,27 @@ class Plyr {
|
|||||||
callback.call(this.elements.original);
|
callback.call(this.elements.original);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear for GC
|
// Reset state
|
||||||
|
this.ready = false;
|
||||||
|
|
||||||
|
// Clear for garbage collection
|
||||||
|
setTimeout(() => {
|
||||||
this.elements = null;
|
this.elements = null;
|
||||||
|
this.media = null;
|
||||||
|
}, 200);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Stop playback
|
||||||
|
this.stop();
|
||||||
|
|
||||||
// Type specific stuff
|
// Type specific stuff
|
||||||
switch (`${this.provider}:${this.type}`) {
|
switch (`${this.provider}:${this.type}`) {
|
||||||
case 'html5:video':
|
case 'html5:video':
|
||||||
case 'html5:audio':
|
case 'html5:audio':
|
||||||
|
// Clear timeout
|
||||||
|
clearTimeout(this.timers.loading);
|
||||||
|
|
||||||
// Restore native video controls
|
// Restore native video controls
|
||||||
ui.toggleNativeControls.call(this, true);
|
ui.toggleNativeControls.call(this, true);
|
||||||
|
|
||||||
@ -1090,11 +1147,11 @@ class Plyr {
|
|||||||
|
|
||||||
case 'youtube:video':
|
case 'youtube:video':
|
||||||
// Clear timers
|
// Clear timers
|
||||||
window.clearInterval(this.timers.buffering);
|
clearInterval(this.timers.buffering);
|
||||||
window.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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1111,7 +1168,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vimeo does not always return
|
// Vimeo does not always return
|
||||||
window.setTimeout(done, 200);
|
setTimeout(done, 200);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
14
src/js/plyr.polyfilled.js
Normal file
14
src/js/plyr.polyfilled.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Plyr Polyfilled Build
|
||||||
|
// plyr.js v3.0.7
|
||||||
|
// https://github.com/sampotts/plyr
|
||||||
|
// License: The MIT License (MIT)
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
import 'babel-polyfill';
|
||||||
|
|
||||||
|
import 'custom-event-polyfill';
|
||||||
|
|
||||||
|
import Plyr from './plyr';
|
||||||
|
|
||||||
|
export default Plyr;
|
@ -12,6 +12,7 @@ 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() {
|
||||||
|
try {
|
||||||
if (!('localStorage' in window)) {
|
if (!('localStorage' in window)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -20,9 +21,9 @@ class Storage {
|
|||||||
|
|
||||||
// Try to use it (it might be disabled, e.g. user is in private mode)
|
// Try to use it (it might be disabled, e.g. user is in private mode)
|
||||||
// see: https://github.com/sampotts/plyr/issues/131
|
// see: https://github.com/sampotts/plyr/issues/131
|
||||||
try {
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,13 +30,9 @@ const support = {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'youtube:video':
|
case 'youtube:video':
|
||||||
api = true;
|
|
||||||
ui = support.rangeInput && (!browser.isIPhone || playsInline);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'vimeo:video':
|
case 'vimeo:video':
|
||||||
api = true;
|
api = true;
|
||||||
ui = support.rangeInput && !browser.isIPhone;
|
ui = support.rangeInput && (!browser.isIPhone || playsInline);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -147,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
|
||||||
|
36
src/js/ui.js
36
src/js/ui.js
@ -5,7 +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 listeners from './listeners';
|
import i18n from './i18n';
|
||||||
|
|
||||||
const ui = {
|
const ui = {
|
||||||
addStyleHook() {
|
addStyleHook() {
|
||||||
@ -25,8 +25,8 @@ const ui = {
|
|||||||
// Setup the UI
|
// Setup the UI
|
||||||
build() {
|
build() {
|
||||||
// Re-attach media element listeners
|
// Re-attach media element listeners
|
||||||
// TODO: Use event bubbling
|
// TODO: Use event bubbling?
|
||||||
listeners.media.call(this);
|
this.listeners.media();
|
||||||
|
|
||||||
// Don't setup interface if no support
|
// Don't setup interface if no support
|
||||||
if (!this.supported.ui) {
|
if (!this.supported.ui) {
|
||||||
@ -45,7 +45,7 @@ const ui = {
|
|||||||
controls.inject.call(this);
|
controls.inject.call(this);
|
||||||
|
|
||||||
// Re-attach control listeners
|
// Re-attach control listeners
|
||||||
listeners.controls.call(this);
|
this.listeners.controls();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's no controls, bail
|
// If there's no controls, bail
|
||||||
@ -84,7 +84,9 @@ const ui = {
|
|||||||
this.ready = true;
|
this.ready = true;
|
||||||
|
|
||||||
// Ready event at end of execution stack
|
// Ready event at end of execution stack
|
||||||
|
setTimeout(() => {
|
||||||
utils.dispatchEvent.call(this, this.media, 'ready');
|
utils.dispatchEvent.call(this, this.media, 'ready');
|
||||||
|
}, 0);
|
||||||
|
|
||||||
// Set the title
|
// Set the title
|
||||||
ui.setTitle.call(this);
|
ui.setTitle.call(this);
|
||||||
@ -93,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)) {
|
||||||
@ -122,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));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -132,10 +134,8 @@ const ui = {
|
|||||||
utils.toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
|
utils.toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.paused);
|
utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.paused);
|
||||||
|
|
||||||
// Set aria state
|
// Set ARIA state
|
||||||
if (utils.is.nodeList(this.elements.buttons.play)) {
|
utils.toggleState(this.elements.buttons.play, this.playing);
|
||||||
Array.from(this.elements.buttons.play).forEach(button => utils.toggleState(button, this.playing));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle controls
|
// Toggle controls
|
||||||
this.toggleControls(!this.playing);
|
this.toggleControls(!this.playing);
|
||||||
@ -256,21 +256,7 @@ const ui = {
|
|||||||
// Check buffer status
|
// Check buffer status
|
||||||
case 'playing':
|
case 'playing':
|
||||||
case 'progress':
|
case 'progress':
|
||||||
value = (() => {
|
ui.setProgress.call(this, this.elements.display.buffer, this.buffered * 100);
|
||||||
const { buffered } = this.media;
|
|
||||||
|
|
||||||
if (buffered && buffered.length) {
|
|
||||||
// HTML5
|
|
||||||
return utils.getPercentage(buffered.end(0), this.duration);
|
|
||||||
} else if (utils.is.number(buffered)) {
|
|
||||||
// YouTube returns between 0 and 1
|
|
||||||
return buffered * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
})();
|
|
||||||
|
|
||||||
ui.setProgress.call(this, this.elements.display.buffer, value);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
166
src/js/utils.js
166
src/js/utils.js
@ -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';
|
||||||
|
|
||||||
@ -83,7 +85,7 @@ const utils = {
|
|||||||
|
|
||||||
// Fetch wrapper
|
// Fetch wrapper
|
||||||
// Using XHR to avoid issues with older browsers
|
// Using XHR to avoid issues with older browsers
|
||||||
fetch(url) {
|
fetch(url, responseType = 'text') {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const request = new XMLHttpRequest();
|
const request = new XMLHttpRequest();
|
||||||
@ -94,11 +96,15 @@ const utils = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
request.addEventListener('load', () => {
|
request.addEventListener('load', () => {
|
||||||
|
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 {
|
||||||
|
resolve(request.response);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
request.addEventListener('error', () => {
|
request.addEventListener('error', () => {
|
||||||
@ -106,6 +112,10 @@ const utils = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
request.open('GET', url, true);
|
request.open('GET', url, true);
|
||||||
|
|
||||||
|
// Set the required response type
|
||||||
|
request.responseType = responseType;
|
||||||
|
|
||||||
request.send();
|
request.send();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
@ -114,55 +124,13 @@ const utils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Load an external script
|
// Load an external script
|
||||||
loadScript(url, callback, error) {
|
loadScript(url) {
|
||||||
const current = document.querySelector(`script[src="${url}"]`);
|
return new Promise((resolve, reject) => {
|
||||||
|
loadjs(url, {
|
||||||
// Check script is not already referenced, if so wait for load
|
success: resolve,
|
||||||
if (current !== null) {
|
error: reject,
|
||||||
current.callbacks = current.callbacks || [];
|
});
|
||||||
current.callbacks.push(callback);
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the element
|
|
||||||
const element = document.createElement('script');
|
|
||||||
|
|
||||||
// Callback queue
|
|
||||||
element.callbacks = element.callbacks || [];
|
|
||||||
element.callbacks.push(callback);
|
|
||||||
|
|
||||||
// Error queue
|
|
||||||
element.errors = element.errors || [];
|
|
||||||
element.errors.push(error);
|
|
||||||
|
|
||||||
// Bind callback
|
|
||||||
if (utils.is.function(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);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Load an external SVG sprite
|
// Load an external SVG sprite
|
||||||
@ -175,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;
|
||||||
|
|
||||||
@ -183,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);
|
||||||
@ -200,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,7 +197,7 @@ const utils = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSprite.call(container, result);
|
injectSprite.call(container, result);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
@ -233,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.
|
||||||
@ -344,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);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -472,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),
|
||||||
@ -565,7 +534,7 @@ 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;
|
||||||
@ -587,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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,19 +576,19 @@ 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);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Trigger event
|
// Trigger event
|
||||||
dispatchEvent(element, type, bubbles, detail) {
|
dispatchEvent(element, type, bubbles, detail) {
|
||||||
// Bail if no element
|
// Bail if no element
|
||||||
if (!element || !type) {
|
if (!utils.is.element(element) || !utils.is.string(type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -638,6 +607,12 @@ const utils = {
|
|||||||
// Toggle aria-pressed state on a toggle button
|
// Toggle aria-pressed state on a toggle button
|
||||||
// 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) {
|
||||||
|
// If multiple elements passed
|
||||||
|
if (utils.is.array(element) || utils.is.nodeList(element)) {
|
||||||
|
Array.from(element).forEach(target => utils.toggleState(target, input));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Bail if no target
|
// Bail if no target
|
||||||
if (!utils.is.element(element)) {
|
if (!utils.is.element(element)) {
|
||||||
return;
|
return;
|
||||||
@ -656,6 +631,7 @@ const utils = {
|
|||||||
if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
|
if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (current / max * 100).toFixed(2);
|
return (current / max * 100).toFixed(2);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -696,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) {
|
||||||
@ -839,7 +853,7 @@ const utils = {
|
|||||||
|
|
||||||
// Force repaint of element
|
// Force repaint of element
|
||||||
repaint(element) {
|
repaint(element) {
|
||||||
window.setTimeout(() => {
|
setTimeout(() => {
|
||||||
utils.toggleHidden(element, true);
|
utils.toggleHidden(element, true);
|
||||||
element.offsetHeight; // eslint-disable-line
|
element.offsetHeight; // eslint-disable-line
|
||||||
utils.toggleHidden(element, false);
|
utils.toggleHidden(element, false);
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
font-family: $plyr-font-family;
|
font-family: $plyr-font-family;
|
||||||
|
font-variant-numeric: tabular-nums; // Force monosace-esque number widths
|
||||||
font-weight: $plyr-font-weight-regular;
|
font-weight: $plyr-font-weight-regular;
|
||||||
line-height: $plyr-line-height;
|
line-height: $plyr-line-height;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -1,16 +1,27 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Advertisments
|
// Advertisements
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
.plyr__ads {
|
.plyr__ads {
|
||||||
|
border-radius: inherit;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 3; // Above the controls
|
z-index: -1; // Hide it by default
|
||||||
|
|
||||||
|
// Make sure the inner container is big enough for the ad creative.
|
||||||
|
> div,
|
||||||
|
> div iframe {
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The countdown label
|
||||||
&::after {
|
&::after {
|
||||||
background: rgba($plyr-color-gunmetal, 0.8);
|
background: rgba($plyr-color-gunmetal, 0.8);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
Reference in New Issue
Block a user