Compare commits

...

123 Commits

Author SHA1 Message Date
Sam Potts 2c8a337f26 v3.4.7
-   Fix for Vimeo fullscreen with non native aspect ratios (fixes #854)
2018-11-08 23:34:10 +11:00
Sam Potts c24e52d97d Package updates 2018-11-08 23:29:54 +11:00
Sam Potts 574f40949c Merge branch 'master' into develop 2018-11-08 23:19:57 +11:00
Sam Potts cf3848fbd5 Merge branch 'develop' of github.com:sampotts/plyr into develop 2018-11-08 23:19:26 +11:00
Sam Potts a19ad69038 Fix for Vimeo fullscreen with non 16:9 aspect ratios 2018-11-08 23:19:11 +11:00
Sam Potts d6f20e2756 Package upgrades 2018-11-08 23:18:23 +11:00
Sam Potts e2fc20ca76 Styling tweaks 2018-11-08 23:18:04 +11:00
Sam Potts 37c3f7109d Additional listener for checking for audio tracks 2018-11-08 23:17:44 +11:00
Sam Potts 99d5211a33 Merge pull request #1247 from danielcgold/patch-1
[Edit README] Halfhalftravel uses Plyr!
2018-11-06 10:17:55 +11:00
Dan Gold b97f143195 Halfhalftravel uses Plyr!
https://www.halfhalftravel.com/events/medellin-photo-walk.html
https://www.halfhalftravel.com/travel-guides/how-to-travel-to-santa-fe-de-antioquia-colombia.html
2018-11-05 10:57:23 -05:00
Sam Potts e8da4326b6 Prevent scroll on focus 2018-11-03 21:17:46 +11:00
Sam Potts 67f908aa8d Load media after UI is built 2018-11-03 21:17:32 +11:00
Sam Potts 65eb5c1b8b Fix support check 2018-11-03 21:16:40 +11:00
Sam Potts 7d484c6e09 Merge pull request #1232 from tocsinde/patch-1
Readme: Add missing annotation to PIP support
2018-10-26 08:37:33 +11:00
Stephan Fischer 8252e13eb9 Readme: Add missing annotation to PIP support
PIP only works on HTML videos, so I added the number of the (already existing) annotation.
2018-10-25 23:35:20 +02:00
Sam Potts 1a9b860e68 v3.4.6
-   Added picture-in-picture support for Chrome 70+
-   Fixed issue with versioning the SVG sprite in the gulp build script
2018-10-25 09:44:40 +11:00
Sam Potts cede7d0f35 Fix gulp build 2018-10-25 09:21:37 +11:00
Sam Potts fe26d383f1 Added support for picture-in-picture in Chrome 2018-10-25 09:17:15 +11:00
Sam Potts df4bc268dc Merge branch 'master' into develop 2018-10-25 00:14:14 +11:00
Sam Potts e49da6c13f v3.4.5 2018-10-24 23:04:18 +11:00
Sam Potts 67b7262764 Revert PR #1211 2018-10-24 23:00:54 +11:00
Sam Potts 88528ef979 Merge pull request #1197 from TechGuard/fix-html5-quality-settings
Fix html5 quality settings
2018-10-24 22:39:34 +11:00
Sam Potts b6175b1ca9 Merge branch 'develop' into fix-html5-quality-settings 2018-10-24 22:39:10 +11:00
Sam Potts aa20ebaa9c Merge pull request #1211 from melbahja/develop
duration after changing video quality
2018-10-24 22:37:49 +11:00
Sam Potts 779e45c11b Merge branch 'master' into develop 2018-10-24 22:32:55 +11:00
Sam Potts 5d5a6eabaa Merge branch 'develop' of github.com:sampotts/plyr into develop 2018-10-24 22:31:47 +11:00
Sam Potts 03c9b53232 Allow custom download URL (for streaming, etc) 2018-10-24 22:31:35 +11:00
Sam Potts ebaded66b4 Package updates 2018-10-24 22:31:16 +11:00
Sam Potts c232eb2478 Fix SVG issue for older browsers (fixes #1191) 2018-10-24 22:30:41 +11:00
Sam Potts 7559cc6897 Merge pull request #1226 from jamesoflol/dont-hide-mobile-controls-immediately
Prevent immediate hiding of controls on mobile
2018-10-23 11:09:11 +11:00
James 69d0d6d7ee Prevent immediate hiding of controls on mobile 2018-10-23 10:08:46 +11:00
Sam Potts 3e9336b15d Merge pull request #1217 from epalmans/master
typo
2018-10-17 16:55:49 +11:00
e_palm 5c78ecc15d typo 2018-10-16 14:48:10 +02:00
Mohamed Elbahja 06db3f702d Update plyr.js 2018-10-13 13:23:42 +01:00
Mohamed Elbahja a2a82a96a6 fix: continue with the current duration after changing video quality 2018-10-13 12:59:59 +01:00
Robin van Nunen a86bbae851 Only save quality setting when it's updated by the user. Fixes bug in html5 player where it would override the settings if the current video does not support the given quality. 2018-09-29 21:23:10 +02:00
Sam Potts fac134dd95 Added download button 2018-09-28 00:42:42 +10:00
Sam Potts 515ae32160 Moved hardcoded resources to i18n 2018-09-28 00:30:27 +10:00
Sam Potts df8f040795 Remove link styles from anchor controls 2018-09-28 00:29:59 +10:00
Sam Potts 64a23992f0 SVG cleanup 2018-09-28 00:29:42 +10:00
Sam Potts f5baff6e6b Merge pull request #1192 from jamesoflol/more-mobile-touch-issues
Don't hide controls on focusout event
2018-09-27 21:33:37 +10:00
James 8bdd90a2a8 Don't hide controls on focusout event
It was immediately hiding controls on some touch-enabled devices. It will now also wait 4s to close after tabbing out, instead of immediately.
2018-09-26 14:48:10 +10:00
Sam Potts 5536e97482 Typo 2018-09-25 23:48:33 +10:00
Sam Potts de47071256 v3.4.4
-   Fixed issue with double binding for `click` and `touchstart` for `clickToPlay` option
-   Improved "faux" fullscreen on iPhone X/XS phones with notch
-   Babel 7 upgrade (which reduced the polyfilled build by ~10kb!)
2018-09-25 23:36:50 +10:00
Sam Potts 87072cb690 Clean up 2018-09-25 23:29:43 +10:00
Sam Potts d9565e9250 Improved fullscreen on iPhone X etc 2018-09-25 23:29:32 +10:00
Sam Potts f80b568e67 Reverted large pause button 2018-09-25 23:07:48 +10:00
Sam Potts 7fed689f9a Yarn lock file 2018-09-25 22:25:46 +10:00
Sam Potts 3f48df8f10 Remove babel-polyfill in favour of core-js 2018-09-25 22:25:35 +10:00
Sam Potts cc55092ca6 Babel upgrades 2018-09-25 22:25:04 +10:00
Sam Potts 3331d9d01d Package upgrades 2018-09-25 20:46:58 +10:00
Sam Potts 62d80e6b76 Fix touch vs click issue 2018-09-25 20:43:09 +10:00
Sam Potts 7dc4d9cd22 v3.4.3 2018-08-14 12:16:34 +10:00
Sam Potts 8fb8ae1260 Merge pull request #1163 from sampotts/develop
Fix bug with nodeList for play buttons
2018-08-14 12:14:58 +10:00
Sam Potts 90304369f4 Fix watch 2018-08-14 12:13:16 +10:00
Sam Potts 922456c46c Fix for nodeList as buttons 2018-08-14 12:13:00 +10:00
Sam Potts eaeccd66ae v3.4.2 2018-08-14 11:17:33 +10:00
Sam Potts 7a43649c13 Fix play/pause button state 2018-08-14 11:17:27 +10:00
Sam Potts 525bbf313e v3.4.1 2018-08-14 09:18:09 +10:00
Sam Potts cfaebe9bf2 Fix for controls missing (fixes #1161) 2018-08-14 09:17:58 +10:00
Sam Potts b57b7b2153 v3.4.0
-   Accessibility improvements (see #905)
-   Improvements to the way the controls work on iOS
-   Demo code clean up
-   YouTube quality selection removed due to their poor support for it. As a result, the `qualityrequested` event has been removed
-   Controls spacing improvements
-   Fix for pressed property missing with custom controls (Fixes #1062)
-   Fix #1153: Captions language fallback (thanks @friday)
-   Fix for setting pressed property of undefined (Fixes #1102)
2018-08-14 00:02:01 +10:00
Sam Potts 48bf368316 Merge pull request #1160 from sampotts/develop
v3.4.0
2018-08-14 00:00:24 +10:00
Sam Potts 8f94ce86a0 Merge branch 'master' into develop
# Conflicts:
#	readme.md
2018-08-13 23:59:19 +10:00
Sam Potts 10a9cf08f1 Changelog 2018-08-13 23:57:46 +10:00
Sam Potts 286d0d1794 Fix for pressed property missing with custom controls (Fixes #1062) 2018-08-13 23:52:10 +10:00
Sam Potts 95f6fa2731 Fix for setting pressed property of undefined (Fixes #1102) 2018-08-13 23:46:58 +10:00
Sam Potts 1aeef81288 Controls spacing improvements 2018-08-13 23:43:22 +10:00
Sam Potts 211ad6c8f5 Removed YouTube quality controls 2018-08-13 23:43:08 +10:00
Sam Potts 468b20d227 Moved mute button inside plyr__volume 2018-08-13 23:42:12 +10:00
Sam Potts f6bc42c2bc Fix IE11 issue in demo 2018-08-13 23:03:08 +10:00
Sam Potts 2c01b8ba76 Yarn lock file 2018-08-13 23:02:31 +10:00
Sam Potts 4e1df8677f Fix tooltip alignment 2018-08-13 23:02:14 +10:00
Sam Potts 6953a12e2a Set background color for video 2018-08-13 23:01:56 +10:00
Sam Potts 1d0db89194 Update wrong reference in docs 2018-08-13 23:01:38 +10:00
Sam Potts 297f297d18 Moved i18n to utils 2018-08-13 21:39:16 +10:00
Sam Potts 059205c378 Package updates 2018-08-13 21:39:02 +10:00
Sam Potts f94e53ffb1 Merge pull request #1158 from friday/1153
Fix #1153: Captions language fallback
2018-08-13 09:24:25 +10:00
Albin Larsson a4f1fdec5d Fix #1153: Captions language fallback 2018-08-12 20:12:22 +02:00
Sam Potts 75374eb154 Merge pull request #1147 from jamesoflol/fix-ios-fullscreen-while-stopped
Remove 'video is playing' requirement for iosNative fullscreen
2018-08-05 22:46:58 +10:00
Sam Potts 3ad118c026 3.4.0-beta.2 2018-08-05 22:43:35 +10:00
Sam Potts 0bc6b1f1b3 Fix issue where enter key wasn’t setting focus correctly 2018-08-05 22:41:21 +10:00
Sam Potts 4ea458e1a3 Rounded aria-valuetext to 1 decimal place 2018-08-05 21:48:42 +10:00
Sam Potts aacb172017 Removed aria-labelled-by 2018-08-05 21:48:21 +10:00
James dbf768b1bd Remove 'video is playing' requirement for iosNative fullscreen 2018-08-03 09:58:51 +10:00
Sam Potts b96fcfc8ac v3.4.0-beta.1 2018-08-02 00:55:48 +10:00
Sam Potts 18b4d26bee Merge pull request #1142 from sampotts/a11y-improvements
A11y improvements
2018-08-02 00:47:57 +10:00
Sam Potts 7f4b74e2d4 Fix for hover over iframed players not showing controls 2018-08-02 00:47:03 +10:00
Sam Potts a8f8486cf4 Merge pull request #1143 from mhluska/patch-1
Fix Readme typo (Patron -> Patreon)
2018-08-01 15:09:00 +10:00
Maros Hluska a343e58e53 Fix Readme typo (Patron -> Patreon) 2018-08-01 12:08:02 +07:00
Sam Potts 0892d69ba2 Handle race condition for ads lib loading after source change 2018-08-01 13:56:49 +10:00
Sam Potts ba511b51c7 Box shadow fix for range track 2018-08-01 13:00:51 +10:00
Sam Potts e090581913 Ads on dev or prod only 2018-08-01 11:49:42 +10:00
Sam Potts aaa56caa9c Only focus button if menu wasn’t hidden already 2018-08-01 01:38:57 +10:00
Sam Potts c8db1e55dd Escape closes menu 2018-08-01 01:26:15 +10:00
Sam Potts 58079393e6 Build 2018-08-01 00:58:27 +10:00
Sam Potts 0b44f2d897 Demo config 2018-08-01 00:57:45 +10:00
Sam Potts 2371619486 Linting 2018-08-01 00:56:44 +10:00
Sam Potts 13a54b5dbe Merge branch 'develop' into a11y-improvements
# Conflicts:
#	src/js/controls.js
2018-08-01 00:46:26 +10:00
Sam Potts fa0861ff2e Merge pull request #1141 from friday/1137
Improve captions positioning consistency
2018-08-01 00:41:48 +10:00
Sam Potts 748aa5179f Comments about keydown vs keyup for Firefox 2018-08-01 00:38:19 +10:00
Sam Potts 56a485bac6 Fix Firefox spacebar issue 2018-08-01 00:37:55 +10:00
Albin Larsson 9488de30e5 Fix #1137: Improve captions positioning consistency 2018-07-31 16:26:34 +02:00
Sam Potts e3dfd16096 Merge pull request #1139 from friday/controls-input
Controls input fixes
2018-07-31 09:08:08 +10:00
Albin Larsson c230ccce86 Update controls.md docs 2018-07-31 00:44:07 +02:00
Albin Larsson db22a8e9c4 Improve handling of the 'controls' argument 2018-07-31 00:43:56 +02:00
Sam Potts 3a3358e2b4 Make iOS range fix more universal 2018-07-30 23:29:14 +10:00
Sam Potts 248005e8e0 Fix merge 2018-07-30 23:29:02 +10:00
Sam Potts dae272ef66 Merge branch 'develop' into a11y-improvements
# Conflicts:
#	demo/dist/demo.css
#	dist/plyr.css
#	dist/plyr.js.map
#	dist/plyr.min.js
#	dist/plyr.min.js.map
#	dist/plyr.polyfilled.js.map
#	dist/plyr.polyfilled.min.js
#	dist/plyr.polyfilled.min.js.map
#	package.json
#	src/js/plyr.js
2018-07-30 23:09:12 +10:00
Sam Potts 599b33e55f Click to play fix, poster fix, iOS controls fixes 2018-07-30 01:13:12 +10:00
Sam Potts 3a8332bdb3 Fix for webkit redrawing issue 2018-07-29 12:32:26 +10:00
Sam Potts 53a3d06103 Merge branch 'develop' into a11y-improvements
# Conflicts:
#	demo/dist/demo.css
#	demo/dist/demo.js.map
#	demo/dist/demo.min.js
#	demo/dist/demo.min.js.map
#	dist/plyr.css
#	dist/plyr.js.map
#	dist/plyr.min.js
#	dist/plyr.min.js.map
#	dist/plyr.polyfilled.js.map
#	dist/plyr.polyfilled.min.js
#	dist/plyr.polyfilled.min.js.map
#	package.json
#	yarn.lock
2018-07-24 09:38:26 +10:00
Sam Potts e63ad7c74b Keyboard and focus improvements 2018-07-15 19:23:28 +10:00
Sam Potts ead6601394 Merge 2018-07-02 23:11:59 +10:00
Sam Potts e61ebd8d05 Merge branch 'develop' into a11y-improvements 2018-07-02 23:11:50 +10:00
Sam Potts 3bf1c59bd6 Work on key bindings for menu 2018-06-28 23:44:07 +10:00
Sam Potts e59fe1aacf Merge branch 'develop' into a11y-improvements
# Conflicts:
#	dist/plyr.js
#	dist/plyr.js.map
#	dist/plyr.min.js
#	dist/plyr.min.js.map
#	dist/plyr.polyfilled.js
#	dist/plyr.polyfilled.js.map
#	dist/plyr.polyfilled.min.js
#	dist/plyr.polyfilled.min.js.map
#	src/js/listeners.js
2018-06-25 23:09:13 +10:00
Sam Potts 1f1d74ba50 Work on menus 2018-06-21 09:01:16 +10:00
Sam Potts bb546fe43f Merge branch 'develop' into a11y-improvements
# Conflicts:
#	dist/plyr.js.map
#	dist/plyr.min.js
#	dist/plyr.min.js.map
#	dist/plyr.polyfilled.js.map
#	dist/plyr.polyfilled.min.js
#	dist/plyr.polyfilled.min.js.map
2018-06-19 19:24:47 +10:00
Sam Potts 9e1218547b WIP 2018-06-19 09:11:35 +10:00
Sam Potts 715b88c09b Merge branch 'develop' into a11y-improvements
# Conflicts:
#	dist/plyr.js.map
#	dist/plyr.min.js
#	dist/plyr.min.js.map
#	dist/plyr.polyfilled.js.map
#	dist/plyr.polyfilled.min.js
#	dist/plyr.polyfilled.min.js.map
2018-06-18 23:29:25 +10:00
Sam Potts 7b9ef7d757 More work on menus 2018-06-18 23:13:40 +10:00
Sam Potts d64ed4ba5a Merge branch 'develop' into a11y-improvements
# Conflicts:
#	dist/plyr.js.map
#	dist/plyr.min.js
#	dist/plyr.min.js.map
2018-06-18 22:17:34 +10:00
Sam Potts ffd864ed39 Work on controls 2018-06-18 21:39:47 +10:00
81 changed files with 23348 additions and 25494 deletions
+1 -1
View File
@@ -8,4 +8,4 @@ npm-debug.log
yarn-error.log
package-lock.json
*.webm
.idea/
.idea/
+2 -1
View File
@@ -2,5 +2,6 @@
"useTabs": false,
"tabWidth": 4,
"singleQuote": true,
"trailingComma": "all"
"trailingComma": "all",
"printWidth": 120
}
+217 -174
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -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:
- `Array` of options (this builds the default controls based on your choices)
- `Element` with the controls
- `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
## Using default controls
@@ -26,6 +28,7 @@ controls: [
'settings', // Settings menu
'pip', // Picture-in-picture (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
];
```
+1 -1
View File
File diff suppressed because one or more lines are too long
+275 -316
View File
@@ -1833,7 +1833,6 @@ typeof navigator === "object" && (function () {
};
this._fetchDefaults = {
method: 'POST',
keepalive: true,
// 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
// 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
// this value matches package.json during build.
// See: https://github.com/getsentry/raven-js/issues/465
VERSION: '3.26.3',
VERSION: '3.27.0',
debug: false,
@@ -2612,34 +2611,40 @@ typeof navigator === "object" && (function () {
)
return;
options = options || {};
options = objectMerge$1(
{
eventId: this.lastEventId(),
dsn: this._dsn,
user: this._globalContext.user || {}
},
options
);
var lastEventId = options.eventId || this.lastEventId();
if (!lastEventId) {
if (!options.eventId) {
throw new configError('Missing eventId');
}
var dsn = options.dsn || this._dsn;
if (!dsn) {
if (!options.dsn) {
throw new configError('Missing DSN');
}
var encode = encodeURIComponent;
var qs = '';
qs += '?eventId=' + encode(lastEventId);
qs += '&dsn=' + encode(dsn);
var encodedOptions = [];
var user = options.user || this._globalContext.user;
if (user) {
if (user.name) qs += '&name=' + encode(user.name);
if (user.email) qs += '&email=' + encode(user.email);
for (var key in options) {
if (key === 'user') {
var user = options.user;
if (user.name) encodedOptions.push('name=' + encode(user.name));
if (user.email) encodedOptions.push('email=' + encode(user.email));
} else {
encodedOptions.push(encode(key) + '=' + encode(options[key]));
}
}
var globalServer = this._getGlobalServer(this._parseDSN(dsn));
var globalServer = this._getGlobalServer(this._parseDSN(options.dsn));
var script = _document.createElement('script');
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);
},
@@ -4087,309 +4092,263 @@ typeof navigator === "object" && (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
// For demo site (https://plyr.io) only
if (isLive) {
singleton.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
}
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);
}
}
if (window.shr) {
window.shr.setup({
count: {
classname: 'button__count'
}
});
});
} // Setup tab focus
// Google analytics
// For demo site (https://plyr.io) only
/* eslint-disable */
if (isLive) {
(function (i, s, o, g, r, a, m) {
i.GoogleAnalyticsObject = r;
i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments);
var tabClassName = 'tab-focus'; // Remove class on blur
document.addEventListener('focusout', function (event) {
if (!event.target.classList || container.contains(event.target)) {
return;
}
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();
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 */
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=\"".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
/* eslint-disable */
if (env.prod) {
(function (i, s, o, g, r, a, m) {
i.GoogleAnalyticsObject = r;
i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments);
};
i[r].l = 1 * new Date();
a = s.createElement(o);
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
window.ga('create', 'UA-40881672-11', 'auto');
window.ga('send', 'pageview');
}
/* eslint-enable */
})();
}());
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+15 -14
View File
@@ -91,21 +91,22 @@
</header>
<main>
<video controls crossorigin playsinline 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">
<!-- <source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4" type="video/mp4" size="1440"> -->
<div id="container">
<video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
<!-- Video 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 -->
<track kind="captions" 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">
<!-- 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"
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 -->
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" download>Download</a>
</video>
<!-- 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>
</video>
</div>
<ul>
<li class="plyr__cite plyr__cite--video" hidden>
@@ -166,7 +167,7 @@
</svg>
<p>If you think Plyr's good,
<a href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&amp;url=http%3A%2F%2Fplyr.io&amp;via=Sam_Potts"
target="_blank" data-shr-network="twitter">tweet it</a>
target="_blank" data-shr-network="twitter">tweet it</a> 👍
</p>
</aside>
+30 -64
View File
@@ -7,16 +7,17 @@
import Raven from 'raven-js';
(() => {
const isLive = window.location.host === 'plyr.io';
// Raven / Sentry
// For demo site (https://plyr.io) only
if (isLive) {
Raven.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
}
const { host } = window.location;
const env = {
prod: host === 'plyr.io',
dev: host === 'dev.plyr.io',
};
document.addEventListener('DOMContentLoaded', () => {
Raven.context(() => {
const selector = '#player';
const container = document.getElementById('container');
if (window.shr) {
window.shr.setup({
count: {
@@ -30,6 +31,10 @@ import Raven from 'raven-js';
// Remove class on blur
document.addEventListener('focusout', event => {
if (!event.target.classList || container.contains(event.target)) {
return;
}
event.target.classList.remove(tabClassName);
});
@@ -42,12 +47,18 @@ import Raven from 'raven-js';
// Delay the adding of classname until the focus has changed
// This event fires before the focusin event
setTimeout(() => {
document.activeElement.classList.add(tabClassName);
}, 0);
const focused = document.activeElement;
if (!focused || !focused.classList || container.contains(focused)) {
return;
}
focused.classList.add(tabClassName);
}, 10);
});
// Setup the player
const player = new Plyr('#player', {
const player = new Plyr(selector, {
debug: true,
title: 'View From A Blue Moon',
iconUrl: '../dist/plyr.svg',
@@ -57,57 +68,6 @@ import Raven from 'raven-js';
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,
},
@@ -115,7 +75,7 @@ import Raven from 'raven-js';
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
},
ads: {
enabled: true,
enabled: env.prod || env.dev,
publisherId: '918848828995742',
},
});
@@ -311,11 +271,17 @@ import Raven from 'raven-js';
});
});
// Raven / Sentry
// For demo site (https://plyr.io) only
if (env.prod) {
Raven.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
}
// Google analytics
// For demo site (https://plyr.io) only
/* eslint-disable */
if (isLive) {
(function(i, s, o, g, r, a, m) {
if (env.prod) {
((i, s, o, g, r, a, m) => {
i.GoogleAnalyticsObject = r;
i[r] =
i[r] ||
+6 -3
View File
@@ -7,7 +7,8 @@
font-family: 'Gordita';
font-style: normal;
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 {
@@ -33,7 +34,8 @@
font-family: 'Gordita';
font-style: normal;
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 {
@@ -41,5 +43,6 @@
font-family: 'Gordita';
font-style: normal;
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');
}
+3
View File
@@ -11,6 +11,9 @@ $plyr-font-size-small: 12px;
$plyr-font-size-time: 11px;
$plyr-font-size-badges: 9px;
// Other
$plyr-font-smoothing: true;
// Captions
$plyr-font-size-captions-base: $plyr-font-size-base;
$plyr-font-size-captions-small: $plyr-font-size-small;
+2 -1
View File
@@ -2,7 +2,8 @@
// 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-small: 13;
+1 -1
View File
File diff suppressed because one or more lines are too long
+7589 -8024
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+10340 -13465
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

+55 -76
View File
@@ -12,7 +12,6 @@ const concat = require('gulp-concat');
const filter = require('gulp-filter');
const sass = require('gulp-sass');
const cleancss = require('gulp-clean-css');
const run = require('run-sequence');
const header = require('gulp-header');
const prefix = require('gulp-autoprefixer');
const gitbranch = require('git-branch');
@@ -44,7 +43,7 @@ const paths = {
// Source paths
src: {
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'),
},
@@ -55,7 +54,7 @@ const paths = {
// Source paths
src: {
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
@@ -88,34 +87,33 @@ const sizeOptions = { showFiles: true, gzip: true };
const browsers = ['> 1%'];
// Babel config
const babelrc = {
const babelrc = (polyfill = false) => ({
presets: [
[
'env',
'@babel/preset-env',
{
targets: {
browsers,
},
useBuiltIns: true,
useBuiltIns: polyfill ? 'usage' : false,
modules: false,
},
],
],
plugins: ['external-helpers'],
babelrc: false,
exclude: 'node_modules/**',
};
});
// Clean out /dist
gulp.task('clean', () => {
const dirs = [paths.plyr.output, paths.demo.output].map(dir =>
path.join(dir, '**/*'),
);
gulp.task('clean', done => {
const dirs = [paths.plyr.output, paths.demo.output].map(dir => path.join(dir, '**/*'));
// Don't delete the mp4
dirs.push(`!${path.join(paths.plyr.output, '**/*.mp4')}`);
del(dirs);
done();
});
const build = {
@@ -124,6 +122,7 @@ const build = {
const name = `js:${key}`;
tasks.js.push(name);
const { output } = paths[bundle];
const polyfill = name.includes('polyfilled');
return gulp.task(name, () =>
gulp
@@ -133,11 +132,7 @@ const build = {
.pipe(
rollup(
{
plugins: [
resolve(),
commonjs(),
babel(babelrc),
],
plugins: [resolve(), commonjs(), babel(babelrc(polyfill))],
},
options,
),
@@ -207,31 +202,25 @@ build.sass(bundles.demo.sass, 'demo');
build.js(bundles.demo.js, 'demo', { format: 'iife' });
// Build all JS
gulp.task('js', () => {
run(tasks.js);
});
gulp.task('js', () => gulp.parallel(tasks.js));
// Watch for file changes
gulp.task('watch', () => {
// Plyr core
gulp.watch(paths.plyr.src.js, tasks.js);
gulp.watch(paths.plyr.src.sass, tasks.sass);
gulp.watch(paths.plyr.src.sprite, tasks.sprite);
gulp.watch(paths.plyr.src.js, gulp.parallel(tasks.js));
gulp.watch(paths.plyr.src.sass, gulp.parallel(tasks.sass));
gulp.watch(paths.plyr.src.sprite, gulp.parallel(tasks.sprite));
// Demo
gulp.watch(paths.demo.src.js, tasks.js);
gulp.watch(paths.demo.src.sass, tasks.sass);
gulp.watch(paths.demo.src.js, gulp.parallel(tasks.js));
gulp.watch(paths.demo.src.sass, gulp.parallel(tasks.sass));
});
// Build distribution
gulp.task('build', () => {
run(tasks.clean, tasks.js, tasks.sass, tasks.sprite);
});
gulp.task('build', gulp.series(tasks.clean, gulp.parallel(tasks.js, tasks.sass, tasks.sprite)));
// Default gulp task
gulp.task('default', () => {
run('build', 'watch');
});
gulp.task('default', gulp.series('build', 'watch'));
// Publish a version to CDN and demo
// --------------------------------------------
@@ -244,10 +233,7 @@ try {
}
// If deployment is setup
if (
Object.keys(credentials).includes('aws') &&
Object.keys(credentials).includes('fastly')
) {
if (Object.keys(credentials).includes('aws') && Object.keys(credentials).includes('fastly')) {
const { version } = pkg;
const { aws, fastly } = credentials;
@@ -269,8 +255,7 @@ if (
demo: {
uploadPath: branch.current === branch.develop ? 'beta/' : null,
headers: {
'Cache-Control':
'no-cache, no-store, must-revalidate, max-age=0',
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
Vary: 'Accept-Encoding',
},
},
@@ -279,8 +264,7 @@ if (
headers: {
// http://stackoverflow.com/questions/2272835/amazon-s3-object-redirect
'x-amz-website-redirect-location': `/${ver}/${filename}`,
'Cache-Control':
'no-cache, no-store, must-revalidate, max-age=0',
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
},
};
},
@@ -303,11 +287,7 @@ if (
const allowed = [branch.master, branch.develop];
if (!allowed.includes(branch.current)) {
console.error(
`Must be on ${allowed.join(', ')} to publish! (current: ${
branch.current
})`,
);
console.error(`Must be on ${allowed.join(', ')} to publish! (current: ${branch.current})`);
return false;
}
@@ -323,13 +303,13 @@ if (
console.log(`Updating versions to '${version}'...`);
// 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
.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(cdnpath, `${aws.cdn.domain}/${version}/`))
.pipe(gulp.dest(path.join(root, 'src/js/')));
.pipe(gulp.dest('./'));
});
// Publish version to CDN bucket
@@ -349,8 +329,7 @@ if (
.pipe(
replace(
/sourceMappingURL=([\w-?.]+)/,
(match, p1) =>
`sourceMappingURL=${p1.replace(minSuffix, '')}`,
(match, p1) => `sourceMappingURL=${p1.replace(minSuffix, '')}`,
),
)
.pipe(
@@ -368,27 +347,30 @@ if (
gulp.task('purge', () => {
const list = [];
return gulp.src(paths.upload).pipe(
through.obj((file, enc, cb) => {
const filename = file.path.split('/').pop();
list.push(`${versionPath}/${filename}`);
cb(null);
}),
).on('end', () => {
const purge = new FastlyPurge(fastly.token);
return gulp
.src(paths.upload)
.pipe(
through.obj((file, enc, cb) => {
const filename = file.path.split('/').pop();
list.push(`${versionPath}/${filename}`);
cb(null);
}),
)
.on('end', () => {
const purge = new FastlyPurge(fastly.token);
list.forEach(url => {
console.log(`Purging ${url}...`);
list.forEach(url => {
console.log(`Purging ${url}...`);
purge.url(url, (error, result) => {
if (error) {
console.log(error);
} else if (result) {
console.log(result);
}
purge.url(url, (error, result) => {
if (error) {
console.log(error);
} else if (result) {
console.log(result);
}
});
});
});
});
});
// Publish to demo bucket
@@ -400,8 +382,7 @@ if (
console.log(`Uploading '${version}' demo to ${aws.demo.domain}...`);
// Replace versioned files in readme.md
gulp
.src([`${root}/readme.md`])
gulp.src([`${root}/readme.md`])
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
.pipe(gulp.dest(root));
@@ -415,8 +396,7 @@ if (
pages.push(error);
}
gulp
.src(pages)
gulp.src(pages)
.pipe(replace(localPath, versionPath))
.pipe(s3(aws.demo, options.demo));
@@ -467,16 +447,15 @@ if (
});
// Do everything
gulp.task('deploy', () =>
run(
gulp.task(
'deploy',
gulp.series(
'version',
tasks.clean,
tasks.js,
tasks.sass,
tasks.sprite,
gulp.parallel(tasks.js, tasks.sass, tasks.sprite),
'cdn',
'purge',
'demo',
'purge',
'open',
),
);
+33 -37
View File
@@ -1,8 +1,7 @@
{
"name": "plyr",
"version": "3.3.23",
"description":
"A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
"version": "3.4.7",
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
"homepage": "https://plyr.io",
"author": "Sam Potts <sam@potts.es>",
"keywords": [
@@ -32,64 +31,61 @@
"scripts": {
"build": "gulp build",
"lint": "eslint src/js && npm run-script remark",
"remark":
"remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
"remark": "remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.6",
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.7.0",
"@babel/core": "^7.1.5",
"babel-eslint": "^10.0.1",
"@babel/preset-env": "^7.1.5",
"del": "^3.0.0",
"eslint": "^5.2.0",
"eslint-config-airbnb-base": "^13.0.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.13.0",
"eslint": "^5.8.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-config-prettier": "^3.1.0",
"eslint-plugin-import": "^2.14.0",
"fastly-purge": "^1.0.1",
"git-branch": "^2.0.1",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^5.0.0",
"gulp-better-rollup": "^3.3.0",
"gulp-clean-css": "^3.9.4",
"gulp": "^4.0.0",
"gulp-autoprefixer": "^6.0.0",
"gulp-better-rollup": "^3.4.0",
"gulp-clean-css": "^3.10.0",
"gulp-concat": "^2.6.1",
"gulp-filter": "^5.1.0",
"gulp-header": "^2.0.5",
"gulp-open": "^3.0.1",
"gulp-postcss": "^7.0.1",
"gulp-postcss": "^8.0.0",
"gulp-rename": "^1.4.0",
"gulp-replace": "^1.0.0",
"gulp-s3": "^0.11.0",
"gulp-sass": "^4.0.1",
"gulp-sass": "^4.0.2",
"gulp-size": "^3.0.0",
"gulp-sourcemaps": "^2.6.4",
"gulp-svgmin": "^1.2.4",
"gulp-svgstore": "^6.1.1",
"gulp-svgmin": "^2.1.0",
"gulp-svgstore": "^7.0.0",
"gulp-uglify-es": "^1.0.4",
"gulp-util": "^3.0.8",
"postcss-custom-properties": "^7.0.0",
"postcss-custom-properties": "^8.0.9",
"prettier-eslint": "^8.8.2",
"prettier-stylelint": "^0.4.2",
"remark-cli": "^5.0.0",
"remark-validate-links": "^7.0.0",
"rollup-plugin-babel": "^3.0.7",
"rollup-plugin-commonjs": "^9.1.4",
"rollup-plugin-node-resolve": "^3.3.0",
"run-sequence": "^2.2.1",
"stylelint": "^9.4.0",
"stylelint-config-prettier": "^3.3.0",
"remark-cli": "^6.0.0",
"remark-validate-links": "^7.1.0",
"rollup-plugin-babel": "^4.0.3",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-node-resolve": "^3.4.0",
"stylelint": "^9.7.1",
"stylelint-config-prettier": "^4.0.0",
"stylelint-config-recommended": "^2.1.0",
"stylelint-config-sass-guidelines": "^5.0.0",
"stylelint-order": "^0.8.1",
"stylelint-scss": "^3.2.0",
"stylelint-config-sass-guidelines": "^5.2.0",
"stylelint-order": "^1.0.0",
"stylelint-scss": "^3.4.0",
"stylelint-selector-bem-pattern": "^2.0.0",
"through2": "^2.0.3"
"through2": "^3.0.0"
},
"dependencies": {
"babel-polyfill": "^6.26.0",
"core-js": "^2.5.7",
"custom-event-polyfill": "^1.0.6",
"loadjs": "^3.5.4",
"raven-js": "^3.26.4",
"url-polyfill": "^1.0.13"
"raven-js": "^3.27.0",
"url-polyfill": "^1.1.0"
}
}
+96 -95
View File
@@ -8,26 +8,26 @@ A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo medi
## Features
* **Accessible** - full support for VTT captions and screen readers
* **[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
- **Accessible** - full support for VTT captions and screen readers
- **[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
`<span>` or `<a href="#">` button hacks
* **Responsive** - works with any screen size
* **HTML Video & Audio** - support for both formats
* **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
* **[Monetization](#ads)** - make money from your videos
* **[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
* **[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
* **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
* **Picture-in-Picture** - supports Safari's picture-in-picture mode
* **Playsinline** - supports the `playsinline` attribute
* **Speed controls** - adjust speed on the fly
* **Multiple captions** - support for multiple caption tracks
* **i18n support** - support for internationalization of controls
* **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
* **SASS** - to include in your build processes
- **Responsive** - works with any screen size
- **HTML Video & Audio** - support for both formats
- **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
- **[Monetization](#ads)** - make money from your videos
- **[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
- **[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
- **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
- **Picture-in-Picture** - supports Safari's picture-in-picture mode
- **Playsinline** - supports the `playsinline` attribute
- **Speed controls** - adjust speed on the fly
- **Multiple captions** - support for multiple caption tracks
- **i18n support** - support for internationalization of controls
- **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
- **SASS** - to include in your build processes
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.
```html
<script src="https://cdn.plyr.io/3.3.23/plyr.js"></script>
<script src="https://cdn.plyr.io/3.4.7/plyr.js"></script>
```
...or...
```html
<script src="https://cdn.plyr.io/3.3.23/plyr.polyfilled.js"></script>
<script src="https://cdn.plyr.io/3.4.7/plyr.polyfilled.js"></script>
```
### 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:
```html
<link rel="stylesheet" href="https://cdn.plyr.io/3.3.23/plyr.css">
<link rel="stylesheet" href="https://cdn.plyr.io/3.4.7/plyr.css">
```
### 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
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.7/plyr.svg`.
## Ads
Plyr has partnered up with [vi.ai](http://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy:
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)
* Grab your publisher ID from the code snippet
* Enable ads in the [config options](#options) and enter your publisher ID
- [Sign up for a vi.ai account](https://vi.ai/publisher-video-monetization/?aid=plyrio)
- Grab your publisher ID from the code snippet
- Enable ads in the [config options](#options) and enter your publisher ID
Any questions regarding the ads can be sent straight to vi.ai and any issues with rendering raised through GitHub issues.
@@ -175,7 +175,7 @@ Any questions regarding the ads can be sent straight to vi.ai and any issues wit
### 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
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
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:
* 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 [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList)
* A [jQuery](https://jquery.com) object
- 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 [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList)
- 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.
@@ -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 |
| `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 |
| `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. |
| `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. |
| `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. |
| `autopause`&sup1; | 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. |
@@ -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. |
| `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. |
| `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) |
| `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. |
@@ -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. |
| `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. |
| `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
@@ -365,9 +366,9 @@ player.fullscreen.enter(); // Enter fullscreen
| `fullscreen.exit()` | - | Exit fullscreen. |
| `fullscreen.toggle()` | - | Toggle fullscreen. |
| `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. |
| `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. |
| `supports(type)` | String | Check support for a mime type. |
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
@@ -391,32 +392,32 @@ player.currentTime; // 10
player.fullscreen.active; // false;
```
| Property | Getter | Setter | Description |
| -------------------- | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `isHTML5` | ✓ | - | Returns a boolean indicating if the current player is HTML5. |
| `isEmbed` | ✓ | - | Returns a boolean indicating if the current player is an embedded player. |
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
| `stopped` | ✓ | - | Returns a boolean indicating if the current player is stopped. |
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
| `buffered` | ✓ | - | Returns a float between 0 and 1 indicating how much of the media is buffered |
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
| `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. |
| `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. |
| `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`&sup1; | ✓ | ✓ | 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. |
| `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. |
| `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 |
| Property | Getter | Setter | Description |
| -------------------- | ------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `isHTML5` | ✓ | - | Returns a boolean indicating if the current player is HTML5. |
| `isEmbed` | ✓ | - | Returns a boolean indicating if the current player is an embedded player. |
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
| `stopped` | ✓ | - | Returns a boolean indicating if the current player is stopped. |
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
| `buffered` | ✓ | - | Returns a float between 0 and 1 indicating how much of the media is buffered |
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
| `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. |
| `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. |
| `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`&sup1; | ✓ | ✓ | 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. |
| `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. |
| `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 |
| `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.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+. |
| `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. |
| `pip`&sup2; | ✓ | ✓ | 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.
2. HTML5 only
@@ -565,6 +566,7 @@ player.on('ready', event => {
| `loadstart` | Sent when loading of the media begins. |
| `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. |
| `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`. |
| `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. |
@@ -575,11 +577,9 @@ player.on('ready', event => {
#### YouTube only
| 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. |
| `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. |
| 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. |
_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
third party APIs. More info on the respective API's here:
* [YouTube iframe API Reference](https://developers.google.com/youtube/iframe_api_reference)
* [Vimeo player.js Reference](https://github.com/vimeo/player.js)
- [YouTube iframe API Reference](https://developers.google.com/youtube/iframe_api_reference)
- [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.
@@ -652,9 +652,9 @@ const supported = Plyr.supported('video', 'html5', true);
The arguments are:
* Media type (`audio` or `video`)
* Provider (`html5`, `youtube` or `vimeo`)
* Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
- Media type (`audio` or `video`)
- Provider (`html5`, `youtube` or `vimeo`)
- Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
### 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...
* [Donate via Patron](https://www.patreon.com/plyr)
* [Donate via PayPal](https://www.paypal.me/pottsy/20usd)
- [Donate via Patreon](https://www.patreon.com/plyr)
- [Donate via PayPal](https://www.paypal.me/pottsy/20usd)
## Mentions
* [ProductHunt](https://www.producthunt.com/tech/plyr)
* [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
* [HTML5 Weekly #177](http://html5weekly.com/issues/177)
* [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/)
* [Hacker News](https://news.ycombinator.com/item?id=9136774)
* [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)
* [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)
- [ProductHunt](https://www.producthunt.com/tech/plyr)
- [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
- [HTML5 Weekly #177](http://html5weekly.com/issues/177)
- [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/)
- [Hacker News](https://news.ycombinator.com/item?id=9136774)
- [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)
- [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)
## Used by
* [Selz.com](https://selz.com)
* [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)
* [TomTom.com](http://prioritydriving.tomtom.com/)
* [DIGBMX](http://digbmx.com/)
* [Grime Archive](https://grimearchive.com/)
* [koel - A personal music streaming server that works.](http://koel.phanan.net/)
* [Oscar Radio](http://oscar-radio.xyz/)
* [Sparkk TV](https://www.sparkktv.com/)
- [Selz.com](https://selz.com)
- [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)
- [TomTom.com](http://prioritydriving.tomtom.com/)
- [DIGBMX](http://digbmx.com/)
- [Grime Archive](https://grimearchive.com/)
- [koel - A personal music streaming server that works.](http://koel.phanan.net/)
- [Oscar Radio](http://oscar-radio.xyz/)
- [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 :-)
@@ -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:
* [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)
- [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)
## Thanks
+3 -4
View File
@@ -4,7 +4,6 @@
// ==========================================================================
import controls from './controls';
import i18n from './i18n';
import support from './support';
import { dedupe } from './utils/arrays';
import browser from './utils/browser';
@@ -18,6 +17,7 @@ import {
} from './utils/elements';
import { on, triggerEvent } from './utils/events';
import fetch from './utils/fetch';
import i18n from './utils/i18n';
import is from './utils/is';
import { getHTML } from './utils/strings';
import { parseUrl } from './utils/urls';
@@ -83,9 +83,8 @@ const captions = {
// * active: The state preferred by user settings or config
// * toggled: The real captions state
const languages = dedupe(
Array.from(navigator.languages || navigator.language || navigator.userLanguage).map(language => language.split('-')[0]),
);
const browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];
const languages = dedupe(browserLanguages.map(language => language.split('-')[0]));
let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
+13 -15
View File
@@ -60,7 +60,7 @@ const defaults = {
// Sprite (for icons)
loadSprite: true,
iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.3.12/plyr.svg',
iconUrl: 'https://cdn.plyr.io/3.4.7/plyr.svg',
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@@ -68,19 +68,7 @@ const defaults = {
// Quality default
quality: {
default: 576,
options: [
4320,
2880,
2160,
1440,
1080,
720,
576,
480,
360,
240,
'default', // YouTube's "auto"
],
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],
},
// Set loops
@@ -145,6 +133,7 @@ const defaults = {
'settings',
'pip',
'airplay',
// 'download',
'fullscreen',
],
settings: ['captions', 'quality', 'speed'],
@@ -167,6 +156,7 @@ const defaults = {
unmute: 'Unmute',
enableCaptions: 'Enable captions',
disableCaptions: 'Disable captions',
download: 'Download',
enterFullscreen: 'Enter fullscreen',
exitFullscreen: 'Exit fullscreen',
frameTitle: 'Player for {title}',
@@ -196,6 +186,7 @@ const defaults = {
// URLs
urls: {
download: null,
vimeo: {
sdk: 'https://player.vimeo.com/api/player.js',
iframe: 'https://player.vimeo.com/video/{0}?{1}',
@@ -222,6 +213,7 @@ const defaults = {
mute: null,
volume: null,
captions: null,
download: null,
fullscreen: null,
pip: null,
airplay: null,
@@ -257,6 +249,7 @@ const defaults = {
'cuechange',
// Custom events
'download',
'enterfullscreen',
'exitfullscreen',
'captionsenabled',
@@ -268,8 +261,9 @@ const defaults = {
// YouTube
'statechange',
// Quality
'qualitychange',
'qualityrequested',
// Ads
'adsloaded',
@@ -301,6 +295,7 @@ const defaults = {
fastForward: '[data-plyr="fast-forward"]',
mute: '[data-plyr="mute"]',
captions: '[data-plyr="captions"]',
download: '[data-plyr="download"]',
fullscreen: '[data-plyr="fullscreen"]',
pip: '[data-plyr="pip"]',
airplay: '[data-plyr="airplay"]',
@@ -354,6 +349,9 @@ const defaults = {
isTouch: 'plyr--is-touch',
uiSupported: 'plyr--full-ui',
noTransition: 'plyr--no-transition',
display: {
time: 'plyr__time',
},
menu: {
value: 'plyr__menu__value',
badge: 'plyr__badge',
+10
View File
@@ -0,0 +1,10 @@
// ==========================================================================
// Plyr states
// ==========================================================================
export const pip = {
active: 'picture-in-picture',
inactive: 'inline',
};
export default { pip };
+1 -1
View File
@@ -15,7 +15,7 @@ export const types = {
/**
* Get provider by URL
* @param {string} url
* @param {String} url
*/
export function getProviderByUrl(url) {
// YouTube
+585 -368
View File
File diff suppressed because it is too large Load Diff
+34 -3
View File
@@ -1,8 +1,10 @@
// ==========================================================================
// Fullscreen wrapper
// 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 { hasClass, toggleClass, trapFocus } from './utils/elements';
import { on, triggerEvent } from './utils/events';
@@ -45,6 +47,37 @@ function toggleFallback(toggle = false) {
// Toggle class hook
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
onChange.call(this);
}
@@ -177,9 +210,7 @@ class Fullscreen {
// iOS native fullscreen doesn't need the request step
if (browser.isIos && this.player.config.fullscreen.iosNative) {
if (this.player.playing) {
this.target.webkitEnterFullscreen();
}
this.target.webkitEnterFullscreen();
} else if (!Fullscreen.native) {
toggleFallback.call(this, true);
} else if (!this.prefix) {
+359 -278
View File
@@ -4,8 +4,9 @@
import controls from './controls';
import ui from './ui';
import { repaint } from './utils/animation';
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 is from './utils/is';
@@ -13,14 +14,19 @@ class Listeners {
constructor(player) {
this.player = player;
this.lastKey = null;
this.focusTimer = null;
this.lastKeyDown = null;
this.handleKey = this.handleKey.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
this.setTabFocus = this.setTabFocus.bind(this);
this.firstTouch = this.firstTouch.bind(this);
}
// Handle key presses
handleKey(event) {
const { player } = this;
const { elements } = player;
const code = event.keyCode ? event.keyCode : event.which;
const pressed = event.type === 'keydown';
const repeat = pressed && code === this.lastKey;
@@ -39,27 +45,32 @@ class Listeners {
// Seek by the number keys
const seekByKey = () => {
// 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
// Reset on keyup
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
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
const focused = getFocusElement();
if (
is.element(focused) &&
(focused !== this.player.elements.inputs.seek &&
matches(focused, this.player.config.selectors.editable))
) {
return;
const focused = document.activeElement;
if (is.element(focused)) {
const { editable } = player.config.selectors;
const { seek } = elements.inputs;
if (focused !== seek && matches(focused, editable)) {
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 (preventDefault.includes(code)) {
event.preventDefault();
@@ -87,55 +98,55 @@ class Listeners {
case 75:
// Space and K key
if (!repeat) {
this.player.togglePlay();
player.togglePlay();
}
break;
case 38:
// Arrow up
this.player.increaseVolume(0.1);
player.increaseVolume(0.1);
break;
case 40:
// Arrow down
this.player.decreaseVolume(0.1);
player.decreaseVolume(0.1);
break;
case 77:
// M key
if (!repeat) {
this.player.muted = !this.player.muted;
player.muted = !player.muted;
}
break;
case 39:
// Arrow forward
this.player.forward();
player.forward();
break;
case 37:
// Arrow back
this.player.rewind();
player.rewind();
break;
case 70:
// F key
this.player.fullscreen.toggle();
player.fullscreen.toggle();
break;
case 67:
// C key
if (!repeat) {
this.player.toggleCaptions();
player.toggleCaptions();
}
break;
case 76:
// L key
this.player.loop = !this.player.loop;
player.loop = !player.loop;
break;
/* case 73:
/* case 73:
this.setLoop('start');
break;
@@ -153,8 +164,8 @@ class Listeners {
// Escape is handle natively when in full screen
// So we only need to worry about non native
if (!this.player.fullscreen.enabled && this.player.fullscreen.active && code === 27) {
this.player.fullscreen.toggle();
if (!player.fullscreen.enabled && player.fullscreen.active && code === 27) {
player.fullscreen.toggle();
}
// Store last code for next cycle
@@ -171,61 +182,102 @@ class Listeners {
// Device is touch enabled
firstTouch() {
this.player.touch = true;
const { player } = this;
const { elements } = player;
player.touch = true;
// 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(toggle = true) {
const { player } = this;
// Keyboard shortcuts
if (this.player.config.keyboard.global) {
toggleListener.call(this.player, window, 'keydown keyup', this.handleKey, toggle, false);
if (player.config.keyboard.global) {
toggleListener.call(player, window, 'keydown keyup', this.handleKey, toggle, false);
}
// 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
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() {
const { player } = this;
const { elements } = player;
// Keyboard shortcuts
if (!this.player.config.keyboard.global && this.player.config.keyboard.focused) {
on.call(this.player, this.player.elements.container, 'keydown keyup', this.handleKey, false);
if (!player.config.keyboard.global && player.config.keyboard.focused) {
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
on.call(
this.player,
this.player.elements.container,
player,
elements.container,
'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen',
event => {
const { controls } = this.player.elements;
const { controls } = elements;
// Remove button states for fullscreen
if (event.type === 'enterfullscreen') {
if (controls && event.type === 'enterfullscreen') {
controls.pressed = false;
controls.hover = false;
}
@@ -236,114 +288,117 @@ class Listeners {
let delay = 0;
if (show) {
ui.toggleControls.call(this.player, true);
ui.toggleControls.call(player, true);
// Use longer timeout for touch devices
delay = this.player.touch ? 3000 : 2000;
delay = player.touch ? 3000 : 2000;
}
// Clear timer
clearTimeout(this.player.timers.controls);
// Timer to prevent flicker when seeking
this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
clearTimeout(player.timers.controls);
// Set new timer to prevent flicker when seeking
player.timers.controls = setTimeout(() => ui.toggleControls.call(player, false), delay);
},
);
}
// Listen for media events
media() {
const { player } = this;
const { elements } = player;
// Time change on media
on.call(this.player, this.player.media, 'timeupdate seeking seeked', event =>
controls.timeUpdate.call(this.player, event),
);
on.call(player, player.media, 'timeupdate seeking seeked', event => controls.timeUpdate.call(player, event));
// Display duration
on.call(this.player, this.player.media, 'durationchange loadeddata loadedmetadata', event =>
controls.durationUpdate.call(this.player, event),
on.call(player, player.media, 'durationchange loadeddata loadedmetadata', event =>
controls.durationUpdate.call(player, event),
);
// Check for audio tracks on load
// 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', () => {
toggleHidden(this.player.elements.volume, !this.player.hasAudio);
toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio);
on.call(player, player.media, 'canplay loadeddata', () => {
toggleHidden(elements.volume, !player.hasAudio);
toggleHidden(elements.buttons.mute, !player.hasAudio);
});
// Handle the media finishing
on.call(this.player, this.player.media, 'ended', () => {
on.call(player, player.media, 'ended', () => {
// Show poster on end
if (this.player.isHTML5 && this.player.isVideo && this.player.config.resetOnEnd) {
if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) {
// Restart
this.player.restart();
player.restart();
}
});
// Check for buffer progress
on.call(this.player, this.player.media, 'progress playing seeking seeked', event =>
controls.updateProgress.call(this.player, event),
on.call(player, player.media, 'progress playing seeking seeked', event =>
controls.updateProgress.call(player, event),
);
// Handle volume changes
on.call(this.player, this.player.media, 'volumechange', event =>
controls.updateVolume.call(this.player, event),
);
on.call(player, player.media, 'volumechange', event => controls.updateVolume.call(player, event));
// Handle play/pause
on.call(this.player, this.player.media, 'playing play pause ended emptied timeupdate', event =>
ui.checkPlaying.call(this.player, event),
on.call(player, player.media, 'playing play pause ended emptied timeupdate', event =>
ui.checkPlaying.call(player, event),
);
// Loading state
on.call(this.player, this.player.media, 'waiting canplay seeked playing', event =>
ui.checkLoading.call(this.player, event),
);
on.call(player, player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(player, event));
// 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
on.call(this.player, this.player.media, 'playing', () => {
if (!this.player.ads) {
on.call(player, player.media, 'playing', () => {
if (!player.ads) {
return;
}
// 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
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
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
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)
if (!is.element(wrapper)) {
return;
}
// On click play, pause ore restart
on.call(this.player, wrapper, 'click', () => {
// Touch devices will just show controls (if we're hiding controls)
if (this.player.config.hideControls && this.player.touch && !this.player.paused) {
// On click play, pause or restart
on.call(player, elements.container, 'click', event => {
const targets = [elements.container, wrapper];
// Ignore if click if not container or in video wrapper
if (!targets.includes(event.target) && !wrapper.contains(event.target)) {
return;
}
if (this.player.paused) {
this.player.play();
} else if (this.player.ended) {
this.player.restart();
this.player.play();
// Touch devices will just show controls (if hidden)
if (player.touch && player.config.hideControls) {
return;
}
if (player.ended) {
player.restart();
player.play();
} else {
this.player.pause();
player.togglePlay();
}
});
}
// Disable right click
if (this.player.supported.ui && this.player.config.disableContextMenu) {
if (player.supported.ui && player.config.disableContextMenu) {
on.call(
this.player,
this.player.elements.wrapper,
player,
elements.wrapper,
'contextmenu',
event => {
event.preventDefault();
@@ -353,220 +408,248 @@ class Listeners {
}
// Volume change
on.call(this.player, this.player.media, 'volumechange', () => {
on.call(player, player.media, 'volumechange', () => {
// 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
on.call(this.player, this.player.media, 'ratechange', () => {
on.call(player, player.media, 'ratechange', () => {
// Update UI
controls.updateSetting.call(this.player, 'speed');
controls.updateSetting.call(player, 'speed');
// Save to storage
this.player.storage.set({ speed: this.player.speed });
});
// Quality request
on.call(this.player, this.player.media, 'qualityrequested', event => {
// Save to storage
this.player.storage.set({ quality: event.detail.quality });
player.storage.set({ speed: player.speed });
});
// Quality change
on.call(this.player, this.player.media, 'qualitychange', event => {
on.call(player, player.media, 'qualitychange', event => {
// 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
// Bubble up key events for Edge
const proxyEvents = this.player.config.events.concat(['keyup', 'keydown']).join(' ');
on.call(this.player, this.player.media, proxyEvents, event => {
const proxyEvents = player.config.events.concat(['keyup', 'keydown']).join(' ');
on.call(player, player.media, proxyEvents, event => {
let { detail = {} } = event;
// Get error details from media
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
controls() {
const { player } = this;
const { elements } = player;
// IE doesn't support input event, so we fallback to change
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
if (this.player.elements.buttons.play) {
Array.from(this.player.elements.buttons.play).forEach(button => {
bind(button, 'click', this.player.togglePlay, 'play');
if (elements.buttons.play) {
Array.from(elements.buttons.play).forEach(button => {
this.bind(button, 'click', player.togglePlay, 'play');
});
}
// Pause
bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
this.bind(elements.buttons.restart, 'click', player.restart, 'restart');
// Rewind
bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
this.bind(elements.buttons.rewind, 'click', player.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
bind(
this.player.elements.buttons.mute,
this.bind(
elements.buttons.mute,
'click',
() => {
this.player.muted = !this.player.muted;
player.muted = !player.muted;
},
'mute',
);
// Captions toggle
bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions());
this.bind(elements.buttons.captions, 'click', () => player.toggleCaptions());
// Fullscreen toggle
bind(
this.player.elements.buttons.fullscreen,
// Download
this.bind(
elements.buttons.download,
'click',
() => {
this.player.fullscreen.toggle();
triggerEvent.call(player, player.media, 'download');
},
'download',
);
// Fullscreen toggle
this.bind(
elements.buttons.fullscreen,
'click',
() => {
player.fullscreen.toggle();
},
'fullscreen',
);
// Picture-in-Picture
bind(
this.player.elements.buttons.pip,
this.bind(
elements.buttons.pip,
'click',
() => {
this.player.pip = 'toggle';
player.pip = 'toggle';
},
'pip',
);
// Airplay
bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
this.bind(elements.buttons.airplay, 'click', player.airplay, 'airplay');
// Settings menu
bind(this.player.elements.buttons.settings, 'click', event => {
controls.toggleMenu.call(this.player, event);
});
// Settings menu
bind(this.player.elements.settings.form, 'click', event => {
// Settings menu - click toggle
this.bind(elements.buttons.settings, 'click', event => {
// Prevent the document click listener closing the menu
event.stopPropagation();
// Go back to home tab on click
const showHomeTab = () => {
const id = `plyr-settings-${this.player.id}-home`;
controls.showTab.call(this.player, id);
};
controls.toggleMenu.call(player, event);
});
// Settings menu items - use event delegation as items are added/removed
if (matches(event.target, this.player.config.selectors.inputs.language)) {
proxy(
event,
() => {
this.player.currentTrack = Number(event.target.value);
showHomeTab();
},
'language',
);
} else if (matches(event.target, this.player.config.selectors.inputs.quality)) {
proxy(
event,
() => {
this.player.quality = event.target.value;
showHomeTab();
},
'quality',
);
} else if (matches(event.target, this.player.config.selectors.inputs.speed)) {
proxy(
event,
() => {
this.player.speed = parseFloat(event.target.value);
showHomeTab();
},
'speed',
);
} else {
const tab = event.target;
controls.showTab.call(this.player, tab.getAttribute('aria-controls'));
// Settings menu - keyboard toggle
// We have to bind to keyup otherwise Firefox triggers a click when a keydown event handler shifts focus
// https://bugzilla.mozilla.org/show_bug.cgi?id=1220143
this.bind(
elements.buttons.settings,
'keyup',
event => {
const code = event.which;
// We only care about space and return
if (![13, 32].includes(code)) {
return;
}
// Because return triggers a click anyway, all we need to do is set focus
if (code === 13) {
controls.focusFirstMenuItem.call(player, null, true);
return;
}
// Prevent scroll
event.preventDefault();
// Prevent playing video (Firefox)
event.stopPropagation();
// Toggle menu
controls.toggleMenu.call(player, event);
},
null,
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)
bind(this.player.elements.inputs.seek, 'mousedown mousemove', event => {
const clientRect = this.player.elements.progress.getBoundingClientRect();
const percent = 100 / clientRect.width * (event.pageX - clientRect.left);
this.bind(elements.inputs.seek, 'mousedown mousemove', event => {
const rect = elements.progress.getBoundingClientRect();
const percent = (100 / rect.width) * (event.pageX - rect.left);
event.currentTarget.setAttribute('seek-value', percent);
});
// 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 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;
}
// Record seek time so we can prevent hiding controls for a few seconds after seek
player.lastSeekTime = Date.now();
// Was playing before?
const play = seek.hasAttribute('play-on-seeked');
const play = seek.hasAttribute(attribute);
// Done seeking
const done = ['mouseup', 'touchend', 'keyup'].includes(event.type);
// If we're done seeking and it was playing, resume playback
if (play && done) {
seek.removeAttribute('play-on-seeked');
this.player.play();
} else if (!done && this.player.playing) {
seek.setAttribute('play-on-seeked', '');
this.player.pause();
seek.removeAttribute(attribute);
player.play();
} else if (!done && player.playing) {
seek.setAttribute(attribute, '');
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
bind(
this.player.elements.inputs.seek,
this.bind(
elements.inputs.seek,
inputEvent,
event => {
const seek = event.currentTarget;
@@ -580,88 +663,86 @@ class Listeners {
seek.removeAttribute('seek-value');
this.player.currentTime = seekTo / seek.max * this.player.duration;
player.currentTime = (seekTo / seek.max) * player.duration;
},
'seek',
);
// Current time invert
// Only if one time element is used for both currentTime and duration
if (this.player.config.toggleInvert && !is.element(this.player.elements.display.duration)) {
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',
// Seek tooltip
this.bind(elements.progress, 'mouseenter mouseleave mousemove', event =>
controls.updateSeekTooltip.call(player, event),
);
// Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) {
Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(element => {
bind(element, 'input', event => controls.updateRangeFill.call(this.player, event.target));
Array.from(getElements.call(player, 'input[type="range"]')).forEach(element => {
this.bind(element, 'input', event => controls.updateRangeFill.call(player, event.target));
});
}
// Seek tooltip
bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', event =>
controls.updateSeekTooltip.call(this.player, event),
// Current time invert
// Only if one time element is used for both currentTime and duration
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)
bind(this.player.elements.controls, 'mouseenter mouseleave', event => {
this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
this.bind(elements.controls, 'mouseenter mouseleave', event => {
elements.controls.hover = !player.touch && event.type === 'mouseenter';
});
// 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.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
});
// Focus in/out on controls
bind(this.player.elements.controls, 'focusin focusout', event => {
const { config, elements, timers } = this.player;
// Show controls when they receive focus (e.g., when using keyboard tab key)
this.bind(elements.controls, 'focusin', () => {
const { config, elements, timers } = player;
// 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
ui.toggleControls.call(this.player, event.type === 'focusin');
ui.toggleControls.call(player, true);
// If focusin, hide again after delay
if (event.type === 'focusin') {
// Restore transition
setTimeout(() => {
toggleClass(elements.controls, config.classNames.noTransition, false);
}, 0);
// Restore transition
setTimeout(() => {
toggleClass(elements.controls, config.classNames.noTransition, false);
}, 0);
// Delay a little more for keyboard users
const delay = this.touch ? 3000 : 4000;
// Delay a little more for mouse users
const delay = this.touch ? 3000 : 4000;
// Clear timer
clearTimeout(timers.controls);
// Hide
timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
}
// Clear timer
clearTimeout(timers.controls);
// Hide again after delay
timers.controls = setTimeout(() => ui.toggleControls.call(player, false), delay);
});
// Mouse wheel for volume
bind(
this.player.elements.inputs.volume,
this.bind(
elements.inputs.volume,
'wheel',
event => {
// 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);
// Change the volume by 2%
this.player.increaseVolume(direction / 50);
player.increaseVolume(direction / 50);
// 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)) {
event.preventDefault();
}
+6 -5
View File
@@ -6,9 +6,9 @@
/* global google */
import i18n from '../i18n';
import { createElement } from '../utils/elements';
import { triggerEvent } from '../utils/events';
import i18n from '../utils/i18n';
import is from '../utils/is';
import loadScript from '../utils/loadScript';
import { formatTime } from '../utils/time';
@@ -207,6 +207,11 @@ class Ads {
* @param {Event} adsManagerLoadedEvent
*/
onAdsManagerLoaded(event) {
// Load could occur after a source change (race condition)
if (!this.enabled) {
return;
}
// Get the ads manager
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
this.manager.setVolume(this.player.volume);
+22 -4
View File
@@ -70,8 +70,9 @@ const vimeo = {
// Set aspect ratio
// For Vimeo we have an extra 300% height <div> to hide the standard controls and UI
setAspectRatio(input) {
const [x, y] = (is.string(input) ? input : this.config.ratio).split(':');
const padding = 100 / x * y;
const [x, y] = (is.string(input) ? input : this.config.ratio).split(':').map(Number);
const padding = (100 / x) * y;
vimeo.padding = padding;
this.elements.wrapper.style.paddingBottom = `${padding}%`;
if (this.supported.ui) {
@@ -278,6 +279,7 @@ const vimeo = {
.getVideoUrl()
.then(value => {
currentSrc = value;
controls.setDownloadLink.call(player);
})
.catch(error => {
this.debug.warn(error);
@@ -298,8 +300,8 @@ const vimeo = {
// Set aspect ratio based on video size
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
const ratio = getAspectRatio(dimensions[0], dimensions[1]);
vimeo.setAspectRatio.call(this, ratio);
vimeo.ratio = getAspectRatio(dimensions[0], dimensions[1]);
vimeo.setAspectRatio.call(this, vimeo.ratio);
});
// Set autopause
@@ -403,6 +405,22 @@ const vimeo = {
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
setTimeout(() => ui.build.call(player), 0);
},
-54
View File
@@ -2,9 +2,7 @@
// YouTube plugin
// ==========================================================================
import controls from '../controls';
import ui from '../ui';
import { dedupe } from '../utils/arrays';
import { createElement, replaceElement, toggleClass } from '../utils/elements';
import { triggerEvent } from '../utils/events';
import fetch from '../utils/fetch';
@@ -23,37 +21,6 @@ function parseId(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)
function assurePlaybackState(play) {
if (play && !this.embed.hasPlayed) {
@@ -225,11 +192,6 @@ const youtube = {
triggerEvent.call(player, player.media, 'error');
}
},
onPlaybackQualityChange() {
triggerEvent.call(player, player.media, 'qualitychange', false, {
quality: player.media.quality,
});
},
onPlaybackRateChange(event) {
// Get the instance
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
let { volume } = player.config;
Object.defineProperty(player.media, 'volume', {
@@ -457,12 +409,6 @@ const youtube = {
player.media.duration = instance.getDuration();
triggerEvent.call(player, player.media, 'durationchange');
}
// Get quality
controls.setQualityMenu.call(
player,
mapQualityUnits(instance.getAvailableQualityLevels()),
);
}
break;
+57 -18
View File
@@ -1,12 +1,13 @@
// ==========================================================================
// Plyr
// plyr.js v3.3.23
// plyr.js v3.4.7
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
import captions from './captions';
import defaults from './config/defaults';
import { pip } from './config/states';
import { getProviderByUrl, providers, types } from './config/types';
import Console from './console';
import controls from './controls';
@@ -75,16 +76,17 @@ class Plyr {
// Elements cache
this.elements = {
container: null,
captions: null,
buttons: {},
display: {},
progress: {},
inputs: {},
settings: {
popup: null,
menu: null,
panes: {},
tabs: {},
panels: {},
buttons: {},
},
captions: null,
};
// Captions
@@ -185,7 +187,7 @@ class Plyr {
// YouTube requires the playsinline in the URL
if (this.isYouTube) {
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 {
this.config.playsinline = true;
}
@@ -221,7 +223,7 @@ class Plyr {
if (this.media.hasAttribute('autoplay')) {
this.config.autoplay = true;
}
if (this.media.hasAttribute('playsinline')) {
if (this.media.hasAttribute('playsinline') || this.media.hasAttribute('webkit-playsinline')) {
this.config.playsinline = true;
}
if (this.media.hasAttribute('muted')) {
@@ -293,12 +295,17 @@ class Plyr {
this.fullscreen = new Fullscreen(this);
// Setup ads if provided
this.ads = new Ads(this);
if (this.config.ads.enabled) {
this.ads = new Ads(this);
}
// Autoplay if required
if (this.config.autoplay) {
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,
].find(is.number);
let updateStorage = true;
if (!options.includes(quality)) {
const value = closest(options, quality);
this.debug.warn(`Unsupported quality option: ${quality}, using ${value} instead`);
quality = value;
}
// Trigger request event
triggerEvent.call(this, this.media, 'qualityrequested', false, { quality });
// Don't update storage if quality is not supported
updateStorage = false;
}
// Update config
config.selected = quality;
// Set quality
this.media.quality = quality;
// Save to storage
if (updateStorage) {
this.storage.set({ quality });
}
}
/**
@@ -788,6 +802,15 @@ class Plyr {
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
* @param {input} - the URL for the new poster image
@@ -874,21 +897,28 @@ class Plyr {
* TODO: detect outside changes
*/
set pip(input) {
const states = {
pip: 'picture-in-picture',
inline: 'inline',
};
// Bail if no support
if (!support.pip) {
return;
}
// 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
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 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)) {
controls.toggleMenu.call(this, false);
}
// Trigger event on change
if (hiding !== isHidden) {
const eventName = hiding ? 'controlshidden' : 'controlsshown';
triggerEvent.call(this, this.media, eventName);
}
return !hiding;
}
return false;
}
+1 -2
View File
@@ -1,11 +1,10 @@
// ==========================================================================
// Plyr Polyfilled Build
// plyr.js v3.3.23
// plyr.js v3.4.7
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
import 'babel-polyfill';
import 'custom-event-polyfill';
import 'url-polyfill';
import Plyr from './plyr';
+6 -4
View File
@@ -114,12 +114,9 @@ const source = {
// HTML5 stuff
if (this.isHTML5) {
// Setup captions
if ('tracks' in input) {
if (Object.keys(input).includes('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
@@ -128,6 +125,11 @@ const source = {
ui.build.call(this);
}
if (this.isHTML5) {
// Load HTML5 sources
this.media.load();
}
// Update the fullscreen support
this.fullscreen.update();
},
+27 -13
View File
@@ -36,8 +36,26 @@ const support = {
},
// Picture-in-picture support
// Safari only currently
pip: (() => !browser.isIPhone && is.function(createElement('video').webkitSetPresentationMode))(),
// Safari & Chrome only currently
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
// Safari only currently
@@ -52,25 +70,21 @@ const support = {
// Related: http://www.leanbackplayer.com/test/h5mt.html
mime(inputType) {
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) {
return false;
}
let type;
if (inputType && inputType.includes('codecs=')) {
// Use input directly
type = inputType;
} else if (inputType === 'audio/mpeg') {
// Skip codec
type = 'audio/mpeg;';
} else if (inputType in defaultCodecs) {
// Use codec
type = `${inputType}; codecs="${defaultCodecs[inputType]}"`;
// Add codec if required
if (Object.keys(defaultCodecs).includes(type)) {
type += `; codecs="${defaultCodecs[inputType]}"`;
}
try {
return Boolean(type && this.media.canPlayType(type).replace(/no/, ''));
} catch (err) {
} catch (e) {
return false;
}
},
+6 -3
View File
@@ -4,11 +4,11 @@
import captions from './captions';
import controls from './controls';
import i18n from './i18n';
import support from './support';
import browser from './utils/browser';
import { getElement, toggleClass } from './utils/elements';
import { ready, triggerEvent } from './utils/events';
import i18n from './utils/i18n';
import is from './utils/is';
import loadImage from './utils/loadImage';
@@ -247,8 +247,11 @@ const ui = {
const { controls } = this.elements;
if (controls && this.config.hideControls) {
// Show controls if force, loading, paused, or button interaction, otherwise hide
this.toggleControls(Boolean(force || this.loading || this.paused || controls.pressed || controls.hover));
// 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.)
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));
}
},
};
+7 -3
View File
@@ -23,8 +23,12 @@ export const transitionEndEvent = (() => {
// Force repaint of element
export function repaint(element) {
setTimeout(() => {
toggleHidden(element, true);
element.offsetHeight; // eslint-disable-line
toggleHidden(element, false);
try {
toggleHidden(element, true);
element.offsetHeight; // eslint-disable-line
toggleHidden(element, false);
} catch (e) {
// Do nothing
}
}, 0);
}
+34 -17
View File
@@ -70,12 +70,19 @@ export function createElement(type, attributes, text) {
// Inaert an element after another
export function insertAfter(element, target) {
if (!is.element(element) || !is.element(target)) {
return;
}
target.parentNode.insertBefore(element, target.nextSibling);
}
// Insert a DocumentFragment
export function insertElement(type, parent, attributes, text) {
// Inject the new <element>
if (!is.element(parent)) {
return;
}
parent.appendChild(createElement(type, attributes, text));
}
@@ -95,6 +102,10 @@ export function removeElement(element) {
// Remove all child elements
export function emptyElement(element) {
if (!is.element(element)) {
return;
}
let { length } = element.childNodes;
while (length > 0) {
@@ -180,7 +191,7 @@ export function toggleHidden(element, hidden) {
let hide = hidden;
if (!is.boolean(hide)) {
hide = !element.hasAttribute('hidden');
hide = !element.hidden;
}
if (hide) {
@@ -192,6 +203,10 @@ export function toggleHidden(element, hidden) {
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
export function toggleClass(element, className, force) {
if (is.nodeList(element)) {
return Array.from(element).map(e => toggleClass(e, className, force));
}
if (is.element(element)) {
let method = 'toggle';
if (typeof force !== 'undefined') {
@@ -202,7 +217,7 @@ export function toggleClass(element, className, force) {
return element.classList.contains(className);
}
return null;
return false;
}
// Has class name
@@ -238,19 +253,6 @@ export function getElement(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
export function trapFocus(element = null, toggle = false) {
if (!is.element(element)) {
@@ -268,7 +270,7 @@ export function trapFocus(element = null, toggle = false) {
}
// Get the current focused element
const focused = getFocusElement();
const focused = document.activeElement;
if (focused === last && !event.shiftKey) {
// 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);
}
// 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);
}
}
+16 -3
View File
@@ -2,9 +2,18 @@
// Plyr internationalization
// ==========================================================================
import is from './utils/is';
import { getDeep } from './utils/objects';
import { replaceAll } from './utils/strings';
import is from './is';
import { getDeep } from './objects';
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 = {
get(key = '', config = {}) {
@@ -15,6 +24,10 @@ const i18n = {
let string = getDeep(config.i18n, key);
if (is.empty(string)) {
if (Object.keys(resources).includes(key)) {
return resources[key];
}
return '';
}
+7
View File
@@ -16,6 +16,7 @@ const isNodeList = input => instanceOf(input, NodeList);
const isElement = input => instanceOf(input, Element);
const isTextNode = input => getConstructor(input) === Text;
const isEvent = input => instanceOf(input, Event);
const isKeyboardEvent = input => instanceOf(input, KeyboardEvent);
const isCue = input => instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
const isTrack = input => instanceOf(input, TextTrack) || (!isNullOrUndefined(input) && isString(input.kind));
@@ -30,6 +31,11 @@ const isUrl = input => {
return true;
}
// Must be string from here
if (!isString(input)) {
return false;
}
// Add the protocol if required
let string = input;
if (!input.startsWith('http://') || !input.startsWith('https://')) {
@@ -56,6 +62,7 @@ export default {
element: isElement,
textNode: isTextNode,
event: isEvent,
keyboardEvent: isKeyboardEvent,
cue: isCue,
track: isTrack,
url: isUrl,
+1 -1
View File
@@ -24,7 +24,7 @@ export function getPercentage(current, max) {
return 0;
}
return (current / max * 100).toFixed(2);
return ((current / max) * 100).toFixed(2);
}
// Replace all occurances of a string in a string
+4 -3
View File
@@ -17,7 +17,6 @@
padding: $plyr-control-spacing;
position: absolute;
text-align: center;
transform: translateY(-($plyr-control-spacing * 4));
transition: transform 0.4s ease-in-out;
width: 100%;
@@ -53,6 +52,8 @@
display: block;
}
.plyr--hide-controls .plyr__captions {
transform: translateY(-($plyr-control-spacing * 1.5));
// If the lower controls are shown and not empty
.plyr:not(.plyr--hide-controls) .plyr__controls:not(:empty) ~ .plyr__captions {
transform: translateY(-($plyr-control-spacing * 4));
}
+28 -4
View File
@@ -33,6 +33,16 @@
}
}
// Remove any link styling
a.plyr__control {
text-decoration: none;
&::after,
&::before {
display: none;
}
}
// Change icons on state change
.plyr__control:not(.plyr__control--pressed) .icon--pressed,
.plyr__control.plyr__control--pressed .icon--not-pressed,
@@ -41,7 +51,7 @@
display: none;
}
// Audio styles
// Audio control
.plyr--audio .plyr__control {
&.plyr__tab-focus,
&: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)
.plyr__control--overlaid {
background: rgba($plyr-video-control-bg-hover, 0.8);
@@ -66,11 +91,10 @@
transform: translate(-50%, -50%);
z-index: 2;
// Offset icon to make the play button look right
svg {
height: $plyr-control-icon-size-large;
left: 2px; // Offset to make the play button look right
left: 2px;
position: relative;
width: $plyr-control-icon-size-large;
}
&:hover,
+34 -49
View File
@@ -18,36 +18,48 @@
> .plyr__control,
.plyr__progress,
.plyr__time,
.plyr__menu {
margin-left: ($plyr-control-spacing / 2);
&:first-child,
&:first-child + [data-plyr='pause'] {
margin-left: 0;
margin-right: auto;
}
}
.plyr__menu,
.plyr__volume {
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) {
> .plyr__control,
.plyr__menu,
.plyr__progress,
.plyr__time,
.plyr__menu {
.plyr__volume {
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
.plyr--video .plyr__controls {
background: linear-gradient(
@@ -59,37 +71,18 @@
bottom: 0;
color: $plyr-video-control-color;
left: 0;
padding: ($plyr-control-spacing * 3.5) $plyr-control-spacing
$plyr-control-spacing;
padding: ($plyr-control-spacing * 2) ($plyr-control-spacing / 2) ($plyr-control-spacing / 2);
position: absolute;
right: 0;
transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out;
z-index: 2;
z-index: 3;
.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;
}
@media (min-width: $plyr-bp-sm) {
padding: ($plyr-control-spacing * 3.5) $plyr-control-spacing $plyr-control-spacing;
}
}
// Audio controls
.plyr--audio .plyr__controls {
background: $plyr-audio-controls-bg;
border-radius: inherit;
color: $plyr-audio-control-color;
padding: $plyr-control-spacing;
}
// Hide controls
// Hide video controls
.plyr--video.plyr--hide-controls .plyr__controls {
opacity: 0;
pointer-events: none;
@@ -109,11 +102,3 @@
.plyr--fullscreen-enabled [data-plyr='fullscreen'] {
display: inline-block;
}
.plyr__controls:empty {
display: none;
~ .plyr__captions {
transform: translateY(0);
}
}
+44 -39
View File
@@ -39,7 +39,8 @@
> div {
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
@@ -54,18 +55,16 @@
width: 0;
}
ul {
list-style: none;
margin: 0;
overflow: hidden;
[role='menu'] {
padding: $plyr-control-padding;
}
li {
margin-top: 2px;
[role='menuitem'],
[role='menuitemradio'] {
margin-top: 2px;
&:first-child {
margin-top: 0;
}
&:first-child {
margin-top: 0;
}
}
@@ -75,10 +74,17 @@
color: $plyr-menu-color;
display: flex;
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;
width: 100%;
> span {
align-items: inherit;
display: flex;
width: 100%;
}
&::after {
border: 4px solid transparent;
content: '';
@@ -135,50 +141,49 @@
}
}
label.plyr__control {
.plyr__control[role='menuitemradio'] {
padding-left: $plyr-control-padding;
input[type='radio'] + span {
background: rgba(#000, 0.1);
&::before,
&::after {
border-radius: 100%;
}
&::before {
background: rgba(#000, 0.1);
content: '';
display: block;
flex-shrink: 0;
height: 16px;
margin-right: $plyr-control-spacing;
position: relative;
transition: all 0.3s ease;
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 {
background: $plyr-color-main;
&::after {
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 {
opacity: 1;
transform: scale(1);
transform: translateY(-50%) scale(1);
}
}
input[type='radio']:focus + span {
@include plyr-tab-focus();
}
&.plyr__tab-focus input[type='radio'] + span,
&:hover input[type='radio'] + span {
&.plyr__tab-focus::before,
&:hover::before {
background: rgba(#000, 0.1);
}
}
@@ -188,7 +193,7 @@
align-items: center;
display: flex;
margin-left: auto;
margin-right: -$plyr-control-padding;
margin-right: -($plyr-control-padding - 2);
overflow: hidden;
padding-left: ceil($plyr-control-padding * 3.5);
pointer-events: none;
+1 -2
View File
@@ -12,12 +12,11 @@
opacity: 0;
position: absolute;
top: 0;
transition: opacity 0.3s ease;
transition: opacity 0.2s ease;
width: 100%;
z-index: 1;
}
.plyr--stopped.plyr__poster-enabled .plyr__poster {
opacity: 1;
pointer-events: none;
}
-1
View File
@@ -3,7 +3,6 @@
// --------------------------------------------------------------
.plyr__progress {
display: flex;
flex: 1;
left: $plyr-range-thumb-height / 2;
margin-right: $plyr-range-thumb-height;
+14 -4
View File
@@ -19,7 +19,11 @@
&::-webkit-slider-runnable-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 {
@@ -140,15 +144,21 @@
// Pressed styles
&:active {
&::-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 {
@include plyr-range-thumb-active($plyr-audio-range-thumb-shadow-color);
@include plyr-range-thumb-active(
$plyr-audio-range-thumb-shadow-color
);
}
&::-ms-thumb {
@include plyr-range-thumb-active($plyr-audio-range-thumb-shadow-color);
@include plyr-range-thumb-active(
$plyr-audio-range-thumb-shadow-color
);
}
}
}
+2
View File
@@ -10,6 +10,7 @@
color: $plyr-tooltip-color;
font-size: $plyr-font-size-small;
font-weight: $plyr-font-weight-regular;
left: 50%;
line-height: 1.3;
margin-bottom: ($plyr-tooltip-padding * 2);
opacity: 0;
@@ -64,6 +65,7 @@
// Last tooltip
.plyr__controls > .plyr__control:last-child .plyr__tooltip {
left: auto;
right: 0;
transform: translate(0, 10px) scale(0.8);
transform-origin: 100% 100%;
+1
View File
@@ -3,6 +3,7 @@
// --------------------------------------------------------------
.plyr--video {
background: #000;
overflow: hidden;
// Menu open
+5 -2
View File
@@ -3,20 +3,23 @@
// --------------------------------------------------------------
.plyr__volume {
align-items: center;
display: flex;
flex: 1;
position: relative;
input[type='range'] {
margin-left: ($plyr-control-spacing / 2);
position: relative;
z-index: 2;
}
@media (min-width: $plyr-bp-sm) {
max-width: 50px;
max-width: 90px;
}
@media (min-width: $plyr-bp-md) {
max-width: 80px;
max-width: 110px;
}
}
+2 -2
View File
@@ -5,7 +5,7 @@
// Nicer focus styles
// ---------------------------------------
@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;
}
@@ -28,6 +28,7 @@
border: 0;
border-radius: ($plyr-range-track-height / 2);
height: $plyr-range-track-height;
transition: box-shadow 0.3s ease;
user-select: none;
}
@@ -36,7 +37,6 @@
border: 0;
border-radius: 100%;
box-shadow: $plyr-range-thumb-shadow;
box-sizing: border-box;
height: $plyr-range-thumb-height;
position: relative;
transition: all 0.2s ease;
-1
View File
@@ -3,7 +3,6 @@
// ==========================================================================
$plyr-control-icon-size: 18px !default;
$plyr-control-icon-size-large: 20px !default;
$plyr-control-spacing: 10px !default;
$plyr-control-padding: ($plyr-control-spacing * 0.7) !default;
$plyr-control-radius: 3px !default;
+1 -1
View File
@@ -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;
// 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;
// Fill
+1 -1
View File
@@ -17,4 +17,4 @@ $plyr-font-weight-bold: 600 !default;
$plyr-line-height: 1.7 !default;
$plyr-font-smoothing: true !default;
$plyr-font-smoothing: false !default;
+4
View File
@@ -22,3 +22,7 @@
width: 1px;
}
}
.plyr [hidden] {
display: none !important;
}
+1 -2
View File
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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">
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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>
<polygon points="4 17 14 17 9 11"></polygon>

Before

Width:  |  Height:  |  Size: 486 B

After

Width:  |  Height:  |  Size: 374 B

+1 -3
View File
@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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 -->
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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>
</g>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 945 B

+1 -3
View File
@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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 -->
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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>
</g>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 926 B

+6
View File
@@ -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

+3 -6
View File
@@ -1,7 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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 width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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>
</svg>

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 264 B

+3 -6
View File
@@ -1,7 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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 width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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>
</svg>

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 266 B

+2 -5
View File
@@ -1,6 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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 width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<polygon points="7.875 7.17142857 0 1 0 17 7.875 10.8285714 7.875 17 18 9 7.875 1"></polygon>
</svg>

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 192 B

+4
View File
@@ -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

+4
View File
@@ -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

+3 -6
View File
@@ -1,7 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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 width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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>
</svg>

Before

Width:  |  Height:  |  Size: 750 B

After

Width:  |  Height:  |  Size: 613 B

+3 -6
View File
@@ -1,7 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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 width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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>
</svg>

Before

Width:  |  Height:  |  Size: 503 B

After

Width:  |  Height:  |  Size: 366 B

+3 -6
View File
@@ -1,7 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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 width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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>
</svg>

Before

Width:  |  Height:  |  Size: 500 B

After

Width:  |  Height:  |  Size: 363 B

+2 -5
View File
@@ -1,6 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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 width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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>
</svg>

Before

Width:  |  Height:  |  Size: 534 B

After

Width:  |  Height:  |  Size: 401 B

+2 -5
View File
@@ -1,6 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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 width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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>
</svg>

Before

Width:  |  Height:  |  Size: 570 B

After

Width:  |  Height:  |  Size: 437 B

+2 -5
View File
@@ -1,6 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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 width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<polygon points="10.125 1 0 9 10.125 17 10.125 10.8285714 18 17 18 1 10.125 7.17142857"></polygon>
</svg>

Before

Width:  |  Height:  |  Size: 330 B

After

Width:  |  Height:  |  Size: 197 B

+2 -5
View File
@@ -1,6 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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 width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

+4 -7
View File
@@ -1,8 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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="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 width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<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="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>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

+3286 -2235
View File
File diff suppressed because it is too large Load Diff