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