Compare commits

...

34 Commits

Author SHA1 Message Date
Sam Potts cce143a7da v3.0.11 2018-03-30 23:14:07 +11:00
Sam Potts d593005b32 Muted and autoplay fixes, small bug fixes 2018-03-30 23:09:17 +11:00
Sam Potts 7be9d5d4d3 v3.0.10 2018-03-30 00:16:42 +11:00
Sam Potts d7141d5ed7 Controls docs, package upgrades 2018-03-30 00:11:48 +11:00
Sam Potts 0d0ece94d3 Fix regression 2018-03-29 20:20:37 +11:00
Sam Potts 1c06f6d06d Vimeo hotfix 2018-03-29 19:35:02 +11:00
Sam Potts dda8e30b92 Merge branch 'master' of github.com:sampotts/plyr 2018-03-28 22:45:18 +11:00
Sam Potts c4e2e24643 Bug fixes 2018-03-28 22:45:11 +11:00
Sam Potts e020a105a3 Update readme.md 2018-03-28 00:55:55 +11:00
Sam Potts 2b7fe9a4f9 v3.0.6 2018-03-28 00:17:15 +11:00
Sam Potts 951df64b7f v3.0.5 2018-03-27 23:52:26 +11:00
Sam Potts 0976afe282 v3.0.4 2018-03-27 23:47:58 +11:00
Sam Potts 7b1e4abda7 Controls fixes 2018-03-27 23:43:38 +11:00
Sam Potts 0cf75eed3f Revert API method change 2018-03-27 21:15:11 +11:00
Sam Potts d96957d086 Allow fullscreen in iframe 2018-03-27 21:13:22 +11:00
Sam Potts 1a032ea498 Fix for seeking issue 2018-03-27 21:10:06 +11:00
Sam Potts 5d079da1b8 Use object.entries 2018-03-27 10:41:06 +11:00
Sam Potts 9c1bc6ab08 Fixes for fast forward and issues with event.preventDefault() 2018-03-27 10:36:08 +11:00
Sam Potts 3d2ba8c009 Update readme.md 2018-03-22 09:11:42 +11:00
Sam Potts e872ce3f77 Update readme.md 2018-03-22 09:10:50 +11:00
Sam Potts b77756da04 Typo 2018-03-22 01:15:10 +11:00
Sam Potts 9b23e13ce8 v3.0.3 2018-03-22 01:13:37 +11:00
Sam Potts 5eafe9baff Vimeo offset tweak (fixes #826) 2018-03-22 01:08:08 +11:00
Sam Potts c251c94131 Fix for .stop() method (fixes #819) 2018-03-22 01:02:38 +11:00
Sam Potts 17041efc71 Check for array for speed options (fixes #252) 2018-03-22 00:33:14 +11:00
Sam Potts 05b8e8a6e0 Restore as float (fixes #828) 2018-03-22 00:28:42 +11:00
Sam Potts f998b996fa Fix for Firefox fullscreen oddness (Fixes #821) 2018-03-22 00:26:01 +11:00
Sam Potts 958b47c435 Merge branch 'master' of github.com:sampotts/plyr 2018-03-22 00:06:26 +11:00
Sam Potts a27248d3b6 Merge pull request #820 from saadshahd/patch-1
Fix fast-forward control
2018-03-22 00:05:24 +11:00
Sam Potts 1b1f7be7ff Merge branch 'master' of github.com:sampotts/plyr 2018-03-22 00:04:34 +11:00
Sam Potts 59d4a27240 Improve Sprite checking (fixes #827) 2018-03-22 00:04:28 +11:00
Saad Shahd 75e9f3c2e3 Fix fast-forward control
fast-forward control doesn't work.
2018-03-21 12:15:57 +02:00
Sam Potts 7132eccf50 Merge pull request #822 from DanielRuf/patch/fix-options-link
fix the options link in the readme
2018-03-21 09:12:06 +11:00
Daniel Ruf e953c6398c fix the options link in the readme 2018-03-20 15:05:51 +01:00
38 changed files with 2111 additions and 1780 deletions
+15
View File
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost/dev/plyr/demo",
"webRoot": "${workspaceFolder}"
}
]
}
+51 -2
View File
@@ -1,8 +1,57 @@
# v3.0.2
## 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)
* Check for array for speed options (fixes #817)
* Restore as float (fixes #828)
* Fix for Firefox fullscreen oddness (Fixes #821)
* Improve Sprite checking (fixes #827)
* Fix fast-forward control (thanks @saadshahd)
* Fix the options link in the readme (thanks @DanielRuf)
## 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
+123 -85
View File
@@ -1,27 +1,72 @@
# Controls
This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs.
This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs. You can pass the following to the `controls` option:
## Internationalization using default controls
* `Array` of options (this builds the default controls based on your choices)
* `String` containing the desired HTML
* `Function` that will be executed and should return one of the above
## Using default controls
If you want to use the standard controls as they are, you don't need to pass any options. If you want to turn on off controls, here's the full list:
```javascript
controls: [
'play-large', // The large play button in the center
'restart', // Restart playback
'rewind', // Rewind by the seek time (default 10 seconds)
'play', // Play/pause playback
'fast-forward', // Fast forward by the seek time (default 10 seconds)
'progress', // The progress bar and scrubber for playback and buffering
'current-time', // The current time of playback
'duration', // The full duration of the media
'mute', // Toggle mute
'volume', // Volume control
'captions', // Toggle captions
'settings', // Settings menu
'pip', // Picture-in-picture (currently Safari only)
'airplay', // Airplay (currently Safari only)
'fullscreen', // Toggle fullscreen
];
```
### Internationalization using default controls
You can provide an `i18n` object as one of your options when initialising the plugin which we be used when rendering the controls.
### Example
#### Example
```javascript
i18n: {
restart: "Restart",
rewind: "Rewind {seektime} secs",
play: "Play",
pause: "Pause",
forward: "Forward {seektime} secs",
buffered: "buffered",
currentTime: "Current time",
duration: "Duration",
volume: "Volume",
toggleMute: "Toggle Mute",
toggleCaptions: "Toggle Captions",
toggleFullscreen: "Toggle Fullscreen"
restart: 'Restart',
rewind: 'Rewind {seektime} secs',
play: 'Play',
pause: 'Pause',
fastForward: 'Forward {seektime} secs',
seek: 'Seek',
played: 'Played',
buffered: 'Buffered',
currentTime: 'Current time',
duration: 'Duration',
volume: 'Volume',
mute: 'Mute',
unmute: 'Unmute',
enableCaptions: 'Enable captions',
disableCaptions: 'Disable captions',
enterFullscreen: 'Enter fullscreen',
exitFullscreen: 'Exit fullscreen',
frameTitle: 'Player for {title}',
captions: 'Captions',
settings: 'Settings',
speed: 'Speed',
quality: 'Quality',
loop: 'Loop',
start: 'Start',
end: 'End',
all: 'All',
reset: 'Reset',
disabled: 'Disabled',
advertisement: 'Ad',
}
```
@@ -29,85 +74,78 @@ Note: `{seektime}` will be replaced with your configured seek time or the defaul
## Using custom HTML
You can specify the HTML for the controls using the `html` option.
You can specify the HTML as a `String` or your `Function` return for the controls using the `controls` option.
The classes and data attributes used in your template should match the `selectors` option.
The classes and data attributes used in your template should match the `selectors` option if you change any.
You need to add several placeholders to your html template that are replaced when rendering:
You need to add several placeholders to your HTML template that are replaced when rendering:
- `{id}` - the dynamically generated ID for the player (for form controls)
- `{seektime}` - the seek time specified in options for fast forward and rewind
- `{title}` - the title of your media, if specified
* `{id}` - the dynamically generated ID for the player (for form controls)
* `{seektime}` - the seek time specified in options for fast forward and rewind
* `{title}` - the title of your media, if specified
You can include only the controls you need when specifying custom html.
### Limitations
* Currently the settings menus are not supported with custom controls HTML
* AirPlay and PiP buttons can be added but you will have to manage feature detection
### Example
This is an example `html` option with all controls.
Here's an example of custom controls markup (this is just all default controls shown).
```javascript
var controls = ["<div class='plyr__controls'>",
"<button type='button' data-plyr='restart'>",
"<svg><use xlink:href='#plyr-restart'></use></svg>",
"<span class='plyr__sr-only'>Restart</span>",
"</button>",
"<button type='button' data-plyr='rewind'>",
"<svg><use xlink:href='#plyr-rewind'></use></svg>",
"<span class='plyr__sr-only'>Rewind {seektime} secs</span>",
"</button>",
"<button type='button' data-plyr='play'>",
"<svg><use xlink:href='#plyr-play'></use></svg>",
"<span class='plyr__sr-only'>Play</span>",
"</button>",
"<button type='button' data-plyr='pause'>",
"<svg><use xlink:href='#plyr-pause'></use></svg>",
"<span class='plyr__sr-only'>Pause</span>",
"</button>",
"<button type='button' data-plyr='fast-forward'>",
"<svg><use xlink:href='#plyr-fast-forward'></use></svg>",
"<span class='plyr__sr-only'>Forward {seektime} secs</span>",
"</button>",
"<span class='plyr__progress'>",
"<label for='seek{id}' class='plyr__sr-only'>Seek</label>",
"<input id='seek{id}' class='plyr__progress--seek' type='range' min='0' max='100' step='0.1' value='0' data-plyr='seek'>",
"<progress class='plyr__progress--played' max='100' value='0' role='presentation'></progress>",
"<progress class='plyr__progress--buffer' max='100' value='0'>",
"<span>0</span>% buffered",
"</progress>",
"<span class='plyr__tooltip'>00:00</span>",
"</span>",
"<span class='plyr__time'>",
"<span class='plyr__sr-only'>Current time</span>",
"<span class='plyr__time--current'>00:00</span>",
"</span>",
"<span class='plyr__time'>",
"<span class='plyr__sr-only'>Duration</span>",
"<span class='plyr__time--duration'>00:00</span>",
"</span>",
"<button type='button' data-plyr='mute'>",
"<svg class='icon--muted'><use xlink:href='#plyr-muted'></use></svg>",
"<svg><use xlink:href='#plyr-volume'></use></svg>",
"<span class='plyr__sr-only'>Toggle Mute</span>",
"</button>",
"<span class='plyr__volume'>",
"<label for='volume{id}' class='plyr__sr-only'>Volume</label>",
"<input id='volume{id}' class='plyr__volume--input' type='range' min='0' max='10' value='5' data-plyr='volume'>",
"<progress class='plyr__volume--display' max='10' value='0' role='presentation'></progress>",
"</span>",
"<button type='button' data-plyr='captions'>",
"<svg class='icon--captions-on'><use xlink:href='#plyr-captions-on'></use></svg>",
"<svg><use xlink:href='#plyr-captions-off'></use></svg>",
"<span class='plyr__sr-only'>Toggle Captions</span>",
"</button>",
"<button type='button' data-plyr='fullscreen'>",
"<svg class='icon--exit-fullscreen'><use xlink:href='#plyr-exit-fullscreen'></use></svg>",
"<svg><use xlink:href='#plyr-enter-fullscreen'></use></svg>",
"<span class='plyr__sr-only'>Toggle Fullscreen</span>",
"</button>",
"</div>"].join("");
const controls = `
<div class="plyr__controls">
<button type="button" class="plyr__control" data-plyr="restart">
<svg role="presentation"><use xlink:href="#plyr-restart"></use></svg>
<span class="plyr__tooltip" role="tooltip">Restart</span>
</button>
<button type="button" class="plyr__control" data-plyr="rewind">
<svg role="presentation"><use xlink:href="#plyr-rewind"></use></svg>
<span class="plyr__tooltip" role="tooltip">Rewind {seektime} secs</span>
</button>
<button type="button" class="plyr__control" aria-pressed="false" aria-label="Play, {title}" data-plyr="play">
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-pause"></use></svg>
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-play"></use></svg>
<span class="label--pressed plyr__tooltip" role="tooltip">Pause</span>
<span class="label--not-pressed plyr__tooltip" role="tooltip">Play</span>
</button>
<button type="button" class="plyr__control" data-plyr="fast-forward">
<svg role="presentation"><use xlink:href="#plyr-fast-forward"></use></svg>
<span class="plyr__tooltip" role="tooltip">Forward {seektime} secs</span>
</button>
<div class="plyr__progress">
<label for="plyr-seek-{id}" class="plyr__sr-only">Seek</label>
<input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" id="plyr-seek-{id}">
<progress class="plyr__progress--buffer" min="0" max="100" value="0">% buffered</progress>
<span role="tooltip" class="plyr__tooltip">00:00</span>
</div>
<div class="plyr__time">00:00</div>
<button type="button" class="plyr__control" aria-pressed="false" aria-label="Mute" data-plyr="mute">
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-muted"></use></svg>
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-volume"></use></svg>
<span class="label--pressed plyr__tooltip" role="tooltip">Unmute</span>
<span class="label--not-pressed plyr__tooltip" role="tooltip">Mute</span>
</button>
<div class="plyr__volume">
<label for="plyr-volume-{id}" class="plyr__sr-only">Volume</label>
<input data-plyr="volume" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" id="plyr-volume-{id}">
</div>
<button type="button" class="plyr__control" aria-pressed="true" aria-label="Enable captions" data-plyr="captions">
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-captions-on"></use></svg>
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-captions-off"></use></svg>
<span class="label--pressed plyr__tooltip" role="tooltip">Disable captions</span>
<span class="label--not-pressed plyr__tooltip" role="tooltip">Enable captions</span>
</button>
<button type="button" class="plyr__control" aria-pressed="false" aria-label="Enter fullscreen" data-plyr="fullscreen">
<svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-exit-fullscreen"></use></svg>
<svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-enter-fullscreen"></use></svg>
<span class="label--pressed plyr__tooltip" role="tooltip">Exit fullscreen</span>
<span class="label--not-pressed plyr__tooltip" role="tooltip">Enter fullscreen</span>
</button>
</div>
`;
// Setup the player
plyr.setup('.js-player', {
html: controls
});
const player = new Plyr('#player', { controls });
```
+1 -1
View File
File diff suppressed because one or more lines are too long
+331 -237
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -169,7 +169,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"
crossorigin="anonymous"></script>
<!-- Plyr core script -->
<script src="../dist/plyr.js" crossorigin="anonymous"></script>
+17
View File
@@ -57,6 +57,23 @@ import Raven from 'raven-js';
tooltips: {
controls: true,
},
/* controls: [
'play-large',
'restart',
'rewind',
'play',
'fast-forward',
'progress',
'current-time',
'duration',
'mute',
'volume',
'captions',
'settings',
'pip',
'airplay',
'fullscreen',
], */
captions: {
active: true,
},
+1 -1
View File
File diff suppressed because one or more lines are too long
+478 -470
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+545 -689
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "plyr",
"version": "3.0.2",
"version": "3.0.11",
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
"homepage": "https://plyr.io",
"main": "./dist/plyr.js",
@@ -13,22 +13,22 @@
"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",
"git-branch": "^2.0.1",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^5.0.0",
"gulp-better-rollup": "^3.0.0",
"gulp-better-rollup": "^3.1.0",
"gulp-clean-css": "^3.9.3",
"gulp-concat": "^2.6.1",
"gulp-filter": "^5.1.0",
"gulp-open": "^3.0.0",
"gulp-open": "^3.0.1",
"gulp-rename": "^1.2.2",
"gulp-replace": "^0.6.1",
"gulp-s3": "^0.11.0",
"gulp-sass": "^3.1.0",
"gulp-sass": "^3.2.1",
"gulp-size": "^3.0.0",
"gulp-sourcemaps": "^2.6.4",
"gulp-svgmin": "^1.2.4",
@@ -38,8 +38,8 @@
"prettier-eslint": "^8.8.1",
"prettier-stylelint": "^0.4.2",
"rollup-plugin-babel": "^3.0.3",
"rollup-plugin-commonjs": "^8.4.1",
"rollup-plugin-node-resolve": "^3.2.0",
"rollup-plugin-commonjs": "^9.1.0",
"rollup-plugin-node-resolve": "^3.3.0",
"run-sequence": "^2.2.1",
"stylelint": "^9.1.3",
"stylelint-config-prettier": "^3.0.4",
@@ -68,7 +68,7 @@
"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",
"raven-js": "^3.24.0"
}
}
+10 -6
View File
@@ -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)
[![Image of Plyr](https://cdn.plyr.io/static/demo/screenshot.png?v=3)](https://plyr.io)
@@ -10,7 +10,7 @@ A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo medi
* **Accessible** - full support for VTT captions and screen readers
* **[Customisable](#html)** - make the player look how you want with the markup you want
* **Semantic** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
* **Good HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
`<span>` or `<a href="#">` button hacks
* **Responsive** - works with any screen size
* **HTML Video & Audio** - support for both formats
@@ -21,6 +21,10 @@ A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo medi
* **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
* **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes
* **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
* **Picture-in-Picture** - supports Safari's picture-in-picture mode
* **Playsinline** - supports the `playsinline` attribute
* **Speed controls** - adjust speed on the fly
* **Multiple captions** - support for multiple caption tracks
* **i18n support** - support for internationalization of controls
* **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
* **SASS** - to include in your build processes
@@ -124,7 +128,7 @@ See [initialising](#initialising) for more information on advanced setups.
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
```html
<script src="https://cdn.plyr.io/3.0.2/plyr.js"></script>
<script src="https://cdn.plyr.io/3.0.11/plyr.js"></script>
```
_Note_: Be sure to read the [polyfills](#polyfills) section below about browser compatibility
@@ -140,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.2/plyr.css">
<link rel="stylesheet" href="https://cdn.plyr.io/3.0.11/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.2/plyr.svg`.
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.0.11/plyr.svg`.
## Ads
@@ -236,7 +240,7 @@ The NodeList, HTMLElement or string selector can be the target `<video>`, `<audi
const players = Array.from(document.querySelectorAll('.js-player')).map(player => new Plyr(player));
```
The second argument for the constructor is the [#options](options) object:
The second argument for the constructor is the [options](#options) object:
```javascript
const player = new Plyr('#player', {
+51 -28
View File
@@ -5,6 +5,7 @@
import support from './support';
import utils from './utils';
import ui from './ui';
import i18n from './i18n';
import captions from './captions';
// Sniff out the browser
@@ -74,7 +75,7 @@ const controls = {
// Create hidden text label
createLabel(type, attr) {
let text = this.config.i18n[type];
let text = i18n.get(type, this.config);
const attributes = Object.assign({}, attr);
switch (type) {
@@ -126,7 +127,7 @@ const controls = {
createButton(buttonType, attr) {
const button = utils.createElement('button');
const attributes = Object.assign({}, attr);
let type = buttonType;
let type = utils.toCamelCase(buttonType);
let toggle = false;
let label;
@@ -147,7 +148,7 @@ const controls = {
}
// Large play button
switch (type) {
switch (buttonType) {
case 'play':
toggle = true;
label = 'play';
@@ -189,7 +190,7 @@ const controls = {
default:
label = type;
icon = type;
icon = buttonType;
}
// Setup toggle icon and labels
@@ -204,7 +205,7 @@ const controls = {
// Add aria attributes
attributes['aria-pressed'] = false;
attributes['aria-label'] = this.config.i18n[label];
attributes['aria-label'] = i18n.get(label, this.config);
} else {
button.appendChild(controls.createIcon.call(this, icon));
button.appendChild(controls.createLabel.call(this, label));
@@ -238,7 +239,7 @@ const controls = {
for: attributes.id,
class: this.config.classNames.hidden,
},
this.config.i18n[type],
i18n.get(type, this.config),
);
// Seek input
@@ -291,11 +292,11 @@ const controls = {
let suffix = '';
switch (type) {
case 'played':
suffix = this.config.i18n.played;
suffix = i18n.get('played', this.config);
break;
case 'buffer':
suffix = this.config.i18n.buffered;
suffix = i18n.get('buffered', this.config);
break;
default:
@@ -322,7 +323,7 @@ const controls = {
{
class: this.config.classNames.hidden,
},
this.config.i18n[type],
i18n.get(type, this.config),
),
);
@@ -383,6 +384,16 @@ const controls = {
const clientRect = this.elements.inputs.seek.getBoundingClientRect();
const visible = `${this.config.classNames.tooltip}--visible`;
const toggle = toggle => {
utils.toggleClass(this.elements.display.seekTooltip, visible, toggle);
};
// Hide on touch
if (this.touch) {
toggle(false);
return;
}
// Determine percentage, if already visible
if (utils.is.event(event)) {
percent = 100 / clientRect.width * (event.pageX - clientRect.left);
@@ -411,7 +422,7 @@ const controls = {
'mouseenter',
'mouseleave',
].includes(event.type)) {
utils.toggleClass(this.elements.display.seekTooltip, visible, event.type === 'mouseenter');
toggle(event.type === 'mouseenter');
}
},
@@ -540,7 +551,7 @@ const controls = {
switch (setting) {
case 'captions':
value = this.captions.active ? this.captions.language : '';
value = this.captions.active ? this.captions.language : i18n.get('disabled', this.config);
break;
default:
@@ -617,7 +628,7 @@ const controls = {
class: this.config.classNames.control,
'data-plyr-loop-action': option,
}),
this.config.i18n[option]
i18n.get(option, this.config)
);
if (['start', 'end'].includes(option)) {
@@ -637,11 +648,7 @@ const controls = {
return null;
}
if (!support.textTracks || !captions.getTracks.call(this).length) {
return this.config.i18n.none;
}
if (this.captions.active) {
if (support.textTracks && captions.getTracks.call(this).length && this.captions.active) {
const currentTrack = captions.getCurrentTrack.call(this);
if (utils.is.track(currentTrack)) {
@@ -649,7 +656,7 @@ const controls = {
}
}
return this.config.i18n.disabled;
return i18n.get('disabled', this.config);
},
// Set a list of available captions languages
@@ -676,10 +683,10 @@ const controls = {
label: !utils.is.empty(track.label) ? track.label : track.language.toUpperCase(),
}));
// Add the "None" option to turn off captions
// Add the "Disabled" option to turn off captions
tracks.unshift({
language: '',
label: this.config.i18n.none,
label: i18n.get('disabled', this.config),
});
// Generate options
@@ -699,7 +706,12 @@ const controls = {
},
// Set a list of available captions languages
setSpeedMenu() {
setSpeedMenu(options) {
// Do nothing if not selected
if (!this.config.controls.includes('settings') || !this.config.settings.includes('speed')) {
return;
}
// Menu required
if (!utils.is.element(this.elements.settings.panes.speed)) {
return;
@@ -707,8 +719,8 @@ const controls = {
const type = 'speed';
// Set the default speeds
if (!utils.is.object(this.options.speed) || !Object.keys(this.options.speed).length) {
// Set the speed options
if (!utils.is.array(options)) {
this.options.speed = [
0.5,
0.75,
@@ -718,6 +730,8 @@ const controls = {
1.75,
2,
];
} else {
this.options.speed = options;
}
// Set options if passed and filter based on config
@@ -727,6 +741,9 @@ const controls = {
const toggle = !utils.is.empty(this.options.speed);
controls.toggleTab.call(this, type, toggle);
// Check if we need to toggle the parent
controls.checkMenu.call(this);
// If we're hiding, nothing more to do
if (!toggle) {
return;
@@ -748,6 +765,14 @@ const controls = {
controls.updateSetting.call(this, type, list);
},
// Check if we need to hide/show the settings menu
checkMenu() {
const speedHidden = this.elements.settings.tabs.speed.getAttribute('hidden') !== null;
const languageHidden = this.elements.settings.tabs.captions.getAttribute('hidden') !== null;
utils.toggleHidden(this.elements.settings.menu, speedHidden && languageHidden);
},
// Show/hide menu
toggleMenu(event) {
const { form } = this.elements.settings;
@@ -1069,7 +1094,7 @@ const controls = {
'aria-controls': `plyr-settings-${data.id}-${type}`,
'aria-expanded': false,
}),
this.config.i18n[type],
i18n.get(type, this.config),
);
const value = utils.createElement('span', {
@@ -1109,7 +1134,7 @@ const controls = {
'aria-controls': `plyr-settings-${data.id}-home`,
'aria-expanded': false,
},
this.config.i18n[type],
i18n.get(type, this.config),
);
pane.appendChild(back);
@@ -1152,9 +1177,7 @@ const controls = {
this.elements.controls = container;
if (this.config.controls.includes('settings') && this.config.settings.includes('speed')) {
controls.setSpeedMenu.call(this);
}
controls.setSpeedMenu.call(this);
return container;
},
+7 -5
View File
@@ -56,7 +56,7 @@ const defaults = {
// Sprite (for icons)
loadSprite: true,
iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.0.2/plyr.svg',
iconUrl: 'https://cdn.plyr.io/3.0.11/plyr.svg',
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
@@ -132,7 +132,10 @@ const defaults = {
// Default controls
controls: [
'play-large',
// 'restart',
// 'rewind',
'play',
// 'fast-forward',
'progress',
'current-time',
'mute',
@@ -155,7 +158,7 @@ const defaults = {
rewind: 'Rewind {seektime} secs',
play: 'Play',
pause: 'Pause',
forward: 'Forward {seektime} secs',
fastForward: 'Forward {seektime} secs',
seek: 'Seek',
played: 'Played',
buffered: 'Buffered',
@@ -178,7 +181,6 @@ const defaults = {
end: 'End',
all: 'All',
reset: 'Reset',
none: 'None',
disabled: 'Disabled',
advertisement: 'Ad',
},
@@ -203,7 +205,7 @@ const defaults = {
pause: null,
restart: null,
rewind: null,
forward: null,
fastForward: null,
mute: null,
volume: null,
captions: null,
@@ -283,7 +285,7 @@ const defaults = {
pause: '[data-plyr="pause"]',
restart: '[data-plyr="restart"]',
rewind: '[data-plyr="rewind"]',
forward: '[data-plyr="fast-forward"]',
fastForward: '[data-plyr="fast-forward"]',
mute: '[data-plyr="mute"]',
captions: '[data-plyr="captions"]',
fullscreen: '[data-plyr="fullscreen"]',
+19 -12
View File
@@ -1,5 +1,6 @@
// ==========================================================================
// Fullscreen wrapper
// https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API#prefixing
// ==========================================================================
import utils from './utils';
@@ -54,6 +55,7 @@ class Fullscreen {
// Get prefix
this.prefix = Fullscreen.prefix;
this.name = Fullscreen.name;
// Scroll position
this.scrollPosition = { x: 0, y: 0 };
@@ -85,7 +87,7 @@ class Fullscreen {
// Get the prefix for handlers
static get prefix() {
// No prefix
if (utils.is.function(document.cancelFullScreen)) {
if (utils.is.function(document.exitFullscreen)) {
return false;
}
@@ -98,12 +100,9 @@ class Fullscreen {
];
prefixes.some(pre => {
if (utils.is.function(document[`${pre}CancelFullScreen`])) {
if (utils.is.function(document[`${pre}ExitFullscreen`]) || utils.is.function(document[`${pre}CancelFullScreen`])) {
value = pre;
return true;
} else if (utils.is.function(document.msExitFullscreen)) {
value = 'ms';
return true;
}
return false;
@@ -112,11 +111,18 @@ class Fullscreen {
return value;
}
static get name() {
return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
}
// Determine if fullscreen is enabled
get enabled() {
const fallback = this.player.config.fullscreen.fallback && !utils.inFrame();
return (Fullscreen.native || fallback) && this.player.config.fullscreen.enabled && this.player.supported.ui && this.player.isVideo;
return (
(Fullscreen.native || this.player.config.fullscreen.fallback) &&
this.player.config.fullscreen.enabled &&
this.player.supported.ui &&
this.player.isVideo
);
}
// Get active state
@@ -130,7 +136,7 @@ class Fullscreen {
return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
}
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}FullscreenElement`];
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.name}Element`];
return element === this.target;
}
@@ -166,9 +172,9 @@ class Fullscreen {
} else if (!Fullscreen.native) {
toggleFallback.call(this, true);
} else if (!this.prefix) {
this.target.requestFullScreen();
this.target.requestFullscreen();
} else if (!utils.is.empty(this.prefix)) {
this.target[`${this.prefix}${this.prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen'}`]();
this.target[`${this.prefix}Request${this.name}`]();
}
}
@@ -187,7 +193,8 @@ class Fullscreen {
} else if (!this.prefix) {
document.cancelFullScreen();
} else if (!utils.is.empty(this.prefix)) {
document[`${this.prefix}${this.prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen'}`]();
const action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
document[`${this.prefix}${action}${this.name}`]();
}
}
+31
View File
@@ -0,0 +1,31 @@
// ==========================================================================
// Plyr internationalization
// ==========================================================================
import utils from './utils';
const i18n = {
get(key = '', config = {}) {
if (utils.is.empty(key) || utils.is.empty(config) || !Object.keys(config.i18n).includes(key)) {
return '';
}
let string = config.i18n[key];
const replace = {
'{seektime}': config.seekTime,
'{title}': config.title,
};
Object.entries(replace).forEach(([
key,
value,
]) => {
string = utils.replaceAll(string, key, value);
});
return string;
},
};
export default i18n;
+143 -105
View File
@@ -17,6 +17,7 @@ class Listeners {
this.handleKey = this.handleKey.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
this.firstTouch = this.firstTouch.bind(this);
}
// Handle key presses
@@ -187,6 +188,17 @@ class Listeners {
controls.toggleMenu.call(this.player, event);
}
// Device is touch enabled
firstTouch() {
this.player.touch = true;
// Add touch class
utils.toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true);
// Clean up
utils.off(document.body, 'touchstart', this.firstTouch);
}
// Global window & document listeners
global(toggle = true) {
// Keyboard shortcuts
@@ -196,6 +208,9 @@ class Listeners {
// Click anywhere closes menu
utils.toggleListener(document.body, 'click', this.toggleMenu, toggle);
// Detect touch by events
utils.on(document.body, 'touchstart', this.firstTouch);
}
// Container listeners
@@ -263,18 +278,28 @@ 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
utils.on(this.player.media, 'playing play pause ended', event => ui.checkPlaying.call(this.player, event));
// 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 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
@@ -288,7 +313,7 @@ class Listeners {
// On click play, pause ore restart
utils.on(wrapper, 'click', () => {
// Touch devices will just show controls (if we're hiding controls)
if (this.player.config.hideControls && support.touch && !this.player.paused) {
if (this.player.config.hideControls && this.player.touch && !this.player.paused) {
return;
}
@@ -379,122 +404,132 @@ class Listeners {
// IE doesn't support input event, so we fallback to change
const inputEvent = browser.isIE ? 'change' : 'input';
// Trigger custom and default handlers
const proxy = (event, handlerKey, defaultHandler) => {
const customHandler = this.player.config.listeners[handlerKey];
// Run default and custom handlers
const proxy = (event, defaultHandler, customHandlerKey) => {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = utils.is.function(customHandler);
let returned = true;
// Execute custom handler
if (utils.is.function(customHandler)) {
customHandler.call(this.player, event);
if (hasCustomHandler) {
returned = customHandler.call(this.player, event);
}
// Only call default handler if not prevented in custom handler
if (!event.defaultPrevented && utils.is.function(defaultHandler)) {
if (returned && utils.is.function(defaultHandler)) {
defaultHandler.call(this.player, event);
}
};
// Trigger custom and default handlers
const on = (element, type, defaultHandler, customHandlerKey, passive = true) => {
const customHandler = this.player.config.listeners[customHandlerKey];
const hasCustomHandler = utils.is.function(customHandler);
utils.on(element, type, event => proxy(event, defaultHandler, customHandlerKey), passive && !hasCustomHandler);
};
// Play/pause toggle
utils.on(this.player.elements.buttons.play, 'click', event =>
proxy(event, 'play', () => {
this.player.togglePlay();
}),
);
on(this.player.elements.buttons.play, 'click', this.player.togglePlay, 'play');
// Pause
utils.on(this.player.elements.buttons.restart, 'click', event =>
proxy(event, 'restart', () => {
this.player.restart();
}),
);
on(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
// Rewind
utils.on(this.player.elements.buttons.rewind, 'click', event =>
proxy(event, 'rewind', () => {
this.player.rewind();
}),
);
on(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
// Rewind
utils.on(this.player.elements.buttons.forward, 'click', event =>
proxy(event, 'forward', () => {
this.player.forward();
}),
);
on(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
// Mute toggle
utils.on(this.player.elements.buttons.mute, 'click', event =>
proxy(event, 'mute', () => {
on(
this.player.elements.buttons.mute,
'click',
() => {
this.player.muted = !this.player.muted;
}),
},
'mute',
);
// Captions toggle
utils.on(this.player.elements.buttons.captions, 'click', event =>
proxy(event, 'captions', () => {
this.player.toggleCaptions();
}),
);
on(this.player.elements.buttons.captions, 'click', this.player.toggleCaptions);
// Fullscreen toggle
utils.on(this.player.elements.buttons.fullscreen, 'click', event =>
proxy(event, 'fullscreen', () => {
on(
this.player.elements.buttons.fullscreen,
'click',
() => {
this.player.fullscreen.toggle();
}),
},
'fullscreen',
);
// Picture-in-Picture
utils.on(this.player.elements.buttons.pip, 'click', event =>
proxy(event, 'pip', () => {
on(
this.player.elements.buttons.pip,
'click',
() => {
this.player.pip = 'toggle';
}),
},
'pip',
);
// Airplay
utils.on(this.player.elements.buttons.airplay, 'click', event =>
proxy(event, 'airplay', () => {
this.player.airplay();
}),
);
on(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
// Settings menu
utils.on(this.player.elements.buttons.settings, 'click', event => {
on(this.player.elements.buttons.settings, 'click', event => {
controls.toggleMenu.call(this.player, event);
});
// Settings menu
utils.on(this.player.elements.settings.form, 'click', event => {
on(this.player.elements.settings.form, 'click', event => {
event.stopPropagation();
// Settings menu items - use event delegation as items are added/removed
if (utils.matches(event.target, this.player.config.selectors.inputs.language)) {
proxy(event, 'language', () => {
this.player.language = event.target.value;
});
proxy(
event,
() => {
this.player.language = event.target.value;
},
'language',
);
} else if (utils.matches(event.target, this.player.config.selectors.inputs.quality)) {
proxy(event, 'quality', () => {
this.player.quality = event.target.value;
});
proxy(
event,
() => {
this.player.quality = event.target.value;
},
'quality',
);
} else if (utils.matches(event.target, this.player.config.selectors.inputs.speed)) {
proxy(event, 'speed', () => {
this.player.speed = parseFloat(event.target.value);
});
proxy(
event,
() => {
this.player.speed = parseFloat(event.target.value);
},
'speed',
);
} else {
controls.showTab.call(this.player, event);
}
});
// Seek
utils.on(this.player.elements.inputs.seek, inputEvent, event =>
proxy(event, 'seek', () => {
on(
this.player.elements.inputs.seek,
inputEvent,
event => {
this.player.currentTime = event.target.value / event.target.max * this.player.duration;
}),
},
'seek',
);
// Current time invert
// Only if one time element is used for both currentTime and duration
if (this.player.config.toggleInvert && !utils.is.element(this.player.elements.display.duration)) {
utils.on(this.player.elements.display.currentTime, 'click', () => {
on(this.player.elements.display.currentTime, 'click', () => {
// Do nothing if we're at the start
if (this.player.currentTime === 0) {
return;
@@ -506,31 +541,34 @@ class Listeners {
}
// Volume
utils.on(this.player.elements.inputs.volume, inputEvent, event =>
proxy(event, 'volume', () => {
on(
this.player.elements.inputs.volume,
inputEvent,
event => {
this.player.volume = event.target.value;
}),
},
'volume',
);
// Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) {
utils.on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => {
on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => {
controls.updateRangeFill.call(this.player, event.target);
});
}
// Seek tooltip
utils.on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event));
on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event));
// Toggle controls visibility based on mouse movement
if (this.player.config.hideControls) {
// Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.player.elements.controls, 'mouseenter mouseleave', event => {
this.player.elements.controls.hover = event.type === 'mouseenter';
on(this.player.elements.controls, 'mouseenter mouseleave', event => {
this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
});
// Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
this.player.elements.controls.pressed = [
'mousedown',
'touchstart',
@@ -538,50 +576,50 @@ class Listeners {
});
// Focus in/out on controls
utils.on(this.player.elements.controls, 'focusin focusout', event => {
on(this.player.elements.controls, 'focusin focusout', event => {
this.player.toggleControls(event);
});
}
// Mouse wheel for volume
utils.on(
on(
this.player.elements.inputs.volume,
'wheel',
event =>
proxy(event, 'volume', () => {
// Detect "natural" scroll - suppored on OS X Safari only
// Other browsers on OS X will be inverted until support improves
const inverted = event.webkitDirectionInvertedFromDevice;
const step = 1 / 50;
let direction = 0;
event => {
// Detect "natural" scroll - suppored on OS X Safari only
// Other browsers on OS X will be inverted until support improves
const inverted = event.webkitDirectionInvertedFromDevice;
const step = 1 / 50;
let direction = 0;
// Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) {
this.player.decreaseVolume(step);
direction = -1;
} else {
this.player.increaseVolume(step);
direction = 1;
}
// Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) {
this.player.decreaseVolume(step);
direction = -1;
} else {
this.player.increaseVolume(step);
direction = 1;
}
}
// Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) {
this.player.increaseVolume(step);
direction = 1;
} else {
this.player.decreaseVolume(step);
direction = -1;
}
// Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) {
this.player.increaseVolume(step);
direction = 1;
} else {
this.player.decreaseVolume(step);
direction = -1;
}
}
// Don't break page scrolling at max and min
if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) {
event.preventDefault();
}
}),
// Don't break page scrolling at max and min
if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) {
event.preventDefault();
}
},
'volume',
false,
);
}
+1 -1
View File
@@ -46,7 +46,7 @@ const media = {
utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
// Add touch class
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, support.touch);
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
}
// Inject the player wrapper
+23 -14
View File
@@ -7,6 +7,7 @@
/* global google */
import utils from '../utils';
import i18n from '../i18n';
class Ads {
/**
@@ -178,7 +179,7 @@ class Ads {
const update = () => {
const time = utils.formatTime(Math.max(this.manager.getRemainingTime(), 0));
const label = `${this.player.config.i18n.advertisement} - ${time}`;
const label = `${i18n.get('advertisement', this.player.config)} - ${time}`;
this.elements.container.setAttribute('data-badge-text', label);
};
@@ -205,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
@@ -384,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();
@@ -395,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);
}
});
}
+25 -12
View File
@@ -4,6 +4,7 @@
import utils from './../utils';
import captions from './../captions';
import controls from './../controls';
import ui from './../ui';
const vimeo = {
@@ -34,7 +35,7 @@ const vimeo = {
setAspectRatio(input) {
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
const padding = 100 / ratio[0] * ratio[1];
const height = 200;
const height = 240;
const offset = (height - padding) / (height / 50);
this.elements.wrapper.style.paddingBottom = `${padding}%`;
this.media.style.transform = `translateY(-${offset}%)`;
@@ -101,10 +102,8 @@ const vimeo = {
};
player.media.stop = () => {
player.embed.stop().then(() => {
player.media.paused = true;
player.currentTime = 0;
});
player.pause();
player.currentTime = 0;
};
// Seeking
@@ -141,10 +140,18 @@ const vimeo = {
return speed;
},
set(input) {
player.embed.setPlaybackRate(input).then(() => {
speed = input;
utils.dispatchEvent.call(player, player.media, 'ratechange');
});
player.embed
.setPlaybackRate(input)
.then(() => {
speed = input;
utils.dispatchEvent.call(player, player.media, 'ratechange');
})
.catch(error => {
// Hide menu item (and menu if empty)
if (error.name === 'Error') {
controls.setSpeedMenu.call(player, []);
}
});
},
});
@@ -195,9 +202,15 @@ const vimeo = {
// Source
let currentSrc;
player.embed.getVideoUrl().then(value => {
currentSrc = value;
});
player.embed
.getVideoUrl()
.then(value => {
currentSrc = value;
})
.catch(error => {
this.debug.warn(error);
});
Object.defineProperty(player.media, 'currentSrc', {
get() {
return currentSrc;
+12 -1
View File
@@ -294,7 +294,8 @@ const youtube = {
});
// Get available speeds
player.options.speed = instance.getAvailablePlaybackRates();
const options = instance.getAvailablePlaybackRates();
controls.setSpeedMenu.call(player, options);
// Set the tabindex to avoid focus entering iframe
if (player.supported.ui) {
@@ -347,6 +348,16 @@ const youtube = {
// 3 Buffering
// 5 Video cued
switch (event.data) {
case -1:
// Update scrubber
utils.dispatchEvent.call(player, player.media, 'timeupdate');
// Get loaded % from YouTube
player.media.buffered = instance.getVideoLoadedFraction();
utils.dispatchEvent.call(player, player.media, 'progress');
break;
case 0:
player.media.paused = true;
+53 -21
View File
@@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
// plyr.js v3.0.2
// plyr.js v3.0.11
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
@@ -36,6 +36,9 @@ class Plyr {
this.loading = false;
this.failed = false;
// Touch device
this.touch = support.touch;
// Set the media element
this.media = target;
@@ -130,7 +133,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
@@ -283,6 +296,11 @@ class Plyr {
// Setup ads if provided
this.ads = new Ads(this);
// Autoplay if required
if (this.config.autoplay) {
this.play();
}
}
// ---------------------------------------
@@ -320,9 +338,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();
@@ -379,8 +397,11 @@ class Plyr {
* Stop playback
*/
stop() {
this.restart();
this.pause();
if (this.isHTML5) {
this.media.load();
} else if (utils.is.function(this.media.stop)) {
this.media.stop();
}
}
/**
@@ -425,7 +446,7 @@ class Plyr {
}
// Set
this.media.currentTime = targetTime.toFixed(4);
this.media.currentTime = parseFloat(targetTime.toFixed(4));
// Logging
this.debug.log(`Seeking to ${this.currentTime} seconds`);
@@ -518,8 +539,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;
}
}
@@ -951,26 +972,32 @@ class Plyr {
// Is the enter fullscreen event
isEnterFullscreen = toggle.type === 'enterfullscreen';
// Whether to show controls
show = [
'mouseenter',
'mousemove',
// Events that show the controls
const showEvents = [
'touchstart',
'touchmove',
'focusin',
].includes(toggle.type);
// Delay hiding on move events
if ([
'mouseenter',
'mousemove',
'focusin',
];
// Events that delay hiding
const delayEvents = [
'touchmove',
'touchend',
].includes(toggle.type)) {
'mousemove',
];
// Whether to show controls
show = showEvents.includes(toggle.type);
// Delay hiding on move events
if (delayEvents.includes(toggle.type)) {
delay = 2000;
}
// Delay a little more for keyboard users
if (toggle.type === 'focusin') {
if (!this.touch && toggle.type === 'focusin') {
delay = 3000;
utils.toggleClass(this.elements.controls, this.config.classNames.noTransition, true);
}
@@ -998,7 +1025,7 @@ class Plyr {
}
// Delay for hiding on touch
if (support.touch) {
if (this.touch) {
delay = 3000;
}
}
@@ -1007,6 +1034,11 @@ class Plyr {
// then set the timer to hide the controls
if (!show || this.playing) {
this.timers.controls = setTimeout(() => {
// We need controls of course...
if (!utils.is.element(this.elements.controls)) {
return;
}
// If the mouse is over the controls (and not entering fullscreen), bail
if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
return;
+1 -1
View File
@@ -1,6 +1,6 @@
// ==========================================================================
// Plyr Polyfilled Build
// plyr.js v3.0.2
// plyr.js v3.0.11
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
+1 -1
View File
@@ -143,7 +143,7 @@ const support = {
})(),
// Touch
// Remember a device can be moust + touch enabled
// NOTE: Remember a device can be mouse + touch enabled so we check on first touch event
touch: 'ontouchstart' in document.documentElement,
// Detect transitions support
+6 -2
View File
@@ -5,6 +5,7 @@
import utils from './utils';
import captions from './captions';
import controls from './controls';
import i18n from './i18n';
const ui = {
addStyleHook() {
@@ -73,6 +74,9 @@ const ui = {
// Reset quality options
this.options.quality = [];
// Reset volume display
ui.updateVolume.call(this);
// Reset time display
ui.timeUpdate.call(this);
@@ -94,7 +98,7 @@ const ui = {
// Setup aria attribute for play and iframe title
setTitle() {
// Find the current text
let label = this.config.i18n.play;
let label = i18n.get('play', this.config);
// If there's a media title set, use that for the label
if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) {
@@ -123,7 +127,7 @@ const ui = {
// Default to media type
const title = !utils.is.empty(this.config.title) ? this.config.title : 'video';
iframe.setAttribute('title', this.config.i18n.frameTitle.replace('{title}', title));
iframe.setAttribute('title', i18n.get('frameTitle', this.config));
}
},
+63 -24
View File
@@ -143,7 +143,14 @@ const utils = {
const hasId = utils.is.string(id);
let isCached = false;
function updateSprite(data) {
const exists = () => document.querySelectorAll(`#${id}`).length;
function injectSprite(data) {
// Check again incase of race condition
if (hasId && exists()) {
return;
}
// Inject content
this.innerHTML = data;
@@ -151,8 +158,8 @@ const utils = {
document.body.insertBefore(this, document.body.childNodes[0]);
}
// Only load once
if (!hasId || !document.querySelectorAll(`#${id}`).length) {
// Only load once if ID set
if (!hasId || !exists()) {
// Create container
const container = document.createElement('div');
utils.toggleHidden(container, true);
@@ -168,7 +175,7 @@ const utils = {
if (isCached) {
const data = JSON.parse(cached);
updateSprite.call(container, data.content);
injectSprite.call(container, data.content);
return;
}
}
@@ -190,7 +197,7 @@ const utils = {
);
}
updateSprite.call(container, result);
injectSprite.call(container, result);
})
.catch(() => {});
}
@@ -201,15 +208,6 @@ const utils = {
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
},
// Determine if we're in an iframe
inFrame() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
},
// Wrap an element
wrap(elements, wrapper) {
// Convert `elements` to an array, if necessary.
@@ -312,8 +310,11 @@ const utils = {
return;
}
Object.keys(attributes).forEach(key => {
element.setAttribute(key, attributes[key]);
Object.entries(attributes).forEach(([
key,
value,
]) => {
element.setAttribute(key, value);
});
},
@@ -440,7 +441,7 @@ const utils = {
pause: utils.getElement.call(this, this.config.selectors.buttons.pause),
restart: utils.getElement.call(this, this.config.selectors.buttons.restart),
rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind),
forward: utils.getElement.call(this, this.config.selectors.buttons.forward),
fastForward: utils.getElement.call(this, this.config.selectors.buttons.fastForward),
mute: utils.getElement.call(this, this.config.selectors.buttons.mute),
pip: utils.getElement.call(this, this.config.selectors.buttons.pip),
airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay),
@@ -533,7 +534,7 @@ const utils = {
},
// Toggle event listener
toggleListener(elements, event, callback, toggle, passive, capture) {
toggleListener(elements, event, callback, toggle = false, passive = true, capture = false) {
// Bail if no elemetns, event, or callback
if (utils.is.empty(elements) || utils.is.empty(event) || !utils.is.function(callback)) {
return;
@@ -555,16 +556,16 @@ const utils = {
const events = event.split(' ');
// Build options
// Default to just capture boolean
let options = utils.is.boolean(capture) ? capture : false;
// Default to just the capture boolean for browsers with no passive listener support
let options = capture;
// If passive events listeners are supported
if (support.passiveListeners) {
options = {
// Whether the listener can be passive (i.e. default never prevented)
passive: utils.is.boolean(passive) ? passive : true,
passive,
// Whether the listener is a capturing listener or not
capture: utils.is.boolean(capture) ? capture : false,
capture,
};
}
@@ -575,12 +576,12 @@ const utils = {
},
// Bind event handler
on(element, events, callback, passive, capture) {
on(element, events = '', callback, passive = true, capture = false) {
utils.toggleListener(element, events, callback, true, passive, capture);
},
// Unbind event handler
off(element, events, callback, passive, capture) {
off(element, events = '', callback, passive = true, capture = false) {
utils.toggleListener(element, events, callback, false, passive, capture);
},
@@ -671,6 +672,44 @@ const utils = {
return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
},
// Replace all occurances of a string in a string
replaceAll(input = '', find = '', replace = '') {
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
},
// Convert to title case
toTitleCase(input = '') {
return input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase());
},
// Convert string to pascalCase
toPascalCase(input = '') {
let string = input.toString();
// Convert kebab case
string = utils.replaceAll(string, '-', ' ');
// Convert snake case
string = utils.replaceAll(string, '_', ' ');
// Convert to title case
string = utils.toTitleCase(string);
// Convert to pascal case
return utils.replaceAll(string, ' ', '');
},
// Convert string to pascalCase
toCamelCase(input = '') {
let string = input.toString();
// Convert to pascal case
string = utils.toPascalCase(string);
// Convert first character to lowercase
return string.charAt(0).toLowerCase() + string.slice(1);
},
// Deep extend destination object with N more objects
extend(target = {}, ...sources) {
if (!sources.length) {
+1 -1
View File
@@ -6,7 +6,7 @@
.plyr__video-embed {
// Default to 16:9 ratio but this is set by JavaScript based on config
$padding: ((100 / 16) * 9);
$height: 200;
$height: 240;
$offset: to-percentage(($height - $padding) / ($height / 50));
height: 0;
-2
View File
@@ -84,7 +84,6 @@
position: absolute;
top: 50%;
transform: translateY(-50%);
transition: border-color 0.2s ease;
}
&--forward {
@@ -108,7 +107,6 @@
margin-bottom: floor($plyr-control-padding / 2);
padding-left: ceil($plyr-control-padding * 4);
position: relative;
width: calc(100% - #{$horizontal-padding});
&::after {
+82 -40
View File
@@ -105,7 +105,7 @@ acorn-jsx@^3.0.0:
dependencies:
acorn "^3.0.4"
acorn@5.X, acorn@^5.0.3, acorn@^5.2.1, acorn@^5.5.0:
acorn@5.X, acorn@^5.0.3, acorn@^5.5.0:
version "5.5.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9"
@@ -1775,7 +1775,7 @@ eslint-visitor-keys@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
eslint@^4.0.0, eslint@^4.19.0:
eslint@^4.0.0:
version "4.19.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.0.tgz#9e900efb5506812ac374557034ef6f5c3642fc4c"
dependencies:
@@ -1818,6 +1818,49 @@ eslint@^4.0.0, eslint@^4.19.0:
table "4.0.2"
text-table "~0.2.0"
eslint@^4.19.1:
version "4.19.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300"
dependencies:
ajv "^5.3.0"
babel-code-frame "^6.22.0"
chalk "^2.1.0"
concat-stream "^1.6.0"
cross-spawn "^5.1.0"
debug "^3.1.0"
doctrine "^2.1.0"
eslint-scope "^3.7.1"
eslint-visitor-keys "^1.0.0"
espree "^3.5.4"
esquery "^1.0.0"
esutils "^2.0.2"
file-entry-cache "^2.0.0"
functional-red-black-tree "^1.0.1"
glob "^7.1.2"
globals "^11.0.1"
ignore "^3.3.3"
imurmurhash "^0.1.4"
inquirer "^3.0.6"
is-resolvable "^1.0.0"
js-yaml "^3.9.1"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.3.0"
lodash "^4.17.4"
minimatch "^3.0.2"
mkdirp "^0.5.1"
natural-compare "^1.4.0"
optionator "^0.8.2"
path-is-inside "^1.0.2"
pluralize "^7.0.0"
progress "^2.0.0"
regexpp "^1.0.1"
require-uncached "^1.0.3"
semver "^5.3.0"
strip-ansi "^4.0.0"
strip-json-comments "~2.0.1"
table "4.0.2"
text-table "~0.2.0"
espree@^3.5.4:
version "3.5.4"
resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
@@ -1857,7 +1900,7 @@ estree-walker@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.3.1.tgz#e6b1a51cf7292524e7237c312e5fe6660c1ce1aa"
estree-walker@^0.5.0:
estree-walker@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.1.tgz#64fc375053abc6f57d73e9bd2f004644ad3c5854"
@@ -2444,13 +2487,13 @@ gulp-autoprefixer@^5.0.0:
through2 "^2.0.0"
vinyl-sourcemaps-apply "^0.2.0"
gulp-better-rollup@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/gulp-better-rollup/-/gulp-better-rollup-3.0.0.tgz#2bf5c2d0e7908fae72bba1f2496d24eb3c482e33"
gulp-better-rollup@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/gulp-better-rollup/-/gulp-better-rollup-3.1.0.tgz#b226ba0c672882075472158b82d22ba9976d4ecb"
dependencies:
lodash.camelcase "^4.3.0"
plugin-error "^0.1.2"
rollup "^0.50.0"
rollup ">=0.48 <0.57"
vinyl "^2.1.0"
vinyl-sourcemaps-apply "^0.2.1"
@@ -2479,9 +2522,9 @@ gulp-filter@^5.1.0:
plugin-error "^0.1.2"
streamfilter "^1.0.5"
gulp-open@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/gulp-open/-/gulp-open-3.0.0.tgz#5a572a99044fdc461685d0eb8f7529b34bf9a62c"
gulp-open@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/gulp-open/-/gulp-open-3.0.1.tgz#a2f747b4aa31abec9399b527158b0368c57e2102"
dependencies:
colors "^1.1.2"
opn "5.2.0"
@@ -2510,13 +2553,13 @@ gulp-s3@^0.11.0:
knox ""
mime "~1.2.11"
gulp-sass@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/gulp-sass/-/gulp-sass-3.1.0.tgz#53dc4b68a1f5ddfe4424ab4c247655269a8b74b7"
gulp-sass@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/gulp-sass/-/gulp-sass-3.2.1.tgz#2e3688a96fd8be1c0c01340750c191b2e79fab94"
dependencies:
gulp-util "^3.0"
lodash.clonedeep "^4.3.2"
node-sass "^4.2.0"
node-sass "^4.8.3"
through2 "^2.0.0"
vinyl-sourcemaps-apply "^0.2.0"
@@ -3402,9 +3445,9 @@ load-json-file@^4.0.0:
pify "^3.0.0"
strip-bom "^3.0.0"
loadjs@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/loadjs/-/loadjs-3.5.2.tgz#add0e0e0322859840b362598f762c02f5539ce0b"
loadjs@^3.5.4:
version "3.5.4"
resolved "https://registry.yarnpkg.com/loadjs/-/loadjs-3.5.4.tgz#ef0f4eb5a6ac2b86c7597a3d4de97b83816e36b8"
locate-path@^2.0.0:
version "2.0.0"
@@ -3957,9 +4000,9 @@ mute-stream@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
nan@^2.3.2:
version "2.9.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866"
nan@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
nanomatch@^1.2.9:
version "1.2.9"
@@ -4008,9 +4051,9 @@ node-gyp@^3.3.1:
tar "^2.0.0"
which "1"
node-sass@^4.2.0:
version "4.7.2"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e"
node-sass@^4.8.3:
version "4.8.3"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.8.3.tgz#d077cc20a08ac06f661ca44fb6f19cd2ed41debb"
dependencies:
async-foreach "^0.1.3"
chalk "^1.1.1"
@@ -4024,7 +4067,7 @@ node-sass@^4.2.0:
lodash.mergewith "^4.6.0"
meow "^3.7.0"
mkdirp "^0.5.1"
nan "^2.3.2"
nan "^2.10.0"
node-gyp "^3.3.1"
npmlog "^4.0.0"
request "~2.79.0"
@@ -4662,9 +4705,9 @@ randomatic@^1.1.3:
is-number "^3.0.0"
kind-of "^4.0.0"
raven-js@^3.23.3:
version "3.23.3"
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.23.3.tgz#6174f506c7362eb8bb72b291af5f22edb44ef165"
raven-js@^3.24.0:
version "3.24.0"
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.24.0.tgz#59464d8bc4b3812ae87a282e9bb98ecad5b4b047"
rc@^1.0.1, rc@^1.1.6:
version "1.2.6"
@@ -5070,7 +5113,7 @@ resolve-url@^0.2.1, resolve-url@~0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.4.0, resolve@^1.5.0:
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
dependencies:
@@ -5099,19 +5142,18 @@ rollup-plugin-babel@^3.0.3:
dependencies:
rollup-pluginutils "^1.5.0"
rollup-plugin-commonjs@^8.4.1:
version "8.4.1"
resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.4.1.tgz#5c9cea2b2c3de322f5fbccd147e07ed5e502d7a0"
rollup-plugin-commonjs@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.1.0.tgz#468341aab32499123ee9a04b22f51d9bf26fdd94"
dependencies:
acorn "^5.2.1"
estree-walker "^0.5.0"
estree-walker "^0.5.1"
magic-string "^0.22.4"
resolve "^1.4.0"
resolve "^1.5.0"
rollup-pluginutils "^2.0.1"
rollup-plugin-node-resolve@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.2.0.tgz#31534952f3ab21f9473c1d092be7ed43937ea4d4"
rollup-plugin-node-resolve@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.3.0.tgz#c26d110a36812cbefa7ce117cadcd3439aa1c713"
dependencies:
builtin-modules "^2.0.0"
is-module "^1.0.0"
@@ -5131,9 +5173,9 @@ rollup-pluginutils@^2.0.1:
estree-walker "^0.3.0"
micromatch "^2.3.11"
rollup@^0.50.0:
version "0.50.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.50.1.tgz#e4dafcbf8d2bb0d9f5589d0cc6f64d76b8815730"
"rollup@>=0.48 <0.57":
version "0.56.5"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.56.5.tgz#40fe3cf0cd1659d469baad11f4d5b6336c14ce84"
run-async@^2.2.0:
version "2.3.0"