commit
48bf368316
@ -2,5 +2,6 @@
|
|||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all"
|
"trailingComma": "all",
|
||||||
|
"printWidth": 120
|
||||||
}
|
}
|
||||||
|
11
changelog.md
11
changelog.md
@ -1,3 +1,14 @@
|
|||||||
|
# v3.4.0
|
||||||
|
|
||||||
|
- Accessibility improvements (see #905)
|
||||||
|
- Improvements to the way the controls work on iOS
|
||||||
|
- Demo code clean up
|
||||||
|
- YouTube quality selection removed due to their poor support for it. As a result, the `qualityrequested` event has been removed
|
||||||
|
- Controls spacing improvements
|
||||||
|
- Fix for pressed property missing with custom controls (Fixes #1062)
|
||||||
|
- Fix #1153: Captions language fallback (thanks @friday)
|
||||||
|
- Fix for setting pressed property of undefined (Fixes #1102)
|
||||||
|
|
||||||
# v3.3.23
|
# v3.3.23
|
||||||
|
|
||||||
- Add support for YouTube's hl param (thanks @renaudleo)
|
- Add support for YouTube's hl param (thanks @renaudleo)
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs. You can pass the following to the `controls` option:
|
This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs. You can pass the following to the `controls` option:
|
||||||
|
|
||||||
- `Array` of options (this builds the default controls based on your choices)
|
- `Array` of options (this builds the default controls based on your choices)
|
||||||
|
- `Element` with the controls
|
||||||
- `String` containing the desired HTML
|
- `String` containing the desired HTML
|
||||||
|
- `false` (or empty string or array) to disable all controls
|
||||||
- `Function` that will be executed and should return one of the above
|
- `Function` that will be executed and should return one of the above
|
||||||
|
|
||||||
## Using default controls
|
## Using default controls
|
||||||
|
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
128
demo/dist/demo.js
vendored
128
demo/dist/demo.js
vendored
@ -1874,7 +1874,7 @@ typeof navigator === "object" && (function () {
|
|||||||
// webpack (using a build step causes webpack #1617). Grunt verifies that
|
// webpack (using a build step causes webpack #1617). Grunt verifies that
|
||||||
// this value matches package.json during build.
|
// this value matches package.json during build.
|
||||||
// See: https://github.com/getsentry/raven-js/issues/465
|
// See: https://github.com/getsentry/raven-js/issues/465
|
||||||
VERSION: '3.26.3',
|
VERSION: '3.26.4',
|
||||||
|
|
||||||
debug: false,
|
debug: false,
|
||||||
|
|
||||||
@ -2612,34 +2612,40 @@ typeof navigator === "object" && (function () {
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
options = options || {};
|
options = Object.assign(
|
||||||
|
{
|
||||||
|
eventId: this.lastEventId(),
|
||||||
|
dsn: this._dsn,
|
||||||
|
user: this._globalContext.user || {}
|
||||||
|
},
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
var lastEventId = options.eventId || this.lastEventId();
|
if (!options.eventId) {
|
||||||
if (!lastEventId) {
|
|
||||||
throw new configError('Missing eventId');
|
throw new configError('Missing eventId');
|
||||||
}
|
}
|
||||||
|
|
||||||
var dsn = options.dsn || this._dsn;
|
if (!options.dsn) {
|
||||||
if (!dsn) {
|
|
||||||
throw new configError('Missing DSN');
|
throw new configError('Missing DSN');
|
||||||
}
|
}
|
||||||
|
|
||||||
var encode = encodeURIComponent;
|
var encode = encodeURIComponent;
|
||||||
var qs = '';
|
var encodedOptions = [];
|
||||||
qs += '?eventId=' + encode(lastEventId);
|
|
||||||
qs += '&dsn=' + encode(dsn);
|
|
||||||
|
|
||||||
var user = options.user || this._globalContext.user;
|
for (var key in options) {
|
||||||
if (user) {
|
if (key === 'user') {
|
||||||
if (user.name) qs += '&name=' + encode(user.name);
|
var user = options.user;
|
||||||
if (user.email) qs += '&email=' + encode(user.email);
|
if (user.name) encodedOptions.push('name=' + encode(user.name));
|
||||||
|
if (user.email) encodedOptions.push('email=' + encode(user.email));
|
||||||
|
} else {
|
||||||
|
encodedOptions.push(encode(key) + '=' + encode(options[key]));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
var globalServer = this._getGlobalServer(this._parseDSN(dsn));
|
var globalServer = this._getGlobalServer(this._parseDSN(options.dsn));
|
||||||
|
|
||||||
var script = _document.createElement('script');
|
var script = _document.createElement('script');
|
||||||
script.async = true;
|
script.async = true;
|
||||||
script.src = globalServer + '/api/embed/error-page/' + qs;
|
script.src = globalServer + '/api/embed/error-page/?' + encodedOptions.join('&');
|
||||||
(_document.head || _document.body).appendChild(script);
|
(_document.head || _document.body).appendChild(script);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -4087,16 +4093,18 @@ typeof navigator === "object" && (function () {
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
var isLive = window.location.host === 'plyr.io';
|
var host = window.location.host;
|
||||||
|
|
||||||
// Raven / Sentry
|
var env = {
|
||||||
// For demo site (https://plyr.io) only
|
prod: host === 'plyr.io',
|
||||||
if (isLive) {
|
dev: host === 'dev.plyr.io'
|
||||||
singleton.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
};
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
singleton.context(function () {
|
singleton.context(function () {
|
||||||
|
var selector = '#player';
|
||||||
|
var container = document.getElementById('container');
|
||||||
|
|
||||||
if (window.shr) {
|
if (window.shr) {
|
||||||
window.shr.setup({
|
window.shr.setup({
|
||||||
count: {
|
count: {
|
||||||
@ -4110,6 +4118,9 @@ typeof navigator === "object" && (function () {
|
|||||||
|
|
||||||
// Remove class on blur
|
// Remove class on blur
|
||||||
document.addEventListener('focusout', function (event) {
|
document.addEventListener('focusout', function (event) {
|
||||||
|
if (container.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
event.target.classList.remove(tabClassName);
|
event.target.classList.remove(tabClassName);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -4122,12 +4133,18 @@ typeof navigator === "object" && (function () {
|
|||||||
// 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
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
document.activeElement.classList.add(tabClassName);
|
var focused = document.activeElement;
|
||||||
}, 0);
|
|
||||||
|
if (!focused || container.contains(focused)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
focused.classList.add(tabClassName);
|
||||||
|
}, 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup the player
|
// Setup the player
|
||||||
var player = new Plyr('#player', {
|
var player = new Plyr(selector, {
|
||||||
debug: true,
|
debug: true,
|
||||||
title: 'View From A Blue Moon',
|
title: 'View From A Blue Moon',
|
||||||
iconUrl: '../dist/plyr.svg',
|
iconUrl: '../dist/plyr.svg',
|
||||||
@ -4137,57 +4154,6 @@ typeof navigator === "object" && (function () {
|
|||||||
tooltips: {
|
tooltips: {
|
||||||
controls: true
|
controls: true
|
||||||
},
|
},
|
||||||
clickToPlay: false,
|
|
||||||
/* controls: [
|
|
||||||
'play-large',
|
|
||||||
'restart',
|
|
||||||
'rewind',
|
|
||||||
'play',
|
|
||||||
'fast-forward',
|
|
||||||
'progress',
|
|
||||||
'current-time',
|
|
||||||
'duration',
|
|
||||||
'mute',
|
|
||||||
'volume',
|
|
||||||
'captions',
|
|
||||||
'settings',
|
|
||||||
'pip',
|
|
||||||
'airplay',
|
|
||||||
'fullscreen',
|
|
||||||
], */
|
|
||||||
/* i18n: {
|
|
||||||
restart: '重新開始',
|
|
||||||
rewind: '快退{seektime}秒',
|
|
||||||
play: '播放',
|
|
||||||
pause: '暫停',
|
|
||||||
fastForward: '快進{seektime}秒',
|
|
||||||
seek: '尋求',
|
|
||||||
played: '發揮',
|
|
||||||
buffered: '緩衝的',
|
|
||||||
currentTime: '當前時間戳',
|
|
||||||
duration: '長短',
|
|
||||||
volume: '音量',
|
|
||||||
mute: '靜音',
|
|
||||||
unmute: '取消靜音',
|
|
||||||
enableCaptions: '開啟字幕',
|
|
||||||
disableCaptions: '關閉字幕',
|
|
||||||
enterFullscreen: '進入全螢幕',
|
|
||||||
exitFullscreen: '退出全螢幕',
|
|
||||||
frameTitle: '球員為{title}',
|
|
||||||
captions: '字幕',
|
|
||||||
settings: '設定',
|
|
||||||
speed: '速度',
|
|
||||||
normal: '正常',
|
|
||||||
quality: '質量',
|
|
||||||
loop: '循環',
|
|
||||||
start: 'Start',
|
|
||||||
end: 'End',
|
|
||||||
all: 'All',
|
|
||||||
reset: '重啟',
|
|
||||||
disabled: '殘',
|
|
||||||
enabled: '啟用',
|
|
||||||
advertisement: '廣告',
|
|
||||||
}, */
|
|
||||||
captions: {
|
captions: {
|
||||||
active: true
|
active: true
|
||||||
},
|
},
|
||||||
@ -4195,7 +4161,7 @@ typeof navigator === "object" && (function () {
|
|||||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c'
|
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c'
|
||||||
},
|
},
|
||||||
ads: {
|
ads: {
|
||||||
enabled: true,
|
enabled: env.prod || env.dev,
|
||||||
publisherId: '918848828995742'
|
publisherId: '918848828995742'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -4370,10 +4336,16 @@ typeof navigator === "object" && (function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Raven / Sentry
|
||||||
|
// For demo site (https://plyr.io) only
|
||||||
|
if (env.prod) {
|
||||||
|
singleton.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
||||||
|
}
|
||||||
|
|
||||||
// Google analytics
|
// Google analytics
|
||||||
// For demo site (https://plyr.io) only
|
// For demo site (https://plyr.io) only
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
if (isLive) {
|
if (env.prod) {
|
||||||
(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] || function () {
|
i[r] = i[r] || function () {
|
||||||
|
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
2
demo/dist/demo.min.js
vendored
File diff suppressed because one or more lines are too long
2
demo/dist/demo.min.js.map
vendored
2
demo/dist/demo.min.js.map
vendored
File diff suppressed because one or more lines are too long
2
demo/dist/error.css
vendored
2
demo/dist/error.css
vendored
File diff suppressed because one or more lines are too long
@ -91,12 +91,12 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
<div id="container">
|
||||||
<video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
|
<video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
|
||||||
<!-- Video files -->
|
<!-- Video files -->
|
||||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" type="video/mp4" size="576">
|
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" type="video/mp4" size="576">
|
||||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4" type="video/mp4" size="720">
|
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4" type="video/mp4" size="720">
|
||||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4" type="video/mp4" size="1080">
|
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4" type="video/mp4" size="1080">
|
||||||
<!-- <source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4" type="video/mp4" size="1440"> -->
|
|
||||||
|
|
||||||
<!-- Caption files -->
|
<!-- Caption files -->
|
||||||
<track kind="captions" label="English" srclang="en" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
|
<track kind="captions" label="English" srclang="en" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
|
||||||
@ -106,6 +106,7 @@
|
|||||||
<!-- Fallback for browsers that don't support the <video> element -->
|
<!-- Fallback for browsers that don't support the <video> element -->
|
||||||
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" download>Download</a>
|
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" download>Download</a>
|
||||||
</video>
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li class="plyr__cite plyr__cite--video" hidden>
|
<li class="plyr__cite plyr__cite--video" hidden>
|
||||||
@ -166,7 +167,7 @@
|
|||||||
</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>
|
||||||
|
|
||||||
|
@ -7,16 +7,17 @@
|
|||||||
import Raven from 'raven-js';
|
import Raven from 'raven-js';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const isLive = window.location.host === 'plyr.io';
|
const { host } = window.location;
|
||||||
|
const env = {
|
||||||
// Raven / Sentry
|
prod: host === 'plyr.io',
|
||||||
// For demo site (https://plyr.io) only
|
dev: host === 'dev.plyr.io',
|
||||||
if (isLive) {
|
};
|
||||||
Raven.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
Raven.context(() => {
|
Raven.context(() => {
|
||||||
|
const selector = '#player';
|
||||||
|
const container = document.getElementById('container');
|
||||||
|
|
||||||
if (window.shr) {
|
if (window.shr) {
|
||||||
window.shr.setup({
|
window.shr.setup({
|
||||||
count: {
|
count: {
|
||||||
@ -30,6 +31,10 @@ import Raven from 'raven-js';
|
|||||||
|
|
||||||
// Remove class on blur
|
// Remove class on blur
|
||||||
document.addEventListener('focusout', event => {
|
document.addEventListener('focusout', event => {
|
||||||
|
if (!event.target.classList || container.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
event.target.classList.remove(tabClassName);
|
event.target.classList.remove(tabClassName);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -42,12 +47,18 @@ import Raven from 'raven-js';
|
|||||||
// 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
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.activeElement.classList.add(tabClassName);
|
const focused = document.activeElement;
|
||||||
}, 0);
|
|
||||||
|
if (!focused || !focused.classList || container.contains(focused)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
focused.classList.add(tabClassName);
|
||||||
|
}, 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup the player
|
// Setup the player
|
||||||
const player = new Plyr('#player', {
|
const player = new Plyr(selector, {
|
||||||
debug: true,
|
debug: true,
|
||||||
title: 'View From A Blue Moon',
|
title: 'View From A Blue Moon',
|
||||||
iconUrl: '../dist/plyr.svg',
|
iconUrl: '../dist/plyr.svg',
|
||||||
@ -57,57 +68,6 @@ import Raven from 'raven-js';
|
|||||||
tooltips: {
|
tooltips: {
|
||||||
controls: true,
|
controls: true,
|
||||||
},
|
},
|
||||||
clickToPlay: false,
|
|
||||||
/* controls: [
|
|
||||||
'play-large',
|
|
||||||
'restart',
|
|
||||||
'rewind',
|
|
||||||
'play',
|
|
||||||
'fast-forward',
|
|
||||||
'progress',
|
|
||||||
'current-time',
|
|
||||||
'duration',
|
|
||||||
'mute',
|
|
||||||
'volume',
|
|
||||||
'captions',
|
|
||||||
'settings',
|
|
||||||
'pip',
|
|
||||||
'airplay',
|
|
||||||
'fullscreen',
|
|
||||||
], */
|
|
||||||
/* i18n: {
|
|
||||||
restart: '重新開始',
|
|
||||||
rewind: '快退{seektime}秒',
|
|
||||||
play: '播放',
|
|
||||||
pause: '暫停',
|
|
||||||
fastForward: '快進{seektime}秒',
|
|
||||||
seek: '尋求',
|
|
||||||
played: '發揮',
|
|
||||||
buffered: '緩衝的',
|
|
||||||
currentTime: '當前時間戳',
|
|
||||||
duration: '長短',
|
|
||||||
volume: '音量',
|
|
||||||
mute: '靜音',
|
|
||||||
unmute: '取消靜音',
|
|
||||||
enableCaptions: '開啟字幕',
|
|
||||||
disableCaptions: '關閉字幕',
|
|
||||||
enterFullscreen: '進入全螢幕',
|
|
||||||
exitFullscreen: '退出全螢幕',
|
|
||||||
frameTitle: '球員為{title}',
|
|
||||||
captions: '字幕',
|
|
||||||
settings: '設定',
|
|
||||||
speed: '速度',
|
|
||||||
normal: '正常',
|
|
||||||
quality: '質量',
|
|
||||||
loop: '循環',
|
|
||||||
start: 'Start',
|
|
||||||
end: 'End',
|
|
||||||
all: 'All',
|
|
||||||
reset: '重啟',
|
|
||||||
disabled: '殘',
|
|
||||||
enabled: '啟用',
|
|
||||||
advertisement: '廣告',
|
|
||||||
}, */
|
|
||||||
captions: {
|
captions: {
|
||||||
active: true,
|
active: true,
|
||||||
},
|
},
|
||||||
@ -115,7 +75,7 @@ import Raven from 'raven-js';
|
|||||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
|
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
|
||||||
},
|
},
|
||||||
ads: {
|
ads: {
|
||||||
enabled: true,
|
enabled: env.prod || env.dev,
|
||||||
publisherId: '918848828995742',
|
publisherId: '918848828995742',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -311,11 +271,17 @@ import Raven from 'raven-js';
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Raven / Sentry
|
||||||
|
// For demo site (https://plyr.io) only
|
||||||
|
if (env.prod) {
|
||||||
|
Raven.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
||||||
|
}
|
||||||
|
|
||||||
// Google analytics
|
// Google analytics
|
||||||
// For demo site (https://plyr.io) only
|
// For demo site (https://plyr.io) only
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
if (isLive) {
|
if (env.prod) {
|
||||||
(function(i, s, o, g, r, a, m) {
|
((i, s, o, g, r, a, m) => {
|
||||||
i.GoogleAnalyticsObject = r;
|
i.GoogleAnalyticsObject = r;
|
||||||
i[r] =
|
i[r] =
|
||||||
i[r] ||
|
i[r] ||
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
// Typography
|
// Typography
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$font-sans-serif: 'Gordita', 'Avenir', 'Helvetica Neue', sans-serif;
|
$font-sans-serif: 'Gordita', 'Avenir', 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||||
|
'Segoe UI Symbol';
|
||||||
|
|
||||||
$font-size-base: 15;
|
$font-size-base: 15;
|
||||||
$font-size-small: 13;
|
$font-size-small: 13;
|
||||||
|
2
dist/plyr.css
vendored
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
1377
dist/plyr.js
vendored
1377
dist/plyr.js
vendored
File diff suppressed because it is too large
Load Diff
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
2
dist/plyr.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.min.js.map
vendored
2
dist/plyr.min.js.map
vendored
File diff suppressed because one or more lines are too long
1408
dist/plyr.polyfilled.js
vendored
1408
dist/plyr.polyfilled.js
vendored
File diff suppressed because it is too large
Load Diff
2
dist/plyr.polyfilled.js.map
vendored
2
dist/plyr.polyfilled.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.js
vendored
2
dist/plyr.polyfilled.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.polyfilled.min.js.map
vendored
2
dist/plyr.polyfilled.min.js.map
vendored
File diff suppressed because one or more lines are too long
38
package.json
38
package.json
@ -1,19 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "plyr",
|
"name": "plyr",
|
||||||
"version": "3.3.23",
|
"version": "3.4.0-beta.2",
|
||||||
"description":
|
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||||
"A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
|
||||||
"homepage": "https://plyr.io",
|
"homepage": "https://plyr.io",
|
||||||
"author": "Sam Potts <sam@potts.es>",
|
"author": "Sam Potts <sam@potts.es>",
|
||||||
"keywords": [
|
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
|
||||||
"HTML5 Video",
|
|
||||||
"HTML5 Audio",
|
|
||||||
"Media Player",
|
|
||||||
"DASH",
|
|
||||||
"Shaka",
|
|
||||||
"WordPress",
|
|
||||||
"HLS"
|
|
||||||
],
|
|
||||||
"main": "./dist/plyr.js",
|
"main": "./dist/plyr.js",
|
||||||
"browser": "./dist/plyr.min.js",
|
"browser": "./dist/plyr.min.js",
|
||||||
"sass": "./src/sass/plyr.scss",
|
"sass": "./src/sass/plyr.scss",
|
||||||
@ -32,8 +23,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "gulp build",
|
"build": "gulp build",
|
||||||
"lint": "eslint src/js && npm run-script remark",
|
"lint": "eslint src/js && npm run-script remark",
|
||||||
"remark":
|
"remark": "remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
|
||||||
"remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -42,21 +32,21 @@
|
|||||||
"babel-plugin-external-helpers": "^6.22.0",
|
"babel-plugin-external-helpers": "^6.22.0",
|
||||||
"babel-preset-env": "^1.7.0",
|
"babel-preset-env": "^1.7.0",
|
||||||
"del": "^3.0.0",
|
"del": "^3.0.0",
|
||||||
"eslint": "^5.2.0",
|
"eslint": "^5.3.0",
|
||||||
"eslint-config-airbnb-base": "^13.0.0",
|
"eslint-config-airbnb-base": "^13.1.0",
|
||||||
"eslint-config-prettier": "^2.9.0",
|
"eslint-config-prettier": "^2.9.0",
|
||||||
"eslint-plugin-import": "^2.13.0",
|
"eslint-plugin-import": "^2.14.0",
|
||||||
"fastly-purge": "^1.0.1",
|
"fastly-purge": "^1.0.1",
|
||||||
"git-branch": "^2.0.1",
|
"git-branch": "^2.0.1",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"gulp-autoprefixer": "^5.0.0",
|
"gulp-autoprefixer": "^5.0.0",
|
||||||
"gulp-better-rollup": "^3.3.0",
|
"gulp-better-rollup": "^3.3.0",
|
||||||
"gulp-clean-css": "^3.9.4",
|
"gulp-clean-css": "^3.10.0",
|
||||||
"gulp-concat": "^2.6.1",
|
"gulp-concat": "^2.6.1",
|
||||||
"gulp-filter": "^5.1.0",
|
"gulp-filter": "^5.1.0",
|
||||||
"gulp-header": "^2.0.5",
|
"gulp-header": "^2.0.5",
|
||||||
"gulp-open": "^3.0.1",
|
"gulp-open": "^3.0.1",
|
||||||
"gulp-postcss": "^7.0.1",
|
"gulp-postcss": "^8.0.0",
|
||||||
"gulp-rename": "^1.4.0",
|
"gulp-rename": "^1.4.0",
|
||||||
"gulp-replace": "^1.0.0",
|
"gulp-replace": "^1.0.0",
|
||||||
"gulp-s3": "^0.11.0",
|
"gulp-s3": "^0.11.0",
|
||||||
@ -73,15 +63,15 @@
|
|||||||
"remark-cli": "^5.0.0",
|
"remark-cli": "^5.0.0",
|
||||||
"remark-validate-links": "^7.0.0",
|
"remark-validate-links": "^7.0.0",
|
||||||
"rollup-plugin-babel": "^3.0.7",
|
"rollup-plugin-babel": "^3.0.7",
|
||||||
"rollup-plugin-commonjs": "^9.1.4",
|
"rollup-plugin-commonjs": "^9.1.5",
|
||||||
"rollup-plugin-node-resolve": "^3.3.0",
|
"rollup-plugin-node-resolve": "^3.3.0",
|
||||||
"run-sequence": "^2.2.1",
|
"run-sequence": "^2.2.1",
|
||||||
"stylelint": "^9.4.0",
|
"stylelint": "^9.4.0",
|
||||||
"stylelint-config-prettier": "^3.3.0",
|
"stylelint-config-prettier": "^4.0.0",
|
||||||
"stylelint-config-recommended": "^2.1.0",
|
"stylelint-config-recommended": "^2.1.0",
|
||||||
"stylelint-config-sass-guidelines": "^5.0.0",
|
"stylelint-config-sass-guidelines": "^5.0.0",
|
||||||
"stylelint-order": "^0.8.1",
|
"stylelint-order": "^1.0.0",
|
||||||
"stylelint-scss": "^3.2.0",
|
"stylelint-scss": "^3.3.0",
|
||||||
"stylelint-selector-bem-pattern": "^2.0.0",
|
"stylelint-selector-bem-pattern": "^2.0.0",
|
||||||
"through2": "^2.0.3"
|
"through2": "^2.0.3"
|
||||||
},
|
},
|
||||||
@ -90,6 +80,6 @@
|
|||||||
"custom-event-polyfill": "^1.0.6",
|
"custom-event-polyfill": "^1.0.6",
|
||||||
"loadjs": "^3.5.4",
|
"loadjs": "^3.5.4",
|
||||||
"raven-js": "^3.26.4",
|
"raven-js": "^3.26.4",
|
||||||
"url-polyfill": "^1.0.13"
|
"url-polyfill": "^1.0.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
127
readme.md
127
readme.md
@ -8,26 +8,26 @@ A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo medi
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* **Accessible** - full support for VTT captions and screen readers
|
- **Accessible** - full support for VTT captions and screen readers
|
||||||
* **[Customisable](#html)** - make the player look how you want with the markup you want
|
- **[Customisable](#html)** - make the player look how you want with the markup you want
|
||||||
* **Good HTML** - 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
|
- **[Monetization](#ads)** - make money from your videos
|
||||||
* **[Streaming](#try-plyr-online)** - support for hls.js, Shaka and dash.js streaming playback
|
- **[Streaming](#try-plyr-online)** - 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
|
- **Picture-in-Picture** - supports Safari's picture-in-picture mode
|
||||||
* **Playsinline** - supports the `playsinline` attribute
|
- **Playsinline** - supports the `playsinline` attribute
|
||||||
* **Speed controls** - adjust speed on the fly
|
- **Speed controls** - adjust speed on the fly
|
||||||
* **Multiple captions** - support for multiple caption tracks
|
- **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
|
||||||
|
|
||||||
Oh and yes, it works with Bootstrap.
|
Oh and yes, it works with Bootstrap.
|
||||||
|
|
||||||
@ -132,13 +132,13 @@ See [initialising](#initialising) for more information on advanced setups.
|
|||||||
You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build.
|
You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.plyr.io/3.3.23/plyr.js"></script>
|
<script src="https://cdn.plyr.io/3.4.0-beta.2/plyr.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
...or...
|
...or...
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.plyr.io/3.3.23/plyr.polyfilled.js"></script>
|
<script src="https://cdn.plyr.io/3.4.0-beta.2/plyr.polyfilled.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
### CSS
|
### CSS
|
||||||
@ -152,21 +152,21 @@ 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.3.23/plyr.css">
|
<link rel="stylesheet" href="https://cdn.plyr.io/3.4.0-beta.2/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.3.23/plyr.svg`.
|
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.4.0-beta.2/plyr.svg`.
|
||||||
|
|
||||||
## Ads
|
## 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:
|
Plyr has partnered up with [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy:
|
||||||
|
|
||||||
* [Sign up for a vi.ai account](http://vi.ai/publisher-video-monetization/?aid=plyrio)
|
- [Sign up for a vi.ai account](https://vi.ai/publisher-video-monetization/?aid=plyrio)
|
||||||
* Grab your publisher ID from the code snippet
|
- Grab your publisher ID from the code snippet
|
||||||
* Enable ads in the [config options](#options) and enter your publisher ID
|
- 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.
|
Any questions regarding the ads can be sent straight to vi.ai and any issues with rendering raised through GitHub issues.
|
||||||
|
|
||||||
@ -213,10 +213,10 @@ 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)
|
- A [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList)
|
||||||
* A [jQuery](https://jquery.com) object
|
- A [jQuery](https://jquery.com) object
|
||||||
|
|
||||||
_Note_: If a `NodeList`, `Array`, or jQuery object are passed, the first element will be used for setup. To setup multiple players, see [setting up multiple players](#setting-up-multiple-players) below.
|
_Note_: If a `NodeList`, `Array`, or jQuery object are passed, the first element will be used for setup. To setup multiple players, see [setting up multiple players](#setting-up-multiple-players) below.
|
||||||
|
|
||||||
@ -290,7 +290,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `loadSprite` | Boolean | `true` | Load the SVG sprite specified as the `iconUrl` option (if a URL). If `false`, it is assumed you are handling sprite loading yourself. |
|
| `loadSprite` | Boolean | `true` | Load the SVG sprite specified as the `iconUrl` option (if a URL). If `false`, it is assumed you are handling sprite loading yourself. |
|
||||||
| `iconUrl` | String | `null` | Specify a URL or path to the SVG sprite. See the [SVG section](#svg) for more info. |
|
| `iconUrl` | String | `null` | Specify a URL or path to the SVG sprite. See the [SVG section](#svg) for more info. |
|
||||||
| `iconPrefix` | String | `plyr` | Specify the id prefix for the icons used in the default controls (e.g. "plyr-play" would be "plyr"). This is to prevent clashes if you're using your own SVG sprite but with the default controls. Most people can ignore this option. |
|
| `iconPrefix` | String | `plyr` | Specify the id prefix for the icons used in the default controls (e.g. "plyr-play" would be "plyr"). This is to prevent clashes if you're using your own SVG sprite but with the default controls. Most people can ignore this option. |
|
||||||
| `blankUrl` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. |
|
| `blankVideo` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. |
|
||||||
| `autoplay` | Boolean | `false` | Autoplay the media on load. This is generally advised against on UX grounds. It is also disabled by default in some browsers. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
| `autoplay` | Boolean | `false` | Autoplay the media on load. This is generally advised against on UX grounds. It is also disabled by default in some browsers. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
||||||
| `autopause`¹ | Boolean | `true` | Only allow one player playing at once. |
|
| `autopause`¹ | Boolean | `true` | Only allow one player playing at once. |
|
||||||
| `seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind. |
|
| `seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind. |
|
||||||
@ -392,7 +392,7 @@ player.fullscreen.active; // false;
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Property | Getter | Setter | Description |
|
| Property | Getter | Setter | Description |
|
||||||
| -------------------- | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| -------------------- | ------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `isHTML5` | ✓ | - | Returns a boolean indicating if the current player is HTML5. |
|
| `isHTML5` | ✓ | - | Returns a boolean indicating if the current player is HTML5. |
|
||||||
| `isEmbed` | ✓ | - | Returns a boolean indicating if the current player is an embedded player. |
|
| `isEmbed` | ✓ | - | Returns a boolean indicating if the current player is an embedded player. |
|
||||||
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
|
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
|
||||||
@ -565,6 +565,7 @@ player.on('ready', event => {
|
|||||||
| `loadstart` | Sent when loading of the media begins. |
|
| `loadstart` | Sent when loading of the media begins. |
|
||||||
| `loadeddata` | The first frame of the media has finished loading. |
|
| `loadeddata` | The first frame of the media has finished loading. |
|
||||||
| `loadedmetadata` | The media's metadata has finished loading; all attributes now contain as much useful information as they're going to. |
|
| `loadedmetadata` | The media's metadata has finished loading; all attributes now contain as much useful information as they're going to. |
|
||||||
|
| `qualitychange` | The quality of playback has changed. |
|
||||||
| `canplay` | Sent when enough data is available that the media can be played, at least for a couple of frames. This corresponds to the `HAVE_ENOUGH_DATA` `readyState`. |
|
| `canplay` | Sent when enough data is available that the media can be played, at least for a couple of frames. This corresponds to the `HAVE_ENOUGH_DATA` `readyState`. |
|
||||||
| `canplaythrough` | Sent when the ready state changes to `CAN_PLAY_THROUGH`, indicating that the entire media can be played without interruption, assuming the download rate remains at least at the current level. _Note:_ Manually setting the `currentTime` will eventually fire a `canplaythrough` event in firefox. Other browsers might not fire this event. |
|
| `canplaythrough` | Sent when the ready state changes to `CAN_PLAY_THROUGH`, indicating that the entire media can be played without interruption, assuming the download rate remains at least at the current level. _Note:_ Manually setting the `currentTime` will eventually fire a `canplaythrough` event in firefox. Other browsers might not fire this event. |
|
||||||
| `stalled` | Sent when the user agent is trying to fetch media data, but data is unexpectedly not forthcoming. |
|
| `stalled` | Sent when the user agent is trying to fetch media data, but data is unexpectedly not forthcoming. |
|
||||||
@ -576,10 +577,8 @@ player.on('ready', event => {
|
|||||||
#### YouTube only
|
#### YouTube only
|
||||||
|
|
||||||
| Event Type | Description |
|
| Event Type | Description |
|
||||||
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `statechange` | The state of the player has changed. The code can be accessed via `event.detail.code`. Possible values are `-1`: Unstarted, `0`: Ended, `1`: Playing, `2`: Paused, `3`: Buffering, `5`: Video cued. See the [YouTube Docs](https://developers.google.com/youtube/iframe_api_reference#onStateChange) for more information. |
|
| `statechange` | The state of the player has changed. The code can be accessed via `event.detail.code`. Possible values are `-1`: Unstarted, `0`: Ended, `1`: Playing, `2`: Paused, `3`: Buffering, `5`: Video cued. See the [YouTube Docs](https://developers.google.com/youtube/iframe_api_reference#onStateChange) for more information. |
|
||||||
| `qualitychange` | The quality of playback has changed. |
|
|
||||||
| `qualityrequested` | A change to playback quality has been requested. _Note:_ A change to quality can only be _requested_ via the API. There is no guarantee the quality will change to the level requested. You should listen to the `qualitychange` event for true changes. |
|
|
||||||
|
|
||||||
_Note:_ These events also bubble up the DOM. The event target will be the container element.
|
_Note:_ These events also bubble up the DOM. The event target will be the container element.
|
||||||
|
|
||||||
@ -591,8 +590,8 @@ YouTube and Vimeo are currently supported and function much like a HTML5 video.
|
|||||||
to access the API's directly. You can do so via the `embed` property of your player object - e.g. `player.embed`. You can then use the relevant methods from the
|
to access the API's directly. You can do so via the `embed` property of your player object - e.g. `player.embed`. You can then use the relevant methods from the
|
||||||
third party APIs. More info on the respective API's here:
|
third party APIs. More info on the respective API's here:
|
||||||
|
|
||||||
* [YouTube iframe API Reference](https://developers.google.com/youtube/iframe_api_reference)
|
- [YouTube iframe API Reference](https://developers.google.com/youtube/iframe_api_reference)
|
||||||
* [Vimeo player.js Reference](https://github.com/vimeo/player.js)
|
- [Vimeo player.js Reference](https://github.com/vimeo/player.js)
|
||||||
|
|
||||||
_Note_: Not all API methods may work 100%. Your mileage may vary. It's better to use the Plyr API where possible.
|
_Note_: Not all API methods may work 100%. Your mileage may vary. It's better to use the Plyr API where possible.
|
||||||
|
|
||||||
@ -652,9 +651,9 @@ const supported = Plyr.supported('video', 'html5', true);
|
|||||||
|
|
||||||
The arguments are:
|
The arguments are:
|
||||||
|
|
||||||
* Media type (`audio` or `video`)
|
- Media type (`audio` or `video`)
|
||||||
* Provider (`html5`, `youtube` or `vimeo`)
|
- Provider (`html5`, `youtube` or `vimeo`)
|
||||||
* Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
|
- Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
|
||||||
|
|
||||||
### Disable support programatically
|
### Disable support programatically
|
||||||
|
|
||||||
@ -687,33 +686,33 @@ Plyr is developed by [@sam_potts](https://twitter.com/sam_potts) / [sampotts.me]
|
|||||||
|
|
||||||
Plyr costs money to run, not only my time. I donate my time for free as I enjoy building Plyr but unfortunately have to pay for domains, hosting, and more. Any help with costs is appreciated...
|
Plyr costs money to run, not only my time. I donate my time for free as I enjoy building Plyr but unfortunately have to pay for domains, hosting, and more. Any help with costs is appreciated...
|
||||||
|
|
||||||
* [Donate via Patreon](https://www.patreon.com/plyr)
|
- [Donate via Patreon](https://www.patreon.com/plyr)
|
||||||
* [Donate via PayPal](https://www.paypal.me/pottsy/20usd)
|
- [Donate via PayPal](https://www.paypal.me/pottsy/20usd)
|
||||||
|
|
||||||
## Mentions
|
## Mentions
|
||||||
|
|
||||||
* [ProductHunt](https://www.producthunt.com/tech/plyr)
|
- [ProductHunt](https://www.producthunt.com/tech/plyr)
|
||||||
* [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
|
- [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
|
||||||
* [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
- [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
||||||
* [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
|
- [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
|
||||||
* [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
|
- [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
|
||||||
* [Hacker News](https://news.ycombinator.com/item?id=9136774)
|
- [Hacker News](https://news.ycombinator.com/item?id=9136774)
|
||||||
* [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
|
- [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
|
||||||
* [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
|
- [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
|
||||||
* [The Treehouse Show #131](https://teamtreehouse.com/library/episode-131-origami-react-responsive-hero-images)
|
- [The Treehouse Show #131](https://teamtreehouse.com/library/episode-131-origami-react-responsive-hero-images)
|
||||||
* [noupe.com](http://www.noupe.com/design/html5-plyr-is-a-responsive-and-accessible-video-player-94389.html)
|
- [noupe.com](http://www.noupe.com/design/html5-plyr-is-a-responsive-and-accessible-video-player-94389.html)
|
||||||
|
|
||||||
## Used by
|
## Used by
|
||||||
|
|
||||||
* [Selz.com](https://selz.com)
|
- [Selz.com](https://selz.com)
|
||||||
* [Peugeot.fr](http://www.peugeot.fr/marque-et-technologie/technologies/peugeot-i-cockpit.html)
|
- [Peugeot.fr](http://www.peugeot.fr/marque-et-technologie/technologies/peugeot-i-cockpit.html)
|
||||||
* [Peugeot.de](http://www.peugeot.de/modelle/modellberater/208-3-turer/fotos-videos.html)
|
- [Peugeot.de](http://www.peugeot.de/modelle/modellberater/208-3-turer/fotos-videos.html)
|
||||||
* [TomTom.com](http://prioritydriving.tomtom.com/)
|
- [TomTom.com](http://prioritydriving.tomtom.com/)
|
||||||
* [DIGBMX](http://digbmx.com/)
|
- [DIGBMX](http://digbmx.com/)
|
||||||
* [Grime Archive](https://grimearchive.com/)
|
- [Grime Archive](https://grimearchive.com/)
|
||||||
* [koel - A personal music streaming server that works.](http://koel.phanan.net/)
|
- [koel - A personal music streaming server that works.](http://koel.phanan.net/)
|
||||||
* [Oscar Radio](http://oscar-radio.xyz/)
|
- [Oscar Radio](http://oscar-radio.xyz/)
|
||||||
* [Sparkk TV](https://www.sparkktv.com/)
|
- [Sparkk TV](https://www.sparkktv.com/)
|
||||||
|
|
||||||
Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the above list. It'd be awesome to see how you're using Plyr :-)
|
Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the above list. It'd be awesome to see how you're using Plyr :-)
|
||||||
|
|
||||||
@ -721,8 +720,8 @@ Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the abo
|
|||||||
|
|
||||||
Credit to the PayPal HTML5 Video player from which Plyr's caption functionality was originally ported from:
|
Credit to the PayPal HTML5 Video player from which Plyr's caption functionality was originally ported from:
|
||||||
|
|
||||||
* [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player)
|
- [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player)
|
||||||
* [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
- [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
import i18n from './i18n';
|
|
||||||
import support from './support';
|
import support from './support';
|
||||||
import { dedupe } from './utils/arrays';
|
import { dedupe } from './utils/arrays';
|
||||||
import browser from './utils/browser';
|
import browser from './utils/browser';
|
||||||
@ -18,6 +17,7 @@ import {
|
|||||||
} from './utils/elements';
|
} from './utils/elements';
|
||||||
import { on, triggerEvent } from './utils/events';
|
import { on, triggerEvent } from './utils/events';
|
||||||
import fetch from './utils/fetch';
|
import fetch from './utils/fetch';
|
||||||
|
import i18n from './utils/i18n';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
import { getHTML } from './utils/strings';
|
import { getHTML } from './utils/strings';
|
||||||
import { parseUrl } from './utils/urls';
|
import { parseUrl } from './utils/urls';
|
||||||
@ -83,9 +83,8 @@ const captions = {
|
|||||||
// * active: The state preferred by user settings or config
|
// * active: The state preferred by user settings or config
|
||||||
// * toggled: The real captions state
|
// * toggled: The real captions state
|
||||||
|
|
||||||
const languages = dedupe(
|
const browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];
|
||||||
Array.from(navigator.languages || navigator.language || navigator.userLanguage).map(language => language.split('-')[0]),
|
const languages = dedupe(browserLanguages.map(language => language.split('-')[0]));
|
||||||
);
|
|
||||||
|
|
||||||
let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
|
let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
|
||||||
|
|
||||||
|
@ -68,19 +68,7 @@ const defaults = {
|
|||||||
// Quality default
|
// Quality default
|
||||||
quality: {
|
quality: {
|
||||||
default: 576,
|
default: 576,
|
||||||
options: [
|
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],
|
||||||
4320,
|
|
||||||
2880,
|
|
||||||
2160,
|
|
||||||
1440,
|
|
||||||
1080,
|
|
||||||
720,
|
|
||||||
576,
|
|
||||||
480,
|
|
||||||
360,
|
|
||||||
240,
|
|
||||||
'default', // YouTube's "auto"
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set loops
|
// Set loops
|
||||||
@ -268,8 +256,9 @@ const defaults = {
|
|||||||
|
|
||||||
// YouTube
|
// YouTube
|
||||||
'statechange',
|
'statechange',
|
||||||
|
|
||||||
|
// Quality
|
||||||
'qualitychange',
|
'qualitychange',
|
||||||
'qualityrequested',
|
|
||||||
|
|
||||||
// Ads
|
// Ads
|
||||||
'adsloaded',
|
'adsloaded',
|
||||||
@ -354,6 +343,9 @@ const defaults = {
|
|||||||
isTouch: 'plyr--is-touch',
|
isTouch: 'plyr--is-touch',
|
||||||
uiSupported: 'plyr--full-ui',
|
uiSupported: 'plyr--full-ui',
|
||||||
noTransition: 'plyr--no-transition',
|
noTransition: 'plyr--no-transition',
|
||||||
|
display: {
|
||||||
|
time: 'plyr__time',
|
||||||
|
},
|
||||||
menu: {
|
menu: {
|
||||||
value: 'plyr__menu__value',
|
value: 'plyr__menu__value',
|
||||||
badge: 'plyr__badge',
|
badge: 'plyr__badge',
|
||||||
|
765
src/js/controls.js
vendored
765
src/js/controls.js
vendored
File diff suppressed because it is too large
Load Diff
@ -177,9 +177,7 @@ class Fullscreen {
|
|||||||
|
|
||||||
// iOS native fullscreen doesn't need the request step
|
// iOS native fullscreen doesn't need the request step
|
||||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
if (this.player.playing) {
|
|
||||||
this.target.webkitEnterFullscreen();
|
this.target.webkitEnterFullscreen();
|
||||||
}
|
|
||||||
} else if (!Fullscreen.native) {
|
} else if (!Fullscreen.native) {
|
||||||
toggleFallback.call(this, true);
|
toggleFallback.call(this, true);
|
||||||
} else if (!this.prefix) {
|
} else if (!this.prefix) {
|
||||||
|
@ -82,6 +82,9 @@ const html5 = {
|
|||||||
triggerEvent.call(player, player.media, 'qualitychange', false, {
|
triggerEvent.call(player, player.media, 'qualitychange', false, {
|
||||||
quality: input,
|
quality: input,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Save to storage
|
||||||
|
player.storage.set({ quality: input });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
|
import { repaint } from './utils/animation';
|
||||||
import browser from './utils/browser';
|
import browser from './utils/browser';
|
||||||
import { getElement, getElements, getFocusElement, matches, toggleClass, toggleHidden } from './utils/elements';
|
import { getElement, getElements, hasClass, matches, toggleClass, toggleHidden } from './utils/elements';
|
||||||
import { on, once, toggleListener, triggerEvent } from './utils/events';
|
import { on, once, toggleListener, triggerEvent } from './utils/events';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
|
|
||||||
@ -13,14 +14,19 @@ class Listeners {
|
|||||||
constructor(player) {
|
constructor(player) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.lastKey = null;
|
this.lastKey = null;
|
||||||
|
this.focusTimer = null;
|
||||||
|
this.lastKeyDown = null;
|
||||||
|
|
||||||
this.handleKey = this.handleKey.bind(this);
|
this.handleKey = this.handleKey.bind(this);
|
||||||
this.toggleMenu = this.toggleMenu.bind(this);
|
this.toggleMenu = this.toggleMenu.bind(this);
|
||||||
|
this.setTabFocus = this.setTabFocus.bind(this);
|
||||||
this.firstTouch = this.firstTouch.bind(this);
|
this.firstTouch = this.firstTouch.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle key presses
|
// Handle key presses
|
||||||
handleKey(event) {
|
handleKey(event) {
|
||||||
|
const { player } = this;
|
||||||
|
const { elements } = player;
|
||||||
const code = event.keyCode ? event.keyCode : event.which;
|
const code = event.keyCode ? event.keyCode : event.which;
|
||||||
const pressed = event.type === 'keydown';
|
const pressed = event.type === 'keydown';
|
||||||
const repeat = pressed && code === this.lastKey;
|
const repeat = pressed && code === this.lastKey;
|
||||||
@ -39,27 +45,32 @@ class 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.player.currentTime = this.player.duration / 10 * (code - 48);
|
player.currentTime = player.duration / 10 * (code - 48);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle the key on keydown
|
// Handle the key on keydown
|
||||||
// Reset on keyup
|
// Reset on keyup
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
// Which keycodes should we prevent default
|
|
||||||
const preventDefault = [32, 37, 38, 39, 40, 48, 49, 50, 51, 52, 53, 54, 56, 57, 67, 70, 73, 75, 76, 77, 79];
|
|
||||||
|
|
||||||
// Check focused element
|
// Check focused element
|
||||||
// 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 = getFocusElement();
|
const focused = document.activeElement;
|
||||||
if (
|
if (is.element(focused)) {
|
||||||
is.element(focused) &&
|
const { editable } = player.config.selectors;
|
||||||
(focused !== this.player.elements.inputs.seek &&
|
const { seek } = elements.inputs;
|
||||||
matches(focused, this.player.config.selectors.editable))
|
|
||||||
) {
|
if (focused !== seek && matches(focused, editable)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.which === 32 && matches(focused, 'button, [role^="menuitem"]')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Which keycodes should we prevent default
|
||||||
|
const preventDefault = [32, 37, 38, 39, 40, 48, 49, 50, 51, 52, 53, 54, 56, 57, 67, 70, 73, 75, 76, 77, 79];
|
||||||
|
|
||||||
// If the code is found prevent default (e.g. prevent scrolling for arrows)
|
// If the code is found prevent default (e.g. prevent scrolling for arrows)
|
||||||
if (preventDefault.includes(code)) {
|
if (preventDefault.includes(code)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -87,52 +98,52 @@ class Listeners {
|
|||||||
case 75:
|
case 75:
|
||||||
// Space and K key
|
// Space and K key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
this.player.togglePlay();
|
player.togglePlay();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 38:
|
case 38:
|
||||||
// Arrow up
|
// Arrow up
|
||||||
this.player.increaseVolume(0.1);
|
player.increaseVolume(0.1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 40:
|
case 40:
|
||||||
// Arrow down
|
// Arrow down
|
||||||
this.player.decreaseVolume(0.1);
|
player.decreaseVolume(0.1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 77:
|
case 77:
|
||||||
// M key
|
// M key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
this.player.muted = !this.player.muted;
|
player.muted = !player.muted;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 39:
|
case 39:
|
||||||
// Arrow forward
|
// Arrow forward
|
||||||
this.player.forward();
|
player.forward();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 37:
|
case 37:
|
||||||
// Arrow back
|
// Arrow back
|
||||||
this.player.rewind();
|
player.rewind();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 70:
|
case 70:
|
||||||
// F key
|
// F key
|
||||||
this.player.fullscreen.toggle();
|
player.fullscreen.toggle();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 67:
|
case 67:
|
||||||
// C key
|
// C key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
this.player.toggleCaptions();
|
player.toggleCaptions();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 76:
|
case 76:
|
||||||
// L key
|
// L key
|
||||||
this.player.loop = !this.player.loop;
|
player.loop = !player.loop;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* case 73:
|
/* case 73:
|
||||||
@ -153,8 +164,8 @@ class 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.player.fullscreen.enabled && this.player.fullscreen.active && code === 27) {
|
if (!player.fullscreen.enabled && player.fullscreen.active && code === 27) {
|
||||||
this.player.fullscreen.toggle();
|
player.fullscreen.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store last code for next cycle
|
// Store last code for next cycle
|
||||||
@ -171,61 +182,102 @@ class Listeners {
|
|||||||
|
|
||||||
// Device is touch enabled
|
// Device is touch enabled
|
||||||
firstTouch() {
|
firstTouch() {
|
||||||
this.player.touch = true;
|
const { player } = this;
|
||||||
|
const { elements } = player;
|
||||||
|
|
||||||
|
player.touch = true;
|
||||||
|
|
||||||
// Add touch class
|
// Add touch class
|
||||||
toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true);
|
toggleClass(elements.container, player.config.classNames.isTouch, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTabFocus(event) {
|
||||||
|
const { player } = this;
|
||||||
|
const { elements } = player;
|
||||||
|
|
||||||
|
clearTimeout(this.focusTimer);
|
||||||
|
|
||||||
|
// Ignore any key other than tab
|
||||||
|
if (event.type === 'keydown' && event.which !== 9) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store reference to event timeStamp
|
||||||
|
if (event.type === 'keydown') {
|
||||||
|
this.lastKeyDown = event.timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove current classes
|
||||||
|
const removeCurrent = () => {
|
||||||
|
const className = player.config.classNames.tabFocus;
|
||||||
|
const current = getElements.call(player, `.${className}`);
|
||||||
|
toggleClass(current, className, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine if a key was pressed to trigger this event
|
||||||
|
const wasKeyDown = event.timeStamp - this.lastKeyDown <= 20;
|
||||||
|
|
||||||
|
// Ignore focus events if a key was pressed prior
|
||||||
|
if (event.type === 'focus' && !wasKeyDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all current
|
||||||
|
removeCurrent();
|
||||||
|
|
||||||
|
// Delay the adding of classname until the focus has changed
|
||||||
|
// This event fires before the focusin event
|
||||||
|
this.focusTimer = setTimeout(() => {
|
||||||
|
const focused = document.activeElement;
|
||||||
|
|
||||||
|
// Ignore if current focus element isn't inside the player
|
||||||
|
if (!elements.container.contains(focused)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
|
||||||
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global window & document listeners
|
// Global window & document listeners
|
||||||
global(toggle = true) {
|
global(toggle = true) {
|
||||||
|
const { player } = this;
|
||||||
|
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
if (this.player.config.keyboard.global) {
|
if (player.config.keyboard.global) {
|
||||||
toggleListener.call(this.player, window, 'keydown keyup', this.handleKey, toggle, false);
|
toggleListener.call(player, window, 'keydown keyup', this.handleKey, toggle, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click anywhere closes menu
|
// Click anywhere closes menu
|
||||||
toggleListener.call(this.player, document.body, 'click', this.toggleMenu, toggle);
|
toggleListener.call(player, document.body, 'click', this.toggleMenu, toggle);
|
||||||
|
|
||||||
// Detect touch by events
|
// Detect touch by events
|
||||||
once.call(this.player, document.body, 'touchstart', this.firstTouch);
|
once.call(player, document.body, 'touchstart', this.firstTouch);
|
||||||
|
|
||||||
|
// Tab focus detection
|
||||||
|
toggleListener.call(player, document.body, 'keydown focus blur', this.setTabFocus, toggle, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container listeners
|
// Container listeners
|
||||||
container() {
|
container() {
|
||||||
|
const { player } = this;
|
||||||
|
const { elements } = player;
|
||||||
|
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
if (!this.player.config.keyboard.global && this.player.config.keyboard.focused) {
|
if (!player.config.keyboard.global && player.config.keyboard.focused) {
|
||||||
on.call(this.player, this.player.elements.container, 'keydown keyup', this.handleKey, false);
|
on.call(player, elements.container, 'keydown keyup', this.handleKey, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect tab focus
|
|
||||||
// Remove class on blur/focusout
|
|
||||||
on.call(this.player, this.player.elements.container, 'focusout', event => {
|
|
||||||
toggleClass(event.target, this.player.config.classNames.tabFocus, false);
|
|
||||||
});
|
|
||||||
// Add classname to tabbed elements
|
|
||||||
on.call(this.player, this.player.elements.container, 'keydown', event => {
|
|
||||||
if (event.keyCode !== 9) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delay the adding of classname until the focus has changed
|
|
||||||
// This event fires before the focusin event
|
|
||||||
setTimeout(() => {
|
|
||||||
toggleClass(getFocusElement(), this.player.config.classNames.tabFocus, true);
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Toggle controls on mouse events and entering fullscreen
|
// Toggle controls on mouse events and entering fullscreen
|
||||||
on.call(
|
on.call(
|
||||||
this.player,
|
player,
|
||||||
this.player.elements.container,
|
elements.container,
|
||||||
'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen',
|
'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen',
|
||||||
event => {
|
event => {
|
||||||
const { controls } = this.player.elements;
|
const { controls } = elements;
|
||||||
|
|
||||||
// Remove button states for fullscreen
|
// Remove button states for fullscreen
|
||||||
if (event.type === 'enterfullscreen') {
|
if (controls && event.type === 'enterfullscreen') {
|
||||||
controls.pressed = false;
|
controls.pressed = false;
|
||||||
controls.hover = false;
|
controls.hover = false;
|
||||||
}
|
}
|
||||||
@ -236,85 +288,83 @@ class Listeners {
|
|||||||
let delay = 0;
|
let delay = 0;
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
ui.toggleControls.call(this.player, true);
|
ui.toggleControls.call(player, true);
|
||||||
// Use longer timeout for touch devices
|
// Use longer timeout for touch devices
|
||||||
delay = this.player.touch ? 3000 : 2000;
|
delay = player.touch ? 3000 : 2000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear timer
|
// Clear timer
|
||||||
clearTimeout(this.player.timers.controls);
|
clearTimeout(player.timers.controls);
|
||||||
// Timer to prevent flicker when seeking
|
|
||||||
this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
|
// Set new timer to prevent flicker when seeking
|
||||||
|
player.timers.controls = setTimeout(() => ui.toggleControls.call(player, false), delay);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for media events
|
// Listen for media events
|
||||||
media() {
|
media() {
|
||||||
|
const { player } = this;
|
||||||
|
const { elements } = player;
|
||||||
|
|
||||||
// Time change on media
|
// Time change on media
|
||||||
on.call(this.player, this.player.media, 'timeupdate seeking seeked', event =>
|
on.call(player, player.media, 'timeupdate seeking seeked', event => controls.timeUpdate.call(player, event));
|
||||||
controls.timeUpdate.call(this.player, event),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Display duration
|
// Display duration
|
||||||
on.call(this.player, this.player.media, 'durationchange loadeddata loadedmetadata', event =>
|
on.call(player, player.media, 'durationchange loadeddata loadedmetadata', event =>
|
||||||
controls.durationUpdate.call(this.player, event),
|
controls.durationUpdate.call(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
|
||||||
on.call(this.player, this.player.media, 'canplay', () => {
|
on.call(player, player.media, 'canplay', () => {
|
||||||
toggleHidden(this.player.elements.volume, !this.player.hasAudio);
|
toggleHidden(elements.volume, !player.hasAudio);
|
||||||
toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio);
|
toggleHidden(elements.buttons.mute, !player.hasAudio);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle the media finishing
|
// Handle the media finishing
|
||||||
on.call(this.player, this.player.media, 'ended', () => {
|
on.call(player, player.media, 'ended', () => {
|
||||||
// Show poster on end
|
// Show poster on end
|
||||||
if (this.player.isHTML5 && this.player.isVideo && this.player.config.resetOnEnd) {
|
if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) {
|
||||||
// Restart
|
// Restart
|
||||||
this.player.restart();
|
player.restart();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for buffer progress
|
// Check for buffer progress
|
||||||
on.call(this.player, this.player.media, 'progress playing seeking seeked', event =>
|
on.call(player, player.media, 'progress playing seeking seeked', event =>
|
||||||
controls.updateProgress.call(this.player, event),
|
controls.updateProgress.call(player, event),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle volume changes
|
// Handle volume changes
|
||||||
on.call(this.player, this.player.media, 'volumechange', event =>
|
on.call(player, player.media, 'volumechange', event => controls.updateVolume.call(player, event));
|
||||||
controls.updateVolume.call(this.player, event),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle play/pause
|
// Handle play/pause
|
||||||
on.call(this.player, this.player.media, 'playing play pause ended emptied timeupdate', event =>
|
on.call(player, player.media, 'playing play pause ended emptied timeupdate', event =>
|
||||||
ui.checkPlaying.call(this.player, event),
|
ui.checkPlaying.call(player, event),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Loading state
|
// Loading state
|
||||||
on.call(this.player, this.player.media, 'waiting canplay seeked playing', event =>
|
on.call(player, player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(player, event));
|
||||||
ui.checkLoading.call(this.player, event),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If autoplay, then load advertisement if required
|
// If autoplay, then load advertisement if required
|
||||||
// TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows
|
// TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows
|
||||||
on.call(this.player, this.player.media, 'playing', () => {
|
on.call(player, player.media, 'playing', () => {
|
||||||
if (!this.player.ads) {
|
if (!player.ads) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If ads are enabled, wait for them first
|
// If ads are enabled, wait for them first
|
||||||
if (this.player.ads.enabled && !this.player.ads.initialized) {
|
if (player.ads.enabled && !player.ads.initialized) {
|
||||||
// Wait for manager response
|
// Wait for manager response
|
||||||
this.player.ads.managerPromise.then(() => this.player.ads.play()).catch(() => this.player.play());
|
player.ads.managerPromise.then(() => player.ads.play()).catch(() => player.play());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Click video
|
// Click video
|
||||||
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
|
if (player.supported.ui && player.config.clickToPlay && !player.isAudio) {
|
||||||
// Re-fetch the wrapper
|
// Re-fetch the wrapper
|
||||||
const wrapper = getElement.call(this.player, `.${this.player.config.classNames.video}`);
|
const wrapper = getElement.call(player, `.${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 (!is.element(wrapper)) {
|
if (!is.element(wrapper)) {
|
||||||
@ -322,28 +372,38 @@ class Listeners {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// On click play, pause ore restart
|
// On click play, pause ore restart
|
||||||
on.call(this.player, wrapper, 'click', () => {
|
on.call(player, elements.container, 'click touchstart', event => {
|
||||||
// Touch devices will just show controls (if we're hiding controls)
|
const targets = [elements.container, wrapper];
|
||||||
if (this.player.config.hideControls && this.player.touch && !this.player.paused) {
|
|
||||||
|
// Ignore if click if not container or in video wrapper
|
||||||
|
if (!targets.includes(event.target) && !wrapper.contains(event.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.player.paused) {
|
// First touch on touch devices will just show controls (if we're hiding controls)
|
||||||
this.player.play();
|
// If controls are shown then it'll toggle like a pointer device
|
||||||
} else if (this.player.ended) {
|
if (
|
||||||
this.player.restart();
|
player.config.hideControls &&
|
||||||
this.player.play();
|
player.touch &&
|
||||||
|
hasClass(elements.container, player.config.classNames.hideControls)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.ended) {
|
||||||
|
player.restart();
|
||||||
|
player.play();
|
||||||
} else {
|
} else {
|
||||||
this.player.pause();
|
player.togglePlay();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable right click
|
// Disable right click
|
||||||
if (this.player.supported.ui && this.player.config.disableContextMenu) {
|
if (player.supported.ui && player.config.disableContextMenu) {
|
||||||
on.call(
|
on.call(
|
||||||
this.player,
|
player,
|
||||||
this.player.elements.wrapper,
|
elements.wrapper,
|
||||||
'contextmenu',
|
'contextmenu',
|
||||||
event => {
|
event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -353,220 +413,230 @@ class Listeners {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Volume change
|
// Volume change
|
||||||
on.call(this.player, this.player.media, 'volumechange', () => {
|
on.call(player, player.media, 'volumechange', () => {
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.player.storage.set({ volume: this.player.volume, muted: this.player.muted });
|
player.storage.set({
|
||||||
|
volume: player.volume,
|
||||||
|
muted: player.muted,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Speed change
|
// Speed change
|
||||||
on.call(this.player, this.player.media, 'ratechange', () => {
|
on.call(player, player.media, 'ratechange', () => {
|
||||||
// Update UI
|
// Update UI
|
||||||
controls.updateSetting.call(this.player, 'speed');
|
controls.updateSetting.call(player, 'speed');
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.player.storage.set({ speed: this.player.speed });
|
player.storage.set({ speed: player.speed });
|
||||||
});
|
|
||||||
|
|
||||||
// Quality request
|
|
||||||
on.call(this.player, this.player.media, 'qualityrequested', event => {
|
|
||||||
// Save to storage
|
|
||||||
this.player.storage.set({ quality: event.detail.quality });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quality change
|
// Quality change
|
||||||
on.call(this.player, this.player.media, 'qualitychange', event => {
|
on.call(player, player.media, 'qualitychange', event => {
|
||||||
// Update UI
|
// Update UI
|
||||||
controls.updateSetting.call(this.player, 'quality', null, event.detail.quality);
|
controls.updateSetting.call(player, 'quality', null, event.detail.quality);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Proxy events to container
|
// Proxy events to container
|
||||||
// Bubble up key events for Edge
|
// Bubble up key events for Edge
|
||||||
const proxyEvents = this.player.config.events.concat(['keyup', 'keydown']).join(' ');
|
const proxyEvents = player.config.events.concat(['keyup', 'keydown']).join(' ');
|
||||||
on.call(this.player, this.player.media, proxyEvents, event => {
|
|
||||||
|
on.call(player, player.media, proxyEvents, event => {
|
||||||
let { detail = {} } = event;
|
let { detail = {} } = event;
|
||||||
|
|
||||||
// Get error details from media
|
// Get error details from media
|
||||||
if (event.type === 'error') {
|
if (event.type === 'error') {
|
||||||
detail = this.player.media.error;
|
detail = player.media.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerEvent.call(this.player, this.player.elements.container, event.type, true, detail);
|
triggerEvent.call(player, elements.container, event.type, true, detail);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for control events
|
|
||||||
controls() {
|
|
||||||
// IE doesn't support input event, so we fallback to change
|
|
||||||
const inputEvent = browser.isIE ? 'change' : 'input';
|
|
||||||
|
|
||||||
// Run default and custom handlers
|
// Run default and custom handlers
|
||||||
const proxy = (event, defaultHandler, customHandlerKey) => {
|
proxy(event, defaultHandler, customHandlerKey) {
|
||||||
const customHandler = this.player.config.listeners[customHandlerKey];
|
const { player } = this;
|
||||||
|
const customHandler = player.config.listeners[customHandlerKey];
|
||||||
const hasCustomHandler = is.function(customHandler);
|
const hasCustomHandler = is.function(customHandler);
|
||||||
let returned = true;
|
let returned = true;
|
||||||
|
|
||||||
// Execute custom handler
|
// Execute custom handler
|
||||||
if (hasCustomHandler) {
|
if (hasCustomHandler) {
|
||||||
returned = customHandler.call(this.player, event);
|
returned = customHandler.call(player, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only call default handler if not prevented in custom handler
|
// Only call default handler if not prevented in custom handler
|
||||||
if (returned && is.function(defaultHandler)) {
|
if (returned && is.function(defaultHandler)) {
|
||||||
defaultHandler.call(this.player, event);
|
defaultHandler.call(player, event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Trigger custom and default handlers
|
// Trigger custom and default handlers
|
||||||
const bind = (element, type, defaultHandler, customHandlerKey, passive = true) => {
|
bind(element, type, defaultHandler, customHandlerKey, passive = true) {
|
||||||
const customHandler = this.player.config.listeners[customHandlerKey];
|
const { player } = this;
|
||||||
|
const customHandler = player.config.listeners[customHandlerKey];
|
||||||
const hasCustomHandler = is.function(customHandler);
|
const hasCustomHandler = is.function(customHandler);
|
||||||
|
|
||||||
on.call(
|
on.call(
|
||||||
this.player,
|
player,
|
||||||
element,
|
element,
|
||||||
type,
|
type,
|
||||||
event => proxy(event, defaultHandler, customHandlerKey),
|
event => this.proxy(event, defaultHandler, customHandlerKey),
|
||||||
passive && !hasCustomHandler,
|
passive && !hasCustomHandler,
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
// Listen for control events
|
||||||
|
controls() {
|
||||||
|
const { player } = this;
|
||||||
|
const { elements } = player;
|
||||||
|
|
||||||
|
// IE doesn't support input event, so we fallback to change
|
||||||
|
const inputEvent = browser.isIE ? 'change' : 'input';
|
||||||
|
|
||||||
// Play/pause toggle
|
// Play/pause toggle
|
||||||
if (this.player.elements.buttons.play) {
|
if (elements.buttons.play) {
|
||||||
Array.from(this.player.elements.buttons.play).forEach(button => {
|
Array.from(elements.buttons.play).forEach(button => {
|
||||||
bind(button, 'click', this.player.togglePlay, 'play');
|
this.bind(button, 'click', player.togglePlay, 'play');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause
|
// Pause
|
||||||
bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
|
this.bind(elements.buttons.restart, 'click', player.restart, 'restart');
|
||||||
|
|
||||||
// Rewind
|
// Rewind
|
||||||
bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
|
this.bind(elements.buttons.rewind, 'click', player.rewind, 'rewind');
|
||||||
|
|
||||||
// Rewind
|
// Rewind
|
||||||
bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
|
this.bind(elements.buttons.fastForward, 'click', player.forward, 'fastForward');
|
||||||
|
|
||||||
// Mute toggle
|
// Mute toggle
|
||||||
bind(
|
this.bind(
|
||||||
this.player.elements.buttons.mute,
|
elements.buttons.mute,
|
||||||
'click',
|
'click',
|
||||||
() => {
|
() => {
|
||||||
this.player.muted = !this.player.muted;
|
player.muted = !player.muted;
|
||||||
},
|
},
|
||||||
'mute',
|
'mute',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Captions toggle
|
// Captions toggle
|
||||||
bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions());
|
this.bind(elements.buttons.captions, 'click', () => player.toggleCaptions());
|
||||||
|
|
||||||
// Fullscreen toggle
|
// Fullscreen toggle
|
||||||
bind(
|
this.bind(
|
||||||
this.player.elements.buttons.fullscreen,
|
elements.buttons.fullscreen,
|
||||||
'click',
|
'click',
|
||||||
() => {
|
() => {
|
||||||
this.player.fullscreen.toggle();
|
player.fullscreen.toggle();
|
||||||
},
|
},
|
||||||
'fullscreen',
|
'fullscreen',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Picture-in-Picture
|
// Picture-in-Picture
|
||||||
bind(
|
this.bind(
|
||||||
this.player.elements.buttons.pip,
|
elements.buttons.pip,
|
||||||
'click',
|
'click',
|
||||||
() => {
|
() => {
|
||||||
this.player.pip = 'toggle';
|
player.pip = 'toggle';
|
||||||
},
|
},
|
||||||
'pip',
|
'pip',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Airplay
|
// Airplay
|
||||||
bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
|
this.bind(elements.buttons.airplay, 'click', player.airplay, 'airplay');
|
||||||
|
|
||||||
// Settings menu
|
// Settings menu - click toggle
|
||||||
bind(this.player.elements.buttons.settings, 'click', event => {
|
this.bind(elements.buttons.settings, 'click', event => {
|
||||||
controls.toggleMenu.call(this.player, event);
|
// Prevent the document click listener closing the menu
|
||||||
});
|
|
||||||
|
|
||||||
// Settings menu
|
|
||||||
bind(this.player.elements.settings.form, 'click', event => {
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
// Go back to home tab on click
|
controls.toggleMenu.call(player, event);
|
||||||
const showHomeTab = () => {
|
});
|
||||||
const id = `plyr-settings-${this.player.id}-home`;
|
|
||||||
controls.showTab.call(this.player, id);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Settings menu items - use event delegation as items are added/removed
|
// Settings menu - keyboard toggle
|
||||||
if (matches(event.target, this.player.config.selectors.inputs.language)) {
|
// We have to bind to keyup otherwise Firefox triggers a click when a keydown event handler shifts focus
|
||||||
proxy(
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1220143
|
||||||
event,
|
this.bind(
|
||||||
() => {
|
elements.buttons.settings,
|
||||||
this.player.currentTrack = Number(event.target.value);
|
'keyup',
|
||||||
showHomeTab();
|
event => {
|
||||||
|
const code = event.which;
|
||||||
|
|
||||||
|
// We only care about space and return
|
||||||
|
if (![13, 32].includes(code)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because return triggers a click anyway, all we need to do is set focus
|
||||||
|
if (code === 13) {
|
||||||
|
controls.focusFirstMenuItem.call(player, null, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent scroll
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Prevent playing video (Firefox)
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
// Toggle menu
|
||||||
|
controls.toggleMenu.call(player, event);
|
||||||
},
|
},
|
||||||
'language',
|
null,
|
||||||
|
false, // Can't be passive as we're preventing default
|
||||||
);
|
);
|
||||||
} else if (matches(event.target, this.player.config.selectors.inputs.quality)) {
|
|
||||||
proxy(
|
// Escape closes menu
|
||||||
event,
|
this.bind(elements.settings.menu, 'keydown', event => {
|
||||||
() => {
|
if (event.which === 27) {
|
||||||
this.player.quality = event.target.value;
|
controls.toggleMenu.call(player, event);
|
||||||
showHomeTab();
|
|
||||||
},
|
|
||||||
'quality',
|
|
||||||
);
|
|
||||||
} else if (matches(event.target, this.player.config.selectors.inputs.speed)) {
|
|
||||||
proxy(
|
|
||||||
event,
|
|
||||||
() => {
|
|
||||||
this.player.speed = parseFloat(event.target.value);
|
|
||||||
showHomeTab();
|
|
||||||
},
|
|
||||||
'speed',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const tab = event.target;
|
|
||||||
controls.showTab.call(this.player, tab.getAttribute('aria-controls'));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set range input alternative "value", which matches the tooltip time (#954)
|
// Set range input alternative "value", which matches the tooltip time (#954)
|
||||||
bind(this.player.elements.inputs.seek, 'mousedown mousemove', event => {
|
this.bind(elements.inputs.seek, 'mousedown mousemove', event => {
|
||||||
const clientRect = this.player.elements.progress.getBoundingClientRect();
|
const rect = elements.progress.getBoundingClientRect();
|
||||||
const percent = 100 / clientRect.width * (event.pageX - clientRect.left);
|
const percent = 100 / rect.width * (event.pageX - rect.left);
|
||||||
event.currentTarget.setAttribute('seek-value', percent);
|
event.currentTarget.setAttribute('seek-value', percent);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pause while seeking
|
// Pause while seeking
|
||||||
bind(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
|
this.bind(elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
|
||||||
const seek = event.currentTarget;
|
const seek = event.currentTarget;
|
||||||
|
|
||||||
const code = event.keyCode ? event.keyCode : event.which;
|
const code = event.keyCode ? event.keyCode : event.which;
|
||||||
const eventType = event.type;
|
const attribute = 'play-on-seeked';
|
||||||
|
|
||||||
if ((eventType === 'keydown' || eventType === 'keyup') && (code !== 39 && code !== 37)) {
|
if (is.keyboardEvent(event) && (code !== 39 && code !== 37)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Was playing before?
|
// Was playing before?
|
||||||
const play = seek.hasAttribute('play-on-seeked');
|
const play = seek.hasAttribute(attribute);
|
||||||
|
|
||||||
// Done seeking
|
// Done seeking
|
||||||
const done = ['mouseup', 'touchend', 'keyup'].includes(event.type);
|
const done = ['mouseup', 'touchend', 'keyup'].includes(event.type);
|
||||||
|
|
||||||
// If we're done seeking and it was playing, resume playback
|
// If we're done seeking and it was playing, resume playback
|
||||||
if (play && done) {
|
if (play && done) {
|
||||||
seek.removeAttribute('play-on-seeked');
|
seek.removeAttribute(attribute);
|
||||||
this.player.play();
|
player.play();
|
||||||
} else if (!done && this.player.playing) {
|
} else if (!done && player.playing) {
|
||||||
seek.setAttribute('play-on-seeked', '');
|
seek.setAttribute(attribute, '');
|
||||||
this.player.pause();
|
player.pause();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fix range inputs on iOS
|
||||||
|
// Super weird iOS bug where after you interact with an <input type="range">,
|
||||||
|
// it takes over further interactions on the page. This is a hack
|
||||||
|
if (browser.isIos) {
|
||||||
|
const inputs = getElements.call(player, 'input[type="range"]');
|
||||||
|
Array.from(inputs).forEach(input => this.bind(input, inputEvent, event => repaint(event.target)));
|
||||||
|
}
|
||||||
|
|
||||||
// Seek
|
// Seek
|
||||||
bind(
|
this.bind(
|
||||||
this.player.elements.inputs.seek,
|
elements.inputs.seek,
|
||||||
inputEvent,
|
inputEvent,
|
||||||
event => {
|
event => {
|
||||||
const seek = event.currentTarget;
|
const seek = event.currentTarget;
|
||||||
@ -580,70 +650,71 @@ class Listeners {
|
|||||||
|
|
||||||
seek.removeAttribute('seek-value');
|
seek.removeAttribute('seek-value');
|
||||||
|
|
||||||
this.player.currentTime = seekTo / seek.max * this.player.duration;
|
player.currentTime = seekTo / seek.max * player.duration;
|
||||||
},
|
},
|
||||||
'seek',
|
'seek',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Current time invert
|
// Seek tooltip
|
||||||
// Only if one time element is used for both currentTime and duration
|
this.bind(elements.progress, 'mouseenter mouseleave mousemove', event =>
|
||||||
if (this.player.config.toggleInvert && !is.element(this.player.elements.display.duration)) {
|
controls.updateSeekTooltip.call(player, event),
|
||||||
bind(this.player.elements.display.currentTime, 'click', () => {
|
|
||||||
// Do nothing if we're at the start
|
|
||||||
if (this.player.currentTime === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.player.config.invertTime = !this.player.config.invertTime;
|
|
||||||
|
|
||||||
controls.timeUpdate.call(this.player);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Volume
|
|
||||||
bind(
|
|
||||||
this.player.elements.inputs.volume,
|
|
||||||
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) {
|
||||||
Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(element => {
|
Array.from(getElements.call(player, 'input[type="range"]')).forEach(element => {
|
||||||
bind(element, 'input', event => controls.updateRangeFill.call(this.player, event.target));
|
this.bind(element, 'input', event => controls.updateRangeFill.call(player, event.target));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek tooltip
|
// Current time invert
|
||||||
bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', event =>
|
// Only if one time element is used for both currentTime and duration
|
||||||
controls.updateSeekTooltip.call(this.player, event),
|
if (player.config.toggleInvert && !is.element(elements.display.duration)) {
|
||||||
|
this.bind(elements.display.currentTime, 'click', () => {
|
||||||
|
// Do nothing if we're at the start
|
||||||
|
if (player.currentTime === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.config.invertTime = !player.config.invertTime;
|
||||||
|
|
||||||
|
controls.timeUpdate.call(player);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Volume
|
||||||
|
this.bind(
|
||||||
|
elements.inputs.volume,
|
||||||
|
inputEvent,
|
||||||
|
event => {
|
||||||
|
player.volume = event.target.value;
|
||||||
|
},
|
||||||
|
'volume',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
|
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
|
||||||
bind(this.player.elements.controls, 'mouseenter mouseleave', event => {
|
this.bind(elements.controls, 'mouseenter mouseleave', event => {
|
||||||
this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
|
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
|
// Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
|
||||||
bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
|
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
|
||||||
this.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Focus in/out on controls
|
// Focus in/out on controls
|
||||||
bind(this.player.elements.controls, 'focusin focusout', event => {
|
this.bind(elements.controls, 'focusin focusout', event => {
|
||||||
const { config, elements, timers } = this.player;
|
const { config, elements, timers } = player;
|
||||||
|
const isFocusIn = event.type === 'focusin';
|
||||||
|
|
||||||
// Skip transition to prevent focus from scrolling the parent element
|
// Skip transition to prevent focus from scrolling the parent element
|
||||||
toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin');
|
toggleClass(elements.controls, config.classNames.noTransition, isFocusIn);
|
||||||
|
|
||||||
// Toggle
|
// Toggle
|
||||||
ui.toggleControls.call(this.player, event.type === 'focusin');
|
ui.toggleControls.call(player, isFocusIn);
|
||||||
|
|
||||||
// If focusin, hide again after delay
|
// If focusin, hide again after delay
|
||||||
if (event.type === 'focusin') {
|
if (isFocusIn) {
|
||||||
// Restore transition
|
// Restore transition
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toggleClass(elements.controls, config.classNames.noTransition, false);
|
toggleClass(elements.controls, config.classNames.noTransition, false);
|
||||||
@ -654,14 +725,15 @@ class Listeners {
|
|||||||
|
|
||||||
// Clear timer
|
// Clear timer
|
||||||
clearTimeout(timers.controls);
|
clearTimeout(timers.controls);
|
||||||
|
|
||||||
// Hide
|
// Hide
|
||||||
timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
|
timers.controls = setTimeout(() => ui.toggleControls.call(player, false), delay);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mouse wheel for volume
|
// Mouse wheel for volume
|
||||||
bind(
|
this.bind(
|
||||||
this.player.elements.inputs.volume,
|
elements.inputs.volume,
|
||||||
'wheel',
|
'wheel',
|
||||||
event => {
|
event => {
|
||||||
// Detect "natural" scroll - suppored on OS X Safari only
|
// Detect "natural" scroll - suppored on OS X Safari only
|
||||||
@ -675,10 +747,10 @@ class Listeners {
|
|||||||
const direction = Math.sign(Math.abs(x) > Math.abs(y) ? x : y);
|
const direction = Math.sign(Math.abs(x) > Math.abs(y) ? x : y);
|
||||||
|
|
||||||
// Change the volume by 2%
|
// Change the volume by 2%
|
||||||
this.player.increaseVolume(direction / 50);
|
player.increaseVolume(direction / 50);
|
||||||
|
|
||||||
// Don't break page scrolling at max and min
|
// Don't break page scrolling at max and min
|
||||||
const { volume } = this.player.media;
|
const { volume } = player.media;
|
||||||
if ((direction === 1 && volume < 1) || (direction === -1 && volume > 0)) {
|
if ((direction === 1 && volume < 1) || (direction === -1 && volume > 0)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
/* global google */
|
/* global google */
|
||||||
|
|
||||||
import i18n from '../i18n';
|
|
||||||
import { createElement } from '../utils/elements';
|
import { createElement } from '../utils/elements';
|
||||||
import { triggerEvent } from '../utils/events';
|
import { triggerEvent } from '../utils/events';
|
||||||
|
import i18n from '../utils/i18n';
|
||||||
import is from '../utils/is';
|
import is from '../utils/is';
|
||||||
import loadScript from '../utils/loadScript';
|
import loadScript from '../utils/loadScript';
|
||||||
import { formatTime } from '../utils/time';
|
import { formatTime } from '../utils/time';
|
||||||
@ -207,6 +207,11 @@ class Ads {
|
|||||||
* @param {Event} adsManagerLoadedEvent
|
* @param {Event} adsManagerLoadedEvent
|
||||||
*/
|
*/
|
||||||
onAdsManagerLoaded(event) {
|
onAdsManagerLoaded(event) {
|
||||||
|
// Load could occur after a source change (race condition)
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the ads manager
|
// Get the ads manager
|
||||||
const settings = new google.ima.AdsRenderingSettings();
|
const settings = new google.ima.AdsRenderingSettings();
|
||||||
|
|
||||||
@ -240,10 +245,6 @@ class Ads {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get skippable state
|
|
||||||
// TODO: Skip button
|
|
||||||
// this.player.debug.warn(this.manager.getAdSkippableState());
|
|
||||||
|
|
||||||
// Set volume to match player
|
// Set volume to match player
|
||||||
this.manager.setVolume(this.player.volume);
|
this.manager.setVolume(this.player.volume);
|
||||||
|
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
// YouTube plugin
|
// YouTube plugin
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import controls from '../controls';
|
|
||||||
import ui from '../ui';
|
import ui from '../ui';
|
||||||
import { dedupe } from '../utils/arrays';
|
|
||||||
import { createElement, replaceElement, toggleClass } from '../utils/elements';
|
import { createElement, replaceElement, toggleClass } from '../utils/elements';
|
||||||
import { triggerEvent } from '../utils/events';
|
import { triggerEvent } from '../utils/events';
|
||||||
import fetch from '../utils/fetch';
|
import fetch from '../utils/fetch';
|
||||||
@ -23,37 +21,6 @@ function parseId(url) {
|
|||||||
return url.match(regex) ? RegExp.$2 : url;
|
return url.match(regex) ? RegExp.$2 : url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standardise YouTube quality unit
|
|
||||||
function mapQualityUnit(input) {
|
|
||||||
const qualities = {
|
|
||||||
hd2160: 2160,
|
|
||||||
hd1440: 1440,
|
|
||||||
hd1080: 1080,
|
|
||||||
hd720: 720,
|
|
||||||
large: 480,
|
|
||||||
medium: 360,
|
|
||||||
small: 240,
|
|
||||||
tiny: 144,
|
|
||||||
};
|
|
||||||
|
|
||||||
const entry = Object.entries(qualities).find(entry => entry.includes(input));
|
|
||||||
|
|
||||||
if (entry) {
|
|
||||||
// Get the match corresponding to the input
|
|
||||||
return entry.find(value => value !== input);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'default';
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapQualityUnits(levels) {
|
|
||||||
if (is.empty(levels)) {
|
|
||||||
return levels;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dedupe(levels.map(level => mapQualityUnit(level)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set playback state and trigger change (only on actual change)
|
// Set playback state and trigger change (only on actual change)
|
||||||
function assurePlaybackState(play) {
|
function assurePlaybackState(play) {
|
||||||
if (play && !this.embed.hasPlayed) {
|
if (play && !this.embed.hasPlayed) {
|
||||||
@ -225,11 +192,6 @@ const youtube = {
|
|||||||
triggerEvent.call(player, player.media, 'error');
|
triggerEvent.call(player, player.media, 'error');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPlaybackQualityChange() {
|
|
||||||
triggerEvent.call(player, player.media, 'qualitychange', false, {
|
|
||||||
quality: player.media.quality,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onPlaybackRateChange(event) {
|
onPlaybackRateChange(event) {
|
||||||
// Get the instance
|
// Get the instance
|
||||||
const instance = event.target;
|
const instance = event.target;
|
||||||
@ -299,16 +261,6 @@ const youtube = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quality
|
|
||||||
Object.defineProperty(player.media, 'quality', {
|
|
||||||
get() {
|
|
||||||
return mapQualityUnit(instance.getPlaybackQuality());
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
instance.setPlaybackQuality(mapQualityUnit(input));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Volume
|
// Volume
|
||||||
let { volume } = player.config;
|
let { volume } = player.config;
|
||||||
Object.defineProperty(player.media, 'volume', {
|
Object.defineProperty(player.media, 'volume', {
|
||||||
@ -457,12 +409,6 @@ const youtube = {
|
|||||||
player.media.duration = instance.getDuration();
|
player.media.duration = instance.getDuration();
|
||||||
triggerEvent.call(player, player.media, 'durationchange');
|
triggerEvent.call(player, player.media, 'durationchange');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get quality
|
|
||||||
controls.setQualityMenu.call(
|
|
||||||
player,
|
|
||||||
mapQualityUnits(instance.getAvailableQualityLevels()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr
|
// Plyr
|
||||||
// plyr.js v3.3.23
|
// plyr.js v3.4.0-beta.2
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@ -75,16 +75,17 @@ class Plyr {
|
|||||||
// Elements cache
|
// Elements cache
|
||||||
this.elements = {
|
this.elements = {
|
||||||
container: null,
|
container: null,
|
||||||
|
captions: null,
|
||||||
buttons: {},
|
buttons: {},
|
||||||
display: {},
|
display: {},
|
||||||
progress: {},
|
progress: {},
|
||||||
inputs: {},
|
inputs: {},
|
||||||
settings: {
|
settings: {
|
||||||
|
popup: null,
|
||||||
menu: null,
|
menu: null,
|
||||||
panes: {},
|
panels: {},
|
||||||
tabs: {},
|
buttons: {},
|
||||||
},
|
},
|
||||||
captions: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Captions
|
// Captions
|
||||||
@ -185,7 +186,7 @@ class Plyr {
|
|||||||
// YouTube requires the playsinline in the URL
|
// YouTube requires the playsinline in the URL
|
||||||
if (this.isYouTube) {
|
if (this.isYouTube) {
|
||||||
this.config.playsinline = truthy.includes(url.searchParams.get('playsinline'));
|
this.config.playsinline = truthy.includes(url.searchParams.get('playsinline'));
|
||||||
this.config.hl = url.searchParams.get('hl');
|
this.config.hl = url.searchParams.get('hl'); // TODO: Should this be setting language?
|
||||||
} else {
|
} else {
|
||||||
this.config.playsinline = true;
|
this.config.playsinline = true;
|
||||||
}
|
}
|
||||||
@ -221,7 +222,7 @@ class Plyr {
|
|||||||
if (this.media.hasAttribute('autoplay')) {
|
if (this.media.hasAttribute('autoplay')) {
|
||||||
this.config.autoplay = true;
|
this.config.autoplay = true;
|
||||||
}
|
}
|
||||||
if (this.media.hasAttribute('playsinline')) {
|
if (this.media.hasAttribute('playsinline') || this.media.hasAttribute('webkit-playsinline')) {
|
||||||
this.config.playsinline = true;
|
this.config.playsinline = true;
|
||||||
}
|
}
|
||||||
if (this.media.hasAttribute('muted')) {
|
if (this.media.hasAttribute('muted')) {
|
||||||
@ -293,7 +294,9 @@ class Plyr {
|
|||||||
this.fullscreen = new Fullscreen(this);
|
this.fullscreen = new Fullscreen(this);
|
||||||
|
|
||||||
// Setup ads if provided
|
// Setup ads if provided
|
||||||
|
if (this.config.ads.enabled) {
|
||||||
this.ads = new Ads(this);
|
this.ads = new Ads(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Autoplay if required
|
// Autoplay if required
|
||||||
if (this.config.autoplay) {
|
if (this.config.autoplay) {
|
||||||
@ -695,9 +698,6 @@ class Plyr {
|
|||||||
quality = value;
|
quality = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger request event
|
|
||||||
triggerEvent.call(this, this.media, 'qualityrequested', false, { quality });
|
|
||||||
|
|
||||||
// Update config
|
// Update config
|
||||||
config.selected = quality;
|
config.selected = quality;
|
||||||
|
|
||||||
@ -933,13 +933,16 @@ class Plyr {
|
|||||||
if (hiding && this.config.controls.includes('settings') && !is.empty(this.config.settings)) {
|
if (hiding && this.config.controls.includes('settings') && !is.empty(this.config.settings)) {
|
||||||
controls.toggleMenu.call(this, false);
|
controls.toggleMenu.call(this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger event on change
|
// Trigger event on change
|
||||||
if (hiding !== isHidden) {
|
if (hiding !== isHidden) {
|
||||||
const eventName = hiding ? 'controlshidden' : 'controlsshown';
|
const eventName = hiding ? 'controlshidden' : 'controlsshown';
|
||||||
triggerEvent.call(this, this.media, eventName);
|
triggerEvent.call(this, this.media, eventName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !hiding;
|
return !hiding;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr Polyfilled Build
|
// Plyr Polyfilled Build
|
||||||
// plyr.js v3.3.23
|
// plyr.js v3.4.0-beta.2
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
import captions from './captions';
|
import captions from './captions';
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
import i18n from './i18n';
|
|
||||||
import support from './support';
|
import support from './support';
|
||||||
import browser from './utils/browser';
|
import browser from './utils/browser';
|
||||||
import { getElement, toggleClass } from './utils/elements';
|
import { getElement, toggleClass } from './utils/elements';
|
||||||
import { ready, triggerEvent } from './utils/events';
|
import { ready, triggerEvent } from './utils/events';
|
||||||
|
import i18n from './utils/i18n';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
import loadImage from './utils/loadImage';
|
import loadImage from './utils/loadImage';
|
||||||
|
|
||||||
|
@ -15,7 +15,9 @@ export const transitionEndEvent = (() => {
|
|||||||
transition: 'transitionend',
|
transition: 'transitionend',
|
||||||
};
|
};
|
||||||
|
|
||||||
const type = Object.keys(events).find(event => element.style[event] !== undefined);
|
const type = Object.keys(events).find(
|
||||||
|
event => element.style[event] !== undefined,
|
||||||
|
);
|
||||||
|
|
||||||
return is.string(type) ? events[type] : false;
|
return is.string(type) ? events[type] : false;
|
||||||
})();
|
})();
|
||||||
@ -23,8 +25,12 @@ export const transitionEndEvent = (() => {
|
|||||||
// Force repaint of element
|
// Force repaint of element
|
||||||
export function repaint(element) {
|
export function repaint(element) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
toggleHidden(element, true);
|
toggleHidden(element, true);
|
||||||
element.offsetHeight; // eslint-disable-line
|
element.offsetHeight; // eslint-disable-line
|
||||||
toggleHidden(element, false);
|
toggleHidden(element, false);
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
@ -70,12 +70,19 @@ export function createElement(type, attributes, text) {
|
|||||||
|
|
||||||
// Inaert an element after another
|
// Inaert an element after another
|
||||||
export function insertAfter(element, target) {
|
export function insertAfter(element, target) {
|
||||||
|
if (!is.element(element) || !is.element(target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
target.parentNode.insertBefore(element, target.nextSibling);
|
target.parentNode.insertBefore(element, target.nextSibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert a DocumentFragment
|
// Insert a DocumentFragment
|
||||||
export function insertElement(type, parent, attributes, text) {
|
export function insertElement(type, parent, attributes, text) {
|
||||||
// Inject the new <element>
|
if (!is.element(parent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
parent.appendChild(createElement(type, attributes, text));
|
parent.appendChild(createElement(type, attributes, text));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +102,10 @@ export function removeElement(element) {
|
|||||||
|
|
||||||
// Remove all child elements
|
// Remove all child elements
|
||||||
export function emptyElement(element) {
|
export function emptyElement(element) {
|
||||||
|
if (!is.element(element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let { length } = element.childNodes;
|
let { length } = element.childNodes;
|
||||||
|
|
||||||
while (length > 0) {
|
while (length > 0) {
|
||||||
@ -180,7 +191,7 @@ export function toggleHidden(element, hidden) {
|
|||||||
let hide = hidden;
|
let hide = hidden;
|
||||||
|
|
||||||
if (!is.boolean(hide)) {
|
if (!is.boolean(hide)) {
|
||||||
hide = !element.hasAttribute('hidden');
|
hide = !element.hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hide) {
|
if (hide) {
|
||||||
@ -192,6 +203,10 @@ export function toggleHidden(element, hidden) {
|
|||||||
|
|
||||||
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
|
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
|
||||||
export function toggleClass(element, className, force) {
|
export function toggleClass(element, className, force) {
|
||||||
|
if (is.nodeList(element)) {
|
||||||
|
return Array.from(element).map(e => toggleClass(e, className, force));
|
||||||
|
}
|
||||||
|
|
||||||
if (is.element(element)) {
|
if (is.element(element)) {
|
||||||
let method = 'toggle';
|
let method = 'toggle';
|
||||||
if (typeof force !== 'undefined') {
|
if (typeof force !== 'undefined') {
|
||||||
@ -202,7 +217,7 @@ export function toggleClass(element, className, force) {
|
|||||||
return element.classList.contains(className);
|
return element.classList.contains(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has class name
|
// Has class name
|
||||||
@ -238,19 +253,6 @@ export function getElement(selector) {
|
|||||||
return this.elements.container.querySelector(selector);
|
return this.elements.container.querySelector(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the focused element
|
|
||||||
export function getFocusElement() {
|
|
||||||
let focused = document.activeElement;
|
|
||||||
|
|
||||||
if (!focused || focused === document.body) {
|
|
||||||
focused = null;
|
|
||||||
} else {
|
|
||||||
focused = document.querySelector(':focus');
|
|
||||||
}
|
|
||||||
|
|
||||||
return focused;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trap focus inside container
|
// Trap focus inside container
|
||||||
export function trapFocus(element = null, toggle = false) {
|
export function trapFocus(element = null, toggle = false) {
|
||||||
if (!is.element(element)) {
|
if (!is.element(element)) {
|
||||||
@ -268,7 +270,7 @@ export function trapFocus(element = null, toggle = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the current focused element
|
// Get the current focused element
|
||||||
const focused = getFocusElement();
|
const focused = document.activeElement;
|
||||||
|
|
||||||
if (focused === last && !event.shiftKey) {
|
if (focused === last && !event.shiftKey) {
|
||||||
// Move focus to first element that can be tabbed if Shift isn't used
|
// Move focus to first element that can be tabbed if Shift isn't used
|
||||||
@ -283,3 +285,18 @@ export function trapFocus(element = null, toggle = false) {
|
|||||||
|
|
||||||
toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
|
toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set focus and tab focus class
|
||||||
|
export function setFocus(element = null, tabFocus = false) {
|
||||||
|
if (!is.element(element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set regular focus
|
||||||
|
element.focus();
|
||||||
|
|
||||||
|
// If we want to mimic keyboard focus via tab
|
||||||
|
if (tabFocus) {
|
||||||
|
toggleClass(element, this.config.classNames.tabFocus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
// Plyr internationalization
|
// Plyr internationalization
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import is from './utils/is';
|
import is from './is';
|
||||||
import { getDeep } from './utils/objects';
|
import { getDeep } from './objects';
|
||||||
import { replaceAll } from './utils/strings';
|
import { replaceAll } from './strings';
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
get(key = '', config = {}) {
|
get(key = '', config = {}) {
|
@ -16,6 +16,7 @@ const isNodeList = input => instanceOf(input, NodeList);
|
|||||||
const isElement = input => instanceOf(input, Element);
|
const isElement = input => instanceOf(input, Element);
|
||||||
const isTextNode = input => getConstructor(input) === Text;
|
const isTextNode = input => getConstructor(input) === Text;
|
||||||
const isEvent = input => instanceOf(input, Event);
|
const isEvent = input => instanceOf(input, Event);
|
||||||
|
const isKeyboardEvent = input => instanceOf(input, KeyboardEvent);
|
||||||
const isCue = input => instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
|
const isCue = input => instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
|
||||||
const isTrack = input => instanceOf(input, TextTrack) || (!isNullOrUndefined(input) && isString(input.kind));
|
const isTrack = input => instanceOf(input, TextTrack) || (!isNullOrUndefined(input) && isString(input.kind));
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ export default {
|
|||||||
element: isElement,
|
element: isElement,
|
||||||
textNode: isTextNode,
|
textNode: isTextNode,
|
||||||
event: isEvent,
|
event: isEvent,
|
||||||
|
keyboardEvent: isKeyboardEvent,
|
||||||
cue: isCue,
|
cue: isCue,
|
||||||
track: isTrack,
|
track: isTrack,
|
||||||
url: isUrl,
|
url: isUrl,
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
padding: $plyr-control-spacing;
|
padding: $plyr-control-spacing;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transform: translateY(-($plyr-control-spacing * 4));
|
|
||||||
transition: transform 0.4s ease-in-out;
|
transition: transform 0.4s ease-in-out;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@ -53,6 +52,8 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr--hide-controls .plyr__captions {
|
// If the lower controls are shown and not empty
|
||||||
transform: translateY(-($plyr-control-spacing * 1.5));
|
.plyr:not(.plyr--hide-controls) .plyr__controls:not(:empty) ~ .plyr__captions {
|
||||||
|
transform: translateY(-($plyr-control-spacing * 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audio styles
|
// Audio control
|
||||||
.plyr--audio .plyr__control {
|
.plyr--audio .plyr__control {
|
||||||
&.plyr__tab-focus,
|
&.plyr__tab-focus,
|
||||||
&:hover,
|
&:hover,
|
||||||
@ -51,6 +51,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Video control
|
||||||
|
.plyr--video .plyr__control {
|
||||||
|
svg {
|
||||||
|
filter: drop-shadow(0 1px 1px rgba(#000, 0.15));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hover and tab focus
|
||||||
|
&.plyr__tab-focus,
|
||||||
|
&:hover,
|
||||||
|
&[aria-expanded='true'] {
|
||||||
|
background: $plyr-video-control-bg-hover;
|
||||||
|
color: $plyr-video-control-color-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Large play button (video only)
|
// Large play button (video only)
|
||||||
.plyr__control--overlaid {
|
.plyr__control--overlaid {
|
||||||
background: rgba($plyr-video-control-bg-hover, 0.8);
|
background: rgba($plyr-video-control-bg-hover, 0.8);
|
||||||
|
@ -18,36 +18,48 @@
|
|||||||
> .plyr__control,
|
> .plyr__control,
|
||||||
.plyr__progress,
|
.plyr__progress,
|
||||||
.plyr__time,
|
.plyr__time,
|
||||||
.plyr__menu {
|
.plyr__menu,
|
||||||
margin-left: ($plyr-control-spacing / 2);
|
|
||||||
|
|
||||||
&:first-child,
|
|
||||||
&:first-child + [data-plyr='pause'] {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.plyr__volume {
|
.plyr__volume {
|
||||||
margin-left: ($plyr-control-spacing / 2);
|
margin-left: ($plyr-control-spacing / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: $plyr-bp-sm) {
|
.plyr__menu + .plyr__control,
|
||||||
> .plyr__control,
|
> .plyr__control + .plyr__menu,
|
||||||
.plyr__progress,
|
> .plyr__control + .plyr__control,
|
||||||
.plyr__time,
|
.plyr__progress + .plyr__control {
|
||||||
.plyr__menu {
|
margin-left: floor($plyr-control-spacing / 4);
|
||||||
margin-left: $plyr-control-spacing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .plyr__control + .plyr__control,
|
> .plyr__control:first-child,
|
||||||
.plyr__menu + .plyr__control,
|
> .plyr__control:first-child + [data-plyr='pause'] {
|
||||||
> .plyr__control + .plyr__menu {
|
margin-left: 0;
|
||||||
margin-left: ($plyr-control-spacing / 2);
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide empty controls
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: $plyr-bp-sm) {
|
||||||
|
> .plyr__control,
|
||||||
|
.plyr__menu,
|
||||||
|
.plyr__progress,
|
||||||
|
.plyr__time,
|
||||||
|
.plyr__volume {
|
||||||
|
margin-left: $plyr-control-spacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Audio controls
|
||||||
|
.plyr--audio .plyr__controls {
|
||||||
|
background: $plyr-audio-controls-bg;
|
||||||
|
border-radius: inherit;
|
||||||
|
color: $plyr-audio-control-color;
|
||||||
|
padding: $plyr-control-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
// Video controls
|
// Video controls
|
||||||
.plyr--video .plyr__controls {
|
.plyr--video .plyr__controls {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
@ -59,37 +71,18 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
color: $plyr-video-control-color;
|
color: $plyr-video-control-color;
|
||||||
left: 0;
|
left: 0;
|
||||||
padding: ($plyr-control-spacing * 3.5) $plyr-control-spacing
|
padding: ($plyr-control-spacing * 2) ($plyr-control-spacing / 2) ($plyr-control-spacing / 2);
|
||||||
$plyr-control-spacing;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out;
|
transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out;
|
||||||
z-index: 2;
|
z-index: 3;
|
||||||
|
|
||||||
.plyr__control {
|
@media (min-width: $plyr-bp-sm) {
|
||||||
svg {
|
padding: ($plyr-control-spacing * 3.5) $plyr-control-spacing $plyr-control-spacing;
|
||||||
filter: drop-shadow(0 1px 1px rgba(#000, 0.15));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hover and tab focus
|
|
||||||
&.plyr__tab-focus,
|
|
||||||
&:hover,
|
|
||||||
&[aria-expanded='true'] {
|
|
||||||
background: $plyr-video-control-bg-hover;
|
|
||||||
color: $plyr-video-control-color-hover;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audio controls
|
// Hide video controls
|
||||||
.plyr--audio .plyr__controls {
|
|
||||||
background: $plyr-audio-controls-bg;
|
|
||||||
border-radius: inherit;
|
|
||||||
color: $plyr-audio-control-color;
|
|
||||||
padding: $plyr-control-spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide controls
|
|
||||||
.plyr--video.plyr--hide-controls .plyr__controls {
|
.plyr--video.plyr--hide-controls .plyr__controls {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@ -109,11 +102,3 @@
|
|||||||
.plyr--fullscreen-enabled [data-plyr='fullscreen'] {
|
.plyr--fullscreen-enabled [data-plyr='fullscreen'] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr__controls:empty {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
~ .plyr__captions {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -39,7 +39,8 @@
|
|||||||
|
|
||||||
> div {
|
> div {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: height 0.35s cubic-bezier(0.4, 0, 0.2, 1), width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: height 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arrow
|
// Arrow
|
||||||
@ -54,20 +55,18 @@
|
|||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
[role='menu'] {
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: $plyr-control-padding;
|
padding: $plyr-control-padding;
|
||||||
|
}
|
||||||
|
|
||||||
li {
|
[role='menuitem'],
|
||||||
|
[role='menuitemradio'] {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
.plyr__control {
|
.plyr__control {
|
||||||
@ -75,10 +74,17 @@
|
|||||||
color: $plyr-menu-color;
|
color: $plyr-menu-color;
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: $plyr-font-size-menu;
|
font-size: $plyr-font-size-menu;
|
||||||
padding: ceil($plyr-control-padding / 2) ($plyr-control-padding * 2);
|
padding: ceil($plyr-control-padding / 2)
|
||||||
|
ceil($plyr-control-padding * 1.5);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
align-items: inherit;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
border: 4px solid transparent;
|
border: 4px solid transparent;
|
||||||
content: '';
|
content: '';
|
||||||
@ -135,50 +141,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
label.plyr__control {
|
.plyr__control[role='menuitemradio'] {
|
||||||
padding-left: $plyr-control-padding;
|
padding-left: $plyr-control-padding;
|
||||||
|
|
||||||
input[type='radio'] + span {
|
&::before,
|
||||||
background: rgba(#000, 0.1);
|
&::after {
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background: rgba(#000, 0.1);
|
||||||
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-right: $plyr-control-spacing;
|
margin-right: $plyr-control-spacing;
|
||||||
position: relative;
|
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 100%;
|
border: 0;
|
||||||
content: '';
|
|
||||||
height: 6px;
|
height: 6px;
|
||||||
left: 5px;
|
left: 12px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: absolute;
|
top: 50%;
|
||||||
top: 5px;
|
transform: translateY(-50%) scale(0);
|
||||||
transform: scale(0);
|
|
||||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
width: 6px;
|
width: 6px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
input[type='radio']:checked + span {
|
&[aria-checked='true'] {
|
||||||
|
&::before {
|
||||||
background: $plyr-color-main;
|
background: $plyr-color-main;
|
||||||
|
}
|
||||||
&::after {
|
&::after {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: translateY(-50%) scale(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='radio']:focus + span {
|
&.plyr__tab-focus::before,
|
||||||
@include plyr-tab-focus();
|
&:hover::before {
|
||||||
}
|
|
||||||
|
|
||||||
&.plyr__tab-focus input[type='radio'] + span,
|
|
||||||
&:hover input[type='radio'] + span {
|
|
||||||
background: rgba(#000, 0.1);
|
background: rgba(#000, 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,7 +193,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: -$plyr-control-padding;
|
margin-right: -($plyr-control-padding - 2);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-left: ceil($plyr-control-padding * 3.5);
|
padding-left: ceil($plyr-control-padding * 3.5);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -12,12 +12,11 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.2s ease;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr--stopped.plyr__poster-enabled .plyr__poster {
|
.plyr--stopped.plyr__poster-enabled .plyr__poster {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__progress {
|
.plyr__progress {
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
left: $plyr-range-thumb-height / 2;
|
left: $plyr-range-thumb-height / 2;
|
||||||
margin-right: $plyr-range-thumb-height;
|
margin-right: $plyr-range-thumb-height;
|
||||||
|
@ -19,7 +19,11 @@
|
|||||||
|
|
||||||
&::-webkit-slider-runnable-track {
|
&::-webkit-slider-runnable-track {
|
||||||
@include plyr-range-track();
|
@include plyr-range-track();
|
||||||
background-image: linear-gradient(to right, currentColor var(--value, 0%), transparent var(--value, 0%));
|
background-image: linear-gradient(
|
||||||
|
to right,
|
||||||
|
currentColor var(--value, 0%),
|
||||||
|
transparent var(--value, 0%)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-slider-thumb {
|
&::-webkit-slider-thumb {
|
||||||
@ -140,15 +144,21 @@
|
|||||||
// Pressed styles
|
// Pressed styles
|
||||||
&:active {
|
&:active {
|
||||||
&::-webkit-slider-thumb {
|
&::-webkit-slider-thumb {
|
||||||
@include plyr-range-thumb-active($plyr-audio-range-thumb-shadow-color);
|
@include plyr-range-thumb-active(
|
||||||
|
$plyr-audio-range-thumb-shadow-color
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-moz-range-thumb {
|
&::-moz-range-thumb {
|
||||||
@include plyr-range-thumb-active($plyr-audio-range-thumb-shadow-color);
|
@include plyr-range-thumb-active(
|
||||||
|
$plyr-audio-range-thumb-shadow-color
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-ms-thumb {
|
&::-ms-thumb {
|
||||||
@include plyr-range-thumb-active($plyr-audio-range-thumb-shadow-color);
|
@include plyr-range-thumb-active(
|
||||||
|
$plyr-audio-range-thumb-shadow-color
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
color: $plyr-tooltip-color;
|
color: $plyr-tooltip-color;
|
||||||
font-size: $plyr-font-size-small;
|
font-size: $plyr-font-size-small;
|
||||||
font-weight: $plyr-font-weight-regular;
|
font-weight: $plyr-font-weight-regular;
|
||||||
|
left: 50%;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
margin-bottom: ($plyr-tooltip-padding * 2);
|
margin-bottom: ($plyr-tooltip-padding * 2);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@ -64,6 +65,7 @@
|
|||||||
|
|
||||||
// Last tooltip
|
// Last tooltip
|
||||||
.plyr__controls > .plyr__control:last-child .plyr__tooltip {
|
.plyr__controls > .plyr__control:last-child .plyr__tooltip {
|
||||||
|
left: auto;
|
||||||
right: 0;
|
right: 0;
|
||||||
transform: translate(0, 10px) scale(0.8);
|
transform: translate(0, 10px) scale(0.8);
|
||||||
transform-origin: 100% 100%;
|
transform-origin: 100% 100%;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr--video {
|
.plyr--video {
|
||||||
|
background: #000;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
// Menu open
|
// Menu open
|
||||||
|
@ -3,20 +3,23 @@
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__volume {
|
.plyr__volume {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
input[type='range'] {
|
input[type='range'] {
|
||||||
|
margin-left: ($plyr-control-spacing / 2);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: $plyr-bp-sm) {
|
@media (min-width: $plyr-bp-sm) {
|
||||||
max-width: 50px;
|
max-width: 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: $plyr-bp-md) {
|
@media (min-width: $plyr-bp-md) {
|
||||||
max-width: 80px;
|
max-width: 110px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
// Nicer focus styles
|
// Nicer focus styles
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@mixin plyr-tab-focus($color: $plyr-tab-focus-default-color) {
|
@mixin plyr-tab-focus($color: $plyr-tab-focus-default-color) {
|
||||||
box-shadow: 0 0 0 3px rgba($color, 0.35);
|
box-shadow: 0 0 0 5px rgba($color, 0.5);
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,6 +28,7 @@
|
|||||||
border: 0;
|
border: 0;
|
||||||
border-radius: ($plyr-range-track-height / 2);
|
border-radius: ($plyr-range-track-height / 2);
|
||||||
height: $plyr-range-track-height;
|
height: $plyr-range-track-height;
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +37,6 @@
|
|||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
box-shadow: $plyr-range-thumb-shadow;
|
box-shadow: $plyr-range-thumb-shadow;
|
||||||
box-sizing: border-box;
|
|
||||||
height: $plyr-range-thumb-height;
|
height: $plyr-range-thumb-height;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
@ -22,3 +22,7 @@
|
|||||||
width: 1px;
|
width: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.plyr [hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user