Compare commits
126 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f927d26ce7 | |||
| e8d2f23b81 | |||
| 0e181133c1 | |||
| 2c8a337f26 | |||
| c24e52d97d | |||
| 574f40949c | |||
| cf3848fbd5 | |||
| a19ad69038 | |||
| d6f20e2756 | |||
| e2fc20ca76 | |||
| 37c3f7109d | |||
| 99d5211a33 | |||
| b97f143195 | |||
| e8da4326b6 | |||
| 67f908aa8d | |||
| 65eb5c1b8b | |||
| 7d484c6e09 | |||
| 8252e13eb9 | |||
| 1a9b860e68 | |||
| cede7d0f35 | |||
| fe26d383f1 | |||
| df4bc268dc | |||
| e49da6c13f | |||
| 67b7262764 | |||
| 88528ef979 | |||
| b6175b1ca9 | |||
| aa20ebaa9c | |||
| 779e45c11b | |||
| 5d5a6eabaa | |||
| 03c9b53232 | |||
| ebaded66b4 | |||
| c232eb2478 | |||
| 7559cc6897 | |||
| 69d0d6d7ee | |||
| 3e9336b15d | |||
| 5c78ecc15d | |||
| 06db3f702d | |||
| a2a82a96a6 | |||
| a86bbae851 | |||
| fac134dd95 | |||
| 515ae32160 | |||
| df8f040795 | |||
| 64a23992f0 | |||
| f5baff6e6b | |||
| 8bdd90a2a8 | |||
| 5536e97482 | |||
| de47071256 | |||
| 87072cb690 | |||
| d9565e9250 | |||
| f80b568e67 | |||
| 7fed689f9a | |||
| 3f48df8f10 | |||
| cc55092ca6 | |||
| 3331d9d01d | |||
| 62d80e6b76 | |||
| 7dc4d9cd22 | |||
| 8fb8ae1260 | |||
| 90304369f4 | |||
| 922456c46c | |||
| eaeccd66ae | |||
| 7a43649c13 | |||
| 525bbf313e | |||
| cfaebe9bf2 | |||
| b57b7b2153 | |||
| 48bf368316 | |||
| 8f94ce86a0 | |||
| 10a9cf08f1 | |||
| 286d0d1794 | |||
| 95f6fa2731 | |||
| 1aeef81288 | |||
| 211ad6c8f5 | |||
| 468b20d227 | |||
| f6bc42c2bc | |||
| 2c01b8ba76 | |||
| 4e1df8677f | |||
| 6953a12e2a | |||
| 1d0db89194 | |||
| 297f297d18 | |||
| 059205c378 | |||
| f94e53ffb1 | |||
| a4f1fdec5d | |||
| 75374eb154 | |||
| 3ad118c026 | |||
| 0bc6b1f1b3 | |||
| 4ea458e1a3 | |||
| aacb172017 | |||
| dbf768b1bd | |||
| b96fcfc8ac | |||
| 18b4d26bee | |||
| 7f4b74e2d4 | |||
| a8f8486cf4 | |||
| a343e58e53 | |||
| 0892d69ba2 | |||
| ba511b51c7 | |||
| e090581913 | |||
| aaa56caa9c | |||
| c8db1e55dd | |||
| 58079393e6 | |||
| 0b44f2d897 | |||
| 2371619486 | |||
| 13a54b5dbe | |||
| fa0861ff2e | |||
| 748aa5179f | |||
| 56a485bac6 | |||
| 9488de30e5 | |||
| e3dfd16096 | |||
| c230ccce86 | |||
| db22a8e9c4 | |||
| 3a3358e2b4 | |||
| 248005e8e0 | |||
| dae272ef66 | |||
| 599b33e55f | |||
| 3a8332bdb3 | |||
| 53a3d06103 | |||
| e63ad7c74b | |||
| ead6601394 | |||
| e61ebd8d05 | |||
| 3bf1c59bd6 | |||
| e59fe1aacf | |||
| 1f1d74ba50 | |||
| bb546fe43f | |||
| 9e1218547b | |||
| 715b88c09b | |||
| 7b9ef7d757 | |||
| d64ed4ba5a | |||
| ffd864ed39 |
@@ -2,5 +2,6 @@
|
|||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all"
|
"trailingComma": "all",
|
||||||
|
"printWidth": 120
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -26,6 +28,7 @@ controls: [
|
|||||||
'settings', // Settings menu
|
'settings', // Settings menu
|
||||||
'pip', // Picture-in-picture (currently Safari only)
|
'pip', // Picture-in-picture (currently Safari only)
|
||||||
'airplay', // Airplay (currently Safari only)
|
'airplay', // Airplay (currently Safari only)
|
||||||
|
'download', // Show a download button with a link to either the current source or a custom URL you specify in your options
|
||||||
'fullscreen', // Toggle fullscreen
|
'fullscreen', // Toggle fullscreen
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1833,7 +1833,6 @@ typeof navigator === "object" && (function () {
|
|||||||
};
|
};
|
||||||
this._fetchDefaults = {
|
this._fetchDefaults = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
keepalive: true,
|
|
||||||
// Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default
|
// Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default
|
||||||
// https://caniuse.com/#feat=referrer-policy
|
// https://caniuse.com/#feat=referrer-policy
|
||||||
// It doesn't. And it throw exception instead of ignoring this parameter...
|
// It doesn't. And it throw exception instead of ignoring this parameter...
|
||||||
@@ -1874,7 +1873,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.27.0',
|
||||||
|
|
||||||
debug: false,
|
debug: false,
|
||||||
|
|
||||||
@@ -2612,34 +2611,40 @@ typeof navigator === "object" && (function () {
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
options = options || {};
|
options = objectMerge$1(
|
||||||
|
{
|
||||||
|
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(options.dsn));
|
||||||
var globalServer = this._getGlobalServer(this._parseDSN(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,309 +4092,254 @@ typeof navigator === "object" && (function () {
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
var isLive = window.location.host === 'plyr.io';
|
var host = window.location.host;
|
||||||
|
var env = {
|
||||||
|
prod: host === 'plyr.io',
|
||||||
|
dev: host === 'dev.plyr.io'
|
||||||
|
};
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
singleton.context(function () {
|
||||||
|
var selector = '#player';
|
||||||
|
var container = document.getElementById('container');
|
||||||
|
|
||||||
// Raven / Sentry
|
if (window.shr) {
|
||||||
// For demo site (https://plyr.io) only
|
window.shr.setup({
|
||||||
if (isLive) {
|
count: {
|
||||||
singleton.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
classname: 'button__count'
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
singleton.context(function () {
|
|
||||||
if (window.shr) {
|
|
||||||
window.shr.setup({
|
|
||||||
count: {
|
|
||||||
classname: 'button__count'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup tab focus
|
|
||||||
var tabClassName = 'tab-focus';
|
|
||||||
|
|
||||||
// Remove class on blur
|
|
||||||
document.addEventListener('focusout', function (event) {
|
|
||||||
event.target.classList.remove(tabClassName);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add classname to tabbed elements
|
|
||||||
document.addEventListener('keydown', function (event) {
|
|
||||||
if (event.keyCode !== 9) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delay the adding of classname until the focus has changed
|
|
||||||
// This event fires before the focusin event
|
|
||||||
setTimeout(function () {
|
|
||||||
document.activeElement.classList.add(tabClassName);
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup the player
|
|
||||||
var player = new Plyr('#player', {
|
|
||||||
debug: true,
|
|
||||||
title: 'View From A Blue Moon',
|
|
||||||
iconUrl: '../dist/plyr.svg',
|
|
||||||
keyboard: {
|
|
||||||
global: true
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
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: {
|
|
||||||
active: true
|
|
||||||
},
|
|
||||||
keys: {
|
|
||||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c'
|
|
||||||
},
|
|
||||||
ads: {
|
|
||||||
enabled: true,
|
|
||||||
publisherId: '918848828995742'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expose for tinkering in the console
|
|
||||||
window.player = player;
|
|
||||||
|
|
||||||
// Setup type toggle
|
|
||||||
var buttons = document.querySelectorAll('[data-source]');
|
|
||||||
var types = {
|
|
||||||
video: 'video',
|
|
||||||
audio: 'audio',
|
|
||||||
youtube: 'youtube',
|
|
||||||
vimeo: 'vimeo'
|
|
||||||
};
|
|
||||||
var currentType = window.location.hash.replace('#', '');
|
|
||||||
var historySupport = window.history && window.history.pushState;
|
|
||||||
|
|
||||||
// Toggle class on an element
|
|
||||||
function toggleClass(element, className, state) {
|
|
||||||
if (element) {
|
|
||||||
element.classList[state ? 'add' : 'remove'](className);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a new source
|
|
||||||
function newSource(type, init) {
|
|
||||||
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
|
|
||||||
if (!(type in types) || !init && type === currentType || !currentType.length && type === types.video) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case types.video:
|
|
||||||
player.source = {
|
|
||||||
type: 'video',
|
|
||||||
title: 'View From A Blue Moon',
|
|
||||||
sources: [{
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
|
|
||||||
type: 'video/mp4',
|
|
||||||
size: 576
|
|
||||||
}, {
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
|
|
||||||
type: 'video/mp4',
|
|
||||||
size: 720
|
|
||||||
}, {
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
|
|
||||||
type: 'video/mp4',
|
|
||||||
size: 1080
|
|
||||||
}, {
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
|
|
||||||
type: 'video/mp4',
|
|
||||||
size: 1440
|
|
||||||
}],
|
|
||||||
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
|
||||||
tracks: [{
|
|
||||||
kind: 'captions',
|
|
||||||
label: 'English',
|
|
||||||
srclang: 'en',
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
|
||||||
default: true
|
|
||||||
}, {
|
|
||||||
kind: 'captions',
|
|
||||||
label: 'French',
|
|
||||||
srclang: 'fr',
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt'
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case types.audio:
|
|
||||||
player.source = {
|
|
||||||
type: 'audio',
|
|
||||||
title: 'Kishi Bashi – “It All Began With A Burst”',
|
|
||||||
sources: [{
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
|
|
||||||
type: 'audio/mp3'
|
|
||||||
}, {
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
|
|
||||||
type: 'audio/ogg'
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case types.youtube:
|
|
||||||
player.source = {
|
|
||||||
type: 'video',
|
|
||||||
sources: [{
|
|
||||||
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
|
||||||
provider: 'youtube'
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case types.vimeo:
|
|
||||||
player.source = {
|
|
||||||
type: 'video',
|
|
||||||
sources: [{
|
|
||||||
src: 'https://vimeo.com/76979871',
|
|
||||||
provider: 'vimeo'
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the current type for next time
|
|
||||||
currentType = type;
|
|
||||||
|
|
||||||
// Remove active classes
|
|
||||||
Array.from(buttons).forEach(function (button) {
|
|
||||||
return toggleClass(button.parentElement, 'active', false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set active on parent
|
|
||||||
toggleClass(document.querySelector('[data-source="' + type + '"]'), 'active', true);
|
|
||||||
|
|
||||||
// Show cite
|
|
||||||
Array.from(document.querySelectorAll('.plyr__cite')).forEach(function (cite) {
|
|
||||||
cite.setAttribute('hidden', '');
|
|
||||||
});
|
|
||||||
document.querySelector('.plyr__cite--' + type).removeAttribute('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind to each button
|
|
||||||
Array.from(buttons).forEach(function (button) {
|
|
||||||
button.addEventListener('click', function () {
|
|
||||||
var type = button.getAttribute('data-source');
|
|
||||||
|
|
||||||
newSource(type);
|
|
||||||
|
|
||||||
if (historySupport) {
|
|
||||||
window.history.pushState({ type: type }, '', '#' + type);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// List for backwards/forwards
|
|
||||||
window.addEventListener('popstate', function (event) {
|
|
||||||
if (event.state && 'type' in event.state) {
|
|
||||||
newSource(event.state.type);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// On load
|
|
||||||
if (historySupport) {
|
|
||||||
var video = !currentType.length;
|
|
||||||
|
|
||||||
// If there's no current type set, assume video
|
|
||||||
if (video) {
|
|
||||||
currentType = types.video;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace current history state
|
|
||||||
if (currentType in types) {
|
|
||||||
window.history.replaceState({
|
|
||||||
type: currentType
|
|
||||||
}, '', video ? '' : '#' + currentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's not video, load the source
|
|
||||||
if (currentType !== types.video) {
|
|
||||||
newSource(currentType, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
} // Setup tab focus
|
||||||
|
|
||||||
// Google analytics
|
|
||||||
// For demo site (https://plyr.io) only
|
var tabClassName = 'tab-focus'; // Remove class on blur
|
||||||
/* eslint-disable */
|
|
||||||
if (isLive) {
|
document.addEventListener('focusout', function (event) {
|
||||||
(function (i, s, o, g, r, a, m) {
|
if (!event.target.classList || container.contains(event.target)) {
|
||||||
i.GoogleAnalyticsObject = r;
|
return;
|
||||||
i[r] = i[r] || function () {
|
}
|
||||||
(i[r].q = i[r].q || []).push(arguments);
|
|
||||||
|
event.target.classList.remove(tabClassName);
|
||||||
|
}); // Add classname to tabbed elements
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function (event) {
|
||||||
|
if (event.keyCode !== 9) {
|
||||||
|
return;
|
||||||
|
} // Delay the adding of classname until the focus has changed
|
||||||
|
// This event fires before the focusin event
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
var focused = document.activeElement;
|
||||||
|
|
||||||
|
if (!focused || !focused.classList || container.contains(focused)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
focused.classList.add(tabClassName);
|
||||||
|
}, 10);
|
||||||
|
}); // Setup the player
|
||||||
|
|
||||||
|
var player = new Plyr(selector, {
|
||||||
|
debug: true,
|
||||||
|
title: 'View From A Blue Moon',
|
||||||
|
iconUrl: '../dist/plyr.svg',
|
||||||
|
keyboard: {
|
||||||
|
global: true
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
controls: true
|
||||||
|
},
|
||||||
|
captions: {
|
||||||
|
active: true
|
||||||
|
},
|
||||||
|
keys: {
|
||||||
|
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c'
|
||||||
|
},
|
||||||
|
ads: {
|
||||||
|
enabled: env.prod || env.dev,
|
||||||
|
publisherId: '918848828995742'
|
||||||
|
}
|
||||||
|
}); // Expose for tinkering in the console
|
||||||
|
|
||||||
|
window.player = player; // Setup type toggle
|
||||||
|
|
||||||
|
var buttons = document.querySelectorAll('[data-source]');
|
||||||
|
var types = {
|
||||||
|
video: 'video',
|
||||||
|
audio: 'audio',
|
||||||
|
youtube: 'youtube',
|
||||||
|
vimeo: 'vimeo'
|
||||||
|
};
|
||||||
|
var currentType = window.location.hash.replace('#', '');
|
||||||
|
var historySupport = window.history && window.history.pushState; // Toggle class on an element
|
||||||
|
|
||||||
|
function toggleClass(element, className, state) {
|
||||||
|
if (element) {
|
||||||
|
element.classList[state ? 'add' : 'remove'](className);
|
||||||
|
}
|
||||||
|
} // Set a new source
|
||||||
|
|
||||||
|
|
||||||
|
function newSource(type, init) {
|
||||||
|
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
|
||||||
|
if (!(type in types) || !init && type === currentType || !currentType.length && type === types.video) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case types.video:
|
||||||
|
player.source = {
|
||||||
|
type: 'video',
|
||||||
|
title: 'View From A Blue Moon',
|
||||||
|
sources: [{
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
|
||||||
|
type: 'video/mp4',
|
||||||
|
size: 576
|
||||||
|
}, {
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
|
||||||
|
type: 'video/mp4',
|
||||||
|
size: 720
|
||||||
|
}, {
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
|
||||||
|
type: 'video/mp4',
|
||||||
|
size: 1080
|
||||||
|
}, {
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
|
||||||
|
type: 'video/mp4',
|
||||||
|
size: 1440
|
||||||
|
}],
|
||||||
|
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
||||||
|
tracks: [{
|
||||||
|
kind: 'captions',
|
||||||
|
label: 'English',
|
||||||
|
srclang: 'en',
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
||||||
|
default: true
|
||||||
|
}, {
|
||||||
|
kind: 'captions',
|
||||||
|
label: 'French',
|
||||||
|
srclang: 'fr',
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt'
|
||||||
|
}]
|
||||||
};
|
};
|
||||||
i[r].l = 1 * new Date();
|
break;
|
||||||
a = s.createElement(o);
|
|
||||||
m = s.getElementsByTagName(o)[0];
|
case types.audio:
|
||||||
a.async = 1;
|
player.source = {
|
||||||
a.src = g;
|
type: 'audio',
|
||||||
m.parentNode.insertBefore(a, m);
|
title: 'Kishi Bashi – “It All Began With A Burst”',
|
||||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
sources: [{
|
||||||
window.ga('create', 'UA-40881672-11', 'auto');
|
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
|
||||||
window.ga('send', 'pageview');
|
type: 'audio/mp3'
|
||||||
}
|
}, {
|
||||||
/* eslint-enable */
|
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
|
||||||
|
type: 'audio/ogg'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case types.youtube:
|
||||||
|
player.source = {
|
||||||
|
type: 'video',
|
||||||
|
sources: [{
|
||||||
|
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
||||||
|
provider: 'youtube'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case types.vimeo:
|
||||||
|
player.source = {
|
||||||
|
type: 'video',
|
||||||
|
sources: [{
|
||||||
|
src: 'https://vimeo.com/76979871',
|
||||||
|
provider: 'vimeo'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
} // Set the current type for next time
|
||||||
|
|
||||||
|
|
||||||
|
currentType = type; // Remove active classes
|
||||||
|
|
||||||
|
Array.from(buttons).forEach(function (button) {
|
||||||
|
return toggleClass(button.parentElement, 'active', false);
|
||||||
|
}); // Set active on parent
|
||||||
|
|
||||||
|
toggleClass(document.querySelector("[data-source=\"".concat(type, "\"]")), 'active', true); // Show cite
|
||||||
|
|
||||||
|
Array.from(document.querySelectorAll('.plyr__cite')).forEach(function (cite) {
|
||||||
|
cite.setAttribute('hidden', '');
|
||||||
|
});
|
||||||
|
document.querySelector(".plyr__cite--".concat(type)).removeAttribute('hidden');
|
||||||
|
} // Bind to each button
|
||||||
|
|
||||||
|
|
||||||
|
Array.from(buttons).forEach(function (button) {
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
var type = button.getAttribute('data-source');
|
||||||
|
newSource(type);
|
||||||
|
|
||||||
|
if (historySupport) {
|
||||||
|
window.history.pushState({
|
||||||
|
type: type
|
||||||
|
}, '', "#".concat(type));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}); // List for backwards/forwards
|
||||||
|
|
||||||
|
window.addEventListener('popstate', function (event) {
|
||||||
|
if (event.state && 'type' in event.state) {
|
||||||
|
newSource(event.state.type);
|
||||||
|
}
|
||||||
|
}); // On load
|
||||||
|
|
||||||
|
if (historySupport) {
|
||||||
|
var video = !currentType.length; // If there's no current type set, assume video
|
||||||
|
|
||||||
|
if (video) {
|
||||||
|
currentType = types.video;
|
||||||
|
} // Replace current history state
|
||||||
|
|
||||||
|
|
||||||
|
if (currentType in types) {
|
||||||
|
window.history.replaceState({
|
||||||
|
type: currentType
|
||||||
|
}, '', video ? '' : "#".concat(currentType));
|
||||||
|
} // If it's not video, load the source
|
||||||
|
|
||||||
|
|
||||||
|
if (currentType !== types.video) {
|
||||||
|
newSource(currentType, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}); // Raven / Sentry
|
||||||
|
// For demo site (https://plyr.io) only
|
||||||
|
|
||||||
|
if (env.prod) {
|
||||||
|
singleton.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
||||||
|
} // Google analytics
|
||||||
|
// For demo site (https://plyr.io) only
|
||||||
|
|
||||||
|
|
||||||
|
if (env.prod) {
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
|
||||||
|
var gtag = function gtag() {
|
||||||
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
||||||
|
args[_key] = arguments[_key];
|
||||||
|
}
|
||||||
|
|
||||||
|
window.dataLayer.push(args);
|
||||||
|
};
|
||||||
|
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', 'UA-132699580-1');
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|||||||
@@ -1,190 +1,290 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
property="og:description"
|
||||||
|
content="A simple HTML5 media player with custom controls and WebVTT captions."
|
||||||
|
/>
|
||||||
|
<meta name="author" content="Sam Potts" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
<head>
|
<!-- Icons -->
|
||||||
<meta charset="utf-8" />
|
<link rel="icon" href="https://cdn.plyr.io/static/icons/favicon.ico" />
|
||||||
<title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title>
|
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/32x32.png" sizes="32x32" />
|
||||||
<meta name="description" property="og:description" content="A simple HTML5 media player with custom controls and WebVTT captions.">
|
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16" />
|
||||||
<meta name="author" content="Sam Potts">
|
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
<!-- Icons -->
|
<!-- Opengraph -->
|
||||||
<link rel="icon" href="https://cdn.plyr.io/static/icons/favicon.ico">
|
<meta
|
||||||
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/32x32.png" sizes="32x32">
|
property="og:title"
|
||||||
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16">
|
content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player"
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png">
|
/>
|
||||||
|
<meta property="og:site_name" content="Plyr" />
|
||||||
|
<meta property="og:url" content="https://plyr.io" />
|
||||||
|
<meta property="og:image" content="https://cdn.plyr.io/static/icons/1200x630.png" />
|
||||||
|
|
||||||
<!-- Opengraph -->
|
<!-- Twitter -->
|
||||||
<meta property="og:title" content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player">
|
<meta name="twitter:card" content="summary" />
|
||||||
<meta property="og:site_name" content="Plyr">
|
<meta name="twitter:site" content="@sam_potts" />
|
||||||
<meta property="og:url" content="https://plyr.io">
|
<meta name="twitter:creator" content="@sam_potts" />
|
||||||
<meta property="og:image" content="https://cdn.plyr.io/static/icons/1200x630.png">
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
|
||||||
<!-- Twitter -->
|
<!-- Docs styles -->
|
||||||
<meta name="twitter:card" content="summary">
|
<link rel="stylesheet" href="dist/demo.css?v=2" />
|
||||||
<meta name="twitter:site" content="@sam_potts">
|
|
||||||
<meta name="twitter:creator" content="@sam_potts">
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
|
||||||
|
|
||||||
<!-- Docs styles -->
|
<!-- Preload -->
|
||||||
<link rel="stylesheet" href="dist/demo.css?v=2">
|
<link
|
||||||
|
rel="preload"
|
||||||
|
as="font"
|
||||||
|
crossorigin
|
||||||
|
type="font/woff2"
|
||||||
|
href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="preload"
|
||||||
|
as="font"
|
||||||
|
crossorigin
|
||||||
|
type="font/woff2"
|
||||||
|
href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
|
||||||
<!-- Preload -->
|
<body>
|
||||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
|
<div class="grid">
|
||||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2">
|
<header>
|
||||||
</head>
|
<h1>Plyr</h1>
|
||||||
|
<p>
|
||||||
<body>
|
A simple, accessible and customisable media player for
|
||||||
<div class="grid">
|
<button type="button" class="faux-link" data-source="video">
|
||||||
<header>
|
<svg class="icon">
|
||||||
<h1>Plyr</h1>
|
<title>HTML5</title>
|
||||||
<p>A simple, accessible and customisable media player for
|
<path
|
||||||
<button type="button" class="faux-link" data-source="video">
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
<svg class="icon">
|
></path></svg
|
||||||
<title>HTML5</title>
|
>Video</button
|
||||||
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
>,
|
||||||
</svg>Video</button>,
|
<button type="button" class="faux-link" data-source="audio">
|
||||||
<button type="button" class="faux-link" data-source="audio">
|
<svg class="icon">
|
||||||
<svg class="icon">
|
<title>HTML5</title>
|
||||||
<title>HTML5</title>
|
<path
|
||||||
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
</svg>Audio</button>,
|
></path></svg
|
||||||
<button type="button" class="faux-link" data-source="youtube">
|
>Audio</button
|
||||||
<svg class="icon" role="presentation">
|
>,
|
||||||
<title>YouTube</title>
|
<button type="button" class="faux-link" data-source="youtube">
|
||||||
<path d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
|
||||||
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
|
||||||
M6,11V5l5,3L6,11z"></path>
|
|
||||||
</svg>YouTube</button> and
|
|
||||||
<button type="button" class="faux-link" data-source="vimeo">
|
|
||||||
<svg class="icon" role="presentation">
|
|
||||||
<title>Vimeo</title>
|
|
||||||
<path d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
|
||||||
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
|
||||||
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"></path>
|
|
||||||
</svg>Vimeo</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>Premium video monitization from
|
|
||||||
<a href="https://vi.ai/publisher-video-monetization/?aid=plyrio" target="_blank" class="no-border">
|
|
||||||
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi">
|
|
||||||
<span class="sr-only">ai.vi</span>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="call-to-action">
|
|
||||||
<span class="button--with-count">
|
|
||||||
<a href="https://github.com/sampotts/plyr" target="_blank" class="button" data-shr-network="github">
|
|
||||||
<svg class="icon" role="presentation">
|
<svg class="icon" role="presentation">
|
||||||
<title>GitHub</title>
|
<title>YouTube</title>
|
||||||
<path d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
|
<path
|
||||||
|
d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
||||||
|
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
||||||
|
M6,11V5l5,3L6,11z"
|
||||||
|
></path></svg
|
||||||
|
>YouTube
|
||||||
|
</button>
|
||||||
|
and
|
||||||
|
<button type="button" class="faux-link" data-source="vimeo">
|
||||||
|
<svg class="icon" role="presentation">
|
||||||
|
<title>Vimeo</title>
|
||||||
|
<path
|
||||||
|
d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
||||||
|
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
||||||
|
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"
|
||||||
|
></path></svg
|
||||||
|
>Vimeo
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Premium video monitization from
|
||||||
|
<a href="https://vi.ai/publisher-video-monetization/?aid=plyrio" target="_blank" class="no-border">
|
||||||
|
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi" />
|
||||||
|
<span class="sr-only">ai.vi</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="call-to-action">
|
||||||
|
<span class="button--with-count">
|
||||||
|
<a
|
||||||
|
href="https://github.com/sampotts/plyr"
|
||||||
|
target="_blank"
|
||||||
|
class="button"
|
||||||
|
data-shr-network="github"
|
||||||
|
>
|
||||||
|
<svg class="icon" role="presentation">
|
||||||
|
<title>GitHub</title>
|
||||||
|
<path
|
||||||
|
d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
|
||||||
C5.9,15.9,6,15.6,6,15.4c0-0.2,0-0.7,0-1.4C3.8,14.5,3.3,13,3.3,13c-0.4-0.9-0.9-1.2-0.9-1.2c-0.7-0.5,0.1-0.5,0.1-0.5
|
C5.9,15.9,6,15.6,6,15.4c0-0.2,0-0.7,0-1.4C3.8,14.5,3.3,13,3.3,13c-0.4-0.9-0.9-1.2-0.9-1.2c-0.7-0.5,0.1-0.5,0.1-0.5
|
||||||
c0.8,0.1,1.2,0.8,1.2,0.8C4.4,13.4,5.6,13,6,12.8c0.1-0.5,0.3-0.9,0.5-1.1c-1.8-0.2-3.6-0.9-3.6-4c0-0.9,0.3-1.6,0.8-2.1
|
c0.8,0.1,1.2,0.8,1.2,0.8C4.4,13.4,5.6,13,6,12.8c0.1-0.5,0.3-0.9,0.5-1.1c-1.8-0.2-3.6-0.9-3.6-4c0-0.9,0.3-1.6,0.8-2.1
|
||||||
c-0.1-0.2-0.4-1,0.1-2.1c0,0,0.7-0.2,2.2,0.8c0.6-0.2,1.3-0.3,2-0.3c0.7,0,1.4,0.1,2,0.3c1.5-1,2.2-0.8,2.2-0.8
|
c-0.1-0.2-0.4-1,0.1-2.1c0,0,0.7-0.2,2.2,0.8c0.6-0.2,1.3-0.3,2-0.3c0.7,0,1.4,0.1,2,0.3c1.5-1,2.2-0.8,2.2-0.8
|
||||||
c0.4,1.1,0.2,1.9,0.1,2.1c0.5,0.6,0.8,1.3,0.8,2.1c0,3.1-1.9,3.7-3.7,3.9C9.7,12,10,12.5,10,13.2c0,1.1,0,1.9,0,2.2
|
c0.4,1.1,0.2,1.9,0.1,2.1c0.5,0.6,0.8,1.3,0.8,2.1c0,3.1-1.9,3.7-3.7,3.9C9.7,12,10,12.5,10,13.2c0,1.1,0,1.9,0,2.2
|
||||||
c0,0.2,0.1,0.5,0.6,0.4c3.2-1.1,5.5-4.1,5.5-7.6C16,3.8,12.4,0.2,8,0.2z"></path>
|
c0,0.2,0.1,0.5,0.6,0.4c3.2-1.1,5.5-4.1,5.5-7.6C16,3.8,12.4,0.2,8,0.2z"
|
||||||
</svg>
|
></path>
|
||||||
Download on GitHub
|
</svg>
|
||||||
</a>
|
Download on GitHub
|
||||||
</span>
|
</a>
|
||||||
</div>
|
</span>
|
||||||
</header>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
|
<div id="container">
|
||||||
<!-- Video files -->
|
<video
|
||||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" type="video/mp4" size="576">
|
controls
|
||||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4" type="video/mp4" size="720">
|
crossorigin
|
||||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4" type="video/mp4" size="1080">
|
playsinline
|
||||||
<!-- <source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4" type="video/mp4" size="1440"> -->
|
poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg"
|
||||||
|
id="player"
|
||||||
|
>
|
||||||
|
<!-- 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-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"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 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
|
||||||
default>
|
kind="captions"
|
||||||
<track kind="captions" label="Français" srclang="fr" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
|
label="English"
|
||||||
|
srclang="en"
|
||||||
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
|
||||||
|
default
|
||||||
|
/>
|
||||||
|
<track
|
||||||
|
kind="captions"
|
||||||
|
label="Français"
|
||||||
|
srclang="fr"
|
||||||
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 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
|
||||||
</video>
|
>Download</a
|
||||||
|
>
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li class="plyr__cite plyr__cite--video" hidden>
|
<li class="plyr__cite plyr__cite--video" hidden>
|
||||||
<small>
|
<small>
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<title>HTML5</title>
|
<title>HTML5</title>
|
||||||
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
<path
|
||||||
</svg>
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
<a href="https://itunes.apple.com/au/movie/view-from-a-blue-moon/id1041586323" target="_blank">View From A Blue Moon</a> © Brainfarm
|
></path>
|
||||||
</small>
|
</svg>
|
||||||
</li>
|
<a
|
||||||
<li class="plyr__cite plyr__cite--audio" hidden>
|
href="https://itunes.apple.com/au/movie/view-from-a-blue-moon/id1041586323"
|
||||||
<small>
|
target="_blank"
|
||||||
<svg class="icon" title="HTML5">
|
>View From A Blue Moon</a
|
||||||
<title>HTML5</title>
|
>
|
||||||
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
© Brainfarm
|
||||||
</svg>
|
</small>
|
||||||
<a href="http://www.kishibashi.com/" target="_blank">Kishi Bashi – “It All Began With A Burst”</a> © Kishi Bashi
|
</li>
|
||||||
</small>
|
<li class="plyr__cite plyr__cite--audio" hidden>
|
||||||
</li>
|
<small>
|
||||||
<li class="plyr__cite plyr__cite--youtube" hidden>
|
<svg class="icon" title="HTML5">
|
||||||
<small>
|
<title>HTML5</title>
|
||||||
<a href="https://www.youtube.com/watch?v=bTqVqk7FSmY" target="_blank">View From A Blue Moon</a> on
|
<path
|
||||||
<span class="color--youtube">
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
<svg class="icon" role="presentation">
|
></path>
|
||||||
<title>YouTube</title>
|
</svg>
|
||||||
<path d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
<a href="http://www.kishibashi.com/" target="_blank"
|
||||||
|
>Kishi Bashi – “It All Began With A Burst”</a
|
||||||
|
>
|
||||||
|
© Kishi Bashi
|
||||||
|
</small>
|
||||||
|
</li>
|
||||||
|
<li class="plyr__cite plyr__cite--youtube" hidden>
|
||||||
|
<small>
|
||||||
|
<a href="https://www.youtube.com/watch?v=bTqVqk7FSmY" target="_blank"
|
||||||
|
>View From A Blue Moon</a
|
||||||
|
>
|
||||||
|
on
|
||||||
|
<span class="color--youtube">
|
||||||
|
<svg class="icon" role="presentation">
|
||||||
|
<title>YouTube</title>
|
||||||
|
<path
|
||||||
|
d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
||||||
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
||||||
M6,11V5l5,3L6,11z"></path>
|
M6,11V5l5,3L6,11z"
|
||||||
</svg>YouTube
|
></path></svg
|
||||||
</span>
|
>YouTube
|
||||||
</small>
|
</span>
|
||||||
</li>
|
</small>
|
||||||
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
</li>
|
||||||
<small>
|
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
||||||
<a href="https://vimeo.com/76979871" target="_blank">The New Vimeo Player</a> on
|
<small>
|
||||||
<span class="color--vimeo">
|
<a href="https://vimeo.com/76979871" target="_blank">The New Vimeo Player</a> on
|
||||||
<svg class="icon" role="presentation">
|
<span class="color--vimeo">
|
||||||
<title>Vimeo</title>
|
<svg class="icon" role="presentation">
|
||||||
<path d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
<title>Vimeo</title>
|
||||||
|
<path
|
||||||
|
d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
||||||
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
||||||
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"></path>
|
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"
|
||||||
</svg>Vimeo
|
></path></svg
|
||||||
</span>
|
>Vimeo
|
||||||
</small>
|
</span>
|
||||||
</li>
|
</small>
|
||||||
</ul>
|
</li>
|
||||||
</main>
|
</ul>
|
||||||
</div>
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
<aside>
|
<aside>
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<title>Twitter</title>
|
<title>Twitter</title>
|
||||||
<path d="M16,3c-0.6,0.3-1.2,0.4-1.9,0.5c0.7-0.4,1.2-1,1.4-1.8c-0.6,0.4-1.3,0.6-2.1,0.8c-0.6-0.6-1.5-1-2.4-1
|
<path
|
||||||
|
d="M16,3c-0.6,0.3-1.2,0.4-1.9,0.5c0.7-0.4,1.2-1,1.4-1.8c-0.6,0.4-1.3,0.6-2.1,0.8c-0.6-0.6-1.5-1-2.4-1
|
||||||
C9.3,1.5,7.8,3,7.8,4.8c0,0.3,0,0.5,0.1,0.7C5.2,5.4,2.7,4.1,1.1,2.1c-0.3,0.5-0.4,1-0.4,1.7c0,1.1,0.6,2.1,1.5,2.7
|
C9.3,1.5,7.8,3,7.8,4.8c0,0.3,0,0.5,0.1,0.7C5.2,5.4,2.7,4.1,1.1,2.1c-0.3,0.5-0.4,1-0.4,1.7c0,1.1,0.6,2.1,1.5,2.7
|
||||||
c-0.5,0-1-0.2-1.5-0.4c0,0,0,0,0,0c0,1.6,1.1,2.9,2.6,3.2C3,9.4,2.7,9.4,2.4,9.4c-0.2,0-0.4,0-0.6-0.1c0.4,1.3,1.6,2.3,3.1,2.3
|
c-0.5,0-1-0.2-1.5-0.4c0,0,0,0,0,0c0,1.6,1.1,2.9,2.6,3.2C3,9.4,2.7,9.4,2.4,9.4c-0.2,0-0.4,0-0.6-0.1c0.4,1.3,1.6,2.3,3.1,2.3
|
||||||
c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"></path>
|
c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"
|
||||||
</svg>
|
></path>
|
||||||
<p>If you think Plyr's good,
|
</svg>
|
||||||
<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"
|
<p>
|
||||||
target="_blank" data-shr-network="twitter">tweet it</a>
|
If you think Plyr's good,
|
||||||
</p>
|
<a
|
||||||
</aside>
|
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
|
||||||
|
>
|
||||||
|
👍
|
||||||
|
</p>
|
||||||
|
</aside>
|
||||||
|
|
||||||
<!-- Polyfills -->
|
<!-- Polyfills -->
|
||||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent,Object.entries,Object.values,URL"
|
<script
|
||||||
crossorigin="anonymous"></script>
|
src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent,Object.entries,Object.values,URL"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
|
||||||
<!-- Plyr core script -->
|
<!-- Plyr core script -->
|
||||||
<script src="../dist/plyr.js" crossorigin="anonymous"></script>
|
<script src="../dist/plyr.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Sharing libary (https://shr.one) -->
|
<!-- Sharing libary (https://shr.one) -->
|
||||||
<script src="https://cdn.shr.one/1.0.1/shr.js" crossorigin="anonymous"></script>
|
<script src="https://cdn.shr.one/1.0.1/shr.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
|
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
|
||||||
<script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async crossorigin="anonymous"></script>
|
<script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Docs script -->
|
|
||||||
<script src="dist/demo.js" crossorigin="anonymous"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
|
<!-- Docs script -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-132699580-1"></script>
|
||||||
|
<script src="dist/demo.js" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -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,26 +271,20 @@ 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 */
|
if (env.prod) {
|
||||||
if (isLive) {
|
window.dataLayer = window.dataLayer || [];
|
||||||
(function(i, s, o, g, r, a, m) {
|
const gtag = (...args) => {
|
||||||
i.GoogleAnalyticsObject = r;
|
window.dataLayer.push(args);
|
||||||
i[r] =
|
};
|
||||||
i[r] ||
|
gtag('js', new Date());
|
||||||
function() {
|
gtag('config', 'UA-132699580-1');
|
||||||
(i[r].q = i[r].q || []).push(arguments);
|
|
||||||
};
|
|
||||||
i[r].l = 1 * new Date();
|
|
||||||
a = s.createElement(o);
|
|
||||||
m = s.getElementsByTagName(o)[0];
|
|
||||||
a.async = 1;
|
|
||||||
a.src = g;
|
|
||||||
m.parentNode.insertBefore(a, m);
|
|
||||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
|
||||||
window.ga('create', 'UA-40881672-11', 'auto');
|
|
||||||
window.ga('send', 'pageview');
|
|
||||||
}
|
}
|
||||||
/* eslint-enable */
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-light;
|
font-weight: $font-weight-light;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-light.woff2') format('woff2'), url('https://cdn.plyr.io/static/fonts/gordita-light.woff') format('woff');
|
src: url('https://cdn.plyr.io/static/fonts/gordita-light.woff2') format('woff2'),
|
||||||
|
url('https://cdn.plyr.io/static/fonts/gordita-light.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -33,7 +34,8 @@
|
|||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-bold.woff2') format('woff2'), url('https://cdn.plyr.io/static/fonts/gordita-bold.woff') format('woff');
|
src: url('https://cdn.plyr.io/static/fonts/gordita-bold.woff2') format('woff2'),
|
||||||
|
url('https://cdn.plyr.io/static/fonts/gordita-bold.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -41,5 +43,6 @@
|
|||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-black;
|
font-weight: $font-weight-black;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-black.woff2') format('woff2'), url('https://cdn.plyr.io/static/fonts/gordita-black.woff') format('woff');
|
src: url('https://cdn.plyr.io/static/fonts/gordita-black.woff2') format('woff2'),
|
||||||
|
url('https://cdn.plyr.io/static/fonts/gordita-black.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ $plyr-font-size-small: 12px;
|
|||||||
$plyr-font-size-time: 11px;
|
$plyr-font-size-time: 11px;
|
||||||
$plyr-font-size-badges: 9px;
|
$plyr-font-size-badges: 9px;
|
||||||
|
|
||||||
|
// Other
|
||||||
|
$plyr-font-smoothing: true;
|
||||||
|
|
||||||
// Captions
|
// Captions
|
||||||
$plyr-font-size-captions-base: $plyr-font-size-base;
|
$plyr-font-size-captions-base: $plyr-font-size-base;
|
||||||
$plyr-font-size-captions-small: $plyr-font-size-small;
|
$plyr-font-size-captions-small: $plyr-font-size-small;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -12,7 +12,6 @@ const concat = require('gulp-concat');
|
|||||||
const filter = require('gulp-filter');
|
const filter = require('gulp-filter');
|
||||||
const sass = require('gulp-sass');
|
const sass = require('gulp-sass');
|
||||||
const cleancss = require('gulp-clean-css');
|
const cleancss = require('gulp-clean-css');
|
||||||
const run = require('run-sequence');
|
|
||||||
const header = require('gulp-header');
|
const header = require('gulp-header');
|
||||||
const prefix = require('gulp-autoprefixer');
|
const prefix = require('gulp-autoprefixer');
|
||||||
const gitbranch = require('git-branch');
|
const gitbranch = require('git-branch');
|
||||||
@@ -44,7 +43,7 @@ const paths = {
|
|||||||
// Source paths
|
// Source paths
|
||||||
src: {
|
src: {
|
||||||
sass: path.join(root, 'src/sass/**/*.scss'),
|
sass: path.join(root, 'src/sass/**/*.scss'),
|
||||||
js: path.join(root, 'src/js/**/*'),
|
js: path.join(root, 'src/js/**/*.js'),
|
||||||
sprite: path.join(root, 'src/sprite/*.svg'),
|
sprite: path.join(root, 'src/sprite/*.svg'),
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ const paths = {
|
|||||||
// Source paths
|
// Source paths
|
||||||
src: {
|
src: {
|
||||||
sass: path.join(root, 'demo/src/sass/**/*.scss'),
|
sass: path.join(root, 'demo/src/sass/**/*.scss'),
|
||||||
js: path.join(root, 'demo/src/js/**/*'),
|
js: path.join(root, 'demo/src/js/**/*.js'),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Output paths
|
// Output paths
|
||||||
@@ -88,34 +87,33 @@ const sizeOptions = { showFiles: true, gzip: true };
|
|||||||
const browsers = ['> 1%'];
|
const browsers = ['> 1%'];
|
||||||
|
|
||||||
// Babel config
|
// Babel config
|
||||||
const babelrc = {
|
const babelrc = (polyfill = false) => ({
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
'env',
|
'@babel/preset-env',
|
||||||
{
|
{
|
||||||
targets: {
|
targets: {
|
||||||
browsers,
|
browsers,
|
||||||
},
|
},
|
||||||
useBuiltIns: true,
|
useBuiltIns: polyfill ? 'usage' : false,
|
||||||
modules: false,
|
modules: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
plugins: ['external-helpers'],
|
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
exclude: 'node_modules/**',
|
exclude: 'node_modules/**',
|
||||||
};
|
});
|
||||||
|
|
||||||
// Clean out /dist
|
// Clean out /dist
|
||||||
gulp.task('clean', () => {
|
gulp.task('clean', done => {
|
||||||
const dirs = [paths.plyr.output, paths.demo.output].map(dir =>
|
const dirs = [paths.plyr.output, paths.demo.output].map(dir => path.join(dir, '**/*'));
|
||||||
path.join(dir, '**/*'),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Don't delete the mp4
|
// Don't delete the mp4
|
||||||
dirs.push(`!${path.join(paths.plyr.output, '**/*.mp4')}`);
|
dirs.push(`!${path.join(paths.plyr.output, '**/*.mp4')}`);
|
||||||
|
|
||||||
del(dirs);
|
del(dirs);
|
||||||
|
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
const build = {
|
const build = {
|
||||||
@@ -124,6 +122,7 @@ const build = {
|
|||||||
const name = `js:${key}`;
|
const name = `js:${key}`;
|
||||||
tasks.js.push(name);
|
tasks.js.push(name);
|
||||||
const { output } = paths[bundle];
|
const { output } = paths[bundle];
|
||||||
|
const polyfill = name.includes('polyfilled');
|
||||||
|
|
||||||
return gulp.task(name, () =>
|
return gulp.task(name, () =>
|
||||||
gulp
|
gulp
|
||||||
@@ -133,11 +132,7 @@ const build = {
|
|||||||
.pipe(
|
.pipe(
|
||||||
rollup(
|
rollup(
|
||||||
{
|
{
|
||||||
plugins: [
|
plugins: [resolve(), commonjs(), babel(babelrc(polyfill))],
|
||||||
resolve(),
|
|
||||||
commonjs(),
|
|
||||||
babel(babelrc),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
options,
|
options,
|
||||||
),
|
),
|
||||||
@@ -207,31 +202,25 @@ build.sass(bundles.demo.sass, 'demo');
|
|||||||
build.js(bundles.demo.js, 'demo', { format: 'iife' });
|
build.js(bundles.demo.js, 'demo', { format: 'iife' });
|
||||||
|
|
||||||
// Build all JS
|
// Build all JS
|
||||||
gulp.task('js', () => {
|
gulp.task('js', () => gulp.parallel(tasks.js));
|
||||||
run(tasks.js);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Watch for file changes
|
// Watch for file changes
|
||||||
gulp.task('watch', () => {
|
gulp.task('watch', () => {
|
||||||
// Plyr core
|
// Plyr core
|
||||||
gulp.watch(paths.plyr.src.js, tasks.js);
|
gulp.watch(paths.plyr.src.js, gulp.parallel(tasks.js));
|
||||||
gulp.watch(paths.plyr.src.sass, tasks.sass);
|
gulp.watch(paths.plyr.src.sass, gulp.parallel(tasks.sass));
|
||||||
gulp.watch(paths.plyr.src.sprite, tasks.sprite);
|
gulp.watch(paths.plyr.src.sprite, gulp.parallel(tasks.sprite));
|
||||||
|
|
||||||
// Demo
|
// Demo
|
||||||
gulp.watch(paths.demo.src.js, tasks.js);
|
gulp.watch(paths.demo.src.js, gulp.parallel(tasks.js));
|
||||||
gulp.watch(paths.demo.src.sass, tasks.sass);
|
gulp.watch(paths.demo.src.sass, gulp.parallel(tasks.sass));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build distribution
|
// Build distribution
|
||||||
gulp.task('build', () => {
|
gulp.task('build', gulp.series(tasks.clean, gulp.parallel(tasks.js, tasks.sass, tasks.sprite)));
|
||||||
run(tasks.clean, tasks.js, tasks.sass, tasks.sprite);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Default gulp task
|
// Default gulp task
|
||||||
gulp.task('default', () => {
|
gulp.task('default', gulp.series('build', 'watch'));
|
||||||
run('build', 'watch');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Publish a version to CDN and demo
|
// Publish a version to CDN and demo
|
||||||
// --------------------------------------------
|
// --------------------------------------------
|
||||||
@@ -244,10 +233,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If deployment is setup
|
// If deployment is setup
|
||||||
if (
|
if (Object.keys(credentials).includes('aws') && Object.keys(credentials).includes('fastly')) {
|
||||||
Object.keys(credentials).includes('aws') &&
|
|
||||||
Object.keys(credentials).includes('fastly')
|
|
||||||
) {
|
|
||||||
const { version } = pkg;
|
const { version } = pkg;
|
||||||
const { aws, fastly } = credentials;
|
const { aws, fastly } = credentials;
|
||||||
|
|
||||||
@@ -269,8 +255,7 @@ if (
|
|||||||
demo: {
|
demo: {
|
||||||
uploadPath: branch.current === branch.develop ? 'beta/' : null,
|
uploadPath: branch.current === branch.develop ? 'beta/' : null,
|
||||||
headers: {
|
headers: {
|
||||||
'Cache-Control':
|
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
|
||||||
'no-cache, no-store, must-revalidate, max-age=0',
|
|
||||||
Vary: 'Accept-Encoding',
|
Vary: 'Accept-Encoding',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -279,8 +264,7 @@ if (
|
|||||||
headers: {
|
headers: {
|
||||||
// http://stackoverflow.com/questions/2272835/amazon-s3-object-redirect
|
// http://stackoverflow.com/questions/2272835/amazon-s3-object-redirect
|
||||||
'x-amz-website-redirect-location': `/${ver}/${filename}`,
|
'x-amz-website-redirect-location': `/${ver}/${filename}`,
|
||||||
'Cache-Control':
|
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
|
||||||
'no-cache, no-store, must-revalidate, max-age=0',
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -303,11 +287,7 @@ if (
|
|||||||
const allowed = [branch.master, branch.develop];
|
const allowed = [branch.master, branch.develop];
|
||||||
|
|
||||||
if (!allowed.includes(branch.current)) {
|
if (!allowed.includes(branch.current)) {
|
||||||
console.error(
|
console.error(`Must be on ${allowed.join(', ')} to publish! (current: ${branch.current})`);
|
||||||
`Must be on ${allowed.join(', ')} to publish! (current: ${
|
|
||||||
branch.current
|
|
||||||
})`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -323,13 +303,13 @@ if (
|
|||||||
console.log(`Updating versions to '${version}'...`);
|
console.log(`Updating versions to '${version}'...`);
|
||||||
|
|
||||||
// Replace versioned URLs in source
|
// Replace versioned URLs in source
|
||||||
const files = ['plyr.js', 'plyr.polyfilled.js', 'defaults.js'];
|
const files = ['plyr.js', 'plyr.polyfilled.js', 'config/defaults.js'];
|
||||||
|
|
||||||
return gulp
|
return gulp
|
||||||
.src(files.map(file => path.join(root, `src/js/${file}`)))
|
.src(files.map(file => path.join(root, `src/js/${file}`)), { base: '.' })
|
||||||
.pipe(replace(semver, `v${version}`))
|
.pipe(replace(semver, `v${version}`))
|
||||||
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
||||||
.pipe(gulp.dest(path.join(root, 'src/js/')));
|
.pipe(gulp.dest('./'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Publish version to CDN bucket
|
// Publish version to CDN bucket
|
||||||
@@ -349,8 +329,7 @@ if (
|
|||||||
.pipe(
|
.pipe(
|
||||||
replace(
|
replace(
|
||||||
/sourceMappingURL=([\w-?.]+)/,
|
/sourceMappingURL=([\w-?.]+)/,
|
||||||
(match, p1) =>
|
(match, p1) => `sourceMappingURL=${p1.replace(minSuffix, '')}`,
|
||||||
`sourceMappingURL=${p1.replace(minSuffix, '')}`,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -368,27 +347,30 @@ if (
|
|||||||
gulp.task('purge', () => {
|
gulp.task('purge', () => {
|
||||||
const list = [];
|
const list = [];
|
||||||
|
|
||||||
return gulp.src(paths.upload).pipe(
|
return gulp
|
||||||
through.obj((file, enc, cb) => {
|
.src(paths.upload)
|
||||||
const filename = file.path.split('/').pop();
|
.pipe(
|
||||||
list.push(`${versionPath}/${filename}`);
|
through.obj((file, enc, cb) => {
|
||||||
cb(null);
|
const filename = file.path.split('/').pop();
|
||||||
}),
|
list.push(`${versionPath}/${filename}`);
|
||||||
).on('end', () => {
|
cb(null);
|
||||||
const purge = new FastlyPurge(fastly.token);
|
}),
|
||||||
|
)
|
||||||
|
.on('end', () => {
|
||||||
|
const purge = new FastlyPurge(fastly.token);
|
||||||
|
|
||||||
list.forEach(url => {
|
list.forEach(url => {
|
||||||
console.log(`Purging ${url}...`);
|
console.log(`Purging ${url}...`);
|
||||||
|
|
||||||
purge.url(url, (error, result) => {
|
purge.url(url, (error, result) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
} else if (result) {
|
} else if (result) {
|
||||||
console.log(result);
|
console.log(result);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Publish to demo bucket
|
// Publish to demo bucket
|
||||||
@@ -400,8 +382,7 @@ if (
|
|||||||
console.log(`Uploading '${version}' demo to ${aws.demo.domain}...`);
|
console.log(`Uploading '${version}' demo to ${aws.demo.domain}...`);
|
||||||
|
|
||||||
// Replace versioned files in readme.md
|
// Replace versioned files in readme.md
|
||||||
gulp
|
gulp.src([`${root}/readme.md`])
|
||||||
.src([`${root}/readme.md`])
|
|
||||||
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
||||||
.pipe(gulp.dest(root));
|
.pipe(gulp.dest(root));
|
||||||
|
|
||||||
@@ -415,8 +396,7 @@ if (
|
|||||||
pages.push(error);
|
pages.push(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
gulp
|
gulp.src(pages)
|
||||||
.src(pages)
|
|
||||||
.pipe(replace(localPath, versionPath))
|
.pipe(replace(localPath, versionPath))
|
||||||
.pipe(s3(aws.demo, options.demo));
|
.pipe(s3(aws.demo, options.demo));
|
||||||
|
|
||||||
@@ -467,16 +447,15 @@ if (
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Do everything
|
// Do everything
|
||||||
gulp.task('deploy', () =>
|
gulp.task(
|
||||||
run(
|
'deploy',
|
||||||
|
gulp.series(
|
||||||
'version',
|
'version',
|
||||||
tasks.clean,
|
tasks.clean,
|
||||||
tasks.js,
|
gulp.parallel(tasks.js, tasks.sass, tasks.sprite),
|
||||||
tasks.sass,
|
|
||||||
tasks.sprite,
|
|
||||||
'cdn',
|
'cdn',
|
||||||
'purge',
|
|
||||||
'demo',
|
'demo',
|
||||||
|
'purge',
|
||||||
'open',
|
'open',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "plyr",
|
"name": "plyr",
|
||||||
"version": "3.3.23",
|
"version": "3.4.8",
|
||||||
"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": [
|
||||||
@@ -32,64 +31,61 @@
|
|||||||
"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": {
|
||||||
"babel-core": "^6.26.3",
|
"@babel/core": "^7.1.5",
|
||||||
"babel-eslint": "^8.2.6",
|
"babel-eslint": "^10.0.1",
|
||||||
"babel-plugin-external-helpers": "^6.22.0",
|
"@babel/preset-env": "^7.1.5",
|
||||||
"babel-preset-env": "^1.7.0",
|
|
||||||
"del": "^3.0.0",
|
"del": "^3.0.0",
|
||||||
"eslint": "^5.2.0",
|
"eslint": "^5.8.0",
|
||||||
"eslint-config-airbnb-base": "^13.0.0",
|
"eslint-config-airbnb-base": "^13.1.0",
|
||||||
"eslint-config-prettier": "^2.9.0",
|
"eslint-config-prettier": "^3.1.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": "^4.0.0",
|
||||||
"gulp-autoprefixer": "^5.0.0",
|
"gulp-autoprefixer": "^6.0.0",
|
||||||
"gulp-better-rollup": "^3.3.0",
|
"gulp-better-rollup": "^3.4.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",
|
||||||
"gulp-sass": "^4.0.1",
|
"gulp-sass": "^4.0.2",
|
||||||
"gulp-size": "^3.0.0",
|
"gulp-size": "^3.0.0",
|
||||||
"gulp-sourcemaps": "^2.6.4",
|
"gulp-sourcemaps": "^2.6.4",
|
||||||
"gulp-svgmin": "^1.2.4",
|
"gulp-svgmin": "^2.1.0",
|
||||||
"gulp-svgstore": "^6.1.1",
|
"gulp-svgstore": "^7.0.0",
|
||||||
"gulp-uglify-es": "^1.0.4",
|
"gulp-uglify-es": "^1.0.4",
|
||||||
"gulp-util": "^3.0.8",
|
"gulp-util": "^3.0.8",
|
||||||
"postcss-custom-properties": "^7.0.0",
|
"postcss-custom-properties": "^8.0.9",
|
||||||
"prettier-eslint": "^8.8.2",
|
"prettier-eslint": "^8.8.2",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"remark-cli": "^5.0.0",
|
"remark-cli": "^6.0.0",
|
||||||
"remark-validate-links": "^7.0.0",
|
"remark-validate-links": "^7.1.0",
|
||||||
"rollup-plugin-babel": "^3.0.7",
|
"rollup-plugin-babel": "^4.0.3",
|
||||||
"rollup-plugin-commonjs": "^9.1.4",
|
"rollup-plugin-commonjs": "^9.2.0",
|
||||||
"rollup-plugin-node-resolve": "^3.3.0",
|
"rollup-plugin-node-resolve": "^3.4.0",
|
||||||
"run-sequence": "^2.2.1",
|
"stylelint": "^9.7.1",
|
||||||
"stylelint": "^9.4.0",
|
"stylelint-config-prettier": "^4.0.0",
|
||||||
"stylelint-config-prettier": "^3.3.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.2.0",
|
||||||
"stylelint-order": "^0.8.1",
|
"stylelint-order": "^1.0.0",
|
||||||
"stylelint-scss": "^3.2.0",
|
"stylelint-scss": "^3.4.0",
|
||||||
"stylelint-selector-bem-pattern": "^2.0.0",
|
"stylelint-selector-bem-pattern": "^2.0.0",
|
||||||
"through2": "^2.0.3"
|
"through2": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-polyfill": "^6.26.0",
|
"core-js": "^2.5.7",
|
||||||
"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.27.0",
|
||||||
"url-polyfill": "^1.0.13"
|
"url-polyfill": "^1.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.8/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.8/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.8/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.8/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.
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ Any questions regarding the ads can be sent straight to vi.ai and any issues wit
|
|||||||
### SASS
|
### SASS
|
||||||
|
|
||||||
You can use `bundle.scss` file included in `/src` as part of your build and change variables to suit your design. The SASS require you to
|
You can use `bundle.scss` file included in `/src` as part of your build and change variables to suit your design. The SASS require you to
|
||||||
use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you be should already!) as all declarations use the W3C definitions.
|
use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you should be already!) as all declarations use the W3C definitions.
|
||||||
|
|
||||||
The HTML markup uses the BEM methodology with `plyr` as the block, e.g. `.plyr__controls`. You can change the class hooks in the options to match any custom CSS
|
The HTML markup uses the BEM methodology with `plyr` as the block, e.g. `.plyr__controls`. You can change the class hooks in the options to match any custom CSS
|
||||||
you write. Check out the JavaScript source for more on this.
|
you write. Check out the JavaScript source for more on this.
|
||||||
@@ -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.
|
||||||
|
|
||||||
@@ -286,11 +286,11 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `debug` | Boolean | `false` | Display debugging information in the console |
|
| `debug` | Boolean | `false` | Display debugging information in the console |
|
||||||
| `controls` | Array, Function or Element | `['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']` | If a function is passed, it is assumed your method will return either an element or HTML string for the controls. Three arguments will be passed to your function; `id` (the unique id for the player), `seektime` (the seektime step in seconds), and `title` (the media title). See [controls.md](controls.md) for more info on how the html needs to be structured. |
|
| `controls` | Array, Function or Element | `['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']` | If a function is passed, it is assumed your method will return either an element or HTML string for the controls. Three arguments will be passed to your function; `id` (the unique id for the player), `seektime` (the seektime step in seconds), and `title` (the media title). See [controls.md](controls.md) for more info on how the html needs to be structured. |
|
||||||
| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If you're using the default controls are used then you can specify which settings to show in the menu |
|
| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If you're using the default controls are used then you can specify which settings to show in the menu |
|
||||||
| `i18n` | Object | See [defaults.js](/src/js/config/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
|
| `i18n` | Object | See [defaults.js](/src/js/config/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
|
||||||
| `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. |
|
||||||
@@ -307,7 +307,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `invertTime` | Boolean | `true` | Display the current time as a countdown rather than an incremental counter. |
|
| `invertTime` | Boolean | `true` | Display the current time as a countdown rather than an incremental counter. |
|
||||||
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
|
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
|
||||||
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
|
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
|
||||||
| `captions` | Object | `{ active: false, language: 'auto', update: false }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). 'auto' uses the browser language. `update`: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in unselectable language options). |
|
| `captions` | Object | `{ active: false, language: 'auto', update: false }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). 'auto' uses the browser language. `update`: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in unselectable language options). |
|
||||||
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution. `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) |
|
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution. `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) |
|
||||||
| `ratio` | String | `16:9` | The aspect ratio you want to use for embedded players. |
|
| `ratio` | String | `16:9` | The aspect ratio you want to use for embedded players. |
|
||||||
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
||||||
@@ -315,6 +315,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display. |
|
| `quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display. |
|
||||||
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
||||||
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable vi.ai ads. `publisherId`: Your unique vi.ai publisher ID. |
|
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable vi.ai ads. `publisherId`: Your unique vi.ai publisher ID. |
|
||||||
|
| `urls` | Object | See source. | If you wish to override any API URLs then you can do so here. You can also set a custom download URL for the download button. |
|
||||||
|
|
||||||
1. Vimeo only
|
1. Vimeo only
|
||||||
|
|
||||||
@@ -365,9 +366,9 @@ player.fullscreen.enter(); // Enter fullscreen
|
|||||||
| `fullscreen.exit()` | - | Exit fullscreen. |
|
| `fullscreen.exit()` | - | Exit fullscreen. |
|
||||||
| `fullscreen.toggle()` | - | Toggle fullscreen. |
|
| `fullscreen.toggle()` | - | Toggle fullscreen. |
|
||||||
| `airplay()` | - | Trigger the airplay dialog on supported devices. |
|
| `airplay()` | - | Trigger the airplay dialog on supported devices. |
|
||||||
| `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. |
|
| `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. |
|
||||||
| `on(event, function)` | String, Function | Add an event listener for the specified event. |
|
| `on(event, function)` | String, Function | Add an event listener for the specified event. |
|
||||||
| `once(event, function)` | String, Function | Add an event listener for the specified event once. |
|
| `once(event, function)` | String, Function | Add an event listener for the specified event once. |
|
||||||
| `off(event, function)` | String, Function | Remove an event listener for the specified event. |
|
| `off(event, function)` | String, Function | Remove an event listener for the specified event. |
|
||||||
| `supports(type)` | String | Check support for a mime type. |
|
| `supports(type)` | String | Check support for a mime type. |
|
||||||
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
|
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
|
||||||
@@ -391,32 +392,32 @@ player.currentTime; // 10
|
|||||||
player.fullscreen.active; // false;
|
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. |
|
||||||
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
|
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
|
||||||
| `stopped` | ✓ | - | Returns a boolean indicating if the current player is stopped. |
|
| `stopped` | ✓ | - | Returns a boolean indicating if the current player is stopped. |
|
||||||
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
|
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
|
||||||
| `buffered` | ✓ | - | Returns a float between 0 and 1 indicating how much of the media is buffered |
|
| `buffered` | ✓ | - | Returns a float between 0 and 1 indicating how much of the media is buffered |
|
||||||
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
|
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
|
||||||
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
|
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
|
||||||
| `duration` | ✓ | - | Returns the duration for the current media. |
|
| `duration` | ✓ | - | Returns the duration for the current media. |
|
||||||
| `volume` | ✓ | ✓ | Gets or sets the volume for the player. The setter accepts a float between 0 and 1. |
|
| `volume` | ✓ | ✓ | Gets or sets the volume for the player. The setter accepts a float between 0 and 1. |
|
||||||
| `muted` | ✓ | ✓ | Gets or sets the muted state of the player. The setter accepts a boolean. |
|
| `muted` | ✓ | ✓ | Gets or sets the muted state of the player. The setter accepts a boolean. |
|
||||||
| `hasAudio` | ✓ | - | Returns a boolean indicating if the current media has an audio track. |
|
| `hasAudio` | ✓ | - | Returns a boolean indicating if the current media has an audio track. |
|
||||||
| `speed` | ✓ | ✓ | Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5. |
|
| `speed` | ✓ | ✓ | Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5. |
|
||||||
| `quality`¹ | ✓ | ✓ | Gets or sets the quality for the player. The setter accepts a value from the options specified in your config. |
|
| `quality`¹ | ✓ | ✓ | Gets or sets the quality for the player. The setter accepts a value from the options specified in your config. |
|
||||||
| `loop` | ✓ | ✓ | Gets or sets the current loop state of the player. The setter accepts a boolean. |
|
| `loop` | ✓ | ✓ | Gets or sets the current loop state of the player. The setter accepts a boolean. |
|
||||||
| `source` | ✓ | ✓ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#the-source-setter) below for examples. |
|
| `source` | ✓ | ✓ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#the-source-setter) below for examples. |
|
||||||
| `poster` | ✓ | ✓ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image. |
|
| `poster` | ✓ | ✓ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image. |
|
||||||
| `autoplay` | ✓ | ✓ | Gets or sets the autoplay state of the player. The setter accepts a boolean. |
|
| `autoplay` | ✓ | ✓ | Gets or sets the autoplay state of the player. The setter accepts a boolean. |
|
||||||
| `currentTrack` | ✓ | ✓ | Gets or sets the caption track by index. `-1` means the track is missing or captions is not active |
|
| `currentTrack` | ✓ | ✓ | Gets or sets the caption track by index. `-1` means the track is missing or captions is not active |
|
||||||
| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. If your captions don't have any language data, or if you have multiple tracks with the same language, you may want to use `currentTrack` instead. |
|
| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. If your captions don't have any language data, or if you have multiple tracks with the same language, you may want to use `currentTrack` instead. |
|
||||||
| `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. |
|
| `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. |
|
||||||
| `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. |
|
| `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. |
|
||||||
| `pip` | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+. |
|
| `pip`² | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ (on MacOS Sierra+ and iOS 10+) and Chrome 70+. |
|
||||||
|
|
||||||
1. YouTube only. HTML5 will follow.
|
1. YouTube only. HTML5 will follow.
|
||||||
2. HTML5 only
|
2. HTML5 only
|
||||||
@@ -565,6 +566,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. |
|
||||||
@@ -575,11 +577,9 @@ 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 +591,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 +652,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 +687,34 @@ 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 Patron](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/)
|
||||||
|
- [@halfhalftravel](https://www.halfhalftravel.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 +722,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();
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const defaults = {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/3.3.12/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.4.8/plyr.svg',
|
||||||
|
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
@@ -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
|
||||||
@@ -145,6 +133,7 @@ const defaults = {
|
|||||||
'settings',
|
'settings',
|
||||||
'pip',
|
'pip',
|
||||||
'airplay',
|
'airplay',
|
||||||
|
// 'download',
|
||||||
'fullscreen',
|
'fullscreen',
|
||||||
],
|
],
|
||||||
settings: ['captions', 'quality', 'speed'],
|
settings: ['captions', 'quality', 'speed'],
|
||||||
@@ -167,6 +156,7 @@ const defaults = {
|
|||||||
unmute: 'Unmute',
|
unmute: 'Unmute',
|
||||||
enableCaptions: 'Enable captions',
|
enableCaptions: 'Enable captions',
|
||||||
disableCaptions: 'Disable captions',
|
disableCaptions: 'Disable captions',
|
||||||
|
download: 'Download',
|
||||||
enterFullscreen: 'Enter fullscreen',
|
enterFullscreen: 'Enter fullscreen',
|
||||||
exitFullscreen: 'Exit fullscreen',
|
exitFullscreen: 'Exit fullscreen',
|
||||||
frameTitle: 'Player for {title}',
|
frameTitle: 'Player for {title}',
|
||||||
@@ -196,6 +186,7 @@ const defaults = {
|
|||||||
|
|
||||||
// URLs
|
// URLs
|
||||||
urls: {
|
urls: {
|
||||||
|
download: null,
|
||||||
vimeo: {
|
vimeo: {
|
||||||
sdk: 'https://player.vimeo.com/api/player.js',
|
sdk: 'https://player.vimeo.com/api/player.js',
|
||||||
iframe: 'https://player.vimeo.com/video/{0}?{1}',
|
iframe: 'https://player.vimeo.com/video/{0}?{1}',
|
||||||
@@ -222,6 +213,7 @@ const defaults = {
|
|||||||
mute: null,
|
mute: null,
|
||||||
volume: null,
|
volume: null,
|
||||||
captions: null,
|
captions: null,
|
||||||
|
download: null,
|
||||||
fullscreen: null,
|
fullscreen: null,
|
||||||
pip: null,
|
pip: null,
|
||||||
airplay: null,
|
airplay: null,
|
||||||
@@ -257,6 +249,7 @@ const defaults = {
|
|||||||
'cuechange',
|
'cuechange',
|
||||||
|
|
||||||
// Custom events
|
// Custom events
|
||||||
|
'download',
|
||||||
'enterfullscreen',
|
'enterfullscreen',
|
||||||
'exitfullscreen',
|
'exitfullscreen',
|
||||||
'captionsenabled',
|
'captionsenabled',
|
||||||
@@ -268,8 +261,9 @@ const defaults = {
|
|||||||
|
|
||||||
// YouTube
|
// YouTube
|
||||||
'statechange',
|
'statechange',
|
||||||
|
|
||||||
|
// Quality
|
||||||
'qualitychange',
|
'qualitychange',
|
||||||
'qualityrequested',
|
|
||||||
|
|
||||||
// Ads
|
// Ads
|
||||||
'adsloaded',
|
'adsloaded',
|
||||||
@@ -301,6 +295,7 @@ const defaults = {
|
|||||||
fastForward: '[data-plyr="fast-forward"]',
|
fastForward: '[data-plyr="fast-forward"]',
|
||||||
mute: '[data-plyr="mute"]',
|
mute: '[data-plyr="mute"]',
|
||||||
captions: '[data-plyr="captions"]',
|
captions: '[data-plyr="captions"]',
|
||||||
|
download: '[data-plyr="download"]',
|
||||||
fullscreen: '[data-plyr="fullscreen"]',
|
fullscreen: '[data-plyr="fullscreen"]',
|
||||||
pip: '[data-plyr="pip"]',
|
pip: '[data-plyr="pip"]',
|
||||||
airplay: '[data-plyr="airplay"]',
|
airplay: '[data-plyr="airplay"]',
|
||||||
@@ -354,6 +349,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',
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Plyr states
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
export const pip = {
|
||||||
|
active: 'picture-in-picture',
|
||||||
|
inactive: 'inline',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default { pip };
|
||||||
@@ -15,7 +15,7 @@ export const types = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get provider by URL
|
* Get provider by URL
|
||||||
* @param {string} url
|
* @param {String} url
|
||||||
*/
|
*/
|
||||||
export function getProviderByUrl(url) {
|
export function getProviderByUrl(url) {
|
||||||
// YouTube
|
// YouTube
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Fullscreen wrapper
|
// Fullscreen wrapper
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API#prefixing
|
// https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API#prefixing
|
||||||
|
// https://webkit.org/blog/7929/designing-websites-for-iphone-x/
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
import { repaint } from './utils/animation';
|
||||||
import browser from './utils/browser';
|
import browser from './utils/browser';
|
||||||
import { hasClass, toggleClass, trapFocus } from './utils/elements';
|
import { hasClass, toggleClass, trapFocus } from './utils/elements';
|
||||||
import { on, triggerEvent } from './utils/events';
|
import { on, triggerEvent } from './utils/events';
|
||||||
@@ -45,6 +47,37 @@ function toggleFallback(toggle = false) {
|
|||||||
// Toggle class hook
|
// Toggle class hook
|
||||||
toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
||||||
|
|
||||||
|
// Force full viewport on iPhone X+
|
||||||
|
if (browser.isIos) {
|
||||||
|
let viewport = document.head.querySelector('meta[name="viewport"]');
|
||||||
|
const property = 'viewport-fit=cover';
|
||||||
|
|
||||||
|
// Inject the viewport meta if required
|
||||||
|
if (!viewport) {
|
||||||
|
viewport = document.createElement('meta');
|
||||||
|
viewport.setAttribute('name', 'viewport');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the property already exists
|
||||||
|
const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
|
||||||
|
|
||||||
|
if (toggle) {
|
||||||
|
this.cleanupViewport = !hasProperty;
|
||||||
|
|
||||||
|
if (!hasProperty) {
|
||||||
|
viewport.content += `,${property}`;
|
||||||
|
}
|
||||||
|
} else if (this.cleanupViewport) {
|
||||||
|
viewport.content = viewport.content
|
||||||
|
.split(',')
|
||||||
|
.filter(part => part.trim() !== property)
|
||||||
|
.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force a repaint as sometimes Safari doesn't want to fill the screen
|
||||||
|
setTimeout(() => repaint(this.target), 100);
|
||||||
|
}
|
||||||
|
|
||||||
// Toggle button and fire events
|
// Toggle button and fire events
|
||||||
onChange.call(this);
|
onChange.call(this);
|
||||||
}
|
}
|
||||||
@@ -177,9 +210,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) {
|
||||||
|
|||||||
@@ -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, 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,55 +98,55 @@ 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:
|
||||||
this.setLoop('start');
|
this.setLoop('start');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -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,114 +288,117 @@ 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 loadeddata', () => {
|
||||||
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)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// On click play, pause ore restart
|
// On click play, pause or restart
|
||||||
on.call(this.player, wrapper, 'click', () => {
|
on.call(player, elements.container, 'click', 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) {
|
// Touch devices will just show controls (if hidden)
|
||||||
this.player.play();
|
if (player.touch && player.config.hideControls) {
|
||||||
} else if (this.player.ended) {
|
return;
|
||||||
this.player.restart();
|
}
|
||||||
this.player.play();
|
|
||||||
|
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 +408,248 @@ 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update download link when ready and if quality changes
|
||||||
|
on.call(player, player.media, 'ready qualitychange', () => {
|
||||||
|
controls.setDownloadLink.call(player);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run default and custom handlers
|
||||||
|
proxy(event, defaultHandler, customHandlerKey) {
|
||||||
|
const { player } = this;
|
||||||
|
const customHandler = player.config.listeners[customHandlerKey];
|
||||||
|
const hasCustomHandler = is.function(customHandler);
|
||||||
|
let returned = true;
|
||||||
|
|
||||||
|
// Execute custom handler
|
||||||
|
if (hasCustomHandler) {
|
||||||
|
returned = customHandler.call(player, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only call default handler if not prevented in custom handler
|
||||||
|
if (returned && is.function(defaultHandler)) {
|
||||||
|
defaultHandler.call(player, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger custom and default handlers
|
||||||
|
bind(element, type, defaultHandler, customHandlerKey, passive = true) {
|
||||||
|
const { player } = this;
|
||||||
|
const customHandler = player.config.listeners[customHandlerKey];
|
||||||
|
const hasCustomHandler = is.function(customHandler);
|
||||||
|
|
||||||
|
on.call(
|
||||||
|
player,
|
||||||
|
element,
|
||||||
|
type,
|
||||||
|
event => this.proxy(event, defaultHandler, customHandlerKey),
|
||||||
|
passive && !hasCustomHandler,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Listen for control events
|
// Listen for control events
|
||||||
controls() {
|
controls() {
|
||||||
|
const { player } = this;
|
||||||
|
const { elements } = player;
|
||||||
|
|
||||||
// IE doesn't support input event, so we fallback to change
|
// IE doesn't support input event, so we fallback to change
|
||||||
const inputEvent = browser.isIE ? 'change' : 'input';
|
const inputEvent = browser.isIE ? 'change' : 'input';
|
||||||
|
|
||||||
// Run default and custom handlers
|
|
||||||
const proxy = (event, defaultHandler, customHandlerKey) => {
|
|
||||||
const customHandler = this.player.config.listeners[customHandlerKey];
|
|
||||||
const hasCustomHandler = is.function(customHandler);
|
|
||||||
let returned = true;
|
|
||||||
|
|
||||||
// Execute custom handler
|
|
||||||
if (hasCustomHandler) {
|
|
||||||
returned = customHandler.call(this.player, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only call default handler if not prevented in custom handler
|
|
||||||
if (returned && is.function(defaultHandler)) {
|
|
||||||
defaultHandler.call(this.player, event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Trigger custom and default handlers
|
|
||||||
const bind = (element, type, defaultHandler, customHandlerKey, passive = true) => {
|
|
||||||
const customHandler = this.player.config.listeners[customHandlerKey];
|
|
||||||
const hasCustomHandler = is.function(customHandler);
|
|
||||||
|
|
||||||
on.call(
|
|
||||||
this.player,
|
|
||||||
element,
|
|
||||||
type,
|
|
||||||
event => proxy(event, defaultHandler, customHandlerKey),
|
|
||||||
passive && !hasCustomHandler,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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
|
// Download
|
||||||
bind(
|
this.bind(
|
||||||
this.player.elements.buttons.fullscreen,
|
elements.buttons.download,
|
||||||
'click',
|
'click',
|
||||||
() => {
|
() => {
|
||||||
this.player.fullscreen.toggle();
|
triggerEvent.call(player, player.media, 'download');
|
||||||
|
},
|
||||||
|
'download',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fullscreen toggle
|
||||||
|
this.bind(
|
||||||
|
elements.buttons.fullscreen,
|
||||||
|
'click',
|
||||||
|
() => {
|
||||||
|
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;
|
||||||
'language',
|
|
||||||
);
|
// We only care about space and return
|
||||||
} else if (matches(event.target, this.player.config.selectors.inputs.quality)) {
|
if (![13, 32].includes(code)) {
|
||||||
proxy(
|
return;
|
||||||
event,
|
}
|
||||||
() => {
|
|
||||||
this.player.quality = event.target.value;
|
// Because return triggers a click anyway, all we need to do is set focus
|
||||||
showHomeTab();
|
if (code === 13) {
|
||||||
},
|
controls.focusFirstMenuItem.call(player, null, true);
|
||||||
'quality',
|
return;
|
||||||
);
|
}
|
||||||
} else if (matches(event.target, this.player.config.selectors.inputs.speed)) {
|
|
||||||
proxy(
|
// Prevent scroll
|
||||||
event,
|
event.preventDefault();
|
||||||
() => {
|
|
||||||
this.player.speed = parseFloat(event.target.value);
|
// Prevent playing video (Firefox)
|
||||||
showHomeTab();
|
event.stopPropagation();
|
||||||
},
|
|
||||||
'speed',
|
// Toggle menu
|
||||||
);
|
controls.toggleMenu.call(player, event);
|
||||||
} else {
|
},
|
||||||
const tab = event.target;
|
null,
|
||||||
controls.showTab.call(this.player, tab.getAttribute('aria-controls'));
|
false, // Can't be passive as we're preventing default
|
||||||
|
);
|
||||||
|
|
||||||
|
// Escape closes menu
|
||||||
|
this.bind(elements.settings.menu, 'keydown', event => {
|
||||||
|
if (event.which === 27) {
|
||||||
|
controls.toggleMenu.call(player, event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record seek time so we can prevent hiding controls for a few seconds after seek
|
||||||
|
player.lastSeekTime = Date.now();
|
||||||
|
|
||||||
// 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,88 +663,86 @@ 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
|
// Show controls when they receive focus (e.g., when using keyboard tab key)
|
||||||
bind(this.player.elements.controls, 'focusin focusout', event => {
|
this.bind(elements.controls, 'focusin', () => {
|
||||||
const { config, elements, timers } = this.player;
|
const { config, elements, timers } = player;
|
||||||
|
|
||||||
// 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, true);
|
||||||
|
|
||||||
// Toggle
|
// Toggle
|
||||||
ui.toggleControls.call(this.player, event.type === 'focusin');
|
ui.toggleControls.call(player, true);
|
||||||
|
|
||||||
// If focusin, hide again after delay
|
// Restore transition
|
||||||
if (event.type === 'focusin') {
|
setTimeout(() => {
|
||||||
// Restore transition
|
toggleClass(elements.controls, config.classNames.noTransition, false);
|
||||||
setTimeout(() => {
|
}, 0);
|
||||||
toggleClass(elements.controls, config.classNames.noTransition, false);
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
// Delay a little more for keyboard users
|
// Delay a little more for mouse users
|
||||||
const delay = this.touch ? 3000 : 4000;
|
const delay = this.touch ? 3000 : 4000;
|
||||||
|
|
||||||
// Clear timer
|
// Clear timer
|
||||||
clearTimeout(timers.controls);
|
clearTimeout(timers.controls);
|
||||||
// Hide
|
|
||||||
timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
|
// Hide again after 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 +756,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);
|
||||||
|
|
||||||
|
|||||||
@@ -70,8 +70,9 @@ const vimeo = {
|
|||||||
// Set aspect ratio
|
// Set aspect ratio
|
||||||
// For Vimeo we have an extra 300% height <div> to hide the standard controls and UI
|
// For Vimeo we have an extra 300% height <div> to hide the standard controls and UI
|
||||||
setAspectRatio(input) {
|
setAspectRatio(input) {
|
||||||
const [x, y] = (is.string(input) ? input : this.config.ratio).split(':');
|
const [x, y] = (is.string(input) ? input : this.config.ratio).split(':').map(Number);
|
||||||
const padding = 100 / x * y;
|
const padding = (100 / x) * y;
|
||||||
|
vimeo.padding = padding;
|
||||||
this.elements.wrapper.style.paddingBottom = `${padding}%`;
|
this.elements.wrapper.style.paddingBottom = `${padding}%`;
|
||||||
|
|
||||||
if (this.supported.ui) {
|
if (this.supported.ui) {
|
||||||
@@ -278,6 +279,7 @@ const vimeo = {
|
|||||||
.getVideoUrl()
|
.getVideoUrl()
|
||||||
.then(value => {
|
.then(value => {
|
||||||
currentSrc = value;
|
currentSrc = value;
|
||||||
|
controls.setDownloadLink.call(player);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.debug.warn(error);
|
this.debug.warn(error);
|
||||||
@@ -298,8 +300,8 @@ const vimeo = {
|
|||||||
|
|
||||||
// Set aspect ratio based on video size
|
// Set aspect ratio based on video size
|
||||||
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
|
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
|
||||||
const ratio = getAspectRatio(dimensions[0], dimensions[1]);
|
vimeo.ratio = getAspectRatio(dimensions[0], dimensions[1]);
|
||||||
vimeo.setAspectRatio.call(this, ratio);
|
vimeo.setAspectRatio.call(this, vimeo.ratio);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set autopause
|
// Set autopause
|
||||||
@@ -403,6 +405,22 @@ const vimeo = {
|
|||||||
triggerEvent.call(player, player.media, 'error');
|
triggerEvent.call(player, player.media, 'error');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set height/width on fullscreen
|
||||||
|
player.on('enterfullscreen exitfullscreen', event => {
|
||||||
|
const { target } = player.fullscreen;
|
||||||
|
|
||||||
|
// Ignore for iOS native
|
||||||
|
if (target !== player.elements.container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggle = event.type === 'enterfullscreen';
|
||||||
|
const [x, y] = vimeo.ratio.split(':').map(Number);
|
||||||
|
const dimension = x > y ? 'width' : 'height';
|
||||||
|
|
||||||
|
target.style[dimension] = toggle ? `${vimeo.padding}%` : null;
|
||||||
|
});
|
||||||
|
|
||||||
// Rebuild UI
|
// Rebuild UI
|
||||||
setTimeout(() => ui.build.call(player), 0);
|
setTimeout(() => ui.build.call(player), 0);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,12 +1,13 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr
|
// Plyr
|
||||||
// plyr.js v3.3.23
|
// plyr.js v3.4.8
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import captions from './captions';
|
import captions from './captions';
|
||||||
import defaults from './config/defaults';
|
import defaults from './config/defaults';
|
||||||
|
import { pip } from './config/states';
|
||||||
import { getProviderByUrl, providers, types } from './config/types';
|
import { getProviderByUrl, providers, types } from './config/types';
|
||||||
import Console from './console';
|
import Console from './console';
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
@@ -75,16 +76,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 +187,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 +223,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,12 +295,17 @@ class Plyr {
|
|||||||
this.fullscreen = new Fullscreen(this);
|
this.fullscreen = new Fullscreen(this);
|
||||||
|
|
||||||
// Setup ads if provided
|
// Setup ads if provided
|
||||||
this.ads = new Ads(this);
|
if (this.config.ads.enabled) {
|
||||||
|
this.ads = new Ads(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Autoplay if required
|
// Autoplay if required
|
||||||
if (this.config.autoplay) {
|
if (this.config.autoplay) {
|
||||||
this.play();
|
this.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
||||||
|
this.lastSeekTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@@ -689,20 +696,27 @@ class Plyr {
|
|||||||
config.default,
|
config.default,
|
||||||
].find(is.number);
|
].find(is.number);
|
||||||
|
|
||||||
|
let updateStorage = true;
|
||||||
|
|
||||||
if (!options.includes(quality)) {
|
if (!options.includes(quality)) {
|
||||||
const value = closest(options, quality);
|
const value = closest(options, quality);
|
||||||
this.debug.warn(`Unsupported quality option: ${quality}, using ${value} instead`);
|
this.debug.warn(`Unsupported quality option: ${quality}, using ${value} instead`);
|
||||||
quality = value;
|
quality = value;
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger request event
|
// Don't update storage if quality is not supported
|
||||||
triggerEvent.call(this, this.media, 'qualityrequested', false, { quality });
|
updateStorage = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Update config
|
// Update config
|
||||||
config.selected = quality;
|
config.selected = quality;
|
||||||
|
|
||||||
// Set quality
|
// Set quality
|
||||||
this.media.quality = quality;
|
this.media.quality = quality;
|
||||||
|
|
||||||
|
// Save to storage
|
||||||
|
if (updateStorage) {
|
||||||
|
this.storage.set({ quality });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -788,6 +802,15 @@ class Plyr {
|
|||||||
return this.media.currentSrc;
|
return this.media.currentSrc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a download URL (either source or custom)
|
||||||
|
*/
|
||||||
|
get download() {
|
||||||
|
const { download } = this.config.urls;
|
||||||
|
|
||||||
|
return is.url(download) ? download : this.source;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the poster image for a video
|
* Set the poster image for a video
|
||||||
* @param {input} - the URL for the new poster image
|
* @param {input} - the URL for the new poster image
|
||||||
@@ -874,21 +897,28 @@ class Plyr {
|
|||||||
* TODO: detect outside changes
|
* TODO: detect outside changes
|
||||||
*/
|
*/
|
||||||
set pip(input) {
|
set pip(input) {
|
||||||
const states = {
|
|
||||||
pip: 'picture-in-picture',
|
|
||||||
inline: 'inline',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Bail if no support
|
// Bail if no support
|
||||||
if (!support.pip) {
|
if (!support.pip) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle based on current state if not passed
|
// Toggle based on current state if not passed
|
||||||
const toggle = is.boolean(input) ? input : this.pip === states.inline;
|
const toggle = is.boolean(input) ? input : !this.pip;
|
||||||
|
|
||||||
// Toggle based on current state
|
// Toggle based on current state
|
||||||
this.media.webkitSetPresentationMode(toggle ? states.pip : states.inline);
|
// Safari
|
||||||
|
if (is.function(this.media.webkitSetPresentationMode)) {
|
||||||
|
this.media.webkitSetPresentationMode(toggle ? pip.active : pip.inactive);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chrome
|
||||||
|
if (is.function(this.media.requestPictureInPicture)) {
|
||||||
|
if (!this.pip && toggle) {
|
||||||
|
this.media.requestPictureInPicture();
|
||||||
|
} else if (this.pip && !toggle) {
|
||||||
|
document.exitPictureInPicture();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -899,7 +929,13 @@ class Plyr {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.media.webkitPresentationMode;
|
// Safari
|
||||||
|
if (!is.empty(this.media.webkitPresentationMode)) {
|
||||||
|
return this.media.webkitPresentationMode === pip.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chrome
|
||||||
|
return this.media === document.pictureInPictureElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -933,13 +969,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,11 +1,10 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr Polyfilled Build
|
// Plyr Polyfilled Build
|
||||||
// plyr.js v3.3.23
|
// plyr.js v3.4.8
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import 'babel-polyfill';
|
|
||||||
import 'custom-event-polyfill';
|
import 'custom-event-polyfill';
|
||||||
import 'url-polyfill';
|
import 'url-polyfill';
|
||||||
import Plyr from './plyr';
|
import Plyr from './plyr';
|
||||||
|
|||||||
@@ -114,12 +114,9 @@ const source = {
|
|||||||
// HTML5 stuff
|
// HTML5 stuff
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
// Setup captions
|
// Setup captions
|
||||||
if ('tracks' in input) {
|
if (Object.keys(input).includes('tracks')) {
|
||||||
source.insertElements.call(this, 'track', input.tracks);
|
source.insertElements.call(this, 'track', input.tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load HTML5 sources
|
|
||||||
this.media.load();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If HTML5 or embed but not fully supported, setupInterface and call ready now
|
// If HTML5 or embed but not fully supported, setupInterface and call ready now
|
||||||
@@ -128,6 +125,11 @@ const source = {
|
|||||||
ui.build.call(this);
|
ui.build.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isHTML5) {
|
||||||
|
// Load HTML5 sources
|
||||||
|
this.media.load();
|
||||||
|
}
|
||||||
|
|
||||||
// Update the fullscreen support
|
// Update the fullscreen support
|
||||||
this.fullscreen.update();
|
this.fullscreen.update();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,8 +36,26 @@ const support = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Picture-in-picture support
|
// Picture-in-picture support
|
||||||
// Safari only currently
|
// Safari & Chrome only currently
|
||||||
pip: (() => !browser.isIPhone && is.function(createElement('video').webkitSetPresentationMode))(),
|
pip: (() => {
|
||||||
|
if (browser.isIPhone) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safari
|
||||||
|
// https://developer.apple.com/documentation/webkitjs/adding_picture_in_picture_to_your_safari_media_controls
|
||||||
|
if (is.function(createElement('video').webkitSetPresentationMode)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chrome
|
||||||
|
// https://developers.google.com/web/updates/2018/10/watch-video-using-picture-in-picture
|
||||||
|
if (document.pictureInPictureEnabled && !createElement('video').disablePictureInPicture) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})(),
|
||||||
|
|
||||||
// Airplay support
|
// Airplay support
|
||||||
// Safari only currently
|
// Safari only currently
|
||||||
@@ -52,25 +70,21 @@ const support = {
|
|||||||
// Related: http://www.leanbackplayer.com/test/h5mt.html
|
// Related: http://www.leanbackplayer.com/test/h5mt.html
|
||||||
mime(inputType) {
|
mime(inputType) {
|
||||||
const [mediaType] = inputType.split('/');
|
const [mediaType] = inputType.split('/');
|
||||||
|
let type = inputType;
|
||||||
|
|
||||||
|
// Verify we're using HTML5 and there's no media type mismatch
|
||||||
if (!this.isHTML5 || mediaType !== this.type) {
|
if (!this.isHTML5 || mediaType !== this.type) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let type;
|
// Add codec if required
|
||||||
if (inputType && inputType.includes('codecs=')) {
|
if (Object.keys(defaultCodecs).includes(type)) {
|
||||||
// Use input directly
|
type += `; codecs="${defaultCodecs[inputType]}"`;
|
||||||
type = inputType;
|
|
||||||
} else if (inputType === 'audio/mpeg') {
|
|
||||||
// Skip codec
|
|
||||||
type = 'audio/mpeg;';
|
|
||||||
} else if (inputType in defaultCodecs) {
|
|
||||||
// Use codec
|
|
||||||
type = `${inputType}; codecs="${defaultCodecs[inputType]}"`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return Boolean(type && this.media.canPlayType(type).replace(/no/, ''));
|
return Boolean(type && this.media.canPlayType(type).replace(/no/, ''));
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
@@ -247,8 +247,11 @@ const ui = {
|
|||||||
const { controls } = this.elements;
|
const { controls } = this.elements;
|
||||||
|
|
||||||
if (controls && this.config.hideControls) {
|
if (controls && this.config.hideControls) {
|
||||||
// Show controls if force, loading, paused, or button interaction, otherwise hide
|
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
|
||||||
this.toggleControls(Boolean(force || this.loading || this.paused || controls.pressed || controls.hover));
|
const recentTouchSeek = (this.touch && this.lastSeekTime + 2000 > Date.now());
|
||||||
|
|
||||||
|
// Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
|
||||||
|
this.toggleControls(Boolean(force || this.loading || this.paused || controls.pressed || controls.hover || recentTouchSeek));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,8 +23,12 @@ export const transitionEndEvent = (() => {
|
|||||||
// Force repaint of element
|
// Force repaint of element
|
||||||
export function repaint(element) {
|
export function repaint(element) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toggleHidden(element, true);
|
try {
|
||||||
element.offsetHeight; // eslint-disable-line
|
toggleHidden(element, true);
|
||||||
toggleHidden(element, false);
|
element.offsetHeight; // eslint-disable-line
|
||||||
|
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({ preventScroll: true });
|
||||||
|
|
||||||
|
// If we want to mimic keyboard focus via tab
|
||||||
|
if (tabFocus) {
|
||||||
|
toggleClass(element, this.config.classNames.tabFocus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,18 @@
|
|||||||
// 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';
|
||||||
|
|
||||||
|
// Skip i18n for abbreviations and brand names
|
||||||
|
const resources = {
|
||||||
|
pip: 'PIP',
|
||||||
|
airplay: 'AirPlay',
|
||||||
|
html5: 'HTML5',
|
||||||
|
vimeo: 'Vimeo',
|
||||||
|
youtube: 'YouTube',
|
||||||
|
};
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
get(key = '', config = {}) {
|
get(key = '', config = {}) {
|
||||||
@@ -15,6 +24,10 @@ const i18n = {
|
|||||||
let string = getDeep(config.i18n, key);
|
let string = getDeep(config.i18n, key);
|
||||||
|
|
||||||
if (is.empty(string)) {
|
if (is.empty(string)) {
|
||||||
|
if (Object.keys(resources).includes(key)) {
|
||||||
|
return resources[key];
|
||||||
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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));
|
||||||
|
|
||||||
@@ -30,6 +31,11 @@ const isUrl = input => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must be string from here
|
||||||
|
if (!isString(input)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Add the protocol if required
|
// Add the protocol if required
|
||||||
let string = input;
|
let string = input;
|
||||||
if (!input.startsWith('http://') || !input.startsWith('https://')) {
|
if (!input.startsWith('http://') || !input.startsWith('https://')) {
|
||||||
@@ -56,6 +62,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,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function getPercentage(current, max) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (current / max * 100).toFixed(2);
|
return ((current / max) * 100).toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace all occurances of a string in a string
|
// Replace all occurances of a string in a string
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove any link styling
|
||||||
|
a.plyr__control {
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Change icons on state change
|
// Change icons on state change
|
||||||
.plyr__control:not(.plyr__control--pressed) .icon--pressed,
|
.plyr__control:not(.plyr__control--pressed) .icon--pressed,
|
||||||
.plyr__control.plyr__control--pressed .icon--not-pressed,
|
.plyr__control.plyr__control--pressed .icon--not-pressed,
|
||||||
@@ -41,7 +51,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 +61,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);
|
||||||
@@ -66,11 +91,10 @@
|
|||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
||||||
|
// Offset icon to make the play button look right
|
||||||
svg {
|
svg {
|
||||||
height: $plyr-control-icon-size-large;
|
left: 2px;
|
||||||
left: 2px; // Offset to make the play button look right
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: $plyr-control-icon-size-large;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.plyr__menu + .plyr__control,
|
||||||
|
> .plyr__control + .plyr__menu,
|
||||||
|
> .plyr__control + .plyr__control,
|
||||||
|
.plyr__progress + .plyr__control {
|
||||||
|
margin-left: floor($plyr-control-spacing / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .plyr__control:first-child,
|
||||||
|
> .plyr__control:first-child + [data-plyr='pause'] {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide empty controls
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: $plyr-bp-sm) {
|
@media (min-width: $plyr-bp-sm) {
|
||||||
> .plyr__control,
|
> .plyr__control,
|
||||||
|
.plyr__menu,
|
||||||
.plyr__progress,
|
.plyr__progress,
|
||||||
.plyr__time,
|
.plyr__time,
|
||||||
.plyr__menu {
|
.plyr__volume {
|
||||||
margin-left: $plyr-control-spacing;
|
margin-left: $plyr-control-spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .plyr__control + .plyr__control,
|
|
||||||
.plyr__menu + .plyr__control,
|
|
||||||
> .plyr__control + .plyr__menu {
|
|
||||||
margin-left: ($plyr-control-spacing / 2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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,18 +55,16 @@
|
|||||||
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'],
|
||||||
margin-top: 2px;
|
[role='menuitemradio'] {
|
||||||
|
margin-top: 2px;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 100%;
|
|
||||||
content: '';
|
|
||||||
height: 6px;
|
|
||||||
left: 5px;
|
|
||||||
opacity: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 5px;
|
|
||||||
transform: scale(0);
|
|
||||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='radio']:checked + span {
|
&::after {
|
||||||
background: $plyr-color-main;
|
background: #fff;
|
||||||
|
border: 0;
|
||||||
|
height: 6px;
|
||||||
|
left: 12px;
|
||||||
|
opacity: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%) scale(0);
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[aria-checked='true'] {
|
||||||
|
&::before {
|
||||||
|
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;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$plyr-control-icon-size: 18px !default;
|
$plyr-control-icon-size: 18px !default;
|
||||||
$plyr-control-icon-size-large: 20px !default;
|
|
||||||
$plyr-control-spacing: 10px !default;
|
$plyr-control-spacing: 10px !default;
|
||||||
$plyr-control-padding: ($plyr-control-spacing * 0.7) !default;
|
$plyr-control-padding: ($plyr-control-spacing * 0.7) !default;
|
||||||
$plyr-control-radius: 3px !default;
|
$plyr-control-radius: 3px !default;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ $plyr-range-thumb-border: 2px solid transparent !default;
|
|||||||
$plyr-range-thumb-shadow: 0 1px 1px rgba(#000, 0.15), 0 0 0 1px rgba($plyr-color-gunmetal, 0.2) !default;
|
$plyr-range-thumb-shadow: 0 1px 1px rgba(#000, 0.15), 0 0 0 1px rgba($plyr-color-gunmetal, 0.2) !default;
|
||||||
|
|
||||||
// Track
|
// Track
|
||||||
$plyr-range-track-height: 6px !default;
|
$plyr-range-track-height: 4px !default;
|
||||||
$plyr-range-max-height: ($plyr-range-thumb-active-shadow-width * 2) + $plyr-range-thumb-height !default;
|
$plyr-range-max-height: ($plyr-range-thumb-active-shadow-width * 2) + $plyr-range-thumb-height !default;
|
||||||
|
|
||||||
// Fill
|
// Fill
|
||||||
|
|||||||
@@ -17,4 +17,4 @@ $plyr-font-weight-bold: 600 !default;
|
|||||||
|
|
||||||
$plyr-line-height: 1.7 !default;
|
$plyr-line-height: 1.7 !default;
|
||||||
|
|
||||||
$plyr-font-smoothing: true !default;
|
$plyr-font-smoothing: false !default;
|
||||||
|
|||||||
@@ -22,3 +22,7 @@
|
|||||||
width: 1px;
|
width: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.plyr [hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<g>
|
<g>
|
||||||
<path d="M16,1 L2,1 C1.447,1 1,1.447 1,2 L1,12 C1,12.553 1.447,13 2,13 L5,13 L5,11 L3,11 L3,3 L15,3 L15,11 L13,11 L13,13 L16,13 C16.553,13 17,12.553 17,12 L17,2 C17,1.447 16.553,1 16,1 L16,1 Z"></path>
|
<path d="M16,1 L2,1 C1.447,1 1,1.447 1,2 L1,12 C1,12.553 1.447,13 2,13 L5,13 L5,11 L3,11 L3,3 L15,3 L15,11 L13,11 L13,13 L16,13 C16.553,13 17,12.553 17,12 L17,2 C17,1.447 16.553,1 16,1 L16,1 Z"></path>
|
||||||
<polygon points="4 17 14 17 9 11"></polygon>
|
<polygon points="4 17 14 17 9 11"></polygon>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 374 B |
@@ -1,6 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
|
|
||||||
<g fill-rule="evenodd" fill-opacity="0.5">
|
<g fill-rule="evenodd" fill-opacity="0.5">
|
||||||
<path d="M1,1 C0.4,1 0,1.4 0,2 L0,13 C0,13.6 0.4,14 1,14 L5.6,14 L8.3,16.7 C8.5,16.9 8.7,17 9,17 C9.3,17 9.5,16.9 9.7,16.7 L12.4,14 L17,14 C17.6,14 18,13.6 18,13 L18,2 C18,1.4 17.6,1 17,1 L1,1 Z M5.52,11.15 C7.51,11.15 8.53,9.83 8.8,8.74 L7.51,8.35 C7.32,9.01 6.73,9.8 5.52,9.8 C4.38,9.8 3.32,8.97 3.32,7.46 C3.32,5.85 4.44,5.09 5.5,5.09 C6.73,5.09 7.28,5.84 7.45,6.52 L8.75,6.11 C8.47,4.96 7.46,3.76 5.5,3.76 C3.6,3.76 1.89,5.2 1.89,7.46 C1.89,9.72 3.54,11.15 5.52,11.15 Z M13.09,11.15 C15.08,11.15 16.1,9.83 16.37,8.74 L15.08,8.35 C14.89,9.01 14.3,9.8 13.09,9.8 C11.95,9.8 10.89,8.97 10.89,7.46 C10.89,5.85 12.01,5.09 13.07,5.09 C14.3,5.09 14.85,5.84 15.02,6.52 L16.32,6.11 C16.04,4.96 15.03,3.76 13.07,3.76 C11.17,3.76 9.46,5.2 9.46,7.46 C9.46,9.72 11.11,11.15 13.09,11.15 Z"></path>
|
<path d="M1,1 C0.4,1 0,1.4 0,2 L0,13 C0,13.6 0.4,14 1,14 L5.6,14 L8.3,16.7 C8.5,16.9 8.7,17 9,17 C9.3,17 9.5,16.9 9.7,16.7 L12.4,14 L17,14 C17.6,14 18,13.6 18,13 L18,2 C18,1.4 17.6,1 17,1 L1,1 Z M5.52,11.15 C7.51,11.15 8.53,9.83 8.8,8.74 L7.51,8.35 C7.32,9.01 6.73,9.8 5.52,9.8 C4.38,9.8 3.32,8.97 3.32,7.46 C3.32,5.85 4.44,5.09 5.5,5.09 C6.73,5.09 7.28,5.84 7.45,6.52 L8.75,6.11 C8.47,4.96 7.46,3.76 5.5,3.76 C3.6,3.76 1.89,5.2 1.89,7.46 C1.89,9.72 3.54,11.15 5.52,11.15 Z M13.09,11.15 C15.08,11.15 16.1,9.83 16.37,8.74 L15.08,8.35 C14.89,9.01 14.3,9.8 13.09,9.8 C11.95,9.8 10.89,8.97 10.89,7.46 C10.89,5.85 12.01,5.09 13.07,5.09 C14.3,5.09 14.85,5.84 15.02,6.52 L16.32,6.11 C16.04,4.96 15.03,3.76 13.07,3.76 C11.17,3.76 9.46,5.2 9.46,7.46 C9.46,9.72 11.11,11.15 13.09,11.15 Z"></path>
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 945 B |
@@ -1,6 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
|
|
||||||
<g fill-rule="evenodd">
|
<g fill-rule="evenodd">
|
||||||
<path d="M1,1 C0.4,1 0,1.4 0,2 L0,13 C0,13.6 0.4,14 1,14 L5.6,14 L8.3,16.7 C8.5,16.9 8.7,17 9,17 C9.3,17 9.5,16.9 9.7,16.7 L12.4,14 L17,14 C17.6,14 18,13.6 18,13 L18,2 C18,1.4 17.6,1 17,1 L1,1 Z M5.52,11.15 C7.51,11.15 8.53,9.83 8.8,8.74 L7.51,8.35 C7.32,9.01 6.73,9.8 5.52,9.8 C4.38,9.8 3.32,8.97 3.32,7.46 C3.32,5.85 4.44,5.09 5.5,5.09 C6.73,5.09 7.28,5.84 7.45,6.52 L8.75,6.11 C8.47,4.96 7.46,3.76 5.5,3.76 C3.6,3.76 1.89,5.2 1.89,7.46 C1.89,9.72 3.54,11.15 5.52,11.15 Z M13.09,11.15 C15.08,11.15 16.1,9.83 16.37,8.74 L15.08,8.35 C14.89,9.01 14.3,9.8 13.09,9.8 C11.95,9.8 10.89,8.97 10.89,7.46 C10.89,5.85 12.01,5.09 13.07,5.09 C14.3,5.09 14.85,5.84 15.02,6.52 L16.32,6.11 C16.04,4.96 15.03,3.76 13.07,3.76 C11.17,3.76 9.46,5.2 9.46,7.46 C9.46,9.72 11.11,11.15 13.09,11.15 Z"></path>
|
<path d="M1,1 C0.4,1 0,1.4 0,2 L0,13 C0,13.6 0.4,14 1,14 L5.6,14 L8.3,16.7 C8.5,16.9 8.7,17 9,17 C9.3,17 9.5,16.9 9.7,16.7 L12.4,14 L17,14 C17.6,14 18,13.6 18,13 L18,2 C18,1.4 17.6,1 17,1 L1,1 Z M5.52,11.15 C7.51,11.15 8.53,9.83 8.8,8.74 L7.51,8.35 C7.32,9.01 6.73,9.8 5.52,9.8 C4.38,9.8 3.32,8.97 3.32,7.46 C3.32,5.85 4.44,5.09 5.5,5.09 C6.73,5.09 7.28,5.84 7.45,6.52 L8.75,6.11 C8.47,4.96 7.46,3.76 5.5,3.76 C3.6,3.76 1.89,5.2 1.89,7.46 C1.89,9.72 3.54,11.15 5.52,11.15 Z M13.09,11.15 C15.08,11.15 16.1,9.83 16.37,8.74 L15.08,8.35 C14.89,9.01 14.3,9.8 13.09,9.8 C11.95,9.8 10.89,8.97 10.89,7.46 C10.89,5.85 12.01,5.09 13.07,5.09 C14.3,5.09 14.85,5.84 15.02,6.52 L16.32,6.11 C16.04,4.96 15.03,3.76 13.07,3.76 C11.17,3.76 9.46,5.2 9.46,7.46 C9.46,9.72 11.11,11.15 13.09,11.15 Z"></path>
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 926 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(2 1)">
|
||||||
|
<path d="M7,12 C7.3,12 7.5,11.9 7.7,11.7 L13.4,6 L12,4.6 L8,8.6 L8,0 L6,0 L6,8.6 L2,4.6 L0.6,6 L6.3,11.7 C6.5,11.9 6.7,12 7,12 Z" />
|
||||||
|
<rect width="14" height="2" y="14" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 325 B |
@@ -1,7 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<polygon points="10 3 13.6 3 9.6 7 11 8.4 15 4.4 15 8 17 8 17 1 10 1"></polygon>
|
||||||
<g>
|
<polygon points="7 9.6 3 13.6 3 10 1 10 1 17 8 17 8 15 4.4 15 8.4 11"></polygon>
|
||||||
<polygon points="10 3 13.6 3 9.6 7 11 8.4 15 4.4 15 8 17 8 17 1 10 1"></polygon>
|
|
||||||
<polygon points="7 9.6 3 13.6 3 10 1 10 1 17 8 17 8 15 4.4 15 8.4 11"></polygon>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 264 B |
@@ -1,7 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<polygon points="1 12 4.6 12 0.6 16 2 17.4 6 13.4 6 17 8 17 8 10 1 10"></polygon>
|
||||||
<g>
|
<polygon points="16 0.6 12 4.6 12 1 10 1 10 8 17 8 17 6 13.4 6 17.4 2"></polygon>
|
||||||
<polygon points="1 12 4.6 12 0.6 16 2 17.4 6 13.4 6 17 8 17 8 10 1 10"></polygon>
|
|
||||||
<polygon points="16 0.6 12 4.6 12 1 10 1 10 8 17 8 17 6 13.4 6 17.4 2"></polygon>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 266 B |
@@ -1,6 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<polygon points="7.875 7.17142857 0 1 0 17 7.875 10.8285714 7.875 17 18 9 7.875 1"></polygon>
|
||||||
<g>
|
|
||||||
<polygon points="7.875 7.17142857 0 1 0 17 7.875 10.8285714 7.875 17 18 9 7.875 1"></polygon>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 192 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||||
|
<path d="M16,3.3 C15.9,4.9 14.8,7 12.7,9.7 C10.5,12.5 8.7,13.9 7.2,13.9 C6.3,13.9 5.5,13 4.8,11.3 C4,8.9 3.4,4 2,4 C1.9,4 1.5,4.3 0.8,4.8 L0,3.8 C0.8,3.1 3.5,0.4 4.7,0.3 C5.9,0.2 6.7,1 7,2.8 C7.3,4.8 7.8,8.9 8.8,8.9 C9.7,8.9 11.3,5.5 11.4,4.9 C11.5,4 11.1,3 9.1,3.8 C9.9,1.2 11.4,-8.8817842e-16 13.6,-8.8817842e-16 C15.3,0.1 16.1,1.2 16,3.3 Z"
|
||||||
|
transform="translate(1 2)" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 470 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||||
|
<path d="M15.8,2.8 C15.6,1.5 15,0.6 13.6,0.4 C11.4,0 8,0 8,0 C8,0 4.6,0 2.4,0.4 C1,0.6 0.3,1.5 0.2,2.8 C0,4.1 0,6 0,6 C0,6 0,7.9 0.2,9.2 C0.4,10.5 1,11.4 2.4,11.6 C4.6,12 8,12 8,12 C8,12 11.4,12 13.6,11.6 C15,11.3 15.6,10.5 15.8,9.2 C16,7.9 16,6 16,6 C16,6 16,4.1 15.8,2.8 Z M6,9 L6,3 L11,6 L6,9 Z"
|
||||||
|
transform="translate(1 3)" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 425 B |
@@ -1,7 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<polygon points="12.4 12.5 14.5 10.4 16.6 12.5 18 11.1 15.9 9 18 6.9 16.6 5.5 14.5 7.6 12.4 5.5 11 6.9 13.1 9 11 11.1"></polygon>
|
||||||
<g>
|
<path d="M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"></path>
|
||||||
<polygon points="12.4 12.5 14.5 10.4 16.6 12.5 18 11.1 15.9 9 18 6.9 16.6 5.5 14.5 7.6 12.4 5.5 11 6.9 13.1 9 11 11.1"></polygon>
|
|
||||||
<path d="M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 750 B After Width: | Height: | Size: 613 B |
@@ -1,7 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<path d="M6,1 L3,1 C2.4,1 2,1.4 2,2 L2,16 C2,16.6 2.4,17 3,17 L6,17 C6.6,17 7,16.6 7,16 L7,2 C7,1.4 6.6,1 6,1 L6,1 Z"></path>
|
||||||
<g>
|
<path d="M12,1 C11.4,1 11,1.4 11,2 L11,16 C11,16.6 11.4,17 12,17 L15,17 C15.6,17 16,16.6 16,16 L16,2 C16,1.4 15.6,1 15,1 L12,1 Z"></path>
|
||||||
<path d="M6,1 L3,1 C2.4,1 2,1.4 2,2 L2,16 C2,16.6 2.4,17 3,17 L6,17 C6.6,17 7,16.6 7,16 L7,2 C7,1.4 6.6,1 6,1 L6,1 Z"></path>
|
|
||||||
<path d="M12,1 C11.4,1 11,1.4 11,2 L11,16 C11,16.6 11.4,17 12,17 L15,17 C15.6,17 16,16.6 16,16 L16,2 C16,1.4 15.6,1 15,1 L12,1 Z"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 503 B After Width: | Height: | Size: 366 B |
@@ -1,7 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<polygon points="13.293 3.293 7.022 9.564 8.436 10.978 14.707 4.707 17 7 17 1 11 1"></polygon>
|
||||||
<g>
|
<path d="M13,15 L3,15 L3,5 L8,5 L8,3 L2,3 C1.448,3 1,3.448 1,4 L1,16 C1,16.552 1.448,17 2,17 L14,17 C14.552,17 15,16.552 15,16 L15,10 L13,10 L13,15 L13,15 Z"></path>
|
||||||
<polygon points="13.293 3.293 7.022 9.564 8.436 10.978 14.707 4.707 17 7 17 1 11 1"></polygon>
|
|
||||||
<path d="M13,15 L3,15 L3,5 L8,5 L8,3 L2,3 C1.448,3 1,3.448 1,4 L1,16 C1,16.552 1.448,17 2,17 L14,17 C14.552,17 15,16.552 15,16 L15,10 L13,10 L13,15 L13,15 Z"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 500 B After Width: | Height: | Size: 363 B |
@@ -1,6 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<path d="M15.5615866,8.10002147 L3.87056367,0.225209313 C3.05219207,-0.33727727 2,0.225209313 2,1.12518784 L2,16.8748122 C2,17.7747907 3.05219207,18.3372773 3.87056367,17.7747907 L15.5615866,9.89997853 C16.1461378,9.44998927 16.1461378,8.55001073 15.5615866,8.10002147 L15.5615866,8.10002147 Z"></path>
|
||||||
<g>
|
|
||||||
<path d="M15.5615866,8.10002147 L3.87056367,0.225209313 C3.05219207,-0.33727727 2,0.225209313 2,1.12518784 L2,16.8748122 C2,17.7747907 3.05219207,18.3372773 3.87056367,17.7747907 L15.5615866,9.89997853 C16.1461378,9.44998927 16.1461378,8.55001073 15.5615866,8.10002147 L15.5615866,8.10002147 Z"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 534 B After Width: | Height: | Size: 401 B |
@@ -1,6 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<path d="M9.7,1.2 L10.4,7.6 L12.5,5.5 C14.4,7.4 14.4,10.6 12.5,12.5 C11.6,13.5 10.3,14 9,14 C7.7,14 6.4,13.5 5.5,12.5 C3.6,10.6 3.6,7.4 5.5,5.5 C6.1,4.9 6.9,4.4 7.8,4.2 L7.2,2.3 C6,2.6 4.9,3.2 4,4.1 C1.3,6.8 1.3,11.2 4,14 C5.3,15.3 7.1,16 8.9,16 C10.8,16 12.5,15.3 13.8,14 C16.5,11.3 16.5,6.9 13.8,4.1 L16,1.9 L9.7,1.2 L9.7,1.2 Z"></path>
|
||||||
<g>
|
|
||||||
<path d="M9.7,1.2 L10.4,7.6 L12.5,5.5 C14.4,7.4 14.4,10.6 12.5,12.5 C11.6,13.5 10.3,14 9,14 C7.7,14 6.4,13.5 5.5,12.5 C3.6,10.6 3.6,7.4 5.5,5.5 C6.1,4.9 6.9,4.4 7.8,4.2 L7.2,2.3 C6,2.6 4.9,3.2 4,4.1 C1.3,6.8 1.3,11.2 4,14 C5.3,15.3 7.1,16 8.9,16 C10.8,16 12.5,15.3 13.8,14 C16.5,11.3 16.5,6.9 13.8,4.1 L16,1.9 L9.7,1.2 L9.7,1.2 Z"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 570 B After Width: | Height: | Size: 437 B |
@@ -1,6 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<polygon points="10.125 1 0 9 10.125 17 10.125 10.8285714 18 17 18 1 10.125 7.17142857"></polygon>
|
||||||
<g>
|
|
||||||
<polygon points="10.125 1 0 9 10.125 17 10.125 10.8285714 18 17 18 1 10.125 7.17142857"></polygon>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 330 B After Width: | Height: | Size: 197 B |
@@ -1,6 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<path d="M16.135,7.784 C14.832,7.458 14.214,5.966 14.905,4.815 C15.227,4.279 15.13,3.817 14.811,3.499 L14.501,3.189 C14.183,2.871 13.721,2.774 13.185,3.095 C12.033,3.786 10.541,3.168 10.216,1.865 C10.065,1.258 9.669,1 9.219,1 L8.781,1 C8.331,1 7.936,1.258 7.784,1.865 C7.458,3.168 5.966,3.786 4.815,3.095 C4.279,2.773 3.816,2.87 3.498,3.188 L3.188,3.498 C2.87,3.816 2.773,4.279 3.095,4.815 C3.786,5.967 3.168,7.459 1.865,7.784 C1.26,7.935 1,8.33 1,8.781 L1,9.219 C1,9.669 1.258,10.064 1.865,10.216 C3.168,10.542 3.786,12.034 3.095,13.185 C2.773,13.721 2.87,14.183 3.189,14.501 L3.499,14.811 C3.818,15.13 4.281,15.226 4.815,14.905 C5.967,14.214 7.459,14.832 7.784,16.135 C7.935,16.742 8.331,17 8.781,17 L9.219,17 C9.669,17 10.064,16.742 10.216,16.135 C10.542,14.832 12.034,14.214 13.185,14.905 C13.72,15.226 14.182,15.13 14.501,14.811 L14.811,14.501 C15.129,14.183 15.226,13.72 14.905,13.185 C14.214,12.033 14.832,10.541 16.135,10.216 C16.742,10.065 17,9.669 17,9.219 L17,8.781 C17,8.33 16.74,7.935 16.135,7.784 L16.135,7.784 Z M9,12 C7.343,12 6,10.657 6,9 C6,7.343 7.343,6 9,6 C10.657,6 12,7.343 12,9 C12,10.657 10.657,12 9,12 L9,12 Z"></path>
|
||||||
<g>
|
|
||||||
<path d="M16.135,7.784 C14.832,7.458 14.214,5.966 14.905,4.815 C15.227,4.279 15.13,3.817 14.811,3.499 L14.501,3.189 C14.183,2.871 13.721,2.774 13.185,3.095 C12.033,3.786 10.541,3.168 10.216,1.865 C10.065,1.258 9.669,1 9.219,1 L8.781,1 C8.331,1 7.936,1.258 7.784,1.865 C7.458,3.168 5.966,3.786 4.815,3.095 C4.279,2.773 3.816,2.87 3.498,3.188 L3.188,3.498 C2.87,3.816 2.773,4.279 3.095,4.815 C3.786,5.967 3.168,7.459 1.865,7.784 C1.26,7.935 1,8.33 1,8.781 L1,9.219 C1,9.669 1.258,10.064 1.865,10.216 C3.168,10.542 3.786,12.034 3.095,13.185 C2.773,13.721 2.87,14.183 3.189,14.501 L3.499,14.811 C3.818,15.13 4.281,15.226 4.815,14.905 C5.967,14.214 7.459,14.832 7.784,16.135 C7.935,16.742 8.331,17 8.781,17 L9.219,17 C9.669,17 10.064,16.742 10.216,16.135 C10.542,14.832 12.034,14.214 13.185,14.905 C13.72,15.226 14.182,15.13 14.501,14.811 L14.811,14.501 C15.129,14.183 15.226,13.72 14.905,13.185 C14.214,12.033 14.832,10.541 16.135,10.216 C16.742,10.065 17,9.669 17,9.219 L17,8.781 C17,8.33 16.74,7.935 16.135,7.784 L16.135,7.784 Z M9,12 C7.343,12 6,10.657 6,9 C6,7.343 7.343,6 9,6 C10.657,6 12,7.343 12,9 C12,10.657 10.657,12 9,12 L9,12 Z"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,8 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<path d="M15.5999996,3.3 C15.1999996,2.9 14.5999996,2.9 14.1999996,3.3 C13.7999996,3.7 13.7999996,4.3 14.1999996,4.7 C15.3999996,5.9 15.9999996,7.4 15.9999996,9 C15.9999996,10.6 15.3999996,12.1 14.1999996,13.3 C13.7999996,13.7 13.7999996,14.3 14.1999996,14.7 C14.3999996,14.9 14.6999996,15 14.8999996,15 C15.1999996,15 15.3999996,14.9 15.5999996,14.7 C17.0999996,13.2 17.9999996,11.2 17.9999996,9 C17.9999996,6.8 17.0999996,4.8 15.5999996,3.3 L15.5999996,3.3 Z"></path>
|
||||||
<g>
|
<path d="M11.2819745,5.28197449 C10.9060085,5.65794047 10.9060085,6.22188944 11.2819745,6.59785542 C12.0171538,7.33303477 12.2772954,8.05605449 12.2772954,9.00000021 C12.2772954,9.93588462 11.851678,10.9172014 11.2819745,11.4869049 C10.9060085,11.8628709 10.9060085,12.4268199 11.2819745,12.8027859 C11.4271642,12.9479755 11.9176724,13.0649528 12.2998149,12.9592565 C12.4124479,12.9281035 12.5156669,12.8776063 12.5978555,12.8027859 C13.773371,11.732654 14.1311161,10.1597914 14.1312523,9.00000021 C14.1312723,8.8299555 14.1286311,8.66015647 14.119665,8.4897429 C14.0674781,7.49784946 13.8010171,6.48513613 12.5978554,5.28197449 C12.2218894,4.9060085 11.6579405,4.9060085 11.2819745,5.28197449 Z"></path>
|
||||||
<path d="M15.5999996,3.3 C15.1999996,2.9 14.5999996,2.9 14.1999996,3.3 C13.7999996,3.7 13.7999996,4.3 14.1999996,4.7 C15.3999996,5.9 15.9999996,7.4 15.9999996,9 C15.9999996,10.6 15.3999996,12.1 14.1999996,13.3 C13.7999996,13.7 13.7999996,14.3 14.1999996,14.7 C14.3999996,14.9 14.6999996,15 14.8999996,15 C15.1999996,15 15.3999996,14.9 15.5999996,14.7 C17.0999996,13.2 17.9999996,11.2 17.9999996,9 C17.9999996,6.8 17.0999996,4.8 15.5999996,3.3 L15.5999996,3.3 Z"></path>
|
<path d="M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"></path>
|
||||||
<path d="M11.2819745,5.28197449 C10.9060085,5.65794047 10.9060085,6.22188944 11.2819745,6.59785542 C12.0171538,7.33303477 12.2772954,8.05605449 12.2772954,9.00000021 C12.2772954,9.93588462 11.851678,10.9172014 11.2819745,11.4869049 C10.9060085,11.8628709 10.9060085,12.4268199 11.2819745,12.8027859 C11.4271642,12.9479755 11.9176724,13.0649528 12.2998149,12.9592565 C12.4124479,12.9281035 12.5156669,12.8776063 12.5978555,12.8027859 C13.773371,11.732654 14.1311161,10.1597914 14.1312523,9.00000021 C14.1312723,8.8299555 14.1286311,8.66015647 14.119665,8.4897429 C14.0674781,7.49784946 13.8010171,6.48513613 12.5978554,5.28197449 C12.2218894,4.9060085 11.6579405,4.9060085 11.2819745,5.28197449 Z"></path>
|
|
||||||
<path d="M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.6 KiB |