Compare commits
101 Commits
v3.0.0-bet
...
v3.2.4
Author | SHA1 | Date | |
---|---|---|---|
a812650fea | |||
fec7a77d6f | |||
971e261067 | |||
27407ba021 | |||
ef8e58ede4 | |||
f1b275aedc | |||
b647af256c | |||
d2e9ed3467 | |||
5b39986835 | |||
a97b08e8ea | |||
56d1be9447 | |||
a241cb5215 | |||
042b1a8294 | |||
6d79b8cd4c | |||
88d766aeae | |||
119b471b84 | |||
7f079e0ec3 | |||
46fe3eecff | |||
3061a701d5 | |||
e45109e1d7 | |||
e138e6d51e | |||
aef1363b04 | |||
766dd03d81 | |||
ab393651ec | |||
ffd265d0ae | |||
72155472dd | |||
9b7170834e | |||
3e57a87bf7 | |||
a15d1c9f1c | |||
a095a64f90 | |||
2374d6b1c4 | |||
5ed3ff9084 | |||
385be55510 | |||
3082d0d128 | |||
f7e242f054 | |||
2874505004 | |||
ed9e0c13d7 | |||
10be94fa99 | |||
ee79c46145 | |||
384010a2c0 | |||
1e47019122 | |||
536c65e82c | |||
cdf14932ec | |||
3b20dbd9fd | |||
e4d975af00 | |||
2782a00e7c | |||
91d192dd7c | |||
b1e3abc795 | |||
3395e8df90 | |||
cce143a7da | |||
d593005b32 | |||
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 | |||
3bccc0da01 | |||
a0173d991e | |||
600f0eb8a3 | |||
5db73b1327 | |||
5cb1628cd8 | |||
c74b75e8e1 | |||
e0562752ea | |||
e6db374a72 | |||
ab7f277a1b | |||
d5a1a7ca1c |
@ -9,6 +9,7 @@
|
|||||||
"ignore": ["attribute", "class"]
|
"ignore": ["attribute", "class"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"string-no-newline": null,
|
||||||
"indentation": 4,
|
"indentation": 4,
|
||||||
"string-quotes": "single",
|
"string-quotes": "single",
|
||||||
"max-nesting-depth": 2,
|
"max-nesting-depth": 2,
|
||||||
|
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Chrome against localhost",
|
||||||
|
"url": "http://localhost/dev/plyr/demo",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -11,7 +11,7 @@
|
|||||||
"demo": {
|
"demo": {
|
||||||
"sass": {
|
"sass": {
|
||||||
"demo.css": "demo/src/sass/bundles/demo.scss",
|
"demo.css": "demo/src/sass/bundles/demo.scss",
|
||||||
"error.css": "demo/src/sass/bundles/error.csss"
|
"error.css": "demo/src/sass/bundles/error.scss"
|
||||||
},
|
},
|
||||||
"js": {
|
"js": {
|
||||||
"demo.js": "demo/src/js/demo.js"
|
"demo.js": "demo/src/js/demo.js"
|
||||||
|
738
changelog.md
738
changelog.md
File diff suppressed because it is too large
Load Diff
210
controls.md
210
controls.md
@ -1,27 +1,73 @@
|
|||||||
# Controls
|
# 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.
|
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
|
```javascript
|
||||||
i18n: {
|
i18n: {
|
||||||
restart: "Restart",
|
restart: 'Restart',
|
||||||
rewind: "Rewind {seektime} secs",
|
rewind: 'Rewind {seektime} secs',
|
||||||
play: "Play",
|
play: 'Play',
|
||||||
pause: "Pause",
|
pause: 'Pause',
|
||||||
forward: "Forward {seektime} secs",
|
fastForward: 'Forward {seektime} secs',
|
||||||
buffered: "buffered",
|
seek: 'Seek',
|
||||||
currentTime: "Current time",
|
played: 'Played',
|
||||||
duration: "Duration",
|
buffered: 'Buffered',
|
||||||
volume: "Volume",
|
currentTime: 'Current time',
|
||||||
toggleMute: "Toggle Mute",
|
duration: 'Duration',
|
||||||
toggleCaptions: "Toggle Captions",
|
volume: 'Volume',
|
||||||
toggleFullscreen: "Toggle Fullscreen"
|
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',
|
||||||
|
normal: 'Normal',
|
||||||
|
quality: 'Quality',
|
||||||
|
loop: 'Loop',
|
||||||
|
start: 'Start',
|
||||||
|
end: 'End',
|
||||||
|
all: 'All',
|
||||||
|
reset: 'Reset',
|
||||||
|
disabled: 'Disabled',
|
||||||
|
advertisement: 'Ad',
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -29,85 +75,79 @@ Note: `{seektime}` will be replaced with your configured seek time or the defaul
|
|||||||
|
|
||||||
## Using custom HTML
|
## 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)
|
* `{id}` - the dynamically generated ID for the player (for form controls)
|
||||||
- `{seektime}` - the seek time specified in options for fast forward and rewind
|
* `{seektime}` - the seek time specified in options for fast forward and rewind
|
||||||
- `{title}` - the title of your media, if specified
|
* `{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
|
### 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
|
```javascript
|
||||||
var controls = ["<div class='plyr__controls'>",
|
const controls = `
|
||||||
"<button type='button' data-plyr='restart'>",
|
<div class="plyr__controls">
|
||||||
"<svg><use xlink:href='#plyr-restart'></use></svg>",
|
<button type="button" class="plyr__control" data-plyr="restart">
|
||||||
"<span class='plyr__sr-only'>Restart</span>",
|
<svg role="presentation"><use xlink:href="#plyr-restart"></use></svg>
|
||||||
"</button>",
|
<span class="plyr__tooltip" role="tooltip">Restart</span>
|
||||||
"<button type='button' data-plyr='rewind'>",
|
</button>
|
||||||
"<svg><use xlink:href='#plyr-rewind'></use></svg>",
|
<button type="button" class="plyr__control" data-plyr="rewind">
|
||||||
"<span class='plyr__sr-only'>Rewind {seektime} secs</span>",
|
<svg role="presentation"><use xlink:href="#plyr-rewind"></use></svg>
|
||||||
"</button>",
|
<span class="plyr__tooltip" role="tooltip">Rewind {seektime} secs</span>
|
||||||
"<button type='button' data-plyr='play'>",
|
</button>
|
||||||
"<svg><use xlink:href='#plyr-play'></use></svg>",
|
<button type="button" class="plyr__control" aria-pressed="false" aria-label="Play, {title}" data-plyr="play">
|
||||||
"<span class='plyr__sr-only'>Play</span>",
|
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-pause"></use></svg>
|
||||||
"</button>",
|
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-play"></use></svg>
|
||||||
"<button type='button' data-plyr='pause'>",
|
<span class="label--pressed plyr__tooltip" role="tooltip">Pause</span>
|
||||||
"<svg><use xlink:href='#plyr-pause'></use></svg>",
|
<span class="label--not-pressed plyr__tooltip" role="tooltip">Play</span>
|
||||||
"<span class='plyr__sr-only'>Pause</span>",
|
</button>
|
||||||
"</button>",
|
<button type="button" class="plyr__control" data-plyr="fast-forward">
|
||||||
"<button type='button' data-plyr='fast-forward'>",
|
<svg role="presentation"><use xlink:href="#plyr-fast-forward"></use></svg>
|
||||||
"<svg><use xlink:href='#plyr-fast-forward'></use></svg>",
|
<span class="plyr__tooltip" role="tooltip">Forward {seektime} secs</span>
|
||||||
"<span class='plyr__sr-only'>Forward {seektime} secs</span>",
|
</button>
|
||||||
"</button>",
|
<div class="plyr__progress">
|
||||||
"<span class='plyr__progress'>",
|
<label for="plyr-seek-{id}" class="plyr__sr-only">Seek</label>
|
||||||
"<label for='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}">
|
||||||
"<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--buffer" min="0" max="100" value="0">% buffered</progress>
|
||||||
"<progress class='plyr__progress--played' max='100' value='0' role='presentation'></progress>",
|
<span role="tooltip" class="plyr__tooltip">00:00</span>
|
||||||
"<progress class='plyr__progress--buffer' max='100' value='0'>",
|
</div>
|
||||||
"<span>0</span>% buffered",
|
<div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div>
|
||||||
"</progress>",
|
<div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
|
||||||
"<span class='plyr__tooltip'>00:00</span>",
|
<button type="button" class="plyr__control" aria-pressed="false" aria-label="Mute" data-plyr="mute">
|
||||||
"</span>",
|
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-muted"></use></svg>
|
||||||
"<span class='plyr__time'>",
|
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-volume"></use></svg>
|
||||||
"<span class='plyr__sr-only'>Current time</span>",
|
<span class="label--pressed plyr__tooltip" role="tooltip">Unmute</span>
|
||||||
"<span class='plyr__time--current'>00:00</span>",
|
<span class="label--not-pressed plyr__tooltip" role="tooltip">Mute</span>
|
||||||
"</span>",
|
</button>
|
||||||
"<span class='plyr__time'>",
|
<div class="plyr__volume">
|
||||||
"<span class='plyr__sr-only'>Duration</span>",
|
<label for="plyr-volume-{id}" class="plyr__sr-only">Volume</label>
|
||||||
"<span class='plyr__time--duration'>00:00</span>",
|
<input data-plyr="volume" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" id="plyr-volume-{id}">
|
||||||
"</span>",
|
</div>
|
||||||
"<button type='button' data-plyr='mute'>",
|
<button type="button" class="plyr__control" aria-pressed="true" aria-label="Enable captions" data-plyr="captions">
|
||||||
"<svg class='icon--muted'><use xlink:href='#plyr-muted'></use></svg>",
|
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-captions-on"></use></svg>
|
||||||
"<svg><use xlink:href='#plyr-volume'></use></svg>",
|
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-captions-off"></use></svg>
|
||||||
"<span class='plyr__sr-only'>Toggle Mute</span>",
|
<span class="label--pressed plyr__tooltip" role="tooltip">Disable captions</span>
|
||||||
"</button>",
|
<span class="label--not-pressed plyr__tooltip" role="tooltip">Enable captions</span>
|
||||||
"<span class='plyr__volume'>",
|
</button>
|
||||||
"<label for='volume{id}' class='plyr__sr-only'>Volume</label>",
|
<button type="button" class="plyr__control" aria-pressed="false" aria-label="Enter fullscreen" data-plyr="fullscreen">
|
||||||
"<input id='volume{id}' class='plyr__volume--input' type='range' min='0' max='10' value='5' data-plyr='volume'>",
|
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-exit-fullscreen"></use></svg>
|
||||||
"<progress class='plyr__volume--display' max='10' value='0' role='presentation'></progress>",
|
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-enter-fullscreen"></use></svg>
|
||||||
"</span>",
|
<span class="label--pressed plyr__tooltip" role="tooltip">Exit fullscreen</span>
|
||||||
"<button type='button' data-plyr='captions'>",
|
<span class="label--not-pressed plyr__tooltip" role="tooltip">Enter fullscreen</span>
|
||||||
"<svg class='icon--captions-on'><use xlink:href='#plyr-captions-on'></use></svg>",
|
</button>
|
||||||
"<svg><use xlink:href='#plyr-captions-off'></use></svg>",
|
</div>
|
||||||
"<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("");
|
|
||||||
|
|
||||||
// Setup the player
|
// Setup the player
|
||||||
plyr.setup('.js-player', {
|
const player = new Plyr('#player', { controls });
|
||||||
html: controls
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
4381
demo/dist/demo.js
vendored
4381
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
1
demo/dist/error.css
vendored
Normal file
1
demo/dist/error.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -6,8 +6,14 @@
|
|||||||
<title>Doh. Looks like something went wrong.</title>
|
<title>Doh. Looks like something went wrong.</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<!-- Icons -->
|
||||||
|
<link rel="icon" href="https://cdn.plyr.io/static/icons/favicon.ico">
|
||||||
|
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/32x32.png" sizes="32x32">
|
||||||
|
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png">
|
||||||
|
|
||||||
<!-- Docs styles -->
|
<!-- Docs styles -->
|
||||||
<link rel="stylesheet" href="dist/error.css">
|
<link rel="stylesheet" href="dist/error.css?v=2">
|
||||||
|
|
||||||
<!-- Preload -->
|
<!-- Preload -->
|
||||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
|
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
|
||||||
<!-- Docs styles -->
|
<!-- Docs styles -->
|
||||||
<link rel="stylesheet" href="dist/demo.css">
|
<link rel="stylesheet" href="dist/demo.css?v=2">
|
||||||
|
|
||||||
<!-- Preload -->
|
<!-- Preload -->
|
||||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
|
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
|
||||||
@ -93,16 +93,18 @@
|
|||||||
<main>
|
<main>
|
||||||
<video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
|
<video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
|
||||||
<!-- Video files -->
|
<!-- Video files -->
|
||||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" type="video/mp4">
|
<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-HD.webm" type="video/webm">
|
<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">
|
||||||
|
|
||||||
<!-- Text track file -->
|
<!-- Caption files -->
|
||||||
<track kind="captions" label="English" srclang="en" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
|
<track kind="captions" label="English" srclang="en" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
|
||||||
default>
|
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">
|
<track kind="captions" label="Français" srclang="fr" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
|
||||||
|
|
||||||
<!-- Fallback for browsers that don't support the <video> element -->
|
<!-- Fallback for browsers that don't support the <video> element -->
|
||||||
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" download>Download</a>
|
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" download>Download</a>
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
@ -112,7 +114,7 @@
|
|||||||
<title>HTML5</title>
|
<title>HTML5</title>
|
||||||
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<a href="http://viewfromabluemoon.com/" target="_blank">View From A Blue Moon</a> © Brainfarm
|
<a href="https://itunes.apple.com/au/movie/view-from-a-blue-moon/id1041586323" target="_blank">View From A Blue Moon</a> © Brainfarm
|
||||||
</small>
|
</small>
|
||||||
</li>
|
</li>
|
||||||
<li class="plyr__cite plyr__cite--audio" hidden>
|
<li class="plyr__cite plyr__cite--audio" hidden>
|
||||||
@ -139,7 +141,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
||||||
<small>
|
<small>
|
||||||
<a href="https://vimeo.com/ondemand/viewfromabluemoon4k" target="_blank">View From A Blue Moon</a> on
|
<a href="https://vimeo.com/76979871" target="_blank">The New Vimeo Player</a> on
|
||||||
<span class="color--vimeo">
|
<span class="color--vimeo">
|
||||||
<svg class="icon" role="presentation">
|
<svg class="icon" role="presentation">
|
||||||
<title>Vimeo</title>
|
<title>Vimeo</title>
|
||||||
@ -163,25 +165,26 @@
|
|||||||
c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"></path>
|
c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<p>If you think Plyr's good,
|
<p>If you think Plyr's good,
|
||||||
<a href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts"
|
<a href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts"
|
||||||
target="_blank" data-shr-network="twitter">tweet it</a>
|
target="_blank" data-shr-network="twitter">tweet it</a>
|
||||||
</p>
|
</p>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- Polyfills -->
|
<!-- Polyfills -->
|
||||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent"></script>
|
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent,Object.entries,Object.values"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Plyr core script -->
|
<!-- Plyr core script -->
|
||||||
<script src="../dist/plyr.js"></script>
|
<script src="../dist/plyr.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Sharing libary (https://shr.one) -->
|
<!-- Sharing libary (https://shr.one) -->
|
||||||
<script src="https://cdn.shr.one/1.0.1/shr.js"></script>
|
<script src="https://cdn.shr.one/1.0.1/shr.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
|
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
|
||||||
<script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async></script>
|
<script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<!-- Docs script -->
|
<!-- Docs script -->
|
||||||
<script src="dist/demo.js"></script>
|
<script src="dist/demo.js" crossorigin="anonymous"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -4,242 +4,291 @@
|
|||||||
// Please see readme.md in the root or github.com/sampotts/plyr
|
// Please see readme.md in the root or github.com/sampotts/plyr
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
import Raven from 'raven-js';
|
||||||
if (window.shr) {
|
|
||||||
window.shr.setup({
|
(() => {
|
||||||
count: {
|
const isLive = window.location.host === 'plyr.io';
|
||||||
classname: 'button__count',
|
|
||||||
},
|
// Raven / Sentry
|
||||||
});
|
// For demo site (https://plyr.io) only
|
||||||
|
if (isLive) {
|
||||||
|
Raven.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup tab focus
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const tabClassName = 'tab-focus';
|
Raven.context(() => {
|
||||||
|
if (window.shr) {
|
||||||
|
window.shr.setup({
|
||||||
|
count: {
|
||||||
|
classname: 'button__count',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Remove class on blur
|
// Setup tab focus
|
||||||
document.addEventListener('focusout', event => {
|
const tabClassName = 'tab-focus';
|
||||||
event.target.classList.remove(tabClassName);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add classname to tabbed elements
|
// Remove class on blur
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('focusout', event => {
|
||||||
if (event.keyCode !== 9) {
|
event.target.classList.remove(tabClassName);
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Delay the adding of classname until the focus has changed
|
// Add classname to tabbed elements
|
||||||
// This event fires before the focusin event
|
document.addEventListener('keydown', event => {
|
||||||
setTimeout(() => {
|
if (event.keyCode !== 9) {
|
||||||
document.activeElement.classList.add(tabClassName);
|
return;
|
||||||
}, 0);
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Setup the player
|
// Delay the adding of classname until the focus has changed
|
||||||
const player = new Plyr('#player', {
|
// This event fires before the focusin event
|
||||||
debug: true,
|
setTimeout(() => {
|
||||||
title: 'View From A Blue Moon',
|
document.activeElement.classList.add(tabClassName);
|
||||||
iconUrl: '../dist/plyr.svg',
|
}, 0);
|
||||||
keyboard: {
|
});
|
||||||
global: true,
|
|
||||||
},
|
|
||||||
tooltips: {
|
|
||||||
controls: true,
|
|
||||||
},
|
|
||||||
captions: {
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
keys: {
|
|
||||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
|
|
||||||
},
|
|
||||||
ads: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expose for tinkering in the console
|
// Setup the player
|
||||||
window.player = 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
|
// Expose for tinkering in the console
|
||||||
const buttons = document.querySelectorAll('[data-source]');
|
window.player = player;
|
||||||
const types = {
|
|
||||||
video: 'video',
|
|
||||||
audio: 'audio',
|
|
||||||
youtube: 'youtube',
|
|
||||||
vimeo: 'vimeo',
|
|
||||||
};
|
|
||||||
let currentType = window.location.hash.replace('#', '');
|
|
||||||
const historySupport = window.history && window.history.pushState;
|
|
||||||
|
|
||||||
// Toggle class on an element
|
// Setup type toggle
|
||||||
function toggleClass(element, className, state) {
|
const buttons = document.querySelectorAll('[data-source]');
|
||||||
if (element) {
|
const types = {
|
||||||
element.classList[state ? 'add' : 'remove'](className);
|
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
|
// Toggle class on an element
|
||||||
function newSource(type, init) {
|
function toggleClass(element, className, state) {
|
||||||
// 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 (element) {
|
||||||
if (!(type in types) || (!init && type === currentType) || (!currentType.length && type === types.video)) {
|
element.classList[state ? 'add' : 'remove'](className);
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
// Set a new source
|
||||||
case types.video:
|
function newSource(type, init) {
|
||||||
player.source = {
|
// 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
|
||||||
type: 'video',
|
if (!(type in types) || (!init && type === currentType) || (!currentType.length && type === types.video)) {
|
||||||
title: 'View From A Blue Moon',
|
return;
|
||||||
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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
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-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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
case types.audio:
|
break;
|
||||||
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.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:
|
break;
|
||||||
player.source = {
|
|
||||||
type: 'video',
|
|
||||||
title: 'View From A Blue Moon',
|
|
||||||
sources: [{
|
|
||||||
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
|
||||||
provider: 'youtube',
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
case types.youtube:
|
||||||
|
player.source = {
|
||||||
|
type: 'video',
|
||||||
|
sources: [{
|
||||||
|
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
||||||
|
provider: 'youtube',
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
case types.vimeo:
|
break;
|
||||||
player.source = {
|
|
||||||
type: 'video',
|
|
||||||
sources: [{
|
|
||||||
src: 'https://vimeo.com/76979871',
|
|
||||||
provider: 'vimeo',
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
default:
|
||||||
currentType = type;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove active classes
|
// Set the current type for next time
|
||||||
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
|
currentType = type;
|
||||||
|
|
||||||
// Set active on parent
|
// Remove active classes
|
||||||
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
|
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
|
||||||
|
|
||||||
// Show cite
|
// Set active on parent
|
||||||
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
|
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
|
||||||
cite.setAttribute('hidden', '');
|
|
||||||
});
|
|
||||||
document.querySelector(`.plyr__cite--${type}`).removeAttribute('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind to each button
|
// Show cite
|
||||||
Array.from(buttons).forEach(button => {
|
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
|
||||||
button.addEventListener('click', () => {
|
cite.setAttribute('hidden', '');
|
||||||
const type = button.getAttribute('data-source');
|
});
|
||||||
|
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) {
|
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
|
// Google analytics
|
||||||
window.addEventListener('popstate', event => {
|
// For demo site (https://plyr.io) only
|
||||||
if (event.state && 'type' in event.state) {
|
/* eslint-disable */
|
||||||
newSource(event.state.type);
|
if (isLive) {
|
||||||
}
|
(function(i, s, o, g, r, a, m) {
|
||||||
});
|
i.GoogleAnalyticsObject = r;
|
||||||
|
i[r] =
|
||||||
// On load
|
i[r] ||
|
||||||
if (historySupport) {
|
function() {
|
||||||
const video = !currentType.length;
|
(i[r].q = i[r].q || []).push(arguments);
|
||||||
|
};
|
||||||
// If there's no current type set, assume video
|
i[r].l = 1 * new Date();
|
||||||
if (video) {
|
a = s.createElement(o);
|
||||||
currentType = types.video;
|
m = s.getElementsByTagName(o)[0];
|
||||||
}
|
a.async = 1;
|
||||||
|
a.src = g;
|
||||||
// Replace current history state
|
m.parentNode.insertBefore(a, m);
|
||||||
if (currentType in types) {
|
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||||
window.history.replaceState(
|
window.ga('create', 'UA-40881672-11', 'auto');
|
||||||
{
|
window.ga('send', 'pageview');
|
||||||
type: currentType,
|
|
||||||
},
|
|
||||||
'',
|
|
||||||
video ? '' : `#${currentType}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's not video, load the source
|
|
||||||
if (currentType !== types.video) {
|
|
||||||
newSource(currentType, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
/* eslint-enable */
|
||||||
|
})();
|
||||||
// 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 */
|
|
||||||
|
@ -3,12 +3,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@charset 'UTF-8';
|
@charset 'UTF-8';
|
||||||
|
|
||||||
// Libs
|
|
||||||
@import '../lib/fontface';
|
|
||||||
@import '../lib/mixins';
|
|
||||||
@import '../lib/normalize';
|
|
||||||
@import '../lib/reset';
|
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
@import '../settings/colors';
|
@import '../settings/colors';
|
||||||
@import '../settings/cosmetic';
|
@import '../settings/cosmetic';
|
||||||
@ -17,6 +11,12 @@
|
|||||||
@import '../settings/spacing';
|
@import '../settings/spacing';
|
||||||
@import '../settings/type';
|
@import '../settings/type';
|
||||||
|
|
||||||
|
// Libs
|
||||||
|
@import '../lib/fontface';
|
||||||
|
@import '../lib/mixins';
|
||||||
|
@import '../lib/normalize';
|
||||||
|
@import '../lib/reset';
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
@import '../layout/error';
|
@import '../layout/error';
|
||||||
|
|
||||||
|
@ -16,3 +16,4 @@ $plyr-font-size-captions-base: $plyr-font-size-base;
|
|||||||
$plyr-font-size-captions-small: $plyr-font-size-small;
|
$plyr-font-size-captions-small: $plyr-font-size-small;
|
||||||
$plyr-font-size-captions-medium: 18px;
|
$plyr-font-size-captions-medium: 18px;
|
||||||
$plyr-font-size-captions-large: 21px;
|
$plyr-font-size-captions-large: 21px;
|
||||||
|
$plyr-font-size-menu: $plyr-font-size-base;
|
||||||
|
@ -6,5 +6,6 @@ h1 {
|
|||||||
@include font-size($font-size-h1);
|
@include font-size($font-size-h1);
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
letter-spacing: $letter-spacing-headings;
|
letter-spacing: $letter-spacing-headings;
|
||||||
margin: 0 0 ($spacing-base / 2);
|
line-height: 1.2;
|
||||||
|
margin: 0 0 $spacing-base;
|
||||||
}
|
}
|
||||||
|
2
dist/plyr.css
vendored
2
dist/plyr.css
vendored
File diff suppressed because one or more lines are too long
2231
dist/plyr.js
vendored
2231
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
2515
dist/plyr.polyfilled.js
vendored
2515
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/'),
|
root: path.join(root, 'demo/'),
|
||||||
},
|
},
|
||||||
upload: [
|
upload: [
|
||||||
path.join(root, `dist/*${minSuffix}.js`),
|
path.join(root, `dist/*${minSuffix}.*`),
|
||||||
path.join(root, 'dist/*.css'),
|
path.join(root, 'dist/*.css'),
|
||||||
path.join(root, 'dist/*.svg'),
|
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}...`);
|
console.log(`Uploading '${version}' to ${aws.cdn.domain}...`);
|
||||||
|
|
||||||
// Upload to CDN
|
// Upload to CDN
|
||||||
return gulp
|
return (
|
||||||
.src(paths.upload)
|
gulp
|
||||||
.pipe(
|
.src(paths.upload)
|
||||||
rename(p => {
|
.pipe(
|
||||||
p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line
|
rename(p => {
|
||||||
p.dirname = p.dirname.replace('.', version); // eslint-disable-line
|
p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line
|
||||||
}),
|
p.dirname = p.dirname.replace('.', version); // eslint-disable-line
|
||||||
)
|
}),
|
||||||
.pipe(
|
)
|
||||||
size({
|
// Remove min suffix from source map URL
|
||||||
showFiles: true,
|
.pipe(replace(/sourceMappingURL=([\w-?.]+)/, (match, p1) => `sourceMappingURL=${p1.replace(minSuffix, '')}`))
|
||||||
gzip: true,
|
.pipe(
|
||||||
}),
|
size({
|
||||||
)
|
showFiles: true,
|
||||||
.pipe(replace(localPath, versionPath))
|
gzip: true,
|
||||||
.pipe(s3(aws.cdn, options.cdn));
|
}),
|
||||||
|
)
|
||||||
|
.pipe(replace(localPath, versionPath))
|
||||||
|
.pipe(s3(aws.cdn, options.cdn))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Publish to demo bucket
|
// Publish to demo bucket
|
||||||
|
33
package.json
33
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "plyr",
|
"name": "plyr",
|
||||||
"version": "3.0.0-beta.19",
|
"version": "3.2.4",
|
||||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||||
"homepage": "https://plyr.io",
|
"homepage": "https://plyr.io",
|
||||||
"main": "./dist/plyr.js",
|
"main": "./dist/plyr.js",
|
||||||
@ -9,42 +9,44 @@
|
|||||||
"style": "./dist/plyr.css",
|
"style": "./dist/plyr.css",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-eslint": "^8.2.2",
|
"babel-eslint": "^8.2.3",
|
||||||
"babel-plugin-external-helpers": "^6.22.0",
|
"babel-plugin-external-helpers": "^6.22.0",
|
||||||
"babel-preset-env": "^1.6.1",
|
"babel-preset-env": "^1.6.1",
|
||||||
"del": "^3.0.0",
|
"del": "^3.0.0",
|
||||||
"eslint": "^4.18.2",
|
"eslint": "^4.19.1",
|
||||||
"eslint-config-airbnb-base": "^12.1.0",
|
"eslint-config-airbnb-base": "^12.1.0",
|
||||||
"eslint-config-prettier": "^2.9.0",
|
"eslint-config-prettier": "^2.9.0",
|
||||||
"eslint-plugin-import": "^2.9.0",
|
"eslint-plugin-import": "^2.11.0",
|
||||||
"git-branch": "^2.0.1",
|
"git-branch": "^2.0.1",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"gulp-autoprefixer": "^5.0.0",
|
"gulp-autoprefixer": "^5.0.0",
|
||||||
"gulp-better-rollup": "^3.0.0",
|
"gulp-better-rollup": "^3.1.0",
|
||||||
"gulp-clean-css": "^3.9.3",
|
"gulp-clean-css": "^3.9.3",
|
||||||
"gulp-concat": "^2.6.1",
|
"gulp-concat": "^2.6.1",
|
||||||
"gulp-filter": "^5.1.0",
|
"gulp-filter": "^5.1.0",
|
||||||
"gulp-open": "^3.0.0",
|
"gulp-open": "^3.0.1",
|
||||||
"gulp-rename": "^1.2.2",
|
"gulp-rename": "^1.2.2",
|
||||||
"gulp-replace": "^0.6.1",
|
"gulp-replace": "^0.6.1",
|
||||||
"gulp-s3": "^0.11.0",
|
"gulp-s3": "^0.11.0",
|
||||||
"gulp-sass": "^3.1.0",
|
"gulp-sass": "^4.0.1",
|
||||||
"gulp-size": "^3.0.0",
|
"gulp-size": "^3.0.0",
|
||||||
"gulp-sourcemaps": "^2.6.4",
|
"gulp-sourcemaps": "^2.6.4",
|
||||||
"gulp-svgmin": "^1.2.4",
|
"gulp-svgmin": "^1.2.4",
|
||||||
"gulp-svgstore": "^6.1.1",
|
"gulp-svgstore": "^6.1.1",
|
||||||
"gulp-uglify-es": "^1.0.1",
|
"gulp-uglify-es": "^1.0.1",
|
||||||
"gulp-util": "^3.0.8",
|
"gulp-util": "^3.0.8",
|
||||||
"rollup-plugin-babel": "^3.0.3",
|
"prettier-eslint": "^8.8.1",
|
||||||
"rollup-plugin-commonjs": "^8.4.1",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"rollup-plugin-node-resolve": "^3.2.0",
|
"rollup-plugin-babel": "^3.0.4",
|
||||||
|
"rollup-plugin-commonjs": "^9.1.0",
|
||||||
|
"rollup-plugin-node-resolve": "^3.3.0",
|
||||||
"run-sequence": "^2.2.1",
|
"run-sequence": "^2.2.1",
|
||||||
"stylelint": "^9.1.1",
|
"stylelint": "^9.2.0",
|
||||||
"stylelint-config-prettier": "^3.0.4",
|
"stylelint-config-prettier": "^3.2.0",
|
||||||
"stylelint-config-recommended": "^2.1.0",
|
"stylelint-config-recommended": "^2.1.0",
|
||||||
"stylelint-config-sass-guidelines": "^5.0.0",
|
"stylelint-config-sass-guidelines": "^5.0.0",
|
||||||
"stylelint-order": "^0.8.1",
|
"stylelint-order": "^0.8.1",
|
||||||
"stylelint-scss": "^2.5.0",
|
"stylelint-scss": "^3.0.1",
|
||||||
"stylelint-selector-bem-pattern": "^2.0.0"
|
"stylelint-selector-bem-pattern": "^2.0.0"
|
||||||
},
|
},
|
||||||
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
|
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
|
||||||
@ -65,6 +67,9 @@
|
|||||||
"author": "Sam Potts <sam@potts.es>",
|
"author": "Sam Potts <sam@potts.es>",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"custom-event-polyfill": "^0.3.0"
|
"custom-event-polyfill": "^0.3.0",
|
||||||
|
"loadjs": "^3.5.4",
|
||||||
|
"npm": "^6.0.0",
|
||||||
|
"raven-js": "^3.24.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
readme.md
38
readme.md
@ -1,30 +1,30 @@
|
|||||||
---
|
|
||||||
Beware: This version is currently in beta and not production-ready
|
|
||||||
---
|
|
||||||
|
|
||||||
# Plyr
|
# Plyr
|
||||||
|
|
||||||
A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers.
|
A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers.
|
||||||
|
|
||||||
[Checkout the demo](https://plyr.io) - [Donate to support Plyr](#donate) - [Chat on Slack](https://bit.ly/plyr-slack)
|
[Checkout the demo](https://plyr.io) - [Donate to support Plyr](#donate) - [Chat on Slack](https://bit.ly/plyr-chat)
|
||||||
|
|
||||||
[](https://plyr.io)
|
[](https://plyr.io)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* **Accessible** - full support for VTT captions and screen readers
|
* **Accessible** - full support for VTT captions and screen readers
|
||||||
* **Lightweight** - just 18KB minified and gzipped
|
|
||||||
* **[Customisable](#html)** - make the player look how you want with the markup you want
|
* **[Customisable](#html)** - make the player look how you want with the markup you want
|
||||||
* **Semantic** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
|
* **Good HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
|
||||||
`<span>` or `<a href="#">` button hacks
|
`<span>` or `<a href="#">` button hacks
|
||||||
* **Responsive** - works with any screen size
|
* **Responsive** - works with any screen size
|
||||||
* **HTML Video & Audio** - support for both formats
|
* **HTML Video & Audio** - support for both formats
|
||||||
* **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
|
* **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
|
||||||
|
* **[Monetization](#ads)** - make money from your videos
|
||||||
* **[Streaming](#streaming)** - support for hls.js, Shaka and dash.js streaming playback
|
* **[Streaming](#streaming)** - support for hls.js, Shaka and dash.js streaming playback
|
||||||
* **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
* **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
||||||
* **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
|
* **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
|
||||||
* **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes
|
* **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes
|
||||||
* **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
|
* **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
|
||||||
|
* **Picture-in-Picture** - supports Safari's picture-in-picture mode
|
||||||
|
* **Playsinline** - supports the `playsinline` attribute
|
||||||
|
* **Speed controls** - adjust speed on the fly
|
||||||
|
* **Multiple captions** - support for multiple caption tracks
|
||||||
* **i18n support** - support for internationalization of controls
|
* **i18n support** - support for internationalization of controls
|
||||||
* **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
|
* **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
|
||||||
* **SASS** - to include in your build processes
|
* **SASS** - to include in your build processes
|
||||||
@ -128,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:
|
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.plyr.io/3.0.0-beta.19/plyr.js"></script>
|
<script src="https://cdn.plyr.io/3.2.4/plyr.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
_Note_: Be sure to read the [polyfills](#polyfills) section below about browser compatibility
|
_Note_: Be sure to read the [polyfills](#polyfills) section below about browser compatibility
|
||||||
@ -144,13 +144,23 @@ Include the `plyr.css` stylsheet into your `<head>`
|
|||||||
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.0.0-beta.19/plyr.css">
|
<link rel="stylesheet" href="https://cdn.plyr.io/3.2.4/plyr.css">
|
||||||
```
|
```
|
||||||
|
|
||||||
### SVG Sprite
|
### SVG Sprite
|
||||||
|
|
||||||
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
||||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.0-beta.19/plyr.svg`.
|
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.2.4/plyr.svg`.
|
||||||
|
|
||||||
|
## Ads
|
||||||
|
|
||||||
|
Plyr has partnered up with [vi.ai](http://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy:
|
||||||
|
|
||||||
|
* [Sign up for a vi.ai account](http://vi.ai/publisher-video-monetization/?aid=plyrio)
|
||||||
|
* Grab your publisher ID from the code snippet
|
||||||
|
* Enable ads in the [config options](#options) and enter your publisher ID
|
||||||
|
|
||||||
|
Any questions regarding the ads can be sent straight to vi.ai and any issues with rendering raised through GitHub issues.
|
||||||
|
|
||||||
## Advanced
|
## Advanced
|
||||||
|
|
||||||
@ -230,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));
|
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
|
```javascript
|
||||||
const player = new Plyr('#player', {
|
const player = new Plyr('#player', {
|
||||||
@ -284,6 +294,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }` | `selected`: The default speed for playback. `options`: Options to display in the menu. Most browsers will refuse to play slower than 0.5. |
|
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] }` | `selected`: The default speed for playback. `options`: Options to display in the menu. Most browsers will refuse to play slower than 0.5. |
|
||||||
| `quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display. |
|
| `quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display. |
|
||||||
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
||||||
|
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable vi.ai ads. `publisherId`: Your unique vi.ai publisher ID. |
|
||||||
|
|
||||||
1. Vimeo only
|
1. Vimeo only
|
||||||
|
|
||||||
@ -366,6 +377,7 @@ player.fullscreen.active; // false;
|
|||||||
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
|
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
|
||||||
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
|
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
|
||||||
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
|
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
|
||||||
|
| `buffered` | ✓ | - | Returns a float between 0 and 1 indicating how much of the media is buffered |
|
||||||
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
|
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
|
||||||
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
|
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
|
||||||
| `duration` | ✓ | - | Returns the duration for the current media. |
|
| `duration` | ✓ | - | Returns the duration for the current media. |
|
||||||
@ -695,7 +707,7 @@ Credit to the PayPal HTML5 Video player from which Plyr's caption functionality
|
|||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
[](https://www.fastly.com/)
|
[](https://www.fastly.com/)
|
||||||
|
|
||||||
Massive thanks to [Fastly](https://www.fastly.com/) for providing the CDN services.
|
Massive thanks to [Fastly](https://www.fastly.com/) for providing the CDN services.
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import support from './support';
|
import support from './support';
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
|
import i18n from './i18n';
|
||||||
|
|
||||||
const captions = {
|
const captions = {
|
||||||
// Setup captions
|
// Setup captions
|
||||||
@ -46,6 +47,7 @@ const captions = {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the container
|
// Inject the container
|
||||||
if (!utils.is.element(this.elements.captions)) {
|
if (!utils.is.element(this.elements.captions)) {
|
||||||
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
|
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
|
||||||
@ -148,7 +150,49 @@ const captions = {
|
|||||||
|
|
||||||
// Get the current track for the current language
|
// Get the current track for the current language
|
||||||
getCurrentTrack() {
|
getCurrentTrack() {
|
||||||
return captions.getTracks.call(this).find(track => track.language.toLowerCase() === this.language);
|
const tracks = captions.getTracks.call(this);
|
||||||
|
|
||||||
|
if (!tracks.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get track based on current language
|
||||||
|
let track = tracks.find(track => track.language.toLowerCase() === this.language);
|
||||||
|
|
||||||
|
// Get the <track> with default attribute
|
||||||
|
if (!track) {
|
||||||
|
track = utils.getElement.call(this, 'track[default]');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first track
|
||||||
|
if (!track) {
|
||||||
|
[track] = tracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
return track;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get UI label for track
|
||||||
|
getLabel(track) {
|
||||||
|
let currentTrack = track;
|
||||||
|
|
||||||
|
if (!utils.is.track(currentTrack) && support.textTracks && this.captions.active) {
|
||||||
|
currentTrack = captions.getCurrentTrack.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (utils.is.track(currentTrack)) {
|
||||||
|
if (!utils.is.empty(currentTrack.label)) {
|
||||||
|
return currentTrack.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!utils.is.empty(currentTrack.language)) {
|
||||||
|
return track.language.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
return i18n.get('enabled', this.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
return i18n.get('disabled', this.config);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Display active caption if it contains text
|
// Display active caption if it contains text
|
||||||
@ -206,11 +250,6 @@ const captions = {
|
|||||||
|
|
||||||
// Display captions container and button (for initialization)
|
// Display captions container and button (for initialization)
|
||||||
show() {
|
show() {
|
||||||
// If there's no caption toggle, bail
|
|
||||||
if (!utils.is.element(this.elements.buttons.captions)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to load the value from storage
|
// Try to load the value from storage
|
||||||
let active = this.storage.get('captions');
|
let active = this.storage.get('captions');
|
||||||
|
|
||||||
|
331
src/js/controls.js
vendored
331
src/js/controls.js
vendored
@ -5,7 +5,9 @@
|
|||||||
import support from './support';
|
import support from './support';
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
|
import i18n from './i18n';
|
||||||
import captions from './captions';
|
import captions from './captions';
|
||||||
|
import html5 from './html5';
|
||||||
|
|
||||||
// Sniff out the browser
|
// Sniff out the browser
|
||||||
const browser = utils.getBrowser();
|
const browser = utils.getBrowser();
|
||||||
@ -13,11 +15,6 @@ const browser = utils.getBrowser();
|
|||||||
const controls = {
|
const controls = {
|
||||||
// Webkit polyfill for lower fill range
|
// Webkit polyfill for lower fill range
|
||||||
updateRangeFill(target) {
|
updateRangeFill(target) {
|
||||||
// WebKit only
|
|
||||||
if (!browser.isWebkit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get range from event if event passed
|
// Get range from event if event passed
|
||||||
const range = utils.is.event(target) ? target.target : target;
|
const range = utils.is.event(target) ? target.target : target;
|
||||||
|
|
||||||
@ -26,6 +23,14 @@ const controls = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set aria value for https://github.com/sampotts/plyr/issues/905
|
||||||
|
range.setAttribute('aria-valuenow', range.value);
|
||||||
|
|
||||||
|
// WebKit only
|
||||||
|
if (!browser.isWebkit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Set CSS custom property
|
// Set CSS custom property
|
||||||
range.style.setProperty('--value', `${range.value / range.max * 100}%`);
|
range.style.setProperty('--value', `${range.value / range.max * 100}%`);
|
||||||
},
|
},
|
||||||
@ -50,6 +55,7 @@ const controls = {
|
|||||||
icon,
|
icon,
|
||||||
utils.extend(attributes, {
|
utils.extend(attributes, {
|
||||||
role: 'presentation',
|
role: 'presentation',
|
||||||
|
focusable: 'false',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -74,7 +80,7 @@ const controls = {
|
|||||||
|
|
||||||
// Create hidden text label
|
// Create hidden text label
|
||||||
createLabel(type, attr) {
|
createLabel(type, attr) {
|
||||||
let text = this.config.i18n[type];
|
let text = i18n.get(type, this.config);
|
||||||
const attributes = Object.assign({}, attr);
|
const attributes = Object.assign({}, attr);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -126,7 +132,7 @@ const controls = {
|
|||||||
createButton(buttonType, attr) {
|
createButton(buttonType, attr) {
|
||||||
const button = utils.createElement('button');
|
const button = utils.createElement('button');
|
||||||
const attributes = Object.assign({}, attr);
|
const attributes = Object.assign({}, attr);
|
||||||
let type = buttonType;
|
let type = utils.toCamelCase(buttonType);
|
||||||
|
|
||||||
let toggle = false;
|
let toggle = false;
|
||||||
let label;
|
let label;
|
||||||
@ -147,7 +153,7 @@ const controls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Large play button
|
// Large play button
|
||||||
switch (type) {
|
switch (buttonType) {
|
||||||
case 'play':
|
case 'play':
|
||||||
toggle = true;
|
toggle = true;
|
||||||
label = 'play';
|
label = 'play';
|
||||||
@ -189,7 +195,7 @@ const controls = {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
label = type;
|
label = type;
|
||||||
icon = type;
|
icon = buttonType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup toggle icon and labels
|
// Setup toggle icon and labels
|
||||||
@ -204,7 +210,6 @@ const controls = {
|
|||||||
|
|
||||||
// Add aria attributes
|
// Add aria attributes
|
||||||
attributes['aria-pressed'] = false;
|
attributes['aria-pressed'] = false;
|
||||||
attributes['aria-label'] = this.config.i18n[label];
|
|
||||||
} else {
|
} else {
|
||||||
button.appendChild(controls.createIcon.call(this, icon));
|
button.appendChild(controls.createIcon.call(this, icon));
|
||||||
button.appendChild(controls.createLabel.call(this, label));
|
button.appendChild(controls.createLabel.call(this, label));
|
||||||
@ -236,9 +241,10 @@ const controls = {
|
|||||||
'label',
|
'label',
|
||||||
{
|
{
|
||||||
for: attributes.id,
|
for: attributes.id,
|
||||||
|
id: `${attributes.id}-label`,
|
||||||
class: this.config.classNames.hidden,
|
class: this.config.classNames.hidden,
|
||||||
},
|
},
|
||||||
this.config.i18n[type],
|
i18n.get(type, this.config),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Seek input
|
// Seek input
|
||||||
@ -253,6 +259,12 @@ const controls = {
|
|||||||
step: 0.01,
|
step: 0.01,
|
||||||
value: 0,
|
value: 0,
|
||||||
autocomplete: 'off',
|
autocomplete: 'off',
|
||||||
|
// A11y fixes for https://github.com/sampotts/plyr/issues/905
|
||||||
|
role: 'slider',
|
||||||
|
'aria-labelledby': `${attributes.id}-label`,
|
||||||
|
'aria-valuemin': 0,
|
||||||
|
'aria-valuemax': 100,
|
||||||
|
'aria-valuenow': 0,
|
||||||
},
|
},
|
||||||
attributes,
|
attributes,
|
||||||
),
|
),
|
||||||
@ -279,6 +291,8 @@ const controls = {
|
|||||||
min: 0,
|
min: 0,
|
||||||
max: 100,
|
max: 100,
|
||||||
value: 0,
|
value: 0,
|
||||||
|
role: 'presentation',
|
||||||
|
'aria-hidden': true,
|
||||||
},
|
},
|
||||||
attributes,
|
attributes,
|
||||||
),
|
),
|
||||||
@ -291,11 +305,11 @@ const controls = {
|
|||||||
let suffix = '';
|
let suffix = '';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'played':
|
case 'played':
|
||||||
suffix = this.config.i18n.played;
|
suffix = i18n.get('played', this.config);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'buffer':
|
case 'buffer':
|
||||||
suffix = this.config.i18n.buffered;
|
suffix = i18n.get('buffered', this.config);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -312,22 +326,14 @@ const controls = {
|
|||||||
|
|
||||||
// Create time display
|
// Create time display
|
||||||
createTime(type) {
|
createTime(type) {
|
||||||
const container = utils.createElement('div', {
|
const attributes = utils.getAttributesFromSelector(this.config.selectors.display[type]);
|
||||||
class: 'plyr__time',
|
|
||||||
});
|
|
||||||
|
|
||||||
container.appendChild(
|
const container = utils.createElement('div', utils.extend(attributes, {
|
||||||
utils.createElement(
|
class: `plyr__time ${attributes.class}`,
|
||||||
'span',
|
'aria-label': i18n.get(type, this.config),
|
||||||
{
|
}), '0:00');
|
||||||
class: this.config.classNames.hidden,
|
|
||||||
},
|
|
||||||
this.config.i18n[type],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
container.appendChild(utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.display[type]), '00:00'));
|
|
||||||
|
|
||||||
|
// Reference for updates
|
||||||
this.elements.display[type] = container;
|
this.elements.display[type] = container;
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
@ -352,7 +358,7 @@ const controls = {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const faux = utils.createElement('span', { 'aria-hidden': true });
|
const faux = utils.createElement('span', { hidden: '' });
|
||||||
|
|
||||||
label.appendChild(radio);
|
label.appendChild(radio);
|
||||||
label.appendChild(faux);
|
label.appendChild(faux);
|
||||||
@ -383,6 +389,16 @@ const controls = {
|
|||||||
const clientRect = this.elements.inputs.seek.getBoundingClientRect();
|
const clientRect = this.elements.inputs.seek.getBoundingClientRect();
|
||||||
const visible = `${this.config.classNames.tooltip}--visible`;
|
const visible = `${this.config.classNames.tooltip}--visible`;
|
||||||
|
|
||||||
|
const toggle = toggle => {
|
||||||
|
utils.toggleClass(this.elements.display.seekTooltip, visible, toggle);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hide on touch
|
||||||
|
if (this.touch) {
|
||||||
|
toggle(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Determine percentage, if already visible
|
// Determine percentage, if already visible
|
||||||
if (utils.is.event(event)) {
|
if (utils.is.event(event)) {
|
||||||
percent = 100 / clientRect.width * (event.pageX - clientRect.left);
|
percent = 100 / clientRect.width * (event.pageX - clientRect.left);
|
||||||
@ -411,21 +427,17 @@ const controls = {
|
|||||||
'mouseenter',
|
'mouseenter',
|
||||||
'mouseleave',
|
'mouseleave',
|
||||||
].includes(event.type)) {
|
].includes(event.type)) {
|
||||||
utils.toggleClass(this.elements.display.seekTooltip, visible, event.type === 'mouseenter');
|
toggle(event.type === 'mouseenter');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Hide/show a tab
|
// Hide/show a tab
|
||||||
toggleTab(setting, toggle) {
|
toggleTab(setting, toggle) {
|
||||||
const tab = this.elements.settings.tabs[setting];
|
utils.toggleHidden(this.elements.settings.tabs[setting], !toggle);
|
||||||
const pane = this.elements.settings.panes[setting];
|
|
||||||
|
|
||||||
utils.toggleHidden(tab, !toggle);
|
|
||||||
utils.toggleHidden(pane, !toggle);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set the YouTube quality menu
|
// Set the quality menu
|
||||||
// TODO: Support for HTML5
|
// TODO: Vimeo support
|
||||||
setQualityMenu(options) {
|
setQualityMenu(options) {
|
||||||
// Menu required
|
// Menu required
|
||||||
if (!utils.is.element(this.elements.settings.panes.quality)) {
|
if (!utils.is.element(this.elements.settings.panes.quality)) {
|
||||||
@ -438,14 +450,15 @@ const controls = {
|
|||||||
// Set options if passed and filter based on config
|
// Set options if passed and filter based on config
|
||||||
if (utils.is.array(options)) {
|
if (utils.is.array(options)) {
|
||||||
this.options.quality = options.filter(quality => this.config.quality.options.includes(quality));
|
this.options.quality = options.filter(quality => this.config.quality.options.includes(quality));
|
||||||
} else {
|
|
||||||
this.options.quality = this.config.quality.options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !utils.is.empty(this.options.quality) && this.isYouTube;
|
const toggle = !utils.is.empty(this.options.quality) && this.options.quality.length > 1;
|
||||||
controls.toggleTab.call(this, type, toggle);
|
controls.toggleTab.call(this, type, toggle);
|
||||||
|
|
||||||
|
// Check if we need to toggle the parent
|
||||||
|
controls.checkMenu.call(this);
|
||||||
|
|
||||||
// If we're hiding, nothing more to do
|
// If we're hiding, nothing more to do
|
||||||
if (!toggle) {
|
if (!toggle) {
|
||||||
return;
|
return;
|
||||||
@ -459,20 +472,18 @@ const controls = {
|
|||||||
let label = '';
|
let label = '';
|
||||||
|
|
||||||
switch (quality) {
|
switch (quality) {
|
||||||
case 'hd2160':
|
case 2160:
|
||||||
label = '4K';
|
label = '4K';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'hd1440':
|
case 1440:
|
||||||
label = 'WQHD';
|
case 1080:
|
||||||
break;
|
case 720:
|
||||||
|
|
||||||
case 'hd1080':
|
|
||||||
label = 'HD';
|
label = 'HD';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'hd720':
|
case 576:
|
||||||
label = 'HD';
|
label = 'SD';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -486,9 +497,16 @@ const controls = {
|
|||||||
return controls.createBadge.call(this, label);
|
return controls.createBadge.call(this, label);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.options.quality.forEach(quality =>
|
// Sort options by the config and then render options
|
||||||
controls.createMenuItem.call(this, quality, list, type, controls.getLabel.call(this, 'quality', quality), getBadge(quality)),
|
this.options.quality
|
||||||
);
|
.sort((a, b) => {
|
||||||
|
const sorting = this.config.quality.options;
|
||||||
|
return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
|
||||||
|
})
|
||||||
|
.forEach(quality => {
|
||||||
|
const label = controls.getLabel.call(this, 'quality', quality);
|
||||||
|
controls.createMenuItem.call(this, quality, list, type, label, getBadge(quality));
|
||||||
|
});
|
||||||
|
|
||||||
controls.updateSetting.call(this, type, list);
|
controls.updateSetting.call(this, type, list);
|
||||||
},
|
},
|
||||||
@ -498,34 +516,17 @@ const controls = {
|
|||||||
getLabel(setting, value) {
|
getLabel(setting, value) {
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
case 'speed':
|
case 'speed':
|
||||||
return value === 1 ? 'Normal' : `${value}×`;
|
return value === 1 ? i18n.get('normal', this.config) : `${value}×`;
|
||||||
|
|
||||||
case 'quality':
|
case 'quality':
|
||||||
switch (value) {
|
if (utils.is.number(value)) {
|
||||||
case 'hd2160':
|
return `${value}p`;
|
||||||
return '2160P';
|
|
||||||
case 'hd1440':
|
|
||||||
return '1440P';
|
|
||||||
case 'hd1080':
|
|
||||||
return '1080P';
|
|
||||||
case 'hd720':
|
|
||||||
return '720P';
|
|
||||||
case 'large':
|
|
||||||
return '480P';
|
|
||||||
case 'medium':
|
|
||||||
return '360P';
|
|
||||||
case 'small':
|
|
||||||
return '240P';
|
|
||||||
case 'tiny':
|
|
||||||
return 'Tiny';
|
|
||||||
case 'default':
|
|
||||||
return 'Auto';
|
|
||||||
default:
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return utils.toTitleCase(value);
|
||||||
|
|
||||||
case 'captions':
|
case 'captions':
|
||||||
return controls.getLanguage.call(this);
|
return captions.getLabel.call(this);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
@ -533,18 +534,27 @@ const controls = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Update the selected setting
|
// Update the selected setting
|
||||||
updateSetting(setting, container) {
|
updateSetting(setting, container, input) {
|
||||||
const pane = this.elements.settings.panes[setting];
|
const pane = this.elements.settings.panes[setting];
|
||||||
let value = null;
|
let value = null;
|
||||||
let list = container;
|
let list = container;
|
||||||
|
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
case 'captions':
|
case 'captions':
|
||||||
value = this.captions.active ? this.captions.language : '';
|
if (this.captions.active) {
|
||||||
|
if (this.options.captions.length > 2 || !this.options.captions.some(lang => lang === 'enabled')) {
|
||||||
|
value = this.captions.language;
|
||||||
|
} else {
|
||||||
|
value = 'enabled';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = '';
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
value = this[setting];
|
value = !utils.is.empty(input) ? input : this[setting];
|
||||||
|
|
||||||
// Get default
|
// Get default
|
||||||
if (utils.is.empty(value)) {
|
if (utils.is.empty(value)) {
|
||||||
@ -552,7 +562,7 @@ const controls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unsupported value
|
// Unsupported value
|
||||||
if (!this.options[setting].includes(value)) {
|
if (!utils.is.empty(this.options[setting]) && !this.options[setting].includes(value)) {
|
||||||
this.debug.warn(`Unsupported value of '${value}' for ${setting}`);
|
this.debug.warn(`Unsupported value of '${value}' for ${setting}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -571,17 +581,19 @@ const controls = {
|
|||||||
list = pane && pane.querySelector('ul');
|
list = pane && pane.querySelector('ul');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the label
|
// If there's no list it means it's not been rendered...
|
||||||
if (!utils.is.empty(value)) {
|
if (!utils.is.element(list)) {
|
||||||
const label = this.elements.settings.tabs[setting].querySelector(`.${this.config.classNames.menu.value}`);
|
return;
|
||||||
label.innerHTML = controls.getLabel.call(this, setting, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the radio option
|
// Update the label
|
||||||
|
const label = this.elements.settings.tabs[setting].querySelector(`.${this.config.classNames.menu.value}`);
|
||||||
|
label.innerHTML = controls.getLabel.call(this, setting, value);
|
||||||
|
|
||||||
|
// Find the radio option and check it
|
||||||
const target = list && list.querySelector(`input[value="${value}"]`);
|
const target = list && list.querySelector(`input[value="${value}"]`);
|
||||||
|
|
||||||
if (utils.is.element(target)) {
|
if (utils.is.element(target)) {
|
||||||
// Check it
|
|
||||||
target.checked = true;
|
target.checked = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -617,7 +629,7 @@ const controls = {
|
|||||||
class: this.config.classNames.control,
|
class: this.config.classNames.control,
|
||||||
'data-plyr-loop-action': option,
|
'data-plyr-loop-action': option,
|
||||||
}),
|
}),
|
||||||
this.config.i18n[option]
|
i18n.get(option, this.config)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (['start', 'end'].includes(option)) {
|
if (['start', 'end'].includes(option)) {
|
||||||
@ -632,25 +644,6 @@ const controls = {
|
|||||||
|
|
||||||
// Get current selected caption language
|
// Get current selected caption language
|
||||||
// TODO: rework this to user the getter in the API?
|
// TODO: rework this to user the getter in the API?
|
||||||
getLanguage() {
|
|
||||||
if (!this.supported.ui) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!support.textTracks || !captions.getTracks.call(this).length) {
|
|
||||||
return this.config.i18n.none;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.captions.active) {
|
|
||||||
const currentTrack = captions.getCurrentTrack.call(this);
|
|
||||||
|
|
||||||
if (utils.is.track(currentTrack)) {
|
|
||||||
return currentTrack.label;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.config.i18n.disabled;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
setCaptionsMenu() {
|
setCaptionsMenu() {
|
||||||
@ -659,27 +652,30 @@ const controls = {
|
|||||||
const list = this.elements.settings.panes.captions.querySelector('ul');
|
const list = this.elements.settings.panes.captions.querySelector('ul');
|
||||||
|
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const hasTracks = captions.getTracks.call(this).length;
|
const toggle = captions.getTracks.call(this).length;
|
||||||
controls.toggleTab.call(this, type, hasTracks);
|
controls.toggleTab.call(this, type, toggle);
|
||||||
|
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
utils.emptyElement(list);
|
utils.emptyElement(list);
|
||||||
|
|
||||||
|
// Check if we need to toggle the parent
|
||||||
|
controls.checkMenu.call(this);
|
||||||
|
|
||||||
// If there's no captions, bail
|
// If there's no captions, bail
|
||||||
if (!hasTracks) {
|
if (!toggle) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-map the tracks into just the data we need
|
// Re-map the tracks into just the data we need
|
||||||
const tracks = captions.getTracks.call(this).map(track => ({
|
const tracks = captions.getTracks.call(this).map(track => ({
|
||||||
language: track.language,
|
language: !utils.is.empty(track.language) ? track.language : 'enabled',
|
||||||
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
|
label: captions.getLabel.call(this, track),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Add the "None" option to turn off captions
|
// Add the "Disabled" option to turn off captions
|
||||||
tracks.unshift({
|
tracks.unshift({
|
||||||
language: '',
|
language: '',
|
||||||
label: this.config.i18n.none,
|
label: i18n.get('disabled', this.config),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate options
|
// Generate options
|
||||||
@ -689,17 +685,25 @@ const controls = {
|
|||||||
track.language,
|
track.language,
|
||||||
list,
|
list,
|
||||||
'language',
|
'language',
|
||||||
track.label || track.language,
|
track.label,
|
||||||
controls.createBadge.call(this, track.language.toUpperCase()),
|
track.language !== 'enabled' ? controls.createBadge.call(this, track.language.toUpperCase()) : null,
|
||||||
track.language.toLowerCase() === this.captions.language.toLowerCase(),
|
track.language.toLowerCase() === this.captions.language.toLowerCase(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Store reference
|
||||||
|
this.options.captions = tracks.map(track => track.language);
|
||||||
|
|
||||||
controls.updateSetting.call(this, type, list);
|
controls.updateSetting.call(this, type, list);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
setSpeedMenu() {
|
setSpeedMenu(options) {
|
||||||
|
// Do nothing if not selected
|
||||||
|
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Menu required
|
// Menu required
|
||||||
if (!utils.is.element(this.elements.settings.panes.speed)) {
|
if (!utils.is.element(this.elements.settings.panes.speed)) {
|
||||||
return;
|
return;
|
||||||
@ -707,8 +711,10 @@ const controls = {
|
|||||||
|
|
||||||
const type = 'speed';
|
const type = 'speed';
|
||||||
|
|
||||||
// Set the default speeds
|
// Set the speed options
|
||||||
if (!utils.is.object(this.options.speed) || !Object.keys(this.options.speed).length) {
|
if (utils.is.array(options)) {
|
||||||
|
this.options.speed = options;
|
||||||
|
} else if (this.isHTML5 || this.isVimeo) {
|
||||||
this.options.speed = [
|
this.options.speed = [
|
||||||
0.5,
|
0.5,
|
||||||
0.75,
|
0.75,
|
||||||
@ -724,9 +730,12 @@ const controls = {
|
|||||||
this.options.speed = this.options.speed.filter(speed => this.config.speed.options.includes(speed));
|
this.options.speed = this.options.speed.filter(speed => this.config.speed.options.includes(speed));
|
||||||
|
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !utils.is.empty(this.options.speed);
|
const toggle = !utils.is.empty(this.options.speed) && this.options.speed.length > 1;
|
||||||
controls.toggleTab.call(this, type, toggle);
|
controls.toggleTab.call(this, type, toggle);
|
||||||
|
|
||||||
|
// Check if we need to toggle the parent
|
||||||
|
controls.checkMenu.call(this);
|
||||||
|
|
||||||
// If we're hiding, nothing more to do
|
// If we're hiding, nothing more to do
|
||||||
if (!toggle) {
|
if (!toggle) {
|
||||||
return;
|
return;
|
||||||
@ -735,19 +744,26 @@ const controls = {
|
|||||||
// Get the list to populate
|
// Get the list to populate
|
||||||
const list = this.elements.settings.panes.speed.querySelector('ul');
|
const list = this.elements.settings.panes.speed.querySelector('ul');
|
||||||
|
|
||||||
// Show the pane and tab
|
|
||||||
utils.toggleHidden(this.elements.settings.tabs.speed, false);
|
|
||||||
utils.toggleHidden(this.elements.settings.panes.speed, false);
|
|
||||||
|
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
utils.emptyElement(list);
|
utils.emptyElement(list);
|
||||||
|
|
||||||
// Create items
|
// Create items
|
||||||
this.options.speed.forEach(speed => controls.createMenuItem.call(this, speed, list, type, controls.getLabel.call(this, 'speed', speed)));
|
this.options.speed.forEach(speed => {
|
||||||
|
const label = controls.getLabel.call(this, 'speed', speed);
|
||||||
|
controls.createMenuItem.call(this, speed, list, type, label);
|
||||||
|
});
|
||||||
|
|
||||||
controls.updateSetting.call(this, type, list);
|
controls.updateSetting.call(this, type, list);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Check if we need to hide/show the settings menu
|
||||||
|
checkMenu() {
|
||||||
|
const { tabs } = this.elements.settings;
|
||||||
|
const visible = !utils.is.empty(tabs) && Object.values(tabs).some(tab => !tab.hidden);
|
||||||
|
|
||||||
|
utils.toggleHidden(this.elements.settings.menu, !visible);
|
||||||
|
},
|
||||||
|
|
||||||
// Show/hide menu
|
// Show/hide menu
|
||||||
toggleMenu(event) {
|
toggleMenu(event) {
|
||||||
const { form } = this.elements.settings;
|
const { form } = this.elements.settings;
|
||||||
@ -758,7 +774,7 @@ const controls = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const show = utils.is.boolean(event) ? event : utils.is.element(form) && form.getAttribute('aria-hidden') === 'true';
|
const show = utils.is.boolean(event) ? event : utils.is.element(form) && form.hasAttribute('hidden');
|
||||||
|
|
||||||
if (utils.is.event(event)) {
|
if (utils.is.event(event)) {
|
||||||
const isMenuItem = utils.is.element(form) && form.contains(event.target);
|
const isMenuItem = utils.is.element(form) && form.contains(event.target);
|
||||||
@ -783,7 +799,7 @@ const controls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (utils.is.element(form)) {
|
if (utils.is.element(form)) {
|
||||||
form.setAttribute('aria-hidden', !show);
|
utils.toggleHidden(form, !show);
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.menu.open, show);
|
utils.toggleClass(this.elements.container, this.config.classNames.menu.open, show);
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
@ -799,7 +815,7 @@ const controls = {
|
|||||||
const clone = tab.cloneNode(true);
|
const clone = tab.cloneNode(true);
|
||||||
clone.style.position = 'absolute';
|
clone.style.position = 'absolute';
|
||||||
clone.style.opacity = 0;
|
clone.style.opacity = 0;
|
||||||
clone.setAttribute('aria-hidden', false);
|
clone.removeAttribute('hidden');
|
||||||
|
|
||||||
// Prevent input's being unchecked due to the name being identical
|
// Prevent input's being unchecked due to the name being identical
|
||||||
Array.from(clone.querySelectorAll('input[name]')).forEach(input => {
|
Array.from(clone.querySelectorAll('input[name]')).forEach(input => {
|
||||||
@ -843,7 +859,7 @@ const controls = {
|
|||||||
|
|
||||||
// Hide all other tabs
|
// Hide all other tabs
|
||||||
// Get other tabs
|
// Get other tabs
|
||||||
const current = menu.querySelector('[role="tabpanel"][aria-hidden="false"]');
|
const current = menu.querySelector('[role="tabpanel"]:not([hidden])');
|
||||||
const container = current.parentNode;
|
const container = current.parentNode;
|
||||||
|
|
||||||
// Set other toggles to be expanded false
|
// Set other toggles to be expanded false
|
||||||
@ -887,11 +903,11 @@ const controls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set attributes on current tab
|
// Set attributes on current tab
|
||||||
current.setAttribute('aria-hidden', true);
|
utils.toggleHidden(current, true);
|
||||||
current.setAttribute('tabindex', -1);
|
current.setAttribute('tabindex', -1);
|
||||||
|
|
||||||
// Set attributes on target
|
// Set attributes on target
|
||||||
pane.setAttribute('aria-hidden', !show);
|
utils.toggleHidden(pane, !show);
|
||||||
tab.setAttribute('aria-expanded', show);
|
tab.setAttribute('aria-expanded', show);
|
||||||
pane.removeAttribute('tabindex');
|
pane.removeAttribute('tabindex');
|
||||||
|
|
||||||
@ -1018,6 +1034,7 @@ const controls = {
|
|||||||
if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) {
|
if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) {
|
||||||
const menu = utils.createElement('div', {
|
const menu = utils.createElement('div', {
|
||||||
class: 'plyr__menu',
|
class: 'plyr__menu',
|
||||||
|
hidden: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
menu.appendChild(
|
menu.appendChild(
|
||||||
@ -1032,7 +1049,7 @@ const controls = {
|
|||||||
const form = utils.createElement('form', {
|
const form = utils.createElement('form', {
|
||||||
class: 'plyr__menu__container',
|
class: 'plyr__menu__container',
|
||||||
id: `plyr-settings-${data.id}`,
|
id: `plyr-settings-${data.id}`,
|
||||||
'aria-hidden': true,
|
hidden: '',
|
||||||
'aria-labelled-by': `plyr-settings-toggle-${data.id}`,
|
'aria-labelled-by': `plyr-settings-toggle-${data.id}`,
|
||||||
role: 'tablist',
|
role: 'tablist',
|
||||||
tabindex: -1,
|
tabindex: -1,
|
||||||
@ -1042,7 +1059,6 @@ const controls = {
|
|||||||
|
|
||||||
const home = utils.createElement('div', {
|
const home = utils.createElement('div', {
|
||||||
id: `plyr-settings-${data.id}-home`,
|
id: `plyr-settings-${data.id}-home`,
|
||||||
'aria-hidden': false,
|
|
||||||
'aria-labelled-by': `plyr-settings-toggle-${data.id}`,
|
'aria-labelled-by': `plyr-settings-toggle-${data.id}`,
|
||||||
role: 'tabpanel',
|
role: 'tabpanel',
|
||||||
});
|
});
|
||||||
@ -1069,7 +1085,7 @@ const controls = {
|
|||||||
'aria-controls': `plyr-settings-${data.id}-${type}`,
|
'aria-controls': `plyr-settings-${data.id}-${type}`,
|
||||||
'aria-expanded': false,
|
'aria-expanded': false,
|
||||||
}),
|
}),
|
||||||
this.config.i18n[type],
|
i18n.get(type, this.config),
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = utils.createElement('span', {
|
const value = utils.createElement('span', {
|
||||||
@ -1093,11 +1109,10 @@ const controls = {
|
|||||||
this.config.settings.forEach(type => {
|
this.config.settings.forEach(type => {
|
||||||
const pane = utils.createElement('div', {
|
const pane = utils.createElement('div', {
|
||||||
id: `plyr-settings-${data.id}-${type}`,
|
id: `plyr-settings-${data.id}-${type}`,
|
||||||
'aria-hidden': true,
|
hidden: '',
|
||||||
'aria-labelled-by': `plyr-settings-${data.id}-${type}-tab`,
|
'aria-labelled-by': `plyr-settings-${data.id}-${type}-tab`,
|
||||||
role: 'tabpanel',
|
role: 'tabpanel',
|
||||||
tabindex: -1,
|
tabindex: -1,
|
||||||
hidden: '',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const back = utils.createElement(
|
const back = utils.createElement(
|
||||||
@ -1109,7 +1124,7 @@ const controls = {
|
|||||||
'aria-controls': `plyr-settings-${data.id}-home`,
|
'aria-controls': `plyr-settings-${data.id}-home`,
|
||||||
'aria-expanded': false,
|
'aria-expanded': false,
|
||||||
},
|
},
|
||||||
this.config.i18n[type],
|
i18n.get(type, this.config),
|
||||||
);
|
);
|
||||||
|
|
||||||
pane.appendChild(back);
|
pane.appendChild(back);
|
||||||
@ -1152,10 +1167,12 @@ const controls = {
|
|||||||
|
|
||||||
this.elements.controls = container;
|
this.elements.controls = container;
|
||||||
|
|
||||||
if (this.config.controls.includes('settings') && this.config.settings.includes('speed')) {
|
if (this.isHTML5) {
|
||||||
controls.setSpeedMenu.call(this);
|
controls.setQualityMenu.call(this, html5.getQualityOptions.call(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controls.setSpeedMenu.call(this);
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1178,17 +1195,21 @@ const controls = {
|
|||||||
let container = null;
|
let container = null;
|
||||||
this.elements.controls = null;
|
this.elements.controls = null;
|
||||||
|
|
||||||
// HTML or Element passed as the option
|
// Set template properties
|
||||||
|
const props = {
|
||||||
|
id: this.id,
|
||||||
|
seektime: this.config.seekTime,
|
||||||
|
title: this.config.title,
|
||||||
|
};
|
||||||
|
let update = true;
|
||||||
|
|
||||||
if (utils.is.string(this.config.controls) || utils.is.element(this.config.controls)) {
|
if (utils.is.string(this.config.controls) || utils.is.element(this.config.controls)) {
|
||||||
|
// String or HTMLElement passed as the option
|
||||||
container = this.config.controls;
|
container = this.config.controls;
|
||||||
} else if (utils.is.function(this.config.controls)) {
|
} else if (utils.is.function(this.config.controls)) {
|
||||||
// A custom function to build controls
|
// A custom function to build controls
|
||||||
// The function can return a HTMLElement or String
|
// The function can return a HTMLElement or String
|
||||||
container = this.config.controls({
|
container = this.config.controls.call(this, props);
|
||||||
id: this.id,
|
|
||||||
seektime: this.config.seekTime,
|
|
||||||
title: this.config.title,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// Create controls
|
// Create controls
|
||||||
container = controls.create.call(this, {
|
container = controls.create.call(this, {
|
||||||
@ -1196,10 +1217,34 @@ const controls = {
|
|||||||
seektime: this.config.seekTime,
|
seektime: this.config.seekTime,
|
||||||
speed: this.speed,
|
speed: this.speed,
|
||||||
quality: this.quality,
|
quality: this.quality,
|
||||||
captions: controls.getLanguage.call(this),
|
captions: captions.getLabel.call(this),
|
||||||
// TODO: Looping
|
// TODO: Looping
|
||||||
// loop: 'None',
|
// loop: 'None',
|
||||||
});
|
});
|
||||||
|
update = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace props with their value
|
||||||
|
const replace = input => {
|
||||||
|
let result = input;
|
||||||
|
|
||||||
|
Object.entries(props).forEach(([
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
]) => {
|
||||||
|
result = utils.replaceAll(result, `{${key}}`, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update markup
|
||||||
|
if (update) {
|
||||||
|
if (utils.is.string(this.config.controls)) {
|
||||||
|
container = replace(container);
|
||||||
|
} else if (utils.is.element(container)) {
|
||||||
|
container.innerHTML = replace(container.innerHTML);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Controls container
|
// Controls container
|
||||||
@ -1218,7 +1263,7 @@ const controls = {
|
|||||||
// Inject controls HTML
|
// Inject controls HTML
|
||||||
if (utils.is.element(container)) {
|
if (utils.is.element(container)) {
|
||||||
target.appendChild(container);
|
target.appendChild(container);
|
||||||
} else {
|
} else if (container) {
|
||||||
target.insertAdjacentHTML('beforeend', container);
|
target.insertAdjacentHTML('beforeend', container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,24 +56,26 @@ const defaults = {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/3.0.0-beta.19/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.2.4/plyr.svg',
|
||||||
|
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
|
|
||||||
// Quality default
|
// Quality default
|
||||||
quality: {
|
quality: {
|
||||||
default: 'default',
|
default: 576,
|
||||||
options: [
|
options: [
|
||||||
'hd2160',
|
4320,
|
||||||
'hd1440',
|
2880,
|
||||||
'hd1080',
|
2160,
|
||||||
'hd720',
|
1440,
|
||||||
'large',
|
1080,
|
||||||
'medium',
|
720,
|
||||||
'small',
|
576,
|
||||||
'tiny',
|
480,
|
||||||
'default',
|
360,
|
||||||
|
240,
|
||||||
|
'default', // YouTube's "auto"
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -113,7 +115,7 @@ const defaults = {
|
|||||||
// Captions settings
|
// Captions settings
|
||||||
captions: {
|
captions: {
|
||||||
active: false,
|
active: false,
|
||||||
language: window.navigator.language.split('-')[0],
|
language: (navigator.language || navigator.userLanguage).split('-')[0],
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fullscreen settings
|
// Fullscreen settings
|
||||||
@ -132,7 +134,10 @@ const defaults = {
|
|||||||
// Default controls
|
// Default controls
|
||||||
controls: [
|
controls: [
|
||||||
'play-large',
|
'play-large',
|
||||||
|
// 'restart',
|
||||||
|
// 'rewind',
|
||||||
'play',
|
'play',
|
||||||
|
// 'fast-forward',
|
||||||
'progress',
|
'progress',
|
||||||
'current-time',
|
'current-time',
|
||||||
'mute',
|
'mute',
|
||||||
@ -155,7 +160,7 @@ const defaults = {
|
|||||||
rewind: 'Rewind {seektime} secs',
|
rewind: 'Rewind {seektime} secs',
|
||||||
play: 'Play',
|
play: 'Play',
|
||||||
pause: 'Pause',
|
pause: 'Pause',
|
||||||
forward: 'Forward {seektime} secs',
|
fastForward: 'Forward {seektime} secs',
|
||||||
seek: 'Seek',
|
seek: 'Seek',
|
||||||
played: 'Played',
|
played: 'Played',
|
||||||
buffered: 'Buffered',
|
buffered: 'Buffered',
|
||||||
@ -172,14 +177,15 @@ const defaults = {
|
|||||||
captions: 'Captions',
|
captions: 'Captions',
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
speed: 'Speed',
|
speed: 'Speed',
|
||||||
|
normal: 'Normal',
|
||||||
quality: 'Quality',
|
quality: 'Quality',
|
||||||
loop: 'Loop',
|
loop: 'Loop',
|
||||||
start: 'Start',
|
start: 'Start',
|
||||||
end: 'End',
|
end: 'End',
|
||||||
all: 'All',
|
all: 'All',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
none: 'None',
|
|
||||||
disabled: 'Disabled',
|
disabled: 'Disabled',
|
||||||
|
enabled: 'Enabled',
|
||||||
advertisement: 'Ad',
|
advertisement: 'Ad',
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -203,7 +209,7 @@ const defaults = {
|
|||||||
pause: null,
|
pause: null,
|
||||||
restart: null,
|
restart: null,
|
||||||
rewind: null,
|
rewind: null,
|
||||||
forward: null,
|
fastForward: null,
|
||||||
mute: null,
|
mute: null,
|
||||||
volume: null,
|
volume: null,
|
||||||
captions: null,
|
captions: null,
|
||||||
@ -283,7 +289,7 @@ const defaults = {
|
|||||||
pause: '[data-plyr="pause"]',
|
pause: '[data-plyr="pause"]',
|
||||||
restart: '[data-plyr="restart"]',
|
restart: '[data-plyr="restart"]',
|
||||||
rewind: '[data-plyr="rewind"]',
|
rewind: '[data-plyr="rewind"]',
|
||||||
forward: '[data-plyr="fast-forward"]',
|
fastForward: '[data-plyr="fast-forward"]',
|
||||||
mute: '[data-plyr="mute"]',
|
mute: '[data-plyr="mute"]',
|
||||||
captions: '[data-plyr="captions"]',
|
captions: '[data-plyr="captions"]',
|
||||||
fullscreen: '[data-plyr="fullscreen"]',
|
fullscreen: '[data-plyr="fullscreen"]',
|
||||||
@ -376,7 +382,7 @@ const defaults = {
|
|||||||
// Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
|
// Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
|
||||||
ads: {
|
ads: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
publisherId: '918848828995742',
|
publisherId: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Fullscreen wrapper
|
// Fullscreen wrapper
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API#prefixing
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
@ -54,6 +55,7 @@ class Fullscreen {
|
|||||||
|
|
||||||
// Get prefix
|
// Get prefix
|
||||||
this.prefix = Fullscreen.prefix;
|
this.prefix = Fullscreen.prefix;
|
||||||
|
this.property = Fullscreen.property;
|
||||||
|
|
||||||
// Scroll position
|
// Scroll position
|
||||||
this.scrollPosition = { x: 0, y: 0 };
|
this.scrollPosition = { x: 0, y: 0 };
|
||||||
@ -66,13 +68,15 @@ class Fullscreen {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Fullscreen toggle on double click
|
// Fullscreen toggle on double click
|
||||||
utils.on(this.player.elements.container, 'dblclick', () => {
|
utils.on(this.player.elements.container, 'dblclick', event => {
|
||||||
|
// Ignore double click in controls
|
||||||
|
if (utils.is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.toggle();
|
this.toggle();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prevent double click on controls bubbling up
|
|
||||||
utils.on(this.player.elements.controls, 'dblclick', event => event.stopPropagation());
|
|
||||||
|
|
||||||
// Update the UI
|
// Update the UI
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
@ -85,8 +89,8 @@ class Fullscreen {
|
|||||||
// Get the prefix for handlers
|
// Get the prefix for handlers
|
||||||
static get prefix() {
|
static get prefix() {
|
||||||
// No prefix
|
// No prefix
|
||||||
if (utils.is.function(document.cancelFullScreen)) {
|
if (utils.is.function(document.exitFullscreen)) {
|
||||||
return false;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for fullscreen support by vendor prefix
|
// Check for fullscreen support by vendor prefix
|
||||||
@ -98,12 +102,9 @@ class Fullscreen {
|
|||||||
];
|
];
|
||||||
|
|
||||||
prefixes.some(pre => {
|
prefixes.some(pre => {
|
||||||
if (utils.is.function(document[`${pre}CancelFullScreen`])) {
|
if (utils.is.function(document[`${pre}ExitFullscreen`]) || utils.is.function(document[`${pre}CancelFullScreen`])) {
|
||||||
value = pre;
|
value = pre;
|
||||||
return true;
|
return true;
|
||||||
} else if (utils.is.function(document.msExitFullscreen)) {
|
|
||||||
value = 'ms';
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -112,11 +113,18 @@ class Fullscreen {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get property() {
|
||||||
|
return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if fullscreen is enabled
|
// Determine if fullscreen is enabled
|
||||||
get enabled() {
|
get enabled() {
|
||||||
const fallback = this.player.config.fullscreen.fallback && !utils.inFrame();
|
return (
|
||||||
|
(Fullscreen.native || this.player.config.fullscreen.fallback) &&
|
||||||
return (Fullscreen.native || fallback) && this.player.config.fullscreen.enabled && this.player.supported.ui && this.player.isVideo;
|
this.player.config.fullscreen.enabled &&
|
||||||
|
this.player.supported.ui &&
|
||||||
|
this.player.isVideo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get active state
|
// Get active state
|
||||||
@ -130,7 +138,7 @@ class Fullscreen {
|
|||||||
return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
|
return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}FullscreenElement`];
|
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.property}Element`];
|
||||||
|
|
||||||
return element === this.target;
|
return element === this.target;
|
||||||
}
|
}
|
||||||
@ -166,9 +174,9 @@ class Fullscreen {
|
|||||||
} else if (!Fullscreen.native) {
|
} else if (!Fullscreen.native) {
|
||||||
toggleFallback.call(this, true);
|
toggleFallback.call(this, true);
|
||||||
} else if (!this.prefix) {
|
} else if (!this.prefix) {
|
||||||
this.target.requestFullScreen();
|
this.target.requestFullscreen();
|
||||||
} else if (!utils.is.empty(this.prefix)) {
|
} else if (!utils.is.empty(this.prefix)) {
|
||||||
this.target[`${this.prefix}${this.prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen'}`]();
|
this.target[`${this.prefix}Request${this.property}`]();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,9 +193,10 @@ class Fullscreen {
|
|||||||
} else if (!Fullscreen.native) {
|
} else if (!Fullscreen.native) {
|
||||||
toggleFallback.call(this, false);
|
toggleFallback.call(this, false);
|
||||||
} else if (!this.prefix) {
|
} else if (!this.prefix) {
|
||||||
document.cancelFullScreen();
|
(document.cancelFullScreen || document.exitFullscreen).call(document);
|
||||||
} else if (!utils.is.empty(this.prefix)) {
|
} else if (!utils.is.empty(this.prefix)) {
|
||||||
document[`${this.prefix}${this.prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen'}`]();
|
const action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
|
||||||
|
document[`${this.prefix}${action}${this.property}`]();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
146
src/js/html5.js
Normal file
146
src/js/html5.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// ==========================================================================
|
||||||
|
// Plyr HTML5 helpers
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
import support from './support';
|
||||||
|
import utils from './utils';
|
||||||
|
|
||||||
|
const html5 = {
|
||||||
|
getSources() {
|
||||||
|
if (!this.isHTML5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.media.querySelectorAll('source');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get quality levels
|
||||||
|
getQualityOptions() {
|
||||||
|
if (!this.isHTML5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get sources
|
||||||
|
const sources = html5.getSources.call(this);
|
||||||
|
|
||||||
|
if (utils.is.empty(sources)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get <source> with size attribute
|
||||||
|
const sizes = Array.from(sources).filter(source => !utils.is.empty(source.getAttribute('size')));
|
||||||
|
|
||||||
|
// If none, bail
|
||||||
|
if (utils.is.empty(sizes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce to unique list
|
||||||
|
return utils.dedupe(sizes.map(source => Number(source.getAttribute('size'))));
|
||||||
|
},
|
||||||
|
|
||||||
|
extend() {
|
||||||
|
if (!this.isHTML5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = this;
|
||||||
|
|
||||||
|
// Quality
|
||||||
|
Object.defineProperty(player.media, 'quality', {
|
||||||
|
get() {
|
||||||
|
// Get sources
|
||||||
|
const sources = html5.getSources.call(player);
|
||||||
|
|
||||||
|
if (utils.is.empty(sources)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = Array.from(sources).filter(source => source.getAttribute('src') === player.source);
|
||||||
|
|
||||||
|
if (utils.is.empty(matches)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(matches[0].getAttribute('size'));
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
// Get sources
|
||||||
|
const sources = html5.getSources.call(player);
|
||||||
|
|
||||||
|
if (utils.is.empty(sources)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get matches for requested size
|
||||||
|
const matches = Array.from(sources).filter(source => Number(source.getAttribute('size')) === input);
|
||||||
|
|
||||||
|
// No matches for requested size
|
||||||
|
if (utils.is.empty(matches)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get supported sources
|
||||||
|
const supported = matches.filter(source => support.mime.call(player, source.getAttribute('type')));
|
||||||
|
|
||||||
|
// No supported sources
|
||||||
|
if (utils.is.empty(supported)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger change event
|
||||||
|
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
|
||||||
|
quality: input,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get current state
|
||||||
|
const { currentTime, playing } = player;
|
||||||
|
|
||||||
|
// Set new source
|
||||||
|
player.media.src = supported[0].getAttribute('src');
|
||||||
|
|
||||||
|
// Load new source
|
||||||
|
player.media.load();
|
||||||
|
|
||||||
|
// Resume playing
|
||||||
|
if (playing) {
|
||||||
|
player.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore time
|
||||||
|
player.currentTime = currentTime;
|
||||||
|
|
||||||
|
// Trigger change event
|
||||||
|
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
|
||||||
|
quality: input,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Cancel current network requests
|
||||||
|
// See https://github.com/sampotts/plyr/issues/174
|
||||||
|
cancelRequests() {
|
||||||
|
if (!this.isHTML5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove child sources
|
||||||
|
utils.removeElement(html5.getSources());
|
||||||
|
|
||||||
|
// Set blank video src attribute
|
||||||
|
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
|
||||||
|
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
|
||||||
|
this.media.setAttribute('src', this.config.blankVideo);
|
||||||
|
|
||||||
|
// Load the new empty source
|
||||||
|
// This will cancel existing requests
|
||||||
|
// See https://github.com/sampotts/plyr/issues/174
|
||||||
|
this.media.load();
|
||||||
|
|
||||||
|
// Debugging
|
||||||
|
this.debug.log('Cancelled network requests');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default html5;
|
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;
|
@ -2,7 +2,6 @@
|
|||||||
// Plyr Event Listeners
|
// Plyr Event Listeners
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import support from './support';
|
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
@ -17,6 +16,7 @@ class Listeners {
|
|||||||
|
|
||||||
this.handleKey = this.handleKey.bind(this);
|
this.handleKey = this.handleKey.bind(this);
|
||||||
this.toggleMenu = this.toggleMenu.bind(this);
|
this.toggleMenu = this.toggleMenu.bind(this);
|
||||||
|
this.firstTouch = this.firstTouch.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle key presses
|
// Handle key presses
|
||||||
@ -187,8 +187,19 @@ class Listeners {
|
|||||||
controls.toggleMenu.call(this.player, event);
|
controls.toggleMenu.call(this.player, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Device is touch enabled
|
||||||
|
firstTouch() {
|
||||||
|
this.player.touch = true;
|
||||||
|
|
||||||
|
// Add touch class
|
||||||
|
utils.toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
utils.off(document.body, 'touchstart', this.firstTouch);
|
||||||
|
}
|
||||||
|
|
||||||
// Global window & document listeners
|
// Global window & document listeners
|
||||||
global(toggle) {
|
global(toggle = true) {
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
if (this.player.config.keyboard.global) {
|
if (this.player.config.keyboard.global) {
|
||||||
utils.toggleListener(window, 'keydown keyup', this.handleKey, toggle, false);
|
utils.toggleListener(window, 'keydown keyup', this.handleKey, toggle, false);
|
||||||
@ -196,6 +207,9 @@ class Listeners {
|
|||||||
|
|
||||||
// Click anywhere closes menu
|
// Click anywhere closes menu
|
||||||
utils.toggleListener(document.body, 'click', this.toggleMenu, toggle);
|
utils.toggleListener(document.body, 'click', this.toggleMenu, toggle);
|
||||||
|
|
||||||
|
// Detect touch by events
|
||||||
|
utils.on(document.body, 'touchstart', this.firstTouch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container listeners
|
// Container listeners
|
||||||
@ -239,7 +253,7 @@ class Listeners {
|
|||||||
utils.on(this.player.media, 'timeupdate seeking', event => ui.timeUpdate.call(this.player, event));
|
utils.on(this.player.media, 'timeupdate seeking', event => ui.timeUpdate.call(this.player, event));
|
||||||
|
|
||||||
// Display duration
|
// Display duration
|
||||||
utils.on(this.player.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this.player, event));
|
utils.on(this.player.media, 'durationchange loadeddata loadedmetadata', event => ui.durationUpdate.call(this.player, event));
|
||||||
|
|
||||||
// Check for audio tracks on load
|
// Check for audio tracks on load
|
||||||
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
||||||
@ -263,18 +277,32 @@ class Listeners {
|
|||||||
// Check for buffer progress
|
// Check for buffer progress
|
||||||
utils.on(this.player.media, 'progress playing', event => ui.updateProgress.call(this.player, event));
|
utils.on(this.player.media, 'progress playing', event => ui.updateProgress.call(this.player, event));
|
||||||
|
|
||||||
// Handle native mute
|
// Handle volume changes
|
||||||
utils.on(this.player.media, 'volumechange', event => ui.updateVolume.call(this.player, event));
|
utils.on(this.player.media, 'volumechange', event => ui.updateVolume.call(this.player, event));
|
||||||
|
|
||||||
// Handle native play/pause
|
// Handle 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
|
// Loading state
|
||||||
utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event));
|
utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event));
|
||||||
|
|
||||||
// Check if media failed to load
|
// Check if media failed to load
|
||||||
// utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event));
|
// utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.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
|
||||||
|
utils.on(this.player.media, 'playing', () => {
|
||||||
|
if (!this.player.ads) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ads are enabled, wait for them first
|
||||||
|
if (this.player.ads.enabled && !this.player.ads.initialized) {
|
||||||
|
// Wait for manager response
|
||||||
|
this.player.ads.managerPromise.then(() => this.player.ads.play()).catch(() => this.player.play());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Click video
|
// Click video
|
||||||
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
|
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
|
||||||
// Re-fetch the wrapper
|
// Re-fetch the wrapper
|
||||||
@ -288,7 +316,7 @@ class Listeners {
|
|||||||
// On click play, pause ore restart
|
// On click play, pause ore restart
|
||||||
utils.on(wrapper, 'click', () => {
|
utils.on(wrapper, 'click', () => {
|
||||||
// Touch devices will just show controls (if we're hiding controls)
|
// Touch devices will just show controls (if we're hiding controls)
|
||||||
if (this.player.config.hideControls && support.touch && !this.player.paused) {
|
if (this.player.config.hideControls && this.player.touch && !this.player.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,7 +334,7 @@ class Listeners {
|
|||||||
// Disable right click
|
// Disable right click
|
||||||
if (this.player.supported.ui && this.player.config.disableContextMenu) {
|
if (this.player.supported.ui && this.player.config.disableContextMenu) {
|
||||||
utils.on(
|
utils.on(
|
||||||
this.player.media,
|
this.player.elements.wrapper,
|
||||||
'contextmenu',
|
'contextmenu',
|
||||||
event => {
|
event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -330,13 +358,16 @@ class Listeners {
|
|||||||
this.player.storage.set({ speed: this.player.speed });
|
this.player.storage.set({ speed: this.player.speed });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quality change
|
// Quality request
|
||||||
utils.on(this.player.media, 'qualitychange', () => {
|
utils.on(this.player.media, 'qualityrequested', event => {
|
||||||
// Update UI
|
|
||||||
controls.updateSetting.call(this.player, 'quality');
|
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
this.player.storage.set({ quality: this.player.quality });
|
this.player.storage.set({ quality: event.detail.quality });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quality change
|
||||||
|
utils.on(this.player.media, 'qualitychange', event => {
|
||||||
|
// Update UI
|
||||||
|
controls.updateSetting.call(this.player, 'quality', null, event.detail.quality);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Caption language change
|
// Caption language change
|
||||||
@ -379,122 +410,132 @@ class Listeners {
|
|||||||
// IE doesn't support input event, so we fallback to change
|
// IE doesn't support input event, so we fallback to change
|
||||||
const inputEvent = browser.isIE ? 'change' : 'input';
|
const inputEvent = browser.isIE ? 'change' : 'input';
|
||||||
|
|
||||||
// Trigger custom and default handlers
|
// Run default and custom handlers
|
||||||
const proxy = (event, handlerKey, defaultHandler) => {
|
const proxy = (event, defaultHandler, customHandlerKey) => {
|
||||||
const customHandler = this.player.config.listeners[handlerKey];
|
const customHandler = this.player.config.listeners[customHandlerKey];
|
||||||
|
const hasCustomHandler = utils.is.function(customHandler);
|
||||||
|
let returned = true;
|
||||||
|
|
||||||
// Execute custom handler
|
// Execute custom handler
|
||||||
if (utils.is.function(customHandler)) {
|
if (hasCustomHandler) {
|
||||||
customHandler.call(this.player, event);
|
returned = customHandler.call(this.player, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only call default handler if not prevented in custom handler
|
// Only call default handler if not prevented in custom handler
|
||||||
if (!event.defaultPrevented && utils.is.function(defaultHandler)) {
|
if (returned && utils.is.function(defaultHandler)) {
|
||||||
defaultHandler.call(this.player, event);
|
defaultHandler.call(this.player, event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Trigger custom and default handlers
|
||||||
|
const on = (element, type, defaultHandler, customHandlerKey, passive = true) => {
|
||||||
|
const customHandler = this.player.config.listeners[customHandlerKey];
|
||||||
|
const hasCustomHandler = utils.is.function(customHandler);
|
||||||
|
|
||||||
|
utils.on(element, type, event => proxy(event, defaultHandler, customHandlerKey), passive && !hasCustomHandler);
|
||||||
|
};
|
||||||
|
|
||||||
// Play/pause toggle
|
// Play/pause toggle
|
||||||
utils.on(this.player.elements.buttons.play, 'click', event =>
|
on(this.player.elements.buttons.play, 'click', this.player.togglePlay, 'play');
|
||||||
proxy(event, 'play', () => {
|
|
||||||
this.player.togglePlay();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Pause
|
// Pause
|
||||||
utils.on(this.player.elements.buttons.restart, 'click', event =>
|
on(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
|
||||||
proxy(event, 'restart', () => {
|
|
||||||
this.player.restart();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Rewind
|
// Rewind
|
||||||
utils.on(this.player.elements.buttons.rewind, 'click', event =>
|
on(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
|
||||||
proxy(event, 'rewind', () => {
|
|
||||||
this.player.rewind();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Rewind
|
// Rewind
|
||||||
utils.on(this.player.elements.buttons.forward, 'click', event =>
|
on(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
|
||||||
proxy(event, 'forward', () => {
|
|
||||||
this.player.forward();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mute toggle
|
// Mute toggle
|
||||||
utils.on(this.player.elements.buttons.mute, 'click', event =>
|
on(
|
||||||
proxy(event, 'mute', () => {
|
this.player.elements.buttons.mute,
|
||||||
|
'click',
|
||||||
|
() => {
|
||||||
this.player.muted = !this.player.muted;
|
this.player.muted = !this.player.muted;
|
||||||
}),
|
},
|
||||||
|
'mute',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Captions toggle
|
// Captions toggle
|
||||||
utils.on(this.player.elements.buttons.captions, 'click', event =>
|
on(this.player.elements.buttons.captions, 'click', this.player.toggleCaptions);
|
||||||
proxy(event, 'captions', () => {
|
|
||||||
this.player.toggleCaptions();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fullscreen toggle
|
// Fullscreen toggle
|
||||||
utils.on(this.player.elements.buttons.fullscreen, 'click', event =>
|
on(
|
||||||
proxy(event, 'fullscreen', () => {
|
this.player.elements.buttons.fullscreen,
|
||||||
|
'click',
|
||||||
|
() => {
|
||||||
this.player.fullscreen.toggle();
|
this.player.fullscreen.toggle();
|
||||||
}),
|
},
|
||||||
|
'fullscreen',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Picture-in-Picture
|
// Picture-in-Picture
|
||||||
utils.on(this.player.elements.buttons.pip, 'click', event =>
|
on(
|
||||||
proxy(event, 'pip', () => {
|
this.player.elements.buttons.pip,
|
||||||
|
'click',
|
||||||
|
() => {
|
||||||
this.player.pip = 'toggle';
|
this.player.pip = 'toggle';
|
||||||
}),
|
},
|
||||||
|
'pip',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Airplay
|
// Airplay
|
||||||
utils.on(this.player.elements.buttons.airplay, 'click', event =>
|
on(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
|
||||||
proxy(event, 'airplay', () => {
|
|
||||||
this.player.airplay();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Settings menu
|
// 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);
|
controls.toggleMenu.call(this.player, event);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Settings menu
|
// Settings menu
|
||||||
utils.on(this.player.elements.settings.form, 'click', event => {
|
on(this.player.elements.settings.form, 'click', event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
// Settings menu items - use event delegation as items are added/removed
|
// Settings menu items - use event delegation as items are added/removed
|
||||||
if (utils.matches(event.target, this.player.config.selectors.inputs.language)) {
|
if (utils.matches(event.target, this.player.config.selectors.inputs.language)) {
|
||||||
proxy(event, 'language', () => {
|
proxy(
|
||||||
this.player.language = event.target.value;
|
event,
|
||||||
});
|
() => {
|
||||||
|
this.player.language = event.target.value;
|
||||||
|
},
|
||||||
|
'language',
|
||||||
|
);
|
||||||
} else if (utils.matches(event.target, this.player.config.selectors.inputs.quality)) {
|
} else if (utils.matches(event.target, this.player.config.selectors.inputs.quality)) {
|
||||||
proxy(event, 'quality', () => {
|
proxy(
|
||||||
this.player.quality = event.target.value;
|
event,
|
||||||
});
|
() => {
|
||||||
|
this.player.quality = event.target.value;
|
||||||
|
},
|
||||||
|
'quality',
|
||||||
|
);
|
||||||
} else if (utils.matches(event.target, this.player.config.selectors.inputs.speed)) {
|
} else if (utils.matches(event.target, this.player.config.selectors.inputs.speed)) {
|
||||||
proxy(event, 'speed', () => {
|
proxy(
|
||||||
this.player.speed = parseFloat(event.target.value);
|
event,
|
||||||
});
|
() => {
|
||||||
|
this.player.speed = parseFloat(event.target.value);
|
||||||
|
},
|
||||||
|
'speed',
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
controls.showTab.call(this.player, event);
|
controls.showTab.call(this.player, event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Seek
|
// Seek
|
||||||
utils.on(this.player.elements.inputs.seek, inputEvent, event =>
|
on(
|
||||||
proxy(event, 'seek', () => {
|
this.player.elements.inputs.seek,
|
||||||
|
inputEvent,
|
||||||
|
event => {
|
||||||
this.player.currentTime = event.target.value / event.target.max * this.player.duration;
|
this.player.currentTime = event.target.value / event.target.max * this.player.duration;
|
||||||
}),
|
},
|
||||||
|
'seek',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Current time invert
|
// Current time invert
|
||||||
// Only if one time element is used for both currentTime and duration
|
// Only if one time element is used for both currentTime and duration
|
||||||
if (this.player.config.toggleInvert && !utils.is.element(this.player.elements.display.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
|
// Do nothing if we're at the start
|
||||||
if (this.player.currentTime === 0) {
|
if (this.player.currentTime === 0) {
|
||||||
return;
|
return;
|
||||||
@ -506,31 +547,34 @@ class Listeners {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Volume
|
// Volume
|
||||||
utils.on(this.player.elements.inputs.volume, inputEvent, event =>
|
on(
|
||||||
proxy(event, 'volume', () => {
|
this.player.elements.inputs.volume,
|
||||||
|
inputEvent,
|
||||||
|
event => {
|
||||||
this.player.volume = event.target.value;
|
this.player.volume = event.target.value;
|
||||||
}),
|
},
|
||||||
|
'volume',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Polyfill for lower fill in <input type="range"> for webkit
|
// Polyfill for lower fill in <input type="range"> for webkit
|
||||||
if (browser.isWebkit) {
|
if (browser.isWebkit) {
|
||||||
utils.on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => {
|
on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => {
|
||||||
controls.updateRangeFill.call(this.player, event.target);
|
controls.updateRangeFill.call(this.player, event.target);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek tooltip
|
// 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
|
// Toggle controls visibility based on mouse movement
|
||||||
if (this.player.config.hideControls) {
|
if (this.player.config.hideControls) {
|
||||||
// Watch for cursor over controls so they don't hide when trying to interact
|
// Watch for cursor over controls so they don't hide when trying to interact
|
||||||
utils.on(this.player.elements.controls, 'mouseenter mouseleave', event => {
|
on(this.player.elements.controls, 'mouseenter mouseleave', event => {
|
||||||
this.player.elements.controls.hover = event.type === 'mouseenter';
|
this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch for cursor over controls so they don't hide when trying to interact
|
// Watch for cursor over controls so they don't hide when trying to interact
|
||||||
utils.on(this.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 = [
|
this.player.elements.controls.pressed = [
|
||||||
'mousedown',
|
'mousedown',
|
||||||
'touchstart',
|
'touchstart',
|
||||||
@ -538,53 +582,58 @@ class Listeners {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Focus in/out on controls
|
// 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);
|
this.player.toggleControls(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mouse wheel for volume
|
// Mouse wheel for volume
|
||||||
utils.on(
|
on(
|
||||||
this.player.elements.inputs.volume,
|
this.player.elements.inputs.volume,
|
||||||
'wheel',
|
'wheel',
|
||||||
event =>
|
event => {
|
||||||
proxy(event, 'volume', () => {
|
// Detect "natural" scroll - suppored on OS X Safari only
|
||||||
// Detect "natural" scroll - suppored on OS X Safari only
|
// Other browsers on OS X will be inverted until support improves
|
||||||
// Other browsers on OS X will be inverted until support improves
|
const inverted = event.webkitDirectionInvertedFromDevice;
|
||||||
const inverted = event.webkitDirectionInvertedFromDevice;
|
const step = 1 / 50;
|
||||||
const step = 1 / 50;
|
let direction = 0;
|
||||||
let direction = 0;
|
|
||||||
|
|
||||||
// Scroll down (or up on natural) to decrease
|
// Scroll down (or up on natural) to decrease
|
||||||
if (event.deltaY < 0 || event.deltaX > 0) {
|
if (event.deltaY < 0 || event.deltaX > 0) {
|
||||||
if (inverted) {
|
if (inverted) {
|
||||||
this.player.decreaseVolume(step);
|
this.player.decreaseVolume(step);
|
||||||
direction = -1;
|
direction = -1;
|
||||||
} else {
|
} else {
|
||||||
this.player.increaseVolume(step);
|
this.player.increaseVolume(step);
|
||||||
direction = 1;
|
direction = 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Scroll up (or down on natural) to increase
|
// Scroll up (or down on natural) to increase
|
||||||
if (event.deltaY > 0 || event.deltaX < 0) {
|
if (event.deltaY > 0 || event.deltaX < 0) {
|
||||||
if (inverted) {
|
if (inverted) {
|
||||||
this.player.increaseVolume(step);
|
this.player.increaseVolume(step);
|
||||||
direction = 1;
|
direction = 1;
|
||||||
} else {
|
} else {
|
||||||
this.player.decreaseVolume(step);
|
this.player.decreaseVolume(step);
|
||||||
direction = -1;
|
direction = -1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't break page scrolling at max and min
|
// Don't break page scrolling at max and min
|
||||||
if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) {
|
if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
|
'volume',
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset on destroy
|
||||||
|
clear() {
|
||||||
|
this.global(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Listeners;
|
export default Listeners;
|
||||||
|
@ -6,6 +6,7 @@ import support from './support';
|
|||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
import youtube from './plugins/youtube';
|
import youtube from './plugins/youtube';
|
||||||
import vimeo from './plugins/vimeo';
|
import vimeo from './plugins/vimeo';
|
||||||
|
import html5 from './html5';
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
|
|
||||||
// Sniff out the browser
|
// Sniff out the browser
|
||||||
@ -46,7 +47,7 @@ const media = {
|
|||||||
utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
|
utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
|
||||||
|
|
||||||
// Add touch class
|
// Add touch class
|
||||||
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, support.touch);
|
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the player wrapper
|
// Inject the player wrapper
|
||||||
@ -75,32 +76,10 @@ const media = {
|
|||||||
}
|
}
|
||||||
} else if (this.isHTML5) {
|
} else if (this.isHTML5) {
|
||||||
ui.setTitle.call(this);
|
ui.setTitle.call(this);
|
||||||
|
|
||||||
|
html5.extend.call(this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Cancel current network requests
|
|
||||||
// See https://github.com/sampotts/plyr/issues/174
|
|
||||||
cancelRequests() {
|
|
||||||
if (!this.isHTML5) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove child sources
|
|
||||||
utils.removeElement(this.media.querySelectorAll('source'));
|
|
||||||
|
|
||||||
// Set blank video src attribute
|
|
||||||
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
|
|
||||||
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
|
|
||||||
this.media.setAttribute('src', this.config.blankVideo);
|
|
||||||
|
|
||||||
// Load the new empty source
|
|
||||||
// This will cancel existing requests
|
|
||||||
// See https://github.com/sampotts/plyr/issues/174
|
|
||||||
this.media.load();
|
|
||||||
|
|
||||||
// Debugging
|
|
||||||
this.debug.log('Cancelled network requests');
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default media;
|
export default media;
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
/* global google */
|
/* global google */
|
||||||
|
|
||||||
import utils from '../utils';
|
import utils from '../utils';
|
||||||
|
import i18n from '../i18n';
|
||||||
|
|
||||||
class Ads {
|
class Ads {
|
||||||
/**
|
/**
|
||||||
@ -171,18 +172,18 @@ class Ads {
|
|||||||
*/
|
*/
|
||||||
pollCountdown(start = false) {
|
pollCountdown(start = false) {
|
||||||
if (!start) {
|
if (!start) {
|
||||||
window.clearInterval(this.countdownTimer);
|
clearInterval(this.countdownTimer);
|
||||||
this.elements.container.removeAttribute('data-badge-text');
|
this.elements.container.removeAttribute('data-badge-text');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
const time = utils.formatTime(Math.max(this.manager.getRemainingTime(), 0));
|
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);
|
this.elements.container.setAttribute('data-badge-text', label);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.countdownTimer = window.setInterval(update, 100);
|
this.countdownTimer = setInterval(update, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -205,21 +206,23 @@ class Ads {
|
|||||||
this.cuePoints = this.manager.getCuePoints();
|
this.cuePoints = this.manager.getCuePoints();
|
||||||
|
|
||||||
// Add advertisement cue's within the time line if available
|
// Add advertisement cue's within the time line if available
|
||||||
this.cuePoints.forEach(cuePoint => {
|
if (!utils.is.empty(this.cuePoints)) {
|
||||||
if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) {
|
this.cuePoints.forEach(cuePoint => {
|
||||||
const seekElement = this.player.elements.progress;
|
if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) {
|
||||||
|
const seekElement = this.player.elements.progress;
|
||||||
|
|
||||||
if (seekElement) {
|
if (utils.is.element(seekElement)) {
|
||||||
const cuePercentage = 100 / this.player.duration * cuePoint;
|
const cuePercentage = 100 / this.player.duration * cuePoint;
|
||||||
const cue = utils.createElement('span', {
|
const cue = utils.createElement('span', {
|
||||||
class: this.player.config.classNames.cues,
|
class: this.player.config.classNames.cues,
|
||||||
});
|
});
|
||||||
|
|
||||||
cue.style.left = `${cuePercentage.toString()}%`;
|
cue.style.left = `${cuePercentage.toString()}%`;
|
||||||
seekElement.appendChild(cue);
|
seekElement.appendChild(cue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
// Get skippable state
|
// Get skippable state
|
||||||
// TODO: Skip button
|
// TODO: Skip button
|
||||||
@ -384,6 +387,10 @@ class Ads {
|
|||||||
this.player.on('seeked', () => {
|
this.player.on('seeked', () => {
|
||||||
const seekedTime = this.player.currentTime;
|
const seekedTime = this.player.currentTime;
|
||||||
|
|
||||||
|
if (utils.is.empty(this.cuePoints)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.cuePoints.forEach((cuePoint, index) => {
|
this.cuePoints.forEach((cuePoint, index) => {
|
||||||
if (time < cuePoint && cuePoint < seekedTime) {
|
if (time < cuePoint && cuePoint < seekedTime) {
|
||||||
this.manager.discardAdBreak();
|
this.manager.discardAdBreak();
|
||||||
@ -395,7 +402,9 @@ class Ads {
|
|||||||
// Listen to the resizing of the window. And resize ad accordingly
|
// Listen to the resizing of the window. And resize ad accordingly
|
||||||
// TODO: eventually implement ResizeObserver
|
// TODO: eventually implement ResizeObserver
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
this.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
|
if (this.manager) {
|
||||||
|
this.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import utils from './../utils';
|
import utils from './../utils';
|
||||||
import captions from './../captions';
|
import captions from './../captions';
|
||||||
|
import controls from './../controls';
|
||||||
import ui from './../ui';
|
import ui from './../ui';
|
||||||
|
|
||||||
const vimeo = {
|
const vimeo = {
|
||||||
@ -34,10 +35,14 @@ const vimeo = {
|
|||||||
setAspectRatio(input) {
|
setAspectRatio(input) {
|
||||||
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
|
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
|
||||||
const padding = 100 / ratio[0] * ratio[1];
|
const padding = 100 / ratio[0] * ratio[1];
|
||||||
const height = 200;
|
|
||||||
const offset = (height - padding) / (height / 50);
|
|
||||||
this.elements.wrapper.style.paddingBottom = `${padding}%`;
|
this.elements.wrapper.style.paddingBottom = `${padding}%`;
|
||||||
this.media.style.transform = `translateY(-${offset}%)`;
|
|
||||||
|
if (this.supported.ui) {
|
||||||
|
const height = 240;
|
||||||
|
const offset = (height - padding) / (height / 50);
|
||||||
|
|
||||||
|
this.media.style.transform = `translateY(-${offset}%)`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// API Ready
|
// API Ready
|
||||||
@ -54,6 +59,7 @@ const vimeo = {
|
|||||||
speed: true,
|
speed: true,
|
||||||
transparent: 0,
|
transparent: 0,
|
||||||
gesture: 'media',
|
gesture: 'media',
|
||||||
|
playsinline: !this.config.fullscreen.iosNative,
|
||||||
};
|
};
|
||||||
const params = utils.buildUrlParams(options);
|
const params = utils.buildUrlParams(options);
|
||||||
|
|
||||||
@ -87,6 +93,11 @@ const vimeo = {
|
|||||||
player.media.paused = true;
|
player.media.paused = true;
|
||||||
player.media.currentTime = 0;
|
player.media.currentTime = 0;
|
||||||
|
|
||||||
|
// Disable native text track rendering
|
||||||
|
if (player.supported.ui) {
|
||||||
|
player.embed.disableTextTrack();
|
||||||
|
}
|
||||||
|
|
||||||
// Create a faux HTML5 API using the Vimeo API
|
// Create a faux HTML5 API using the Vimeo API
|
||||||
player.media.play = () => {
|
player.media.play = () => {
|
||||||
player.embed.play().then(() => {
|
player.embed.play().then(() => {
|
||||||
@ -101,10 +112,8 @@ const vimeo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
player.media.stop = () => {
|
player.media.stop = () => {
|
||||||
player.embed.stop().then(() => {
|
player.pause();
|
||||||
player.media.paused = true;
|
player.currentTime = 0;
|
||||||
player.currentTime = 0;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Seeking
|
// Seeking
|
||||||
@ -125,7 +134,9 @@ const vimeo = {
|
|||||||
utils.dispatchEvent.call(player, player.media, 'seeking');
|
utils.dispatchEvent.call(player, player.media, 'seeking');
|
||||||
|
|
||||||
// Seek after events
|
// Seek after events
|
||||||
player.embed.setCurrentTime(time);
|
player.embed.setCurrentTime(time).catch(() => {
|
||||||
|
// Do nothing
|
||||||
|
});
|
||||||
|
|
||||||
// Restore pause state
|
// Restore pause state
|
||||||
if (paused) {
|
if (paused) {
|
||||||
@ -141,10 +152,18 @@ const vimeo = {
|
|||||||
return speed;
|
return speed;
|
||||||
},
|
},
|
||||||
set(input) {
|
set(input) {
|
||||||
player.embed.setPlaybackRate(input).then(() => {
|
player.embed
|
||||||
speed = input;
|
.setPlaybackRate(input)
|
||||||
utils.dispatchEvent.call(player, player.media, 'ratechange');
|
.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 +214,15 @@ const vimeo = {
|
|||||||
|
|
||||||
// Source
|
// Source
|
||||||
let currentSrc;
|
let currentSrc;
|
||||||
player.embed.getVideoUrl().then(value => {
|
player.embed
|
||||||
currentSrc = value;
|
.getVideoUrl()
|
||||||
});
|
.then(value => {
|
||||||
|
currentSrc = value;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.debug.warn(error);
|
||||||
|
});
|
||||||
|
|
||||||
Object.defineProperty(player.media, 'currentSrc', {
|
Object.defineProperty(player.media, 'currentSrc', {
|
||||||
get() {
|
get() {
|
||||||
return currentSrc;
|
return currentSrc;
|
||||||
@ -297,6 +322,15 @@ const vimeo = {
|
|||||||
if (parseInt(data.percent, 10) === 1) {
|
if (parseInt(data.percent, 10) === 1) {
|
||||||
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
|
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get duration as if we do it before load, it gives an incorrect value
|
||||||
|
// https://github.com/sampotts/plyr/issues/891
|
||||||
|
player.embed.getDuration().then(value => {
|
||||||
|
if (value !== player.media.duration) {
|
||||||
|
player.media.duration = value;
|
||||||
|
utils.dispatchEvent.call(player, player.media, 'durationchange');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
player.embed.on('seeked', () => {
|
player.embed.on('seeked', () => {
|
||||||
|
@ -6,6 +6,64 @@ import utils from './../utils';
|
|||||||
import controls from './../controls';
|
import controls from './../controls';
|
||||||
import ui from './../ui';
|
import ui from './../ui';
|
||||||
|
|
||||||
|
// Standardise YouTube quality unit
|
||||||
|
function mapQualityUnit(input) {
|
||||||
|
switch (input) {
|
||||||
|
case 'hd2160':
|
||||||
|
return 2160;
|
||||||
|
|
||||||
|
case 2160:
|
||||||
|
return 'hd2160';
|
||||||
|
|
||||||
|
case 'hd1440':
|
||||||
|
return 1440;
|
||||||
|
|
||||||
|
case 1440:
|
||||||
|
return 'hd1440';
|
||||||
|
|
||||||
|
case 'hd1080':
|
||||||
|
return 1080;
|
||||||
|
|
||||||
|
case 1080:
|
||||||
|
return 'hd1080';
|
||||||
|
|
||||||
|
case 'hd720':
|
||||||
|
return 720;
|
||||||
|
|
||||||
|
case 720:
|
||||||
|
return 'hd720';
|
||||||
|
|
||||||
|
case 'large':
|
||||||
|
return 480;
|
||||||
|
|
||||||
|
case 480:
|
||||||
|
return 'large';
|
||||||
|
|
||||||
|
case 'medium':
|
||||||
|
return 360;
|
||||||
|
|
||||||
|
case 360:
|
||||||
|
return 'medium';
|
||||||
|
|
||||||
|
case 'small':
|
||||||
|
return 240;
|
||||||
|
|
||||||
|
case 240:
|
||||||
|
return 'small';
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapQualityUnits(levels) {
|
||||||
|
if (utils.is.empty(levels)) {
|
||||||
|
return levels;
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.dedupe(levels.map(level => mapQualityUnit(level)));
|
||||||
|
}
|
||||||
|
|
||||||
const youtube = {
|
const youtube = {
|
||||||
setup() {
|
setup() {
|
||||||
// Add embed class for responsive
|
// Add embed class for responsive
|
||||||
@ -168,14 +226,10 @@ const youtube = {
|
|||||||
|
|
||||||
utils.dispatchEvent.call(player, player.media, 'error');
|
utils.dispatchEvent.call(player, player.media, 'error');
|
||||||
},
|
},
|
||||||
onPlaybackQualityChange(event) {
|
onPlaybackQualityChange() {
|
||||||
// Get the instance
|
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
|
||||||
const instance = event.target;
|
quality: player.media.quality,
|
||||||
|
});
|
||||||
// Get current quality
|
|
||||||
player.media.quality = instance.getPlaybackQuality();
|
|
||||||
|
|
||||||
utils.dispatchEvent.call(player, player.media, 'qualitychange');
|
|
||||||
},
|
},
|
||||||
onPlaybackRateChange(event) {
|
onPlaybackRateChange(event) {
|
||||||
// Get the instance
|
// Get the instance
|
||||||
@ -216,6 +270,9 @@ const youtube = {
|
|||||||
return Number(instance.getCurrentTime());
|
return Number(instance.getCurrentTime());
|
||||||
},
|
},
|
||||||
set(time) {
|
set(time) {
|
||||||
|
// Vimeo will automatically play on seek
|
||||||
|
const { paused } = player.media;
|
||||||
|
|
||||||
// Set seeking flag
|
// Set seeking flag
|
||||||
player.media.seeking = true;
|
player.media.seeking = true;
|
||||||
|
|
||||||
@ -224,6 +281,11 @@ const youtube = {
|
|||||||
|
|
||||||
// Seek after events sent
|
// Seek after events sent
|
||||||
instance.seekTo(time);
|
instance.seekTo(time);
|
||||||
|
|
||||||
|
// Restore pause state
|
||||||
|
if (paused) {
|
||||||
|
player.pause();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -240,15 +302,18 @@ const youtube = {
|
|||||||
// Quality
|
// Quality
|
||||||
Object.defineProperty(player.media, 'quality', {
|
Object.defineProperty(player.media, 'quality', {
|
||||||
get() {
|
get() {
|
||||||
return instance.getPlaybackQuality();
|
return mapQualityUnit(instance.getPlaybackQuality());
|
||||||
},
|
},
|
||||||
set(input) {
|
set(input) {
|
||||||
|
const quality = input;
|
||||||
|
|
||||||
|
// Set via API
|
||||||
|
instance.setPlaybackQuality(mapQualityUnit(quality));
|
||||||
|
|
||||||
// Trigger request event
|
// Trigger request event
|
||||||
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
|
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
|
||||||
quality: input,
|
quality,
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.setPlaybackQuality(input);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -305,10 +370,10 @@ const youtube = {
|
|||||||
utils.dispatchEvent.call(player, player.media, 'durationchange');
|
utils.dispatchEvent.call(player, player.media, 'durationchange');
|
||||||
|
|
||||||
// Reset timer
|
// Reset timer
|
||||||
window.clearInterval(player.timers.buffering);
|
clearInterval(player.timers.buffering);
|
||||||
|
|
||||||
// Setup buffering
|
// Setup buffering
|
||||||
player.timers.buffering = window.setInterval(() => {
|
player.timers.buffering = setInterval(() => {
|
||||||
// Get loaded % from YouTube
|
// Get loaded % from YouTube
|
||||||
player.media.buffered = instance.getVideoLoadedFraction();
|
player.media.buffered = instance.getVideoLoadedFraction();
|
||||||
|
|
||||||
@ -322,7 +387,7 @@ const youtube = {
|
|||||||
|
|
||||||
// Bail if we're at 100%
|
// Bail if we're at 100%
|
||||||
if (player.media.buffered === 1) {
|
if (player.media.buffered === 1) {
|
||||||
window.clearInterval(player.timers.buffering);
|
clearInterval(player.timers.buffering);
|
||||||
|
|
||||||
// Trigger event
|
// Trigger event
|
||||||
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
|
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
|
||||||
@ -337,7 +402,7 @@ const youtube = {
|
|||||||
const instance = event.target;
|
const instance = event.target;
|
||||||
|
|
||||||
// Reset timer
|
// Reset timer
|
||||||
window.clearInterval(player.timers.playing);
|
clearInterval(player.timers.playing);
|
||||||
|
|
||||||
// Handle events
|
// Handle events
|
||||||
// -1 Unstarted
|
// -1 Unstarted
|
||||||
@ -347,6 +412,16 @@ const youtube = {
|
|||||||
// 3 Buffering
|
// 3 Buffering
|
||||||
// 5 Video cued
|
// 5 Video cued
|
||||||
switch (event.data) {
|
switch (event.data) {
|
||||||
|
case -1:
|
||||||
|
// Update scrubber
|
||||||
|
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
||||||
|
|
||||||
|
// Get loaded % from YouTube
|
||||||
|
player.media.buffered = instance.getVideoLoadedFraction();
|
||||||
|
utils.dispatchEvent.call(player, player.media, 'progress');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case 0:
|
case 0:
|
||||||
player.media.paused = true;
|
player.media.paused = true;
|
||||||
|
|
||||||
@ -377,7 +452,7 @@ const youtube = {
|
|||||||
utils.dispatchEvent.call(player, player.media, 'playing');
|
utils.dispatchEvent.call(player, player.media, 'playing');
|
||||||
|
|
||||||
// Poll to get playback progress
|
// Poll to get playback progress
|
||||||
player.timers.playing = window.setInterval(() => {
|
player.timers.playing = setInterval(() => {
|
||||||
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
@ -390,7 +465,7 @@ const youtube = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get quality
|
// Get quality
|
||||||
controls.setQualityMenu.call(player, instance.getAvailableQualityLevels());
|
controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels()));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
248
src/js/plyr.js
248
src/js/plyr.js
@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr
|
// Plyr
|
||||||
// plyr.js v3.0.0-beta.19
|
// plyr.js v3.2.4
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@ -36,6 +36,9 @@ class Plyr {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.failed = false;
|
this.failed = false;
|
||||||
|
|
||||||
|
// Touch device
|
||||||
|
this.touch = support.touch;
|
||||||
|
|
||||||
// Set the media element
|
// Set the media element
|
||||||
this.media = target;
|
this.media = target;
|
||||||
|
|
||||||
@ -54,7 +57,7 @@ class Plyr {
|
|||||||
this.config = utils.extend(
|
this.config = utils.extend(
|
||||||
{},
|
{},
|
||||||
defaults,
|
defaults,
|
||||||
options,
|
options || {},
|
||||||
(() => {
|
(() => {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(this.media.getAttribute('data-plyr-config'));
|
return JSON.parse(this.media.getAttribute('data-plyr-config'));
|
||||||
@ -94,6 +97,7 @@ class Plyr {
|
|||||||
this.options = {
|
this.options = {
|
||||||
speed: [],
|
speed: [],
|
||||||
quality: [],
|
quality: [],
|
||||||
|
captions: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Debugging
|
// Debugging
|
||||||
@ -130,7 +134,17 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cache original element state for .destroy()
|
// Cache original element state for .destroy()
|
||||||
this.elements.original = this.media.cloneNode(true);
|
// TODO: Investigate a better solution as I suspect this causes reported double load issues?
|
||||||
|
setTimeout(() => {
|
||||||
|
const clone = this.media.cloneNode(true);
|
||||||
|
|
||||||
|
// Prevent the clone autoplaying
|
||||||
|
if (clone.getAttribute('autoplay')) {
|
||||||
|
clone.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.elements.original = clone;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
// Set media type based on tag or data attribute
|
// Set media type based on tag or data attribute
|
||||||
// Supported: video, audio, vimeo, youtube
|
// Supported: video, audio, vimeo, youtube
|
||||||
@ -171,12 +185,17 @@ class Plyr {
|
|||||||
if (truthy.includes(params.autoplay)) {
|
if (truthy.includes(params.autoplay)) {
|
||||||
this.config.autoplay = true;
|
this.config.autoplay = true;
|
||||||
}
|
}
|
||||||
if (truthy.includes(params.playsinline)) {
|
|
||||||
this.config.inline = true;
|
|
||||||
}
|
|
||||||
if (truthy.includes(params.loop)) {
|
if (truthy.includes(params.loop)) {
|
||||||
this.config.loop.active = true;
|
this.config.loop.active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: replace fullscreen.iosNative with this playsinline config option
|
||||||
|
// YouTube requires the playsinline in the URL
|
||||||
|
if (this.isYouTube) {
|
||||||
|
this.config.playsinline = truthy.includes(params.playsinline);
|
||||||
|
} else {
|
||||||
|
this.config.playsinline = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// <div> with attributes
|
// <div> with attributes
|
||||||
@ -210,7 +229,7 @@ class Plyr {
|
|||||||
this.config.autoplay = true;
|
this.config.autoplay = true;
|
||||||
}
|
}
|
||||||
if (this.media.hasAttribute('playsinline')) {
|
if (this.media.hasAttribute('playsinline')) {
|
||||||
this.config.inline = true;
|
this.config.playsinline = true;
|
||||||
}
|
}
|
||||||
if (this.media.hasAttribute('muted')) {
|
if (this.media.hasAttribute('muted')) {
|
||||||
this.config.muted = true;
|
this.config.muted = true;
|
||||||
@ -227,7 +246,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for support again but with type
|
// Check for support again but with type
|
||||||
this.supported = support.check(this.type, this.provider, this.config.inline);
|
this.supported = support.check(this.type, this.provider, this.config.playsinline);
|
||||||
|
|
||||||
// If no support for even API, bail
|
// If no support for even API, bail
|
||||||
if (!this.supported.api) {
|
if (!this.supported.api) {
|
||||||
@ -276,13 +295,18 @@ class Plyr {
|
|||||||
this.listeners.container();
|
this.listeners.container();
|
||||||
|
|
||||||
// Global listeners
|
// Global listeners
|
||||||
this.listeners.global(true);
|
this.listeners.global();
|
||||||
|
|
||||||
// Setup fullscreen
|
// Setup fullscreen
|
||||||
this.fullscreen = new Fullscreen(this);
|
this.fullscreen = new Fullscreen(this);
|
||||||
|
|
||||||
// Setup ads if provided
|
// Setup ads if provided
|
||||||
this.ads = new Ads(this);
|
this.ads = new Ads(this);
|
||||||
|
|
||||||
|
// Autoplay if required
|
||||||
|
if (this.config.autoplay) {
|
||||||
|
this.play();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@ -293,33 +317,37 @@ class Plyr {
|
|||||||
* Types and provider helpers
|
* Types and provider helpers
|
||||||
*/
|
*/
|
||||||
get isHTML5() {
|
get isHTML5() {
|
||||||
return this.provider === providers.html5;
|
return Boolean(this.provider === providers.html5);
|
||||||
}
|
}
|
||||||
get isEmbed() {
|
get isEmbed() {
|
||||||
return this.isYouTube || this.isVimeo;
|
return Boolean(this.isYouTube || this.isVimeo);
|
||||||
}
|
}
|
||||||
get isYouTube() {
|
get isYouTube() {
|
||||||
return this.provider === providers.youtube;
|
return Boolean(this.provider === providers.youtube);
|
||||||
}
|
}
|
||||||
get isVimeo() {
|
get isVimeo() {
|
||||||
return this.provider === providers.vimeo;
|
return Boolean(this.provider === providers.vimeo);
|
||||||
}
|
}
|
||||||
get isVideo() {
|
get isVideo() {
|
||||||
return this.type === types.video;
|
return Boolean(this.type === types.video);
|
||||||
}
|
}
|
||||||
get isAudio() {
|
get isAudio() {
|
||||||
return this.type === types.audio;
|
return Boolean(this.type === types.audio);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play the media, or play the advertisement (if they are not blocked)
|
* Play the media, or play the advertisement (if they are not blocked)
|
||||||
*/
|
*/
|
||||||
play() {
|
play() {
|
||||||
// If ads are enabled, wait for them first
|
if (!utils.is.function(this.media.play)) {
|
||||||
if (this.ads.enabled && !this.ads.initialized) {
|
return null;
|
||||||
return this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If ads are enabled, wait for them first
|
||||||
|
/* if (this.ads.enabled && !this.ads.initialized) {
|
||||||
|
return this.ads.managerPromise.then(() => this.ads.play()).catch(() => this.media.play());
|
||||||
|
} */
|
||||||
|
|
||||||
// Return the promise (for HTML5)
|
// Return the promise (for HTML5)
|
||||||
return this.media.play();
|
return this.media.play();
|
||||||
}
|
}
|
||||||
@ -328,7 +356,7 @@ class Plyr {
|
|||||||
* Pause the media
|
* Pause the media
|
||||||
*/
|
*/
|
||||||
pause() {
|
pause() {
|
||||||
if (!this.playing) {
|
if (!this.playing || !utils.is.function(this.media.pause)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,21 +367,21 @@ class Plyr {
|
|||||||
* Get paused state
|
* Get paused state
|
||||||
*/
|
*/
|
||||||
get paused() {
|
get paused() {
|
||||||
return this.media.paused;
|
return Boolean(this.media.paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get playing state
|
* Get playing state
|
||||||
*/
|
*/
|
||||||
get playing() {
|
get playing() {
|
||||||
return !this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true);
|
return Boolean(this.ready && !this.paused && !this.ended && (this.isHTML5 ? this.media.readyState > 2 : true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get ended state
|
* Get ended state
|
||||||
*/
|
*/
|
||||||
get ended() {
|
get ended() {
|
||||||
return this.media.ended;
|
return Boolean(this.media.ended);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -375,8 +403,11 @@ class Plyr {
|
|||||||
* Stop playback
|
* Stop playback
|
||||||
*/
|
*/
|
||||||
stop() {
|
stop() {
|
||||||
this.restart();
|
if (this.isHTML5) {
|
||||||
this.pause();
|
this.media.load();
|
||||||
|
} else if (utils.is.function(this.media.stop)) {
|
||||||
|
this.media.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -421,7 +452,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set
|
// Set
|
||||||
this.media.currentTime = targetTime.toFixed(4);
|
this.media.currentTime = targetTime;
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
this.debug.log(`Seeking to ${this.currentTime} seconds`);
|
this.debug.log(`Seeking to ${this.currentTime} seconds`);
|
||||||
@ -434,11 +465,32 @@ class Plyr {
|
|||||||
return Number(this.media.currentTime);
|
return Number(this.media.currentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get buffered
|
||||||
|
*/
|
||||||
|
get buffered() {
|
||||||
|
const { buffered } = this.media;
|
||||||
|
|
||||||
|
// YouTube / Vimeo return a float between 0-1
|
||||||
|
if (utils.is.number(buffered)) {
|
||||||
|
return buffered;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML5
|
||||||
|
// TODO: Handle buffered chunks of the media
|
||||||
|
// (i.e. seek to another section buffers only that section)
|
||||||
|
if (buffered && buffered.length && this.duration > 0) {
|
||||||
|
return buffered.end(0) / this.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get seeking status
|
* Get seeking status
|
||||||
*/
|
*/
|
||||||
get seeking() {
|
get seeking() {
|
||||||
return this.media.seeking;
|
return Boolean(this.media.seeking);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -446,10 +498,10 @@ class Plyr {
|
|||||||
*/
|
*/
|
||||||
get duration() {
|
get duration() {
|
||||||
// Faux duration set via config
|
// Faux duration set via config
|
||||||
const fauxDuration = parseInt(this.config.duration, 10);
|
const fauxDuration = parseFloat(this.config.duration);
|
||||||
|
|
||||||
// True duration
|
// True duration
|
||||||
const realDuration = Number(this.media.duration);
|
const realDuration = this.media ? Number(this.media.duration) : 0;
|
||||||
|
|
||||||
// If custom duration is funky, use regular duration
|
// If custom duration is funky, use regular duration
|
||||||
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
|
return !Number.isNaN(fauxDuration) ? fauxDuration : realDuration;
|
||||||
@ -493,8 +545,8 @@ class Plyr {
|
|||||||
// Set the player volume
|
// Set the player volume
|
||||||
this.media.volume = volume;
|
this.media.volume = volume;
|
||||||
|
|
||||||
// If muted, and we're increasing volume, reset muted state
|
// If muted, and we're increasing volume manually, reset muted state
|
||||||
if (this.muted && volume > 0) {
|
if (!utils.is.empty(value) && this.muted && volume > 0) {
|
||||||
this.muted = false;
|
this.muted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -503,7 +555,7 @@ class Plyr {
|
|||||||
* Get the current player volume
|
* Get the current player volume
|
||||||
*/
|
*/
|
||||||
get volume() {
|
get volume() {
|
||||||
return this.media.volume;
|
return Number(this.media.volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -552,7 +604,7 @@ class Plyr {
|
|||||||
* Get current muted state
|
* Get current muted state
|
||||||
*/
|
*/
|
||||||
get muted() {
|
get muted() {
|
||||||
return this.media.muted;
|
return Boolean(this.media.muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -569,12 +621,16 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get audio tracks
|
// Get audio tracks
|
||||||
return this.media.mozHasAudio || Boolean(this.media.webkitAudioDecodedByteCount) || Boolean(this.media.audioTracks && this.media.audioTracks.length);
|
return (
|
||||||
|
Boolean(this.media.mozHasAudio) ||
|
||||||
|
Boolean(this.media.webkitAudioDecodedByteCount) ||
|
||||||
|
Boolean(this.media.audioTracks && this.media.audioTracks.length)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set playback speed
|
* Set playback speed
|
||||||
* @param {decimal} speed - the speed of playback (0.5-2.0)
|
* @param {number} speed - the speed of playback (0.5-2.0)
|
||||||
*/
|
*/
|
||||||
set speed(input) {
|
set speed(input) {
|
||||||
let speed = null;
|
let speed = null;
|
||||||
@ -615,34 +671,43 @@ class Plyr {
|
|||||||
* Get current playback speed
|
* Get current playback speed
|
||||||
*/
|
*/
|
||||||
get speed() {
|
get speed() {
|
||||||
return this.media.playbackRate;
|
return Number(this.media.playbackRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set playback quality
|
* Set playback quality
|
||||||
* Currently YouTube only
|
* Currently HTML5 & YouTube only
|
||||||
* @param {string} input - Quality level
|
* @param {number} input - Quality level
|
||||||
*/
|
*/
|
||||||
set quality(input) {
|
set quality(input) {
|
||||||
let quality = null;
|
let quality = null;
|
||||||
|
|
||||||
if (utils.is.string(input)) {
|
if (!utils.is.empty(input)) {
|
||||||
quality = input;
|
quality = Number(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!utils.is.string(quality)) {
|
if (!utils.is.number(quality) || quality === 0) {
|
||||||
quality = this.storage.get('quality');
|
quality = this.storage.get('quality');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!utils.is.string(quality)) {
|
if (!utils.is.number(quality)) {
|
||||||
quality = this.config.quality.selected;
|
quality = this.config.quality.selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.options.quality.includes(quality)) {
|
if (!utils.is.number(quality)) {
|
||||||
this.debug.warn(`Unsupported quality option (${quality})`);
|
quality = this.config.quality.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.options.quality.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.options.quality.includes(quality)) {
|
||||||
|
const closest = utils.closest(this.options.quality, quality);
|
||||||
|
this.debug.warn(`Unsupported quality option: ${quality}, using ${closest} instead`);
|
||||||
|
quality = closest;
|
||||||
|
}
|
||||||
|
|
||||||
// Update config
|
// Update config
|
||||||
this.config.quality.selected = quality;
|
this.config.quality.selected = quality;
|
||||||
|
|
||||||
@ -715,7 +780,7 @@ class Plyr {
|
|||||||
* Get current loop state
|
* Get current loop state
|
||||||
*/
|
*/
|
||||||
get loop() {
|
get loop() {
|
||||||
return this.media.loop;
|
return Boolean(this.media.loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -772,7 +837,7 @@ class Plyr {
|
|||||||
* Get the current autoplay state
|
* Get the current autoplay state
|
||||||
*/
|
*/
|
||||||
get autoplay() {
|
get autoplay() {
|
||||||
return this.config.autoplay;
|
return Boolean(this.config.autoplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -780,13 +845,13 @@ class Plyr {
|
|||||||
* @param {boolean} input - Whether to enable captions
|
* @param {boolean} input - Whether to enable captions
|
||||||
*/
|
*/
|
||||||
toggleCaptions(input) {
|
toggleCaptions(input) {
|
||||||
// If there's no full support, or there's no caption toggle
|
// If there's no full support
|
||||||
if (!this.supported.ui || !utils.is.element(this.elements.buttons.captions)) {
|
if (!this.supported.ui) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the method is called without parameter, toggle based on current value
|
// If the method is called without parameter, toggle based on current value
|
||||||
const show = utils.is.boolean(input) ? input : this.elements.container.className.indexOf(this.config.classNames.captions.active) === -1;
|
const show = utils.is.boolean(input) ? input : !this.elements.container.classList.contains(this.config.classNames.captions.active);
|
||||||
|
|
||||||
// Nothing to change...
|
// Nothing to change...
|
||||||
if (this.captions.active === show) {
|
if (this.captions.active === show) {
|
||||||
@ -816,17 +881,29 @@ class Plyr {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle captions based on input
|
|
||||||
this.toggleCaptions(!utils.is.empty(input));
|
|
||||||
|
|
||||||
// If empty string is passed, assume disable captions
|
// If empty string is passed, assume disable captions
|
||||||
if (utils.is.empty(input)) {
|
if (utils.is.empty(input)) {
|
||||||
|
this.toggleCaptions(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize
|
// Normalize
|
||||||
const language = input.toLowerCase();
|
const language = input.toLowerCase();
|
||||||
|
|
||||||
|
// Check for support
|
||||||
|
if (!this.options.captions.includes(language)) {
|
||||||
|
this.debug.warn(`Unsupported language option: ${language}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure captions are enabled
|
||||||
|
this.toggleCaptions(true);
|
||||||
|
|
||||||
|
// Enabled only
|
||||||
|
if (language === 'enabled') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If nothing to change, bail
|
// If nothing to change, bail
|
||||||
if (this.language === language) {
|
if (this.language === language) {
|
||||||
return;
|
return;
|
||||||
@ -922,26 +999,32 @@ class Plyr {
|
|||||||
// Is the enter fullscreen event
|
// Is the enter fullscreen event
|
||||||
isEnterFullscreen = toggle.type === 'enterfullscreen';
|
isEnterFullscreen = toggle.type === 'enterfullscreen';
|
||||||
|
|
||||||
// Whether to show controls
|
// Events that show the controls
|
||||||
show = [
|
const showEvents = [
|
||||||
'mouseenter',
|
|
||||||
'mousemove',
|
|
||||||
'touchstart',
|
'touchstart',
|
||||||
'touchmove',
|
'touchmove',
|
||||||
'focusin',
|
'mouseenter',
|
||||||
].includes(toggle.type);
|
|
||||||
|
|
||||||
// Delay hiding on move events
|
|
||||||
if ([
|
|
||||||
'mousemove',
|
'mousemove',
|
||||||
|
'focusin',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Events that delay hiding
|
||||||
|
const delayEvents = [
|
||||||
'touchmove',
|
'touchmove',
|
||||||
'touchend',
|
'touchend',
|
||||||
].includes(toggle.type)) {
|
'mousemove',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Whether to show controls
|
||||||
|
show = showEvents.includes(toggle.type);
|
||||||
|
|
||||||
|
// Delay hiding on move events
|
||||||
|
if (delayEvents.includes(toggle.type)) {
|
||||||
delay = 2000;
|
delay = 2000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay a little more for keyboard users
|
// Delay a little more for keyboard users
|
||||||
if (toggle.type === 'focusin') {
|
if (!this.touch && toggle.type === 'focusin') {
|
||||||
delay = 3000;
|
delay = 3000;
|
||||||
utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true);
|
utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true);
|
||||||
}
|
}
|
||||||
@ -951,7 +1034,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear timer on every call
|
// Clear timer on every call
|
||||||
window.clearTimeout(this.timers.controls);
|
clearTimeout(this.timers.controls);
|
||||||
|
|
||||||
// If the mouse is not over the controls, set a timeout to hide them
|
// If the mouse is not over the controls, set a timeout to hide them
|
||||||
if (show || this.paused || this.loading) {
|
if (show || this.paused || this.loading) {
|
||||||
@ -969,7 +1052,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delay for hiding on touch
|
// Delay for hiding on touch
|
||||||
if (support.touch) {
|
if (this.touch) {
|
||||||
delay = 3000;
|
delay = 3000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -978,6 +1061,11 @@ class Plyr {
|
|||||||
// then set the timer to hide the controls
|
// then set the timer to hide the controls
|
||||||
if (!show || this.playing) {
|
if (!show || this.playing) {
|
||||||
this.timers.controls = setTimeout(() => {
|
this.timers.controls = setTimeout(() => {
|
||||||
|
// We need controls of course...
|
||||||
|
if (!utils.is.element(this.elements.controls)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If the mouse is over the controls (and not entering fullscreen), bail
|
// If the mouse is over the controls (and not entering fullscreen), bail
|
||||||
if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
|
if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
|
||||||
return;
|
return;
|
||||||
@ -1029,6 +1117,10 @@ class Plyr {
|
|||||||
* @param {boolean} soft - Whether it's a soft destroy (for source changes etc)
|
* @param {boolean} soft - Whether it's a soft destroy (for source changes etc)
|
||||||
*/
|
*/
|
||||||
destroy(callback, soft = false) {
|
destroy(callback, soft = false) {
|
||||||
|
if (!this.ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const done = () => {
|
const done = () => {
|
||||||
// Reset overflow (incase destroyed while in fullscreen)
|
// Reset overflow (incase destroyed while in fullscreen)
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
@ -1057,12 +1149,12 @@ class Plyr {
|
|||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Unbind listeners
|
||||||
|
this.listeners.clear();
|
||||||
|
|
||||||
// Replace the container with the original element provided
|
// Replace the container with the original element provided
|
||||||
utils.replaceElement(this.elements.original, this.elements.container);
|
utils.replaceElement(this.elements.original, this.elements.container);
|
||||||
|
|
||||||
// Unbind global listeners
|
|
||||||
this.listeners.global(false);
|
|
||||||
|
|
||||||
// Event
|
// Event
|
||||||
utils.dispatchEvent.call(this, this.elements.original, 'destroyed', true);
|
utils.dispatchEvent.call(this, this.elements.original, 'destroyed', true);
|
||||||
|
|
||||||
@ -1071,15 +1163,27 @@ class Plyr {
|
|||||||
callback.call(this.elements.original);
|
callback.call(this.elements.original);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear for GC
|
// Reset state
|
||||||
this.elements = null;
|
this.ready = false;
|
||||||
|
|
||||||
|
// Clear for garbage collection
|
||||||
|
setTimeout(() => {
|
||||||
|
this.elements = null;
|
||||||
|
this.media = null;
|
||||||
|
}, 200);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Stop playback
|
||||||
|
this.stop();
|
||||||
|
|
||||||
// Type specific stuff
|
// Type specific stuff
|
||||||
switch (`${this.provider}:${this.type}`) {
|
switch (`${this.provider}:${this.type}`) {
|
||||||
case 'html5:video':
|
case 'html5:video':
|
||||||
case 'html5:audio':
|
case 'html5:audio':
|
||||||
|
// Clear timeout
|
||||||
|
clearTimeout(this.timers.loading);
|
||||||
|
|
||||||
// Restore native video controls
|
// Restore native video controls
|
||||||
ui.toggleNativeControls.call(this, true);
|
ui.toggleNativeControls.call(this, true);
|
||||||
|
|
||||||
@ -1090,11 +1194,11 @@ class Plyr {
|
|||||||
|
|
||||||
case 'youtube:video':
|
case 'youtube:video':
|
||||||
// Clear timers
|
// Clear timers
|
||||||
window.clearInterval(this.timers.buffering);
|
clearInterval(this.timers.buffering);
|
||||||
window.clearInterval(this.timers.playing);
|
clearInterval(this.timers.playing);
|
||||||
|
|
||||||
// Destroy YouTube API
|
// Destroy YouTube API
|
||||||
if (this.embed !== null) {
|
if (this.embed !== null && utils.is.function(this.embed.destroy)) {
|
||||||
this.embed.destroy();
|
this.embed.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr Polyfilled Build
|
// Plyr Polyfilled Build
|
||||||
// plyr.js v3.0.0-beta.19
|
// plyr.js v3.2.4
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { providers } from './types';
|
import { providers } from './types';
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
|
import html5 from './html5';
|
||||||
import media from './media';
|
import media from './media';
|
||||||
import ui from './ui';
|
import ui from './ui';
|
||||||
import support from './support';
|
import support from './support';
|
||||||
@ -31,13 +32,14 @@ const source = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cancel current network requests
|
// Cancel current network requests
|
||||||
media.cancelRequests.call(this);
|
html5.cancelRequests.call(this);
|
||||||
|
|
||||||
// Destroy instance and re-setup
|
// Destroy instance and re-setup
|
||||||
this.destroy.call(
|
this.destroy.call(
|
||||||
this,
|
this,
|
||||||
() => {
|
() => {
|
||||||
// TODO: Reset menus here
|
// Reset quality options
|
||||||
|
this.options.quality = [];
|
||||||
|
|
||||||
// Remove elements
|
// Remove elements
|
||||||
utils.removeElement(this.media);
|
utils.removeElement(this.media);
|
||||||
@ -53,7 +55,7 @@ const source = {
|
|||||||
this.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5;
|
this.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5;
|
||||||
|
|
||||||
// Check for support
|
// Check for support
|
||||||
this.supported = support.check(this.type, this.provider, this.config.inline);
|
this.supported = support.check(this.type, this.provider, this.config.playsinline);
|
||||||
|
|
||||||
// Create new markup
|
// Create new markup
|
||||||
switch (`${this.provider}:${this.type}`) {
|
switch (`${this.provider}:${this.type}`) {
|
||||||
@ -101,7 +103,7 @@ const source = {
|
|||||||
if (this.config.muted) {
|
if (this.config.muted) {
|
||||||
this.media.setAttribute('muted', '');
|
this.media.setAttribute('muted', '');
|
||||||
}
|
}
|
||||||
if (this.config.inline) {
|
if (this.config.playsinline) {
|
||||||
this.media.setAttribute('playsinline', '');
|
this.media.setAttribute('playsinline', '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,17 +12,18 @@ class Storage {
|
|||||||
|
|
||||||
// Check for actual support (see if we can use it)
|
// Check for actual support (see if we can use it)
|
||||||
static get supported() {
|
static get supported() {
|
||||||
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 {
|
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.setItem(test, test);
|
||||||
window.localStorage.removeItem(test);
|
window.localStorage.removeItem(test);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
@ -30,9 +31,13 @@ class Storage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
get(key) {
|
||||||
|
if (!Storage.supported) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const store = window.localStorage.getItem(this.key);
|
const store = window.localStorage.getItem(this.key);
|
||||||
|
|
||||||
if (!Storage.supported || utils.is.empty(store)) {
|
if (utils.is.empty(store)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,16 +12,16 @@ const support = {
|
|||||||
|
|
||||||
// Check for support
|
// Check for support
|
||||||
// Basic functionality vs full UI
|
// Basic functionality vs full UI
|
||||||
check(type, provider, inline) {
|
check(type, provider, playsinline) {
|
||||||
let api = false;
|
let api = false;
|
||||||
let ui = false;
|
let ui = false;
|
||||||
const browser = utils.getBrowser();
|
const browser = utils.getBrowser();
|
||||||
const playsInline = browser.isIPhone && inline && support.inline;
|
const canPlayInline = browser.isIPhone && playsinline && support.playsinline;
|
||||||
|
|
||||||
switch (`${provider}:${type}`) {
|
switch (`${provider}:${type}`) {
|
||||||
case 'html5:video':
|
case 'html5:video':
|
||||||
api = support.video;
|
api = support.video;
|
||||||
ui = api && support.rangeInput && (!browser.isIPhone || playsInline);
|
ui = api && support.rangeInput && (!browser.isIPhone || canPlayInline);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'html5:audio':
|
case 'html5:audio':
|
||||||
@ -30,13 +30,9 @@ const support = {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'youtube:video':
|
case 'youtube:video':
|
||||||
api = true;
|
|
||||||
ui = support.rangeInput && (!browser.isIPhone || playsInline);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'vimeo:video':
|
case 'vimeo:video':
|
||||||
api = true;
|
api = true;
|
||||||
ui = support.rangeInput && !browser.isIPhone;
|
ui = support.rangeInput && (!browser.isIPhone || canPlayInline);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -63,7 +59,7 @@ const support = {
|
|||||||
|
|
||||||
// Inline playback support
|
// Inline playback support
|
||||||
// https://webkit.org/blog/6784/new-video-policies-for-ios/
|
// https://webkit.org/blog/6784/new-video-policies-for-ios/
|
||||||
inline: 'playsInline' in document.createElement('video'),
|
playsinline: 'playsInline' in document.createElement('video'),
|
||||||
|
|
||||||
// Check for mime type support against a player instance
|
// Check for mime type support against a player instance
|
||||||
// Credits: http://diveintohtml5.info/everything.html
|
// Credits: http://diveintohtml5.info/everything.html
|
||||||
@ -77,6 +73,11 @@ const support = {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check directly if codecs specified
|
||||||
|
if (type.includes('codecs=')) {
|
||||||
|
return media.canPlayType(type).replace(/no/, '');
|
||||||
|
}
|
||||||
|
|
||||||
// Type specific checks
|
// Type specific checks
|
||||||
if (this.isVideo) {
|
if (this.isVideo) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -147,7 +148,7 @@ const support = {
|
|||||||
})(),
|
})(),
|
||||||
|
|
||||||
// Touch
|
// Touch
|
||||||
// Remember a device can be moust + touch enabled
|
// NOTE: Remember a device can be mouse + touch enabled so we check on first touch event
|
||||||
touch: 'ontouchstart' in document.documentElement,
|
touch: 'ontouchstart' in document.documentElement,
|
||||||
|
|
||||||
// Detect transitions support
|
// Detect transitions support
|
||||||
|
40
src/js/ui.js
40
src/js/ui.js
@ -5,7 +5,7 @@
|
|||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
import captions from './captions';
|
import captions from './captions';
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
import listeners from './listeners';
|
import i18n from './i18n';
|
||||||
|
|
||||||
const ui = {
|
const ui = {
|
||||||
addStyleHook() {
|
addStyleHook() {
|
||||||
@ -25,7 +25,7 @@ const ui = {
|
|||||||
// Setup the UI
|
// Setup the UI
|
||||||
build() {
|
build() {
|
||||||
// Re-attach media element listeners
|
// Re-attach media element listeners
|
||||||
// TODO: Use event bubbling
|
// TODO: Use event bubbling?
|
||||||
this.listeners.media();
|
this.listeners.media();
|
||||||
|
|
||||||
// Don't setup interface if no support
|
// Don't setup interface if no support
|
||||||
@ -48,11 +48,6 @@ const ui = {
|
|||||||
this.listeners.controls();
|
this.listeners.controls();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's no controls, bail
|
|
||||||
if (!utils.is.element(this.elements.controls)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove native controls
|
// Remove native controls
|
||||||
ui.toggleNativeControls.call(this);
|
ui.toggleNativeControls.call(this);
|
||||||
|
|
||||||
@ -71,8 +66,11 @@ const ui = {
|
|||||||
// Reset loop state
|
// Reset loop state
|
||||||
this.loop = null;
|
this.loop = null;
|
||||||
|
|
||||||
// Reset quality options
|
// Reset quality setting
|
||||||
this.options.quality = [];
|
this.quality = null;
|
||||||
|
|
||||||
|
// Reset volume display
|
||||||
|
ui.updateVolume.call(this);
|
||||||
|
|
||||||
// Reset time display
|
// Reset time display
|
||||||
ui.timeUpdate.call(this);
|
ui.timeUpdate.call(this);
|
||||||
@ -95,7 +93,7 @@ const ui = {
|
|||||||
// Setup aria attribute for play and iframe title
|
// Setup aria attribute for play and iframe title
|
||||||
setTitle() {
|
setTitle() {
|
||||||
// Find the current text
|
// Find the current text
|
||||||
let label = this.config.i18n.play;
|
let label = i18n.get('play', this.config);
|
||||||
|
|
||||||
// If there's a media title set, use that for the label
|
// If there's a media title set, use that for the label
|
||||||
if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) {
|
if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) {
|
||||||
@ -124,7 +122,7 @@ const ui = {
|
|||||||
// Default to media type
|
// Default to media type
|
||||||
const title = !utils.is.empty(this.config.title) ? this.config.title : 'video';
|
const title = !utils.is.empty(this.config.title) ? this.config.title : 'video';
|
||||||
|
|
||||||
iframe.setAttribute('title', this.config.i18n.frameTitle.replace('{title}', title));
|
iframe.setAttribute('title', i18n.get('frameTitle', this.config));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -256,21 +254,7 @@ const ui = {
|
|||||||
// Check buffer status
|
// Check buffer status
|
||||||
case 'playing':
|
case 'playing':
|
||||||
case 'progress':
|
case 'progress':
|
||||||
value = (() => {
|
ui.setProgress.call(this, this.elements.display.buffer, this.buffered * 100);
|
||||||
const { buffered } = this.media;
|
|
||||||
|
|
||||||
if (buffered && buffered.length) {
|
|
||||||
// HTML5
|
|
||||||
return utils.getPercentage(buffered.end(0), this.duration);
|
|
||||||
} else if (utils.is.number(buffered)) {
|
|
||||||
// YouTube returns between 0 and 1
|
|
||||||
return buffered * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
})();
|
|
||||||
|
|
||||||
ui.setProgress.call(this, this.elements.display.buffer, value);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -288,10 +272,10 @@ const ui = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Always display hours if duration is over an hour
|
// Always display hours if duration is over an hour
|
||||||
const displayHours = utils.getHours(this.duration) > 0;
|
const forceHours = utils.getHours(this.duration) > 0;
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
target.textContent = utils.formatTime(time, displayHours, inverted);
|
target.textContent = utils.formatTime(time, forceHours, inverted);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Handle time change event
|
// Handle time change event
|
||||||
|
205
src/js/utils.js
205
src/js/utils.js
@ -2,6 +2,8 @@
|
|||||||
// Plyr utils
|
// Plyr utils
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
import loadjs from 'loadjs';
|
||||||
|
|
||||||
import support from './support';
|
import support from './support';
|
||||||
import { providers } from './types';
|
import { providers } from './types';
|
||||||
|
|
||||||
@ -97,11 +99,10 @@ const utils = {
|
|||||||
if (responseType === 'text') {
|
if (responseType === 'text') {
|
||||||
try {
|
try {
|
||||||
resolve(JSON.parse(request.responseText));
|
resolve(JSON.parse(request.responseText));
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
resolve(request.responseText);
|
resolve(request.responseText);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
resolve(request.response);
|
resolve(request.response);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -125,52 +126,10 @@ const utils = {
|
|||||||
// Load an external script
|
// Load an external script
|
||||||
loadScript(url) {
|
loadScript(url) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const current = document.querySelector(`script[src="${url}"]`);
|
loadjs(url, {
|
||||||
|
success: resolve,
|
||||||
// Check script is not already referenced, if so wait for load
|
error: reject,
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -184,7 +143,14 @@ const utils = {
|
|||||||
const hasId = utils.is.string(id);
|
const hasId = utils.is.string(id);
|
||||||
let isCached = false;
|
let isCached = false;
|
||||||
|
|
||||||
function updateSprite(data) {
|
const exists = () => document.querySelectorAll(`#${id}`).length;
|
||||||
|
|
||||||
|
function injectSprite(data) {
|
||||||
|
// Check again incase of race condition
|
||||||
|
if (hasId && exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Inject content
|
// Inject content
|
||||||
this.innerHTML = data;
|
this.innerHTML = data;
|
||||||
|
|
||||||
@ -192,8 +158,8 @@ const utils = {
|
|||||||
document.body.insertBefore(this, document.body.childNodes[0]);
|
document.body.insertBefore(this, document.body.childNodes[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only load once
|
// Only load once if ID set
|
||||||
if (!hasId || !document.querySelectorAll(`#${id}`).length) {
|
if (!hasId || !exists()) {
|
||||||
// Create container
|
// Create container
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
utils.toggleHidden(container, true);
|
utils.toggleHidden(container, true);
|
||||||
@ -209,7 +175,7 @@ const utils = {
|
|||||||
|
|
||||||
if (isCached) {
|
if (isCached) {
|
||||||
const data = JSON.parse(cached);
|
const data = JSON.parse(cached);
|
||||||
updateSprite.call(container, data.content);
|
injectSprite.call(container, data.content);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +197,7 @@ const utils = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSprite.call(container, result);
|
injectSprite.call(container, result);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
@ -242,15 +208,6 @@ const utils = {
|
|||||||
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
|
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Determine if we're in an iframe
|
|
||||||
inFrame() {
|
|
||||||
try {
|
|
||||||
return window.self !== window.top;
|
|
||||||
} catch (e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Wrap an element
|
// Wrap an element
|
||||||
wrap(elements, wrapper) {
|
wrap(elements, wrapper) {
|
||||||
// Convert `elements` to an array, if necessary.
|
// Convert `elements` to an array, if necessary.
|
||||||
@ -353,8 +310,11 @@ const utils = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(attributes).forEach(key => {
|
Object.entries(attributes).forEach(([
|
||||||
element.setAttribute(key, attributes[key]);
|
key,
|
||||||
|
value,
|
||||||
|
]) => {
|
||||||
|
element.setAttribute(key, value);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -415,6 +375,25 @@ const utils = {
|
|||||||
return attributes;
|
return attributes;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Toggle hidden
|
||||||
|
toggleHidden(element, hidden) {
|
||||||
|
if (!utils.is.element(element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hide = hidden;
|
||||||
|
|
||||||
|
if (!utils.is.boolean(hide)) {
|
||||||
|
hide = !element.hasAttribute('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hide) {
|
||||||
|
element.setAttribute('hidden', '');
|
||||||
|
} else {
|
||||||
|
element.removeAttribute('hidden');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Toggle class on an element
|
// Toggle class on an element
|
||||||
toggleClass(element, className, toggle) {
|
toggleClass(element, className, toggle) {
|
||||||
if (utils.is.element(element)) {
|
if (utils.is.element(element)) {
|
||||||
@ -433,19 +412,6 @@ const utils = {
|
|||||||
return utils.is.element(element) && element.classList.contains(className);
|
return utils.is.element(element) && element.classList.contains(className);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Toggle hidden attribute on an element
|
|
||||||
toggleHidden(element, toggle) {
|
|
||||||
if (!utils.is.element(element)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toggle) {
|
|
||||||
element.setAttribute('hidden', '');
|
|
||||||
} else {
|
|
||||||
element.removeAttribute('hidden');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Element matches selector
|
// Element matches selector
|
||||||
matches(element, selector) {
|
matches(element, selector) {
|
||||||
const prototype = { Element };
|
const prototype = { Element };
|
||||||
@ -481,7 +447,7 @@ const utils = {
|
|||||||
pause: utils.getElement.call(this, this.config.selectors.buttons.pause),
|
pause: utils.getElement.call(this, this.config.selectors.buttons.pause),
|
||||||
restart: utils.getElement.call(this, this.config.selectors.buttons.restart),
|
restart: utils.getElement.call(this, this.config.selectors.buttons.restart),
|
||||||
rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind),
|
rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind),
|
||||||
forward: utils.getElement.call(this, this.config.selectors.buttons.forward),
|
fastForward: utils.getElement.call(this, this.config.selectors.buttons.fastForward),
|
||||||
mute: utils.getElement.call(this, this.config.selectors.buttons.mute),
|
mute: utils.getElement.call(this, this.config.selectors.buttons.mute),
|
||||||
pip: utils.getElement.call(this, this.config.selectors.buttons.pip),
|
pip: utils.getElement.call(this, this.config.selectors.buttons.pip),
|
||||||
airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay),
|
airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay),
|
||||||
@ -502,8 +468,8 @@ const utils = {
|
|||||||
// Display
|
// Display
|
||||||
this.elements.display = {
|
this.elements.display = {
|
||||||
buffer: utils.getElement.call(this, this.config.selectors.display.buffer),
|
buffer: utils.getElement.call(this, this.config.selectors.display.buffer),
|
||||||
duration: utils.getElement.call(this, this.config.selectors.display.duration),
|
|
||||||
currentTime: utils.getElement.call(this, this.config.selectors.display.currentTime),
|
currentTime: utils.getElement.call(this, this.config.selectors.display.currentTime),
|
||||||
|
duration: utils.getElement.call(this, this.config.selectors.display.duration),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Seek tooltip
|
// Seek tooltip
|
||||||
@ -574,9 +540,9 @@ const utils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Toggle event listener
|
// Toggle event listener
|
||||||
toggleListener(elements, event, callback, toggle, passive, capture) {
|
toggleListener(elements, event, callback, toggle = false, passive = true, capture = false) {
|
||||||
// Bail if no elemetns, event, or callback
|
// Bail if no elemetns, event, or callback
|
||||||
if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) {
|
if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,16 +562,16 @@ const utils = {
|
|||||||
const events = event.split(' ');
|
const events = event.split(' ');
|
||||||
|
|
||||||
// Build options
|
// Build options
|
||||||
// Default to just capture boolean
|
// Default to just the capture boolean for browsers with no passive listener support
|
||||||
let options = utils.is.boolean(capture) ? capture : false;
|
let options = capture;
|
||||||
|
|
||||||
// If passive events listeners are supported
|
// If passive events listeners are supported
|
||||||
if (support.passiveListeners) {
|
if (support.passiveListeners) {
|
||||||
options = {
|
options = {
|
||||||
// Whether the listener can be passive (i.e. default never prevented)
|
// Whether the listener can be passive (i.e. default never prevented)
|
||||||
passive: utils.is.boolean(passive) ? passive : true,
|
passive,
|
||||||
// Whether the listener is a capturing listener or not
|
// Whether the listener is a capturing listener or not
|
||||||
capture: utils.is.boolean(capture) ? capture : false,
|
capture,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -616,25 +582,25 @@ const utils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Bind event handler
|
// Bind event handler
|
||||||
on(element, events, callback, passive, capture) {
|
on(element, events = '', callback, passive = true, capture = false) {
|
||||||
utils.toggleListener(element, events, callback, true, passive, capture);
|
utils.toggleListener(element, events, callback, true, passive, capture);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Unbind event handler
|
// Unbind event handler
|
||||||
off(element, events, callback, passive, capture) {
|
off(element, events = '', callback, passive = true, capture = false) {
|
||||||
utils.toggleListener(element, events, callback, false, passive, capture);
|
utils.toggleListener(element, events, callback, false, passive, capture);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Trigger event
|
// Trigger event
|
||||||
dispatchEvent(element, type, bubbles, detail) {
|
dispatchEvent(element, type = '', bubbles = false, detail = {}) {
|
||||||
// Bail if no element
|
// Bail if no element
|
||||||
if (!utils.is.element(element) || !utils.is.string(type)) {
|
if (!utils.is.element(element) || utils.is.empty(type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and dispatch the event
|
// Create and dispatch the event
|
||||||
const event = new CustomEvent(type, {
|
const event = new CustomEvent(type, {
|
||||||
bubbles: utils.is.boolean(bubbles) ? bubbles : false,
|
bubbles,
|
||||||
detail: Object.assign({}, detail, {
|
detail: Object.assign({}, detail, {
|
||||||
plyr: utils.is.plyr(this) ? this : null,
|
plyr: utils.is.plyr(this) ? this : null,
|
||||||
}),
|
}),
|
||||||
@ -671,6 +637,7 @@ const utils = {
|
|||||||
if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
|
if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (current / max * 100).toFixed(2);
|
return (current / max * 100).toFixed(2);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -711,6 +678,44 @@ const utils = {
|
|||||||
return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Replace all occurances of a string in a string
|
||||||
|
replaceAll(input = '', find = '', replace = '') {
|
||||||
|
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
|
},
|
||||||
|
|
||||||
|
// Convert to title case
|
||||||
|
toTitleCase(input = '') {
|
||||||
|
return input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase());
|
||||||
|
},
|
||||||
|
|
||||||
|
// Convert string to pascalCase
|
||||||
|
toPascalCase(input = '') {
|
||||||
|
let string = input.toString();
|
||||||
|
|
||||||
|
// Convert kebab case
|
||||||
|
string = utils.replaceAll(string, '-', ' ');
|
||||||
|
|
||||||
|
// Convert snake case
|
||||||
|
string = utils.replaceAll(string, '_', ' ');
|
||||||
|
|
||||||
|
// Convert to title case
|
||||||
|
string = utils.toTitleCase(string);
|
||||||
|
|
||||||
|
// Convert to pascal case
|
||||||
|
return utils.replaceAll(string, ' ', '');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Convert string to pascalCase
|
||||||
|
toCamelCase(input = '') {
|
||||||
|
let string = input.toString();
|
||||||
|
|
||||||
|
// Convert to pascal case
|
||||||
|
string = utils.toPascalCase(string);
|
||||||
|
|
||||||
|
// Convert first character to lowercase
|
||||||
|
return string.charAt(0).toLowerCase() + string.slice(1);
|
||||||
|
},
|
||||||
|
|
||||||
// Deep extend destination object with N more objects
|
// Deep extend destination object with N more objects
|
||||||
extend(target = {}, ...sources) {
|
extend(target = {}, ...sources) {
|
||||||
if (!sources.length) {
|
if (!sources.length) {
|
||||||
@ -738,6 +743,24 @@ const utils = {
|
|||||||
return utils.extend(target, ...sources);
|
return utils.extend(target, ...sources);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Remove duplicates in an array
|
||||||
|
dedupe(array) {
|
||||||
|
if (!utils.is.array(array)) {
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array.filter((item, index) => array.indexOf(item) === index);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get the closest value in an array
|
||||||
|
closest(array, value) {
|
||||||
|
if (!utils.is.array(array) || !array.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array.reduce((prev, curr) => (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev));
|
||||||
|
},
|
||||||
|
|
||||||
// Get the provider for a given URL
|
// Get the provider for a given URL
|
||||||
getProviderByUrl(url) {
|
getProviderByUrl(url) {
|
||||||
// YouTube
|
// YouTube
|
||||||
|
@ -26,6 +26,11 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
// Ignore focus
|
// Ignore focus
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
|
@ -3,14 +3,12 @@
|
|||||||
// YouTube, Vimeo, etc
|
// YouTube, Vimeo, etc
|
||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
.plyr__video-embed {
|
// Default to 16:9 ratio but this is set by JavaScript based on config
|
||||||
// Default to 16:9 ratio but this is set by JavaScript based on config
|
$embed-padding: ((100 / 16) * 9);
|
||||||
$padding: ((100 / 16) * 9);
|
|
||||||
$height: 200;
|
|
||||||
$offset: to-percentage(($height - $padding) / ($height / 50));
|
|
||||||
|
|
||||||
|
.plyr__video-embed {
|
||||||
height: 0;
|
height: 0;
|
||||||
padding-bottom: to-percentage($padding);
|
padding-bottom: to-percentage($embed-padding);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
@ -22,6 +20,17 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the full custom UI is supported
|
||||||
|
.plyr--full-ui .plyr__video-embed {
|
||||||
|
$height: 240;
|
||||||
|
$offset: to-percentage(($height - $embed-padding) / ($height / 50));
|
||||||
|
|
||||||
|
// To allow mouse events to be captured if full support
|
||||||
|
iframe {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
// Vimeo hack
|
// Vimeo hack
|
||||||
> div {
|
> div {
|
||||||
@ -30,7 +39,3 @@
|
|||||||
transform: translateY(-$offset);
|
transform: translateY(-$offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// To allow mouse events to be captured if full support
|
|
||||||
.plyr--full-ui .plyr__video-embed iframe {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
right: -3px;
|
right: -3px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
z-index: 1;
|
z-index: 3;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -74,6 +74,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
color: $plyr-menu-color;
|
color: $plyr-menu-color;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
font-size: $plyr-font-size-menu;
|
||||||
padding: ceil($plyr-control-padding / 2) ($plyr-control-padding * 2);
|
padding: ceil($plyr-control-padding / 2) ($plyr-control-padding * 2);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -84,7 +85,6 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
transition: border-color 0.2s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&--forward {
|
&--forward {
|
||||||
@ -108,7 +108,6 @@
|
|||||||
margin-bottom: floor($plyr-control-padding / 2);
|
margin-bottom: floor($plyr-control-padding / 2);
|
||||||
padding-left: ceil($plyr-control-padding * 4);
|
padding-left: ceil($plyr-control-padding * 4);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
width: calc(100% - #{$horizontal-padding});
|
width: calc(100% - #{$horizontal-padding});
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
&::-webkit-slider-runnable-track {
|
&::-webkit-slider-runnable-track {
|
||||||
@include plyr-range-track();
|
@include plyr-range-track();
|
||||||
background-image: linear-gradient(to right, currentColor var(--value), transparent var(--value));
|
background-image: linear-gradient(to right, currentColor var(--value, 0%), transparent var(--value, 0%));
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-slider-thumb {
|
&::-webkit-slider-thumb {
|
||||||
|
@ -23,7 +23,12 @@
|
|||||||
// Hide sound controls on iOS
|
// Hide sound controls on iOS
|
||||||
// It's not supported to change volume using JavaScript:
|
// It's not supported to change volume using JavaScript:
|
||||||
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
|
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
|
||||||
.plyr--is-ios .plyr__volume,
|
.plyr--is-ios .plyr__volume {
|
||||||
.plyr--is-ios [data-plyr='mute'] {
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vimeo has no toggle mute method so hide mute button
|
||||||
|
// https://github.com/vimeo/player.js/issues/236#issuecomment-384663183
|
||||||
|
.plyr--is-ios.plyr--vimeo [data-plyr='mute'] {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,11 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
.plyr__ads {
|
.plyr__ads {
|
||||||
|
border-radius: inherit;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -8,8 +8,9 @@ $plyr-font-size-small: 14px !default;
|
|||||||
$plyr-font-size-large: 18px !default;
|
$plyr-font-size-large: 18px !default;
|
||||||
$plyr-font-size-xlarge: 21px !default;
|
$plyr-font-size-xlarge: 21px !default;
|
||||||
|
|
||||||
$plyr-font-size-time: 14px !default;
|
$plyr-font-size-time: $plyr-font-size-small !default;
|
||||||
$plyr-font-size-badge: 9px !default;
|
$plyr-font-size-badge: 9px !default;
|
||||||
|
$plyr-font-size-menu: $plyr-font-size-small !default;
|
||||||
|
|
||||||
$plyr-font-weight-regular: 500 !default;
|
$plyr-font-weight-regular: 500 !default;
|
||||||
$plyr-font-weight-bold: 600 !default;
|
$plyr-font-weight-bold: 600 !default;
|
||||||
|
@ -2,15 +2,6 @@
|
|||||||
// Hiding content nicely
|
// Hiding content nicely
|
||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
// Attributes
|
|
||||||
.plyr--full-ui [hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plyr--full-ui [aria-hidden='true'] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Screen reader only elements
|
// Screen reader only elements
|
||||||
.plyr__sr-only {
|
.plyr__sr-only {
|
||||||
clip: rect(1px, 1px, 1px, 1px);
|
clip: rect(1px, 1px, 1px, 1px);
|
||||||
|
Reference in New Issue
Block a user