Compare commits
60 Commits
| 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 |
@@ -9,6 +9,7 @@
|
||||
"ignore": ["attribute", "class"]
|
||||
}
|
||||
],
|
||||
"string-no-newline": null,
|
||||
"indentation": 4,
|
||||
"string-quotes": "single",
|
||||
"max-nesting-depth": 2,
|
||||
|
||||
Vendored
+15
@@ -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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
"demo": {
|
||||
"sass": {
|
||||
"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": {
|
||||
"demo.js": "demo/src/js/demo.js"
|
||||
|
||||
+92
-3
@@ -1,4 +1,93 @@
|
||||
# v3.0.3
|
||||
## v3.2.4
|
||||
|
||||
* Fix issue wher player never reports as ready if controls is empty array
|
||||
* Fix issue where screen reader labels were removed from time displays
|
||||
* Fix issue where custom controls placeholders were not populated
|
||||
* Custom controls HTML example updated
|
||||
* Fix for aria-label being set to the initial state on toggle buttons, overriding the inner labels
|
||||
* Fix for hidden mute button on iOS (not functional for Vimeo due to API limitations) (fixes #656)
|
||||
|
||||
## v3.2.3
|
||||
|
||||
* Fix for iOS 9 throwing error for `name` property in fullscreen API (fixes #908)
|
||||
|
||||
## v3.2.2
|
||||
|
||||
* Fix for regression in 3.2.1 resulting in hidden buffer display (fixes #920)
|
||||
* Cleaned up incorrect use of `aria-hidden` attribute
|
||||
|
||||
## v3.2.1
|
||||
|
||||
* Accessibility improvements for the controls (part of #905 fixes)
|
||||
* Fix for context menu showing on YouTube (thanks Anthony Recenello in Slack)
|
||||
* Vimeo fix for their API not returning the right duration until playback begins (fixes #891)
|
||||
|
||||
## v3.2.0
|
||||
|
||||
* Fullscreen fixes (thanks @friday)
|
||||
* Menu fix for if speed not in config
|
||||
* Menu z-index fix (thanks @danielsarin)
|
||||
* i18n fix for missing "Normal" string (thanks @danielsarin)
|
||||
* Safer check for active caption (thanks @Antonio-Laguna)
|
||||
* Add custom property fallback (thanks @friday)
|
||||
* Fixed bug for captions with no srclang and labels and improved logic (fixes #875)
|
||||
* Fix for `playing` false positive (fixes #898)
|
||||
* Fix for IE issue with navigator.language (thanks @nicolasthy) (fixes #893)
|
||||
* Fix for Vimeo controls missing on iOS (thanks @verde-io) (fixes #807)
|
||||
* Fix for double vimeo caption rendering (fixes #877)
|
||||
|
||||
## v3.1.0
|
||||
|
||||
* Styling fixes
|
||||
|
||||
## v3.1.0-beta.2
|
||||
|
||||
* YouTube playback speed fixes
|
||||
|
||||
## v3.1.0-beta.1
|
||||
|
||||
* HTML5 quality selection
|
||||
* Improvements to the YouTube quality selection
|
||||
|
||||
## v3.0.11
|
||||
|
||||
* Muted and autoplay fixes
|
||||
* Small bug fixes from Sentry logs
|
||||
|
||||
## v3.0.10
|
||||
|
||||
* Docs fix
|
||||
* Package upgrades
|
||||
|
||||
## v3.0.9
|
||||
|
||||
* Demo fix
|
||||
* Fix Vimeo regression
|
||||
|
||||
## v3.0.8
|
||||
|
||||
* Vimeo hotfix for private videos
|
||||
|
||||
## v3.0.7
|
||||
|
||||
* Fix for keyboard shortcut error with fast forward
|
||||
* Fix for Vimeo trying to set playback rate when not allowed
|
||||
|
||||
## v3.0.6
|
||||
|
||||
* Improved the logic for the custom handlers preventing default handlers
|
||||
|
||||
## v3.0.5
|
||||
|
||||
* Removed console messages
|
||||
|
||||
## v3.0.4
|
||||
|
||||
* Fixes for fullscreen not working inside iframes
|
||||
* Fixes for custom handlers being able to prevent default
|
||||
* Fixes for controls not hiding/showing correctly on Mobile Safari
|
||||
|
||||
## v3.0.3
|
||||
|
||||
* Vimeo offset tweak (fixes #826)
|
||||
* Fix for .stop() method (fixes #819)
|
||||
@@ -9,11 +98,11 @@
|
||||
* Fix fast-forward control (thanks @saadshahd)
|
||||
* Fix the options link in the readme (thanks @DanielRuf)
|
||||
|
||||
# v3.0.2
|
||||
## v3.0.2
|
||||
|
||||
* Fix for Safari not firing error events when trying to load blocked scripts
|
||||
|
||||
# v3.0.1
|
||||
## v3.0.1
|
||||
|
||||
* Fix for trying to accessing local storage when it's blocked
|
||||
|
||||
|
||||
+125
-85
@@ -1,27 +1,73 @@
|
||||
# Controls
|
||||
|
||||
This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs.
|
||||
This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs. You can pass the following to the `controls` option:
|
||||
|
||||
## Internationalization using default controls
|
||||
* `Array` of options (this builds the default controls based on your choices)
|
||||
* `String` containing the desired HTML
|
||||
* `Function` that will be executed and should return one of the above
|
||||
|
||||
## Using default controls
|
||||
|
||||
If you want to use the standard controls as they are, you don't need to pass any options. If you want to turn on off controls, here's the full list:
|
||||
|
||||
```javascript
|
||||
controls: [
|
||||
'play-large', // The large play button in the center
|
||||
'restart', // Restart playback
|
||||
'rewind', // Rewind by the seek time (default 10 seconds)
|
||||
'play', // Play/pause playback
|
||||
'fast-forward', // Fast forward by the seek time (default 10 seconds)
|
||||
'progress', // The progress bar and scrubber for playback and buffering
|
||||
'current-time', // The current time of playback
|
||||
'duration', // The full duration of the media
|
||||
'mute', // Toggle mute
|
||||
'volume', // Volume control
|
||||
'captions', // Toggle captions
|
||||
'settings', // Settings menu
|
||||
'pip', // Picture-in-picture (currently Safari only)
|
||||
'airplay', // Airplay (currently Safari only)
|
||||
'fullscreen', // Toggle fullscreen
|
||||
];
|
||||
```
|
||||
|
||||
### Internationalization using default controls
|
||||
|
||||
You can provide an `i18n` object as one of your options when initialising the plugin which we be used when rendering the controls.
|
||||
|
||||
### Example
|
||||
#### Example
|
||||
|
||||
```javascript
|
||||
i18n: {
|
||||
restart: "Restart",
|
||||
rewind: "Rewind {seektime} secs",
|
||||
play: "Play",
|
||||
pause: "Pause",
|
||||
forward: "Forward {seektime} secs",
|
||||
buffered: "buffered",
|
||||
currentTime: "Current time",
|
||||
duration: "Duration",
|
||||
volume: "Volume",
|
||||
toggleMute: "Toggle Mute",
|
||||
toggleCaptions: "Toggle Captions",
|
||||
toggleFullscreen: "Toggle Fullscreen"
|
||||
restart: 'Restart',
|
||||
rewind: 'Rewind {seektime} secs',
|
||||
play: 'Play',
|
||||
pause: 'Pause',
|
||||
fastForward: 'Forward {seektime} secs',
|
||||
seek: 'Seek',
|
||||
played: 'Played',
|
||||
buffered: 'Buffered',
|
||||
currentTime: 'Current time',
|
||||
duration: 'Duration',
|
||||
volume: 'Volume',
|
||||
mute: 'Mute',
|
||||
unmute: 'Unmute',
|
||||
enableCaptions: 'Enable captions',
|
||||
disableCaptions: 'Disable captions',
|
||||
enterFullscreen: 'Enter fullscreen',
|
||||
exitFullscreen: 'Exit fullscreen',
|
||||
frameTitle: 'Player for {title}',
|
||||
captions: 'Captions',
|
||||
settings: 'Settings',
|
||||
speed: 'Speed',
|
||||
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
|
||||
|
||||
You can specify the HTML for the controls using the `html` option.
|
||||
You can specify the HTML as a `String` or your `Function` return for the controls using the `controls` option.
|
||||
|
||||
The classes and data attributes used in your template should match the `selectors` option.
|
||||
The classes and data attributes used in your template should match the `selectors` option if you change any.
|
||||
|
||||
You need to add several placeholders to your html template that are replaced when rendering:
|
||||
You need to add several placeholders to your HTML template that are replaced when rendering:
|
||||
|
||||
- `{id}` - the dynamically generated ID for the player (for form controls)
|
||||
- `{seektime}` - the seek time specified in options for fast forward and rewind
|
||||
- `{title}` - the title of your media, if specified
|
||||
* `{id}` - the dynamically generated ID for the player (for form controls)
|
||||
* `{seektime}` - the seek time specified in options for fast forward and rewind
|
||||
* `{title}` - the title of your media, if specified
|
||||
|
||||
You can include only the controls you need when specifying custom html.
|
||||
### Limitations
|
||||
|
||||
* Currently the settings menus are not supported with custom controls HTML
|
||||
* AirPlay and PiP buttons can be added but you will have to manage feature detection
|
||||
|
||||
### Example
|
||||
|
||||
This is an example `html` option with all controls.
|
||||
Here's an example of custom controls markup (this is just all default controls shown).
|
||||
|
||||
```javascript
|
||||
var controls = ["<div class='plyr__controls'>",
|
||||
"<button type='button' data-plyr='restart'>",
|
||||
"<svg><use xlink:href='#plyr-restart'></use></svg>",
|
||||
"<span class='plyr__sr-only'>Restart</span>",
|
||||
"</button>",
|
||||
"<button type='button' data-plyr='rewind'>",
|
||||
"<svg><use xlink:href='#plyr-rewind'></use></svg>",
|
||||
"<span class='plyr__sr-only'>Rewind {seektime} secs</span>",
|
||||
"</button>",
|
||||
"<button type='button' data-plyr='play'>",
|
||||
"<svg><use xlink:href='#plyr-play'></use></svg>",
|
||||
"<span class='plyr__sr-only'>Play</span>",
|
||||
"</button>",
|
||||
"<button type='button' data-plyr='pause'>",
|
||||
"<svg><use xlink:href='#plyr-pause'></use></svg>",
|
||||
"<span class='plyr__sr-only'>Pause</span>",
|
||||
"</button>",
|
||||
"<button type='button' data-plyr='fast-forward'>",
|
||||
"<svg><use xlink:href='#plyr-fast-forward'></use></svg>",
|
||||
"<span class='plyr__sr-only'>Forward {seektime} secs</span>",
|
||||
"</button>",
|
||||
"<span class='plyr__progress'>",
|
||||
"<label for='seek{id}' class='plyr__sr-only'>Seek</label>",
|
||||
"<input id='seek{id}' class='plyr__progress--seek' type='range' min='0' max='100' step='0.1' value='0' data-plyr='seek'>",
|
||||
"<progress class='plyr__progress--played' max='100' value='0' role='presentation'></progress>",
|
||||
"<progress class='plyr__progress--buffer' max='100' value='0'>",
|
||||
"<span>0</span>% buffered",
|
||||
"</progress>",
|
||||
"<span class='plyr__tooltip'>00:00</span>",
|
||||
"</span>",
|
||||
"<span class='plyr__time'>",
|
||||
"<span class='plyr__sr-only'>Current time</span>",
|
||||
"<span class='plyr__time--current'>00:00</span>",
|
||||
"</span>",
|
||||
"<span class='plyr__time'>",
|
||||
"<span class='plyr__sr-only'>Duration</span>",
|
||||
"<span class='plyr__time--duration'>00:00</span>",
|
||||
"</span>",
|
||||
"<button type='button' data-plyr='mute'>",
|
||||
"<svg class='icon--muted'><use xlink:href='#plyr-muted'></use></svg>",
|
||||
"<svg><use xlink:href='#plyr-volume'></use></svg>",
|
||||
"<span class='plyr__sr-only'>Toggle Mute</span>",
|
||||
"</button>",
|
||||
"<span class='plyr__volume'>",
|
||||
"<label for='volume{id}' class='plyr__sr-only'>Volume</label>",
|
||||
"<input id='volume{id}' class='plyr__volume--input' type='range' min='0' max='10' value='5' data-plyr='volume'>",
|
||||
"<progress class='plyr__volume--display' max='10' value='0' role='presentation'></progress>",
|
||||
"</span>",
|
||||
"<button type='button' data-plyr='captions'>",
|
||||
"<svg class='icon--captions-on'><use xlink:href='#plyr-captions-on'></use></svg>",
|
||||
"<svg><use xlink:href='#plyr-captions-off'></use></svg>",
|
||||
"<span class='plyr__sr-only'>Toggle Captions</span>",
|
||||
"</button>",
|
||||
"<button type='button' data-plyr='fullscreen'>",
|
||||
"<svg class='icon--exit-fullscreen'><use xlink:href='#plyr-exit-fullscreen'></use></svg>",
|
||||
"<svg><use xlink:href='#plyr-enter-fullscreen'></use></svg>",
|
||||
"<span class='plyr__sr-only'>Toggle Fullscreen</span>",
|
||||
"</button>",
|
||||
"</div>"].join("");
|
||||
const controls = `
|
||||
<div class="plyr__controls">
|
||||
<button type="button" class="plyr__control" data-plyr="restart">
|
||||
<svg role="presentation"><use xlink:href="#plyr-restart"></use></svg>
|
||||
<span class="plyr__tooltip" role="tooltip">Restart</span>
|
||||
</button>
|
||||
<button type="button" class="plyr__control" data-plyr="rewind">
|
||||
<svg role="presentation"><use xlink:href="#plyr-rewind"></use></svg>
|
||||
<span class="plyr__tooltip" role="tooltip">Rewind {seektime} secs</span>
|
||||
</button>
|
||||
<button type="button" class="plyr__control" aria-pressed="false" aria-label="Play, {title}" data-plyr="play">
|
||||
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-pause"></use></svg>
|
||||
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-play"></use></svg>
|
||||
<span class="label--pressed plyr__tooltip" role="tooltip">Pause</span>
|
||||
<span class="label--not-pressed plyr__tooltip" role="tooltip">Play</span>
|
||||
</button>
|
||||
<button type="button" class="plyr__control" data-plyr="fast-forward">
|
||||
<svg role="presentation"><use xlink:href="#plyr-fast-forward"></use></svg>
|
||||
<span class="plyr__tooltip" role="tooltip">Forward {seektime} secs</span>
|
||||
</button>
|
||||
<div class="plyr__progress">
|
||||
<label for="plyr-seek-{id}" class="plyr__sr-only">Seek</label>
|
||||
<input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" id="plyr-seek-{id}">
|
||||
<progress class="plyr__progress--buffer" min="0" max="100" value="0">% buffered</progress>
|
||||
<span role="tooltip" class="plyr__tooltip">00:00</span>
|
||||
</div>
|
||||
<div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div>
|
||||
<div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
|
||||
<button type="button" class="plyr__control" aria-pressed="false" aria-label="Mute" data-plyr="mute">
|
||||
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-muted"></use></svg>
|
||||
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-volume"></use></svg>
|
||||
<span class="label--pressed plyr__tooltip" role="tooltip">Unmute</span>
|
||||
<span class="label--not-pressed plyr__tooltip" role="tooltip">Mute</span>
|
||||
</button>
|
||||
<div class="plyr__volume">
|
||||
<label for="plyr-volume-{id}" class="plyr__sr-only">Volume</label>
|
||||
<input data-plyr="volume" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" id="plyr-volume-{id}">
|
||||
</div>
|
||||
<button type="button" class="plyr__control" aria-pressed="true" aria-label="Enable captions" data-plyr="captions">
|
||||
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-captions-on"></use></svg>
|
||||
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-captions-off"></use></svg>
|
||||
<span class="label--pressed plyr__tooltip" role="tooltip">Disable captions</span>
|
||||
<span class="label--not-pressed plyr__tooltip" role="tooltip">Enable captions</span>
|
||||
</button>
|
||||
<button type="button" class="plyr__control" aria-pressed="false" aria-label="Enter fullscreen" data-plyr="fullscreen">
|
||||
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-exit-fullscreen"></use></svg>
|
||||
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-enter-fullscreen"></use></svg>
|
||||
<span class="label--pressed plyr__tooltip" role="tooltip">Exit fullscreen</span>
|
||||
<span class="label--not-pressed plyr__tooltip" role="tooltip">Enter fullscreen</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Setup the player
|
||||
plyr.setup('.js-player', {
|
||||
html: controls
|
||||
});
|
||||
const player = new Plyr('#player', { controls });
|
||||
```
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+381
-257
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+7
-1
@@ -6,8 +6,14 @@
|
||||
<title>Doh. Looks like something went wrong.</title>
|
||||
<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 -->
|
||||
<link rel="stylesheet" href="dist/error.css">
|
||||
<link rel="stylesheet" href="dist/error.css?v=2">
|
||||
|
||||
<!-- Preload -->
|
||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
|
||||
|
||||
+11
-8
@@ -27,7 +27,7 @@
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
|
||||
<!-- Docs styles -->
|
||||
<link rel="stylesheet" href="dist/demo.css">
|
||||
<link rel="stylesheet" href="dist/demo.css?v=2">
|
||||
|
||||
<!-- Preload -->
|
||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
|
||||
@@ -93,16 +93,18 @@
|
||||
<main>
|
||||
<video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
|
||||
<!-- Video files -->
|
||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" type="video/mp4">
|
||||
<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-576p.mp4" type="video/mp4" size="576">
|
||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4" type="video/mp4" size="720">
|
||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4" type="video/mp4" size="1080">
|
||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4" type="video/mp4" size="1440">
|
||||
|
||||
<!-- 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"
|
||||
default>
|
||||
<track kind="captions" label="Français" srclang="fr" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
|
||||
|
||||
<!-- Fallback for browsers that don't support the <video> element -->
|
||||
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-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>
|
||||
|
||||
<ul>
|
||||
@@ -112,7 +114,7 @@
|
||||
<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>
|
||||
</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>
|
||||
</li>
|
||||
<li class="plyr__cite plyr__cite--audio" hidden>
|
||||
@@ -139,7 +141,7 @@
|
||||
</li>
|
||||
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
||||
<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">
|
||||
<svg class="icon" role="presentation">
|
||||
<title>Vimeo</title>
|
||||
@@ -169,7 +171,8 @@
|
||||
</aside>
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent" crossorigin="anonymous"></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 -->
|
||||
<script src="../dist/plyr.js" crossorigin="anonymous"></script>
|
||||
|
||||
+24
-6
@@ -65,6 +65,7 @@ import Raven from 'raven-js';
|
||||
'fast-forward',
|
||||
'progress',
|
||||
'current-time',
|
||||
'duration',
|
||||
'mute',
|
||||
'volume',
|
||||
'captions',
|
||||
@@ -80,7 +81,7 @@ import Raven from 'raven-js';
|
||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
|
||||
},
|
||||
ads: {
|
||||
// enabled: true,
|
||||
enabled: true,
|
||||
publisherId: '918848828995742',
|
||||
},
|
||||
});
|
||||
@@ -118,10 +119,28 @@ import Raven from 'raven-js';
|
||||
player.source = {
|
||||
type: 'video',
|
||||
title: 'View From A Blue Moon',
|
||||
sources: [{
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4',
|
||||
type: 'video/mp4',
|
||||
}],
|
||||
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: [
|
||||
{
|
||||
@@ -163,7 +182,6 @@ import Raven from 'raven-js';
|
||||
case types.youtube:
|
||||
player.source = {
|
||||
type: 'video',
|
||||
title: 'View From A Blue Moon',
|
||||
sources: [{
|
||||
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
||||
provider: 'youtube',
|
||||
|
||||
@@ -3,12 +3,6 @@
|
||||
// ==========================================================================
|
||||
@charset 'UTF-8';
|
||||
|
||||
// Libs
|
||||
@import '../lib/fontface';
|
||||
@import '../lib/mixins';
|
||||
@import '../lib/normalize';
|
||||
@import '../lib/reset';
|
||||
|
||||
// Settings
|
||||
@import '../settings/colors';
|
||||
@import '../settings/cosmetic';
|
||||
@@ -17,6 +11,12 @@
|
||||
@import '../settings/spacing';
|
||||
@import '../settings/type';
|
||||
|
||||
// Libs
|
||||
@import '../lib/fontface';
|
||||
@import '../lib/mixins';
|
||||
@import '../lib/normalize';
|
||||
@import '../lib/reset';
|
||||
|
||||
// Layout
|
||||
@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-medium: 18px;
|
||||
$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);
|
||||
font-weight: $font-weight-bold;
|
||||
letter-spacing: $letter-spacing-headings;
|
||||
margin: 0 0 ($spacing-base / 2);
|
||||
line-height: 1.2;
|
||||
margin: 0 0 $spacing-base;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+781
-554
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+848
-773
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+16
-15
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "plyr",
|
||||
"version": "3.0.4",
|
||||
"version": "3.2.4",
|
||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||
"homepage": "https://plyr.io",
|
||||
"main": "./dist/plyr.js",
|
||||
@@ -9,26 +9,26 @@
|
||||
"style": "./dist/plyr.css",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^8.2.2",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"del": "^3.0.0",
|
||||
"eslint": "^4.19.0",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-airbnb-base": "^12.1.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-import": "^2.9.0",
|
||||
"eslint-plugin-import": "^2.11.0",
|
||||
"git-branch": "^2.0.1",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-autoprefixer": "^5.0.0",
|
||||
"gulp-better-rollup": "^3.0.0",
|
||||
"gulp-better-rollup": "^3.1.0",
|
||||
"gulp-clean-css": "^3.9.3",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-filter": "^5.1.0",
|
||||
"gulp-open": "^3.0.0",
|
||||
"gulp-open": "^3.0.1",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-replace": "^0.6.1",
|
||||
"gulp-s3": "^0.11.0",
|
||||
"gulp-sass": "^3.1.0",
|
||||
"gulp-sass": "^4.0.1",
|
||||
"gulp-size": "^3.0.0",
|
||||
"gulp-sourcemaps": "^2.6.4",
|
||||
"gulp-svgmin": "^1.2.4",
|
||||
@@ -37,16 +37,16 @@
|
||||
"gulp-util": "^3.0.8",
|
||||
"prettier-eslint": "^8.8.1",
|
||||
"prettier-stylelint": "^0.4.2",
|
||||
"rollup-plugin-babel": "^3.0.3",
|
||||
"rollup-plugin-commonjs": "^8.4.1",
|
||||
"rollup-plugin-node-resolve": "^3.2.0",
|
||||
"rollup-plugin-babel": "^3.0.4",
|
||||
"rollup-plugin-commonjs": "^9.1.0",
|
||||
"rollup-plugin-node-resolve": "^3.3.0",
|
||||
"run-sequence": "^2.2.1",
|
||||
"stylelint": "^9.1.3",
|
||||
"stylelint-config-prettier": "^3.0.4",
|
||||
"stylelint": "^9.2.0",
|
||||
"stylelint-config-prettier": "^3.2.0",
|
||||
"stylelint-config-recommended": "^2.1.0",
|
||||
"stylelint-config-sass-guidelines": "^5.0.0",
|
||||
"stylelint-order": "^0.8.1",
|
||||
"stylelint-scss": "^2.5.0",
|
||||
"stylelint-scss": "^3.0.1",
|
||||
"stylelint-selector-bem-pattern": "^2.0.0"
|
||||
},
|
||||
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
|
||||
@@ -68,7 +68,8 @@
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"custom-event-polyfill": "^0.3.0",
|
||||
"loadjs": "^3.5.2",
|
||||
"raven-js": "^3.23.3"
|
||||
"loadjs": "^3.5.4",
|
||||
"npm": "^6.0.0",
|
||||
"raven-js": "^3.24.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.plyr.io/3.0.4/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
|
||||
@@ -144,13 +144,13 @@ Include the `plyr.css` stylsheet into your `<head>`
|
||||
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.0.4/plyr.css">
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.2.4/plyr.css">
|
||||
```
|
||||
|
||||
### SVG Sprite
|
||||
|
||||
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.4/plyr.svg`.
|
||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.2.4/plyr.svg`.
|
||||
|
||||
## Ads
|
||||
|
||||
|
||||
+45
-6
@@ -6,6 +6,7 @@
|
||||
import support from './support';
|
||||
import utils from './utils';
|
||||
import controls from './controls';
|
||||
import i18n from './i18n';
|
||||
|
||||
const captions = {
|
||||
// Setup captions
|
||||
@@ -46,6 +47,7 @@ const captions = {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the container
|
||||
if (!utils.is.element(this.elements.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
|
||||
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
|
||||
@@ -206,11 +250,6 @@ const captions = {
|
||||
|
||||
// Display captions container and button (for initialization)
|
||||
show() {
|
||||
// If there's no caption toggle, bail
|
||||
if (!utils.is.element(this.elements.buttons.captions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to load the value from storage
|
||||
let active = this.storage.get('captions');
|
||||
|
||||
|
||||
Vendored
+164
-126
@@ -7,6 +7,7 @@ import utils from './utils';
|
||||
import ui from './ui';
|
||||
import i18n from './i18n';
|
||||
import captions from './captions';
|
||||
import html5 from './html5';
|
||||
|
||||
// Sniff out the browser
|
||||
const browser = utils.getBrowser();
|
||||
@@ -14,11 +15,6 @@ const browser = utils.getBrowser();
|
||||
const controls = {
|
||||
// Webkit polyfill for lower fill range
|
||||
updateRangeFill(target) {
|
||||
// WebKit only
|
||||
if (!browser.isWebkit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get range from event if event passed
|
||||
const range = utils.is.event(target) ? target.target : target;
|
||||
|
||||
@@ -27,6 +23,14 @@ const controls = {
|
||||
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
|
||||
range.style.setProperty('--value', `${range.value / range.max * 100}%`);
|
||||
},
|
||||
@@ -51,6 +55,7 @@ const controls = {
|
||||
icon,
|
||||
utils.extend(attributes, {
|
||||
role: 'presentation',
|
||||
focusable: 'false',
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -205,7 +210,6 @@ const controls = {
|
||||
|
||||
// Add aria attributes
|
||||
attributes['aria-pressed'] = false;
|
||||
attributes['aria-label'] = i18n.get(label, this.config);
|
||||
} else {
|
||||
button.appendChild(controls.createIcon.call(this, icon));
|
||||
button.appendChild(controls.createLabel.call(this, label));
|
||||
@@ -237,6 +241,7 @@ const controls = {
|
||||
'label',
|
||||
{
|
||||
for: attributes.id,
|
||||
id: `${attributes.id}-label`,
|
||||
class: this.config.classNames.hidden,
|
||||
},
|
||||
i18n.get(type, this.config),
|
||||
@@ -254,6 +259,12 @@ const controls = {
|
||||
step: 0.01,
|
||||
value: 0,
|
||||
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,
|
||||
),
|
||||
@@ -280,6 +291,8 @@ const controls = {
|
||||
min: 0,
|
||||
max: 100,
|
||||
value: 0,
|
||||
role: 'presentation',
|
||||
'aria-hidden': true,
|
||||
},
|
||||
attributes,
|
||||
),
|
||||
@@ -313,22 +326,14 @@ const controls = {
|
||||
|
||||
// Create time display
|
||||
createTime(type) {
|
||||
const container = utils.createElement('div', {
|
||||
class: 'plyr__time',
|
||||
});
|
||||
const attributes = utils.getAttributesFromSelector(this.config.selectors.display[type]);
|
||||
|
||||
container.appendChild(
|
||||
utils.createElement(
|
||||
'span',
|
||||
{
|
||||
class: this.config.classNames.hidden,
|
||||
},
|
||||
i18n.get(type, this.config),
|
||||
),
|
||||
);
|
||||
|
||||
container.appendChild(utils.createElement('span', utils.getAttributesFromSelector(this.config.selectors.display[type]), '00:00'));
|
||||
const container = utils.createElement('div', utils.extend(attributes, {
|
||||
class: `plyr__time ${attributes.class}`,
|
||||
'aria-label': i18n.get(type, this.config),
|
||||
}), '0:00');
|
||||
|
||||
// Reference for updates
|
||||
this.elements.display[type] = container;
|
||||
|
||||
return container;
|
||||
@@ -353,7 +358,7 @@ const controls = {
|
||||
}),
|
||||
);
|
||||
|
||||
const faux = utils.createElement('span', { 'aria-hidden': true });
|
||||
const faux = utils.createElement('span', { hidden: '' });
|
||||
|
||||
label.appendChild(radio);
|
||||
label.appendChild(faux);
|
||||
@@ -428,15 +433,11 @@ const controls = {
|
||||
|
||||
// Hide/show a tab
|
||||
toggleTab(setting, toggle) {
|
||||
const tab = this.elements.settings.tabs[setting];
|
||||
const pane = this.elements.settings.panes[setting];
|
||||
|
||||
utils.toggleHidden(tab, !toggle);
|
||||
utils.toggleHidden(pane, !toggle);
|
||||
utils.toggleHidden(this.elements.settings.tabs[setting], !toggle);
|
||||
},
|
||||
|
||||
// Set the YouTube quality menu
|
||||
// TODO: Support for HTML5
|
||||
// Set the quality menu
|
||||
// TODO: Vimeo support
|
||||
setQualityMenu(options) {
|
||||
// Menu required
|
||||
if (!utils.is.element(this.elements.settings.panes.quality)) {
|
||||
@@ -449,14 +450,15 @@ const controls = {
|
||||
// Set options if passed and filter based on config
|
||||
if (utils.is.array(options)) {
|
||||
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
|
||||
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);
|
||||
|
||||
// Check if we need to toggle the parent
|
||||
controls.checkMenu.call(this);
|
||||
|
||||
// If we're hiding, nothing more to do
|
||||
if (!toggle) {
|
||||
return;
|
||||
@@ -470,20 +472,18 @@ const controls = {
|
||||
let label = '';
|
||||
|
||||
switch (quality) {
|
||||
case 'hd2160':
|
||||
case 2160:
|
||||
label = '4K';
|
||||
break;
|
||||
|
||||
case 'hd1440':
|
||||
label = 'WQHD';
|
||||
break;
|
||||
|
||||
case 'hd1080':
|
||||
case 1440:
|
||||
case 1080:
|
||||
case 720:
|
||||
label = 'HD';
|
||||
break;
|
||||
|
||||
case 'hd720':
|
||||
label = 'HD';
|
||||
case 576:
|
||||
label = 'SD';
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -497,9 +497,16 @@ const controls = {
|
||||
return controls.createBadge.call(this, label);
|
||||
};
|
||||
|
||||
this.options.quality.forEach(quality =>
|
||||
controls.createMenuItem.call(this, quality, list, type, controls.getLabel.call(this, 'quality', quality), getBadge(quality)),
|
||||
);
|
||||
// Sort options by the config and then render options
|
||||
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);
|
||||
},
|
||||
@@ -509,34 +516,17 @@ const controls = {
|
||||
getLabel(setting, value) {
|
||||
switch (setting) {
|
||||
case 'speed':
|
||||
return value === 1 ? 'Normal' : `${value}×`;
|
||||
return value === 1 ? i18n.get('normal', this.config) : `${value}×`;
|
||||
|
||||
case 'quality':
|
||||
switch (value) {
|
||||
case 'hd2160':
|
||||
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;
|
||||
if (utils.is.number(value)) {
|
||||
return `${value}p`;
|
||||
}
|
||||
|
||||
return utils.toTitleCase(value);
|
||||
|
||||
case 'captions':
|
||||
return controls.getLanguage.call(this);
|
||||
return captions.getLabel.call(this);
|
||||
|
||||
default:
|
||||
return null;
|
||||
@@ -544,18 +534,27 @@ const controls = {
|
||||
},
|
||||
|
||||
// Update the selected setting
|
||||
updateSetting(setting, container) {
|
||||
updateSetting(setting, container, input) {
|
||||
const pane = this.elements.settings.panes[setting];
|
||||
let value = null;
|
||||
let list = container;
|
||||
|
||||
switch (setting) {
|
||||
case 'captions':
|
||||
value = this.captions.active ? this.captions.language : i18n.get('disabled', this.config);
|
||||
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;
|
||||
|
||||
default:
|
||||
value = this[setting];
|
||||
value = !utils.is.empty(input) ? input : this[setting];
|
||||
|
||||
// Get default
|
||||
if (utils.is.empty(value)) {
|
||||
@@ -563,7 +562,7 @@ const controls = {
|
||||
}
|
||||
|
||||
// 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}`);
|
||||
return;
|
||||
}
|
||||
@@ -582,17 +581,19 @@ const controls = {
|
||||
list = pane && pane.querySelector('ul');
|
||||
}
|
||||
|
||||
// Update the label
|
||||
if (!utils.is.empty(value)) {
|
||||
const label = this.elements.settings.tabs[setting].querySelector(`.${this.config.classNames.menu.value}`);
|
||||
label.innerHTML = controls.getLabel.call(this, setting, value);
|
||||
// If there's no list it means it's not been rendered...
|
||||
if (!utils.is.element(list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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}"]`);
|
||||
|
||||
if (utils.is.element(target)) {
|
||||
// Check it
|
||||
target.checked = true;
|
||||
}
|
||||
},
|
||||
@@ -643,21 +644,6 @@ const controls = {
|
||||
|
||||
// Get current selected caption language
|
||||
// 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 && this.captions.active) {
|
||||
const currentTrack = captions.getCurrentTrack.call(this);
|
||||
|
||||
if (utils.is.track(currentTrack)) {
|
||||
return currentTrack.label;
|
||||
}
|
||||
}
|
||||
|
||||
return i18n.get('disabled', this.config);
|
||||
},
|
||||
|
||||
// Set a list of available captions languages
|
||||
setCaptionsMenu() {
|
||||
@@ -666,21 +652,24 @@ const controls = {
|
||||
const list = this.elements.settings.panes.captions.querySelector('ul');
|
||||
|
||||
// Toggle the pane and tab
|
||||
const hasTracks = captions.getTracks.call(this).length;
|
||||
controls.toggleTab.call(this, type, hasTracks);
|
||||
const toggle = captions.getTracks.call(this).length;
|
||||
controls.toggleTab.call(this, type, toggle);
|
||||
|
||||
// Empty the menu
|
||||
utils.emptyElement(list);
|
||||
|
||||
// Check if we need to toggle the parent
|
||||
controls.checkMenu.call(this);
|
||||
|
||||
// If there's no captions, bail
|
||||
if (!hasTracks) {
|
||||
if (!toggle) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-map the tracks into just the data we need
|
||||
const tracks = captions.getTracks.call(this).map(track => ({
|
||||
language: track.language,
|
||||
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
|
||||
language: !utils.is.empty(track.language) ? track.language : 'enabled',
|
||||
label: captions.getLabel.call(this, track),
|
||||
}));
|
||||
|
||||
// Add the "Disabled" option to turn off captions
|
||||
@@ -696,17 +685,25 @@ const controls = {
|
||||
track.language,
|
||||
list,
|
||||
'language',
|
||||
track.label || track.language,
|
||||
controls.createBadge.call(this, track.language.toUpperCase()),
|
||||
track.label,
|
||||
track.language !== 'enabled' ? controls.createBadge.call(this, track.language.toUpperCase()) : null,
|
||||
track.language.toLowerCase() === this.captions.language.toLowerCase(),
|
||||
);
|
||||
});
|
||||
|
||||
// Store reference
|
||||
this.options.captions = tracks.map(track => track.language);
|
||||
|
||||
controls.updateSetting.call(this, type, list);
|
||||
},
|
||||
|
||||
// Set a list of available captions languages
|
||||
setSpeedMenu() {
|
||||
setSpeedMenu(options) {
|
||||
// Do nothing if not selected
|
||||
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Menu required
|
||||
if (!utils.is.element(this.elements.settings.panes.speed)) {
|
||||
return;
|
||||
@@ -714,8 +711,10 @@ const controls = {
|
||||
|
||||
const type = 'speed';
|
||||
|
||||
// Set the default speeds
|
||||
if (!utils.is.array(this.options.speed) || !this.options.speed.length) {
|
||||
// Set the speed options
|
||||
if (utils.is.array(options)) {
|
||||
this.options.speed = options;
|
||||
} else if (this.isHTML5 || this.isVimeo) {
|
||||
this.options.speed = [
|
||||
0.5,
|
||||
0.75,
|
||||
@@ -731,9 +730,12 @@ const controls = {
|
||||
this.options.speed = this.options.speed.filter(speed => this.config.speed.options.includes(speed));
|
||||
|
||||
// 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);
|
||||
|
||||
// Check if we need to toggle the parent
|
||||
controls.checkMenu.call(this);
|
||||
|
||||
// If we're hiding, nothing more to do
|
||||
if (!toggle) {
|
||||
return;
|
||||
@@ -742,19 +744,26 @@ const controls = {
|
||||
// Get the list to populate
|
||||
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
|
||||
utils.emptyElement(list);
|
||||
|
||||
// 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);
|
||||
},
|
||||
|
||||
// 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
|
||||
toggleMenu(event) {
|
||||
const { form } = this.elements.settings;
|
||||
@@ -765,7 +774,7 @@ const controls = {
|
||||
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)) {
|
||||
const isMenuItem = utils.is.element(form) && form.contains(event.target);
|
||||
@@ -790,7 +799,7 @@ const controls = {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (show) {
|
||||
@@ -806,7 +815,7 @@ const controls = {
|
||||
const clone = tab.cloneNode(true);
|
||||
clone.style.position = 'absolute';
|
||||
clone.style.opacity = 0;
|
||||
clone.setAttribute('aria-hidden', false);
|
||||
clone.removeAttribute('hidden');
|
||||
|
||||
// Prevent input's being unchecked due to the name being identical
|
||||
Array.from(clone.querySelectorAll('input[name]')).forEach(input => {
|
||||
@@ -850,7 +859,7 @@ const controls = {
|
||||
|
||||
// Hide all 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;
|
||||
|
||||
// Set other toggles to be expanded false
|
||||
@@ -894,11 +903,11 @@ const controls = {
|
||||
}
|
||||
|
||||
// Set attributes on current tab
|
||||
current.setAttribute('aria-hidden', true);
|
||||
utils.toggleHidden(current, true);
|
||||
current.setAttribute('tabindex', -1);
|
||||
|
||||
// Set attributes on target
|
||||
pane.setAttribute('aria-hidden', !show);
|
||||
utils.toggleHidden(pane, !show);
|
||||
tab.setAttribute('aria-expanded', show);
|
||||
pane.removeAttribute('tabindex');
|
||||
|
||||
@@ -1025,6 +1034,7 @@ const controls = {
|
||||
if (this.config.controls.includes('settings') && !utils.is.empty(this.config.settings)) {
|
||||
const menu = utils.createElement('div', {
|
||||
class: 'plyr__menu',
|
||||
hidden: '',
|
||||
});
|
||||
|
||||
menu.appendChild(
|
||||
@@ -1039,7 +1049,7 @@ const controls = {
|
||||
const form = utils.createElement('form', {
|
||||
class: 'plyr__menu__container',
|
||||
id: `plyr-settings-${data.id}`,
|
||||
'aria-hidden': true,
|
||||
hidden: '',
|
||||
'aria-labelled-by': `plyr-settings-toggle-${data.id}`,
|
||||
role: 'tablist',
|
||||
tabindex: -1,
|
||||
@@ -1049,7 +1059,6 @@ const controls = {
|
||||
|
||||
const home = utils.createElement('div', {
|
||||
id: `plyr-settings-${data.id}-home`,
|
||||
'aria-hidden': false,
|
||||
'aria-labelled-by': `plyr-settings-toggle-${data.id}`,
|
||||
role: 'tabpanel',
|
||||
});
|
||||
@@ -1100,11 +1109,10 @@ const controls = {
|
||||
this.config.settings.forEach(type => {
|
||||
const pane = utils.createElement('div', {
|
||||
id: `plyr-settings-${data.id}-${type}`,
|
||||
'aria-hidden': true,
|
||||
hidden: '',
|
||||
'aria-labelled-by': `plyr-settings-${data.id}-${type}-tab`,
|
||||
role: 'tabpanel',
|
||||
tabindex: -1,
|
||||
hidden: '',
|
||||
});
|
||||
|
||||
const back = utils.createElement(
|
||||
@@ -1159,10 +1167,12 @@ const controls = {
|
||||
|
||||
this.elements.controls = container;
|
||||
|
||||
if (this.config.controls.includes('settings') && this.config.settings.includes('speed')) {
|
||||
controls.setSpeedMenu.call(this);
|
||||
if (this.isHTML5) {
|
||||
controls.setQualityMenu.call(this, html5.getQualityOptions.call(this));
|
||||
}
|
||||
|
||||
controls.setSpeedMenu.call(this);
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
@@ -1185,17 +1195,21 @@ const controls = {
|
||||
let container = 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)) {
|
||||
// String or HTMLElement passed as the option
|
||||
container = this.config.controls;
|
||||
} else if (utils.is.function(this.config.controls)) {
|
||||
// A custom function to build controls
|
||||
// The function can return a HTMLElement or String
|
||||
container = this.config.controls({
|
||||
id: this.id,
|
||||
seektime: this.config.seekTime,
|
||||
title: this.config.title,
|
||||
});
|
||||
container = this.config.controls.call(this, props);
|
||||
} else {
|
||||
// Create controls
|
||||
container = controls.create.call(this, {
|
||||
@@ -1203,10 +1217,34 @@ const controls = {
|
||||
seektime: this.config.seekTime,
|
||||
speed: this.speed,
|
||||
quality: this.quality,
|
||||
captions: controls.getLanguage.call(this),
|
||||
captions: captions.getLabel.call(this),
|
||||
// TODO: Looping
|
||||
// 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
|
||||
@@ -1225,7 +1263,7 @@ const controls = {
|
||||
// Inject controls HTML
|
||||
if (utils.is.element(container)) {
|
||||
target.appendChild(container);
|
||||
} else {
|
||||
} else if (container) {
|
||||
target.insertAdjacentHTML('beforeend', container);
|
||||
}
|
||||
|
||||
|
||||
+16
-12
@@ -56,24 +56,26 @@ const defaults = {
|
||||
// Sprite (for icons)
|
||||
loadSprite: true,
|
||||
iconPrefix: 'plyr',
|
||||
iconUrl: 'https://cdn.plyr.io/3.0.4/plyr.svg',
|
||||
iconUrl: 'https://cdn.plyr.io/3.2.4/plyr.svg',
|
||||
|
||||
// Blank video (used to prevent errors on source change)
|
||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||
|
||||
// Quality default
|
||||
quality: {
|
||||
default: 'default',
|
||||
default: 576,
|
||||
options: [
|
||||
'hd2160',
|
||||
'hd1440',
|
||||
'hd1080',
|
||||
'hd720',
|
||||
'large',
|
||||
'medium',
|
||||
'small',
|
||||
'tiny',
|
||||
'default',
|
||||
4320,
|
||||
2880,
|
||||
2160,
|
||||
1440,
|
||||
1080,
|
||||
720,
|
||||
576,
|
||||
480,
|
||||
360,
|
||||
240,
|
||||
'default', // YouTube's "auto"
|
||||
],
|
||||
},
|
||||
|
||||
@@ -113,7 +115,7 @@ const defaults = {
|
||||
// Captions settings
|
||||
captions: {
|
||||
active: false,
|
||||
language: window.navigator.language.split('-')[0],
|
||||
language: (navigator.language || navigator.userLanguage).split('-')[0],
|
||||
},
|
||||
|
||||
// Fullscreen settings
|
||||
@@ -175,6 +177,7 @@ const defaults = {
|
||||
captions: 'Captions',
|
||||
settings: 'Settings',
|
||||
speed: 'Speed',
|
||||
normal: 'Normal',
|
||||
quality: 'Quality',
|
||||
loop: 'Loop',
|
||||
start: 'Start',
|
||||
@@ -182,6 +185,7 @@ const defaults = {
|
||||
all: 'All',
|
||||
reset: 'Reset',
|
||||
disabled: 'Disabled',
|
||||
enabled: 'Enabled',
|
||||
advertisement: 'Ad',
|
||||
},
|
||||
|
||||
|
||||
+13
-15
@@ -55,7 +55,7 @@ class Fullscreen {
|
||||
|
||||
// Get prefix
|
||||
this.prefix = Fullscreen.prefix;
|
||||
this.name = Fullscreen.name;
|
||||
this.property = Fullscreen.property;
|
||||
|
||||
// Scroll position
|
||||
this.scrollPosition = { x: 0, y: 0 };
|
||||
@@ -68,13 +68,15 @@ class Fullscreen {
|
||||
});
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
||||
// Prevent double click on controls bubbling up
|
||||
utils.on(this.player.elements.controls, 'dblclick', event => event.stopPropagation());
|
||||
|
||||
// Update the UI
|
||||
this.update();
|
||||
}
|
||||
@@ -88,7 +90,7 @@ class Fullscreen {
|
||||
static get prefix() {
|
||||
// No prefix
|
||||
if (utils.is.function(document.exitFullscreen)) {
|
||||
return false;
|
||||
return '';
|
||||
}
|
||||
|
||||
// Check for fullscreen support by vendor prefix
|
||||
@@ -111,7 +113,7 @@ class Fullscreen {
|
||||
return value;
|
||||
}
|
||||
|
||||
static get name() {
|
||||
static get property() {
|
||||
return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
|
||||
}
|
||||
|
||||
@@ -136,7 +138,7 @@ class Fullscreen {
|
||||
return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
|
||||
}
|
||||
|
||||
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.name}Element`];
|
||||
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.property}Element`];
|
||||
|
||||
return element === this.target;
|
||||
}
|
||||
@@ -164,8 +166,6 @@ class Fullscreen {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn(this.prefix);
|
||||
|
||||
// iOS native fullscreen doesn't need the request step
|
||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||
if (this.player.playing) {
|
||||
@@ -176,7 +176,7 @@ class Fullscreen {
|
||||
} else if (!this.prefix) {
|
||||
this.target.requestFullscreen();
|
||||
} else if (!utils.is.empty(this.prefix)) {
|
||||
this.target[`${this.prefix}Request${this.name}`]();
|
||||
this.target[`${this.prefix}Request${this.property}`]();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,17 +193,15 @@ class Fullscreen {
|
||||
} else if (!Fullscreen.native) {
|
||||
toggleFallback.call(this, false);
|
||||
} else if (!this.prefix) {
|
||||
document.cancelFullScreen();
|
||||
(document.cancelFullScreen || document.exitFullscreen).call(document);
|
||||
} else if (!utils.is.empty(this.prefix)) {
|
||||
const action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
|
||||
document[`${this.prefix}${action}${this.name}`]();
|
||||
document[`${this.prefix}${action}${this.property}`]();
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle state
|
||||
toggle() {
|
||||
console.warn('TOGGLE');
|
||||
|
||||
if (!this.active) {
|
||||
this.enter();
|
||||
} else {
|
||||
|
||||
+146
@@ -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;
|
||||
+32
-15
@@ -2,7 +2,6 @@
|
||||
// Plyr Event Listeners
|
||||
// ==========================================================================
|
||||
|
||||
import support from './support';
|
||||
import utils from './utils';
|
||||
import controls from './controls';
|
||||
import ui from './ui';
|
||||
@@ -129,7 +128,7 @@ class Listeners {
|
||||
|
||||
case 39:
|
||||
// Arrow forward
|
||||
this.player.fastForward();
|
||||
this.player.forward();
|
||||
break;
|
||||
|
||||
case 37:
|
||||
@@ -254,7 +253,7 @@ class Listeners {
|
||||
utils.on(this.player.media, 'timeupdate seeking', event => ui.timeUpdate.call(this.player, event));
|
||||
|
||||
// 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
|
||||
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
||||
@@ -278,18 +277,32 @@ class Listeners {
|
||||
// Check for buffer progress
|
||||
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));
|
||||
|
||||
// Handle native play/pause
|
||||
// Handle play/pause
|
||||
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));
|
||||
|
||||
// Check if media failed to load
|
||||
// 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
|
||||
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
|
||||
// Re-fetch the wrapper
|
||||
@@ -321,7 +334,7 @@ class Listeners {
|
||||
// Disable right click
|
||||
if (this.player.supported.ui && this.player.config.disableContextMenu) {
|
||||
utils.on(
|
||||
this.player.media,
|
||||
this.player.elements.wrapper,
|
||||
'contextmenu',
|
||||
event => {
|
||||
event.preventDefault();
|
||||
@@ -345,13 +358,16 @@ class Listeners {
|
||||
this.player.storage.set({ speed: this.player.speed });
|
||||
});
|
||||
|
||||
// Quality change
|
||||
utils.on(this.player.media, 'qualitychange', () => {
|
||||
// Update UI
|
||||
controls.updateSetting.call(this.player, 'quality');
|
||||
|
||||
// Quality request
|
||||
utils.on(this.player.media, 'qualityrequested', event => {
|
||||
// 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
|
||||
@@ -398,14 +414,15 @@ class Listeners {
|
||||
const proxy = (event, defaultHandler, customHandlerKey) => {
|
||||
const customHandler = this.player.config.listeners[customHandlerKey];
|
||||
const hasCustomHandler = utils.is.function(customHandler);
|
||||
let returned = true;
|
||||
|
||||
// Execute custom handler
|
||||
if (hasCustomHandler) {
|
||||
customHandler.call(this.player, event);
|
||||
returned = customHandler.call(this.player, event);
|
||||
}
|
||||
|
||||
// Only call default handler if not prevented in custom handler
|
||||
if (!event.defaultPrevented && utils.is.function(defaultHandler)) {
|
||||
if (returned && utils.is.function(defaultHandler)) {
|
||||
defaultHandler.call(this.player, event);
|
||||
}
|
||||
};
|
||||
|
||||
+3
-24
@@ -6,6 +6,7 @@ import support from './support';
|
||||
import utils from './utils';
|
||||
import youtube from './plugins/youtube';
|
||||
import vimeo from './plugins/vimeo';
|
||||
import html5 from './html5';
|
||||
import ui from './ui';
|
||||
|
||||
// Sniff out the browser
|
||||
@@ -75,32 +76,10 @@ const media = {
|
||||
}
|
||||
} else if (this.isHTML5) {
|
||||
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;
|
||||
|
||||
+21
-13
@@ -206,21 +206,23 @@ class Ads {
|
||||
this.cuePoints = this.manager.getCuePoints();
|
||||
|
||||
// Add advertisement cue's within the time line if available
|
||||
this.cuePoints.forEach(cuePoint => {
|
||||
if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) {
|
||||
const seekElement = this.player.elements.progress;
|
||||
if (!utils.is.empty(this.cuePoints)) {
|
||||
this.cuePoints.forEach(cuePoint => {
|
||||
if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < this.player.duration) {
|
||||
const seekElement = this.player.elements.progress;
|
||||
|
||||
if (seekElement) {
|
||||
const cuePercentage = 100 / this.player.duration * cuePoint;
|
||||
const cue = utils.createElement('span', {
|
||||
class: this.player.config.classNames.cues,
|
||||
});
|
||||
if (utils.is.element(seekElement)) {
|
||||
const cuePercentage = 100 / this.player.duration * cuePoint;
|
||||
const cue = utils.createElement('span', {
|
||||
class: this.player.config.classNames.cues,
|
||||
});
|
||||
|
||||
cue.style.left = `${cuePercentage.toString()}%`;
|
||||
seekElement.appendChild(cue);
|
||||
cue.style.left = `${cuePercentage.toString()}%`;
|
||||
seekElement.appendChild(cue);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Get skippable state
|
||||
// TODO: Skip button
|
||||
@@ -385,6 +387,10 @@ class Ads {
|
||||
this.player.on('seeked', () => {
|
||||
const seekedTime = this.player.currentTime;
|
||||
|
||||
if (utils.is.empty(this.cuePoints)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cuePoints.forEach((cuePoint, index) => {
|
||||
if (time < cuePoint && cuePoint < seekedTime) {
|
||||
this.manager.discardAdBreak();
|
||||
@@ -396,7 +402,9 @@ class Ads {
|
||||
// Listen to the resizing of the window. And resize ad accordingly
|
||||
// TODO: eventually implement ResizeObserver
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+47
-11
@@ -4,6 +4,7 @@
|
||||
|
||||
import utils from './../utils';
|
||||
import captions from './../captions';
|
||||
import controls from './../controls';
|
||||
import ui from './../ui';
|
||||
|
||||
const vimeo = {
|
||||
@@ -34,10 +35,14 @@ const vimeo = {
|
||||
setAspectRatio(input) {
|
||||
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
|
||||
const padding = 100 / ratio[0] * ratio[1];
|
||||
const height = 240;
|
||||
const offset = (height - padding) / (height / 50);
|
||||
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
|
||||
@@ -54,6 +59,7 @@ const vimeo = {
|
||||
speed: true,
|
||||
transparent: 0,
|
||||
gesture: 'media',
|
||||
playsinline: !this.config.fullscreen.iosNative,
|
||||
};
|
||||
const params = utils.buildUrlParams(options);
|
||||
|
||||
@@ -87,6 +93,11 @@ const vimeo = {
|
||||
player.media.paused = true;
|
||||
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
|
||||
player.media.play = () => {
|
||||
player.embed.play().then(() => {
|
||||
@@ -123,7 +134,9 @@ const vimeo = {
|
||||
utils.dispatchEvent.call(player, player.media, 'seeking');
|
||||
|
||||
// Seek after events
|
||||
player.embed.setCurrentTime(time);
|
||||
player.embed.setCurrentTime(time).catch(() => {
|
||||
// Do nothing
|
||||
});
|
||||
|
||||
// Restore pause state
|
||||
if (paused) {
|
||||
@@ -139,10 +152,18 @@ const vimeo = {
|
||||
return speed;
|
||||
},
|
||||
set(input) {
|
||||
player.embed.setPlaybackRate(input).then(() => {
|
||||
speed = input;
|
||||
utils.dispatchEvent.call(player, player.media, 'ratechange');
|
||||
});
|
||||
player.embed
|
||||
.setPlaybackRate(input)
|
||||
.then(() => {
|
||||
speed = input;
|
||||
utils.dispatchEvent.call(player, player.media, 'ratechange');
|
||||
})
|
||||
.catch(error => {
|
||||
// Hide menu item (and menu if empty)
|
||||
if (error.name === 'Error') {
|
||||
controls.setSpeedMenu.call(player, []);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -193,9 +214,15 @@ const vimeo = {
|
||||
|
||||
// Source
|
||||
let currentSrc;
|
||||
player.embed.getVideoUrl().then(value => {
|
||||
currentSrc = value;
|
||||
});
|
||||
player.embed
|
||||
.getVideoUrl()
|
||||
.then(value => {
|
||||
currentSrc = value;
|
||||
})
|
||||
.catch(error => {
|
||||
this.debug.warn(error);
|
||||
});
|
||||
|
||||
Object.defineProperty(player.media, 'currentSrc', {
|
||||
get() {
|
||||
return currentSrc;
|
||||
@@ -295,6 +322,15 @@ const vimeo = {
|
||||
if (parseInt(data.percent, 10) === 1) {
|
||||
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', () => {
|
||||
|
||||
+78
-15
@@ -6,6 +6,64 @@ import utils from './../utils';
|
||||
import controls from './../controls';
|
||||
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 = {
|
||||
setup() {
|
||||
// Add embed class for responsive
|
||||
@@ -168,14 +226,10 @@ const youtube = {
|
||||
|
||||
utils.dispatchEvent.call(player, player.media, 'error');
|
||||
},
|
||||
onPlaybackQualityChange(event) {
|
||||
// Get the instance
|
||||
const instance = event.target;
|
||||
|
||||
// Get current quality
|
||||
player.media.quality = instance.getPlaybackQuality();
|
||||
|
||||
utils.dispatchEvent.call(player, player.media, 'qualitychange');
|
||||
onPlaybackQualityChange() {
|
||||
utils.dispatchEvent.call(player, player.media, 'qualitychange', false, {
|
||||
quality: player.media.quality,
|
||||
});
|
||||
},
|
||||
onPlaybackRateChange(event) {
|
||||
// Get the instance
|
||||
@@ -216,6 +270,9 @@ const youtube = {
|
||||
return Number(instance.getCurrentTime());
|
||||
},
|
||||
set(time) {
|
||||
// Vimeo will automatically play on seek
|
||||
const { paused } = player.media;
|
||||
|
||||
// Set seeking flag
|
||||
player.media.seeking = true;
|
||||
|
||||
@@ -224,6 +281,11 @@ const youtube = {
|
||||
|
||||
// Seek after events sent
|
||||
instance.seekTo(time);
|
||||
|
||||
// Restore pause state
|
||||
if (paused) {
|
||||
player.pause();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -240,15 +302,18 @@ const youtube = {
|
||||
// Quality
|
||||
Object.defineProperty(player.media, 'quality', {
|
||||
get() {
|
||||
return instance.getPlaybackQuality();
|
||||
return mapQualityUnit(instance.getPlaybackQuality());
|
||||
},
|
||||
set(input) {
|
||||
const quality = input;
|
||||
|
||||
// Set via API
|
||||
instance.setPlaybackQuality(mapQualityUnit(quality));
|
||||
|
||||
// Trigger request event
|
||||
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
|
||||
quality: input,
|
||||
quality,
|
||||
});
|
||||
|
||||
instance.setPlaybackQuality(input);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -339,8 +404,6 @@ const youtube = {
|
||||
// Reset timer
|
||||
clearInterval(player.timers.playing);
|
||||
|
||||
console.warn(event.data);
|
||||
|
||||
// Handle events
|
||||
// -1 Unstarted
|
||||
// 0 Ended
|
||||
@@ -402,7 +465,7 @@ const youtube = {
|
||||
}
|
||||
|
||||
// Get quality
|
||||
controls.setQualityMenu.call(player, instance.getAvailableQualityLevels());
|
||||
controls.setQualityMenu.call(player, mapQualityUnits(instance.getAvailableQualityLevels()));
|
||||
|
||||
break;
|
||||
|
||||
|
||||
+76
-31
@@ -1,6 +1,6 @@
|
||||
// ==========================================================================
|
||||
// Plyr
|
||||
// plyr.js v3.0.4
|
||||
// plyr.js v3.2.4
|
||||
// https://github.com/sampotts/plyr
|
||||
// License: The MIT License (MIT)
|
||||
// ==========================================================================
|
||||
@@ -57,7 +57,7 @@ class Plyr {
|
||||
this.config = utils.extend(
|
||||
{},
|
||||
defaults,
|
||||
options,
|
||||
options || {},
|
||||
(() => {
|
||||
try {
|
||||
return JSON.parse(this.media.getAttribute('data-plyr-config'));
|
||||
@@ -97,6 +97,7 @@ class Plyr {
|
||||
this.options = {
|
||||
speed: [],
|
||||
quality: [],
|
||||
captions: [],
|
||||
};
|
||||
|
||||
// Debugging
|
||||
@@ -133,7 +134,17 @@ class Plyr {
|
||||
}
|
||||
|
||||
// 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
|
||||
// Supported: video, audio, vimeo, youtube
|
||||
@@ -174,12 +185,17 @@ class Plyr {
|
||||
if (truthy.includes(params.autoplay)) {
|
||||
this.config.autoplay = true;
|
||||
}
|
||||
if (truthy.includes(params.playsinline)) {
|
||||
this.config.inline = true;
|
||||
}
|
||||
if (truthy.includes(params.loop)) {
|
||||
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 {
|
||||
// <div> with attributes
|
||||
@@ -213,7 +229,7 @@ class Plyr {
|
||||
this.config.autoplay = true;
|
||||
}
|
||||
if (this.media.hasAttribute('playsinline')) {
|
||||
this.config.inline = true;
|
||||
this.config.playsinline = true;
|
||||
}
|
||||
if (this.media.hasAttribute('muted')) {
|
||||
this.config.muted = true;
|
||||
@@ -230,7 +246,7 @@ class Plyr {
|
||||
}
|
||||
|
||||
// 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 (!this.supported.api) {
|
||||
@@ -286,6 +302,11 @@ class Plyr {
|
||||
|
||||
// Setup ads if provided
|
||||
this.ads = new Ads(this);
|
||||
|
||||
// Autoplay if required
|
||||
if (this.config.autoplay) {
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
@@ -323,9 +344,9 @@ class Plyr {
|
||||
}
|
||||
|
||||
// If ads are enabled, wait for them first
|
||||
if (this.ads.enabled && !this.ads.initialized) {
|
||||
/* 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 this.media.play();
|
||||
@@ -353,7 +374,7 @@ class Plyr {
|
||||
* Get playing state
|
||||
*/
|
||||
get playing() {
|
||||
return Boolean(!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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,7 +405,7 @@ class Plyr {
|
||||
stop() {
|
||||
if (this.isHTML5) {
|
||||
this.media.load();
|
||||
} else {
|
||||
} else if (utils.is.function(this.media.stop)) {
|
||||
this.media.stop();
|
||||
}
|
||||
}
|
||||
@@ -431,7 +452,7 @@ class Plyr {
|
||||
}
|
||||
|
||||
// Set
|
||||
this.media.currentTime = parseFloat(targetTime.toFixed(4));
|
||||
this.media.currentTime = targetTime;
|
||||
|
||||
// Logging
|
||||
this.debug.log(`Seeking to ${this.currentTime} seconds`);
|
||||
@@ -477,7 +498,7 @@ class Plyr {
|
||||
*/
|
||||
get duration() {
|
||||
// Faux duration set via config
|
||||
const fauxDuration = parseInt(this.config.duration, 10);
|
||||
const fauxDuration = parseFloat(this.config.duration);
|
||||
|
||||
// True duration
|
||||
const realDuration = this.media ? Number(this.media.duration) : 0;
|
||||
@@ -524,8 +545,8 @@ class Plyr {
|
||||
// Set the player volume
|
||||
this.media.volume = volume;
|
||||
|
||||
// If muted, and we're increasing volume, reset muted state
|
||||
if (this.muted && volume > 0) {
|
||||
// If muted, and we're increasing volume manually, reset muted state
|
||||
if (!utils.is.empty(value) && this.muted && volume > 0) {
|
||||
this.muted = false;
|
||||
}
|
||||
}
|
||||
@@ -655,29 +676,38 @@ class Plyr {
|
||||
|
||||
/**
|
||||
* Set playback quality
|
||||
* Currently YouTube only
|
||||
* @param {string} input - Quality level
|
||||
* Currently HTML5 & YouTube only
|
||||
* @param {number} input - Quality level
|
||||
*/
|
||||
set quality(input) {
|
||||
let quality = null;
|
||||
|
||||
if (utils.is.string(input)) {
|
||||
quality = input;
|
||||
if (!utils.is.empty(input)) {
|
||||
quality = Number(input);
|
||||
}
|
||||
|
||||
if (!utils.is.string(quality)) {
|
||||
if (!utils.is.number(quality) || quality === 0) {
|
||||
quality = this.storage.get('quality');
|
||||
}
|
||||
|
||||
if (!utils.is.string(quality)) {
|
||||
if (!utils.is.number(quality)) {
|
||||
quality = this.config.quality.selected;
|
||||
}
|
||||
|
||||
if (!this.options.quality.includes(quality)) {
|
||||
this.debug.warn(`Unsupported quality option (${quality})`);
|
||||
if (!utils.is.number(quality)) {
|
||||
quality = this.config.quality.default;
|
||||
}
|
||||
|
||||
if (!this.options.quality.length) {
|
||||
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
|
||||
this.config.quality.selected = quality;
|
||||
|
||||
@@ -815,13 +845,13 @@ class Plyr {
|
||||
* @param {boolean} input - Whether to enable captions
|
||||
*/
|
||||
toggleCaptions(input) {
|
||||
// If there's no full support, or there's no caption toggle
|
||||
if (!this.supported.ui || !utils.is.element(this.elements.buttons.captions)) {
|
||||
// If there's no full support
|
||||
if (!this.supported.ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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...
|
||||
if (this.captions.active === show) {
|
||||
@@ -851,17 +881,29 @@ class Plyr {
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle captions based on input
|
||||
this.toggleCaptions(!utils.is.empty(input));
|
||||
|
||||
// If empty string is passed, assume disable captions
|
||||
if (utils.is.empty(input)) {
|
||||
this.toggleCaptions(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize
|
||||
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 (this.language === language) {
|
||||
return;
|
||||
@@ -1019,7 +1061,10 @@ class Plyr {
|
||||
// then set the timer to hide the controls
|
||||
if (!show || this.playing) {
|
||||
this.timers.controls = setTimeout(() => {
|
||||
console.warn(this.elements.controls.pressed, this.elements.controls.hover, delay);
|
||||
// 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 ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// ==========================================================================
|
||||
// Plyr Polyfilled Build
|
||||
// plyr.js v3.0.4
|
||||
// plyr.js v3.2.4
|
||||
// https://github.com/sampotts/plyr
|
||||
// License: The MIT License (MIT)
|
||||
// ==========================================================================
|
||||
|
||||
+6
-4
@@ -4,6 +4,7 @@
|
||||
|
||||
import { providers } from './types';
|
||||
import utils from './utils';
|
||||
import html5 from './html5';
|
||||
import media from './media';
|
||||
import ui from './ui';
|
||||
import support from './support';
|
||||
@@ -31,13 +32,14 @@ const source = {
|
||||
}
|
||||
|
||||
// Cancel current network requests
|
||||
media.cancelRequests.call(this);
|
||||
html5.cancelRequests.call(this);
|
||||
|
||||
// Destroy instance and re-setup
|
||||
this.destroy.call(
|
||||
this,
|
||||
() => {
|
||||
// TODO: Reset menus here
|
||||
// Reset quality options
|
||||
this.options.quality = [];
|
||||
|
||||
// Remove elements
|
||||
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;
|
||||
|
||||
// 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
|
||||
switch (`${this.provider}:${this.type}`) {
|
||||
@@ -101,7 +103,7 @@ const source = {
|
||||
if (this.config.muted) {
|
||||
this.media.setAttribute('muted', '');
|
||||
}
|
||||
if (this.config.inline) {
|
||||
if (this.config.playsinline) {
|
||||
this.media.setAttribute('playsinline', '');
|
||||
}
|
||||
}
|
||||
|
||||
+10
-5
@@ -12,16 +12,16 @@ const support = {
|
||||
|
||||
// Check for support
|
||||
// Basic functionality vs full UI
|
||||
check(type, provider, inline) {
|
||||
check(type, provider, playsinline) {
|
||||
let api = false;
|
||||
let ui = false;
|
||||
const browser = utils.getBrowser();
|
||||
const playsInline = browser.isIPhone && inline && support.inline;
|
||||
const canPlayInline = browser.isIPhone && playsinline && support.playsinline;
|
||||
|
||||
switch (`${provider}:${type}`) {
|
||||
case 'html5:video':
|
||||
api = support.video;
|
||||
ui = api && support.rangeInput && (!browser.isIPhone || playsInline);
|
||||
ui = api && support.rangeInput && (!browser.isIPhone || canPlayInline);
|
||||
break;
|
||||
|
||||
case 'html5:audio':
|
||||
@@ -32,7 +32,7 @@ const support = {
|
||||
case 'youtube:video':
|
||||
case 'vimeo:video':
|
||||
api = true;
|
||||
ui = support.rangeInput && (!browser.isIPhone || playsInline);
|
||||
ui = support.rangeInput && (!browser.isIPhone || canPlayInline);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -59,7 +59,7 @@ const support = {
|
||||
|
||||
// Inline playback support
|
||||
// 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
|
||||
// Credits: http://diveintohtml5.info/everything.html
|
||||
@@ -73,6 +73,11 @@ const support = {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check directly if codecs specified
|
||||
if (type.includes('codecs=')) {
|
||||
return media.canPlayType(type).replace(/no/, '');
|
||||
}
|
||||
|
||||
// Type specific checks
|
||||
if (this.isVideo) {
|
||||
switch (type) {
|
||||
|
||||
+7
-9
@@ -48,11 +48,6 @@ const ui = {
|
||||
this.listeners.controls();
|
||||
}
|
||||
|
||||
// If there's no controls, bail
|
||||
if (!utils.is.element(this.elements.controls)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove native controls
|
||||
ui.toggleNativeControls.call(this);
|
||||
|
||||
@@ -71,8 +66,11 @@ const ui = {
|
||||
// Reset loop state
|
||||
this.loop = null;
|
||||
|
||||
// Reset quality options
|
||||
this.options.quality = [];
|
||||
// Reset quality setting
|
||||
this.quality = null;
|
||||
|
||||
// Reset volume display
|
||||
ui.updateVolume.call(this);
|
||||
|
||||
// Reset time display
|
||||
ui.timeUpdate.call(this);
|
||||
@@ -274,10 +272,10 @@ const ui = {
|
||||
}
|
||||
|
||||
// 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
|
||||
target.textContent = utils.formatTime(time, displayHours, inverted);
|
||||
target.textContent = utils.formatTime(time, forceHours, inverted);
|
||||
},
|
||||
|
||||
// Handle time change event
|
||||
|
||||
+41
-17
@@ -375,6 +375,25 @@ const utils = {
|
||||
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
|
||||
toggleClass(element, className, toggle) {
|
||||
if (utils.is.element(element)) {
|
||||
@@ -393,19 +412,6 @@ const utils = {
|
||||
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
|
||||
matches(element, selector) {
|
||||
const prototype = { Element };
|
||||
@@ -462,8 +468,8 @@ const utils = {
|
||||
// Display
|
||||
this.elements.display = {
|
||||
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),
|
||||
duration: utils.getElement.call(this, this.config.selectors.display.duration),
|
||||
};
|
||||
|
||||
// Seek tooltip
|
||||
@@ -586,15 +592,15 @@ const utils = {
|
||||
},
|
||||
|
||||
// Trigger event
|
||||
dispatchEvent(element, type, bubbles, detail) {
|
||||
dispatchEvent(element, type = '', bubbles = false, detail = {}) {
|
||||
// Bail if no element
|
||||
if (!utils.is.element(element) || !utils.is.string(type)) {
|
||||
if (!utils.is.element(element) || utils.is.empty(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create and dispatch the event
|
||||
const event = new CustomEvent(type, {
|
||||
bubbles: utils.is.boolean(bubbles) ? bubbles : false,
|
||||
bubbles,
|
||||
detail: Object.assign({}, detail, {
|
||||
plyr: utils.is.plyr(this) ? this : null,
|
||||
}),
|
||||
@@ -737,6 +743,24 @@ const utils = {
|
||||
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
|
||||
getProviderByUrl(url) {
|
||||
// YouTube
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
// Ignore focus
|
||||
&:focus {
|
||||
outline: 0;
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
// YouTube, Vimeo, etc
|
||||
// --------------------------------------------------------------
|
||||
|
||||
.plyr__video-embed {
|
||||
// Default to 16:9 ratio but this is set by JavaScript based on config
|
||||
$padding: ((100 / 16) * 9);
|
||||
$height: 240;
|
||||
$offset: to-percentage(($height - $padding) / ($height / 50));
|
||||
// Default to 16:9 ratio but this is set by JavaScript based on config
|
||||
$embed-padding: ((100 / 16) * 9);
|
||||
|
||||
.plyr__video-embed {
|
||||
height: 0;
|
||||
padding-bottom: to-percentage($padding);
|
||||
padding-bottom: to-percentage($embed-padding);
|
||||
position: relative;
|
||||
|
||||
iframe {
|
||||
@@ -22,6 +20,17 @@
|
||||
user-select: none;
|
||||
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
|
||||
> div {
|
||||
@@ -30,7 +39,3 @@
|
||||
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;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
z-index: 3;
|
||||
|
||||
> div {
|
||||
overflow: hidden;
|
||||
@@ -74,6 +74,7 @@
|
||||
align-items: center;
|
||||
color: $plyr-menu-color;
|
||||
display: flex;
|
||||
font-size: $plyr-font-size-menu;
|
||||
padding: ceil($plyr-control-padding / 2) ($plyr-control-padding * 2);
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
&::-webkit-slider-runnable-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 {
|
||||
|
||||
@@ -23,7 +23,12 @@
|
||||
// Hide sound controls on iOS
|
||||
// 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
|
||||
.plyr--is-ios .plyr__volume,
|
||||
.plyr--is-ios [data-plyr='mute'] {
|
||||
.plyr--is-ios .plyr__volume {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ $plyr-font-size-small: 14px !default;
|
||||
$plyr-font-size-large: 18px !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-menu: $plyr-font-size-small !default;
|
||||
|
||||
$plyr-font-weight-regular: 500 !default;
|
||||
$plyr-font-weight-bold: 600 !default;
|
||||
|
||||
@@ -2,15 +2,6 @@
|
||||
// Hiding content nicely
|
||||
// --------------------------------------------------------------
|
||||
|
||||
// Attributes
|
||||
.plyr--full-ui [hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.plyr--full-ui [aria-hidden='true'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Screen reader only elements
|
||||
.plyr__sr-only {
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
|
||||
Reference in New Issue
Block a user