Compare commits
No commits in common. "master" and "develop" have entirely different histories.
@ -1 +0,0 @@
|
|||||||
19.7.0
|
|
67
CHANGELOG.md
67
CHANGELOG.md
@ -1,58 +1,3 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
### v3.7.8
|
|
||||||
|
|
||||||
- Feat: Minor demo style tweaks
|
|
||||||
- Fix: Minor style fixes related to backgrounds and border radii (🚨 Requires a SCSS/CSS update 🚨)
|
|
||||||
|
|
||||||
### v3.7.7
|
|
||||||
|
|
||||||
- Fix (Accessibility): Don’t set tabindex on parent container
|
|
||||||
- Fix (Accessibility): Add `role="timer"` to time elements
|
|
||||||
- Fix (Accessibility): Leverage native `:focus-visible` in CSS, instead of a custom solution (🚨 Requires a SCSS/CSS update 🚨)
|
|
||||||
|
|
||||||
### v3.7.6
|
|
||||||
|
|
||||||
- Fix: Revert postinstall script
|
|
||||||
|
|
||||||
### v3.7.5
|
|
||||||
|
|
||||||
- Fix: Replace `pnpm` with `npm` in scripts to fix build issues
|
|
||||||
|
|
||||||
### v3.7.4
|
|
||||||
|
|
||||||
- Fix: Fixed event key with space (thanks @royeden!)
|
|
||||||
- Fix: Changing Vimeo function call from `setVolume` to `setMuted` to fix iOS issue (issue #2624) (thanks @HandreMelo and Andre Fernandes Cristofolini Melo!)
|
|
||||||
- Fix: Call preview-thumbnails listeners() function on load (thanks @mogzol!)
|
|
||||||
- Fix: Fullscreen improvements for iOS & iPadOS
|
|
||||||
- Feat: Remove need for iOS-specific styling (please update [volume.scss](https://github.com/sampotts/plyr/blob/master/src/sass/components/volume.scss))
|
|
||||||
|
|
||||||
### v3.7.3
|
|
||||||
|
|
||||||
- Fix: force nowrap in progress tooltips (related: #2549) (thanks @raad-altaie!)
|
|
||||||
- Feat(i18n): Make captions autodetect text direction (#2540) (thanks @ebraminio!)
|
|
||||||
- Fix: fixed menu border radius bug (#2548) (thanks @raad-altaie!)
|
|
||||||
- Chore: navigator.platform is deprecated (#2530) (thanks @stamat!)
|
|
||||||
- Feat: Added configurable property to elements for re-use (#2489) (thanks @NoirHusky!)
|
|
||||||
- Docs: Replace example video ID with one that still works (#2518) (thanks @luvejo!)
|
|
||||||
- Fix: Improve accessibility on control buttons with aria-pressed (#2523) (thanks @emilkarl!)
|
|
||||||
- Fix: Fix for calc() in newer Dart Sass versions (#2519) (thanks @ckhicks!)
|
|
||||||
- Fix: simplify logic for isFunction assertion method
|
|
||||||
- Chore: update types to include string for controls
|
|
||||||
- Chore: upgrade packages
|
|
||||||
- Chore: use `.node-version` instead of `.nvmrc`
|
|
||||||
|
|
||||||
### v3.7.2
|
|
||||||
|
|
||||||
- Fix: Add `@babel/plugin-proposal-optional-chaining` to transform optional chaining in build output
|
|
||||||
|
|
||||||
### v3.7.1
|
|
||||||
|
|
||||||
- Feat: Minor styling improvements to the preview thumbnails (🚨 Requires a SCSS/CSS update 🚨)
|
|
||||||
- Fix: Fix invalid CSS @charset rule in Sass files (thanks @Hashen110!)
|
|
||||||
- Chore: Replace deprecated KeyboardEvent `keyCode` references to use `key` instead (thanks @Hashen110!)
|
|
||||||
- Various other code clean up and typo fixes (thanks @Hashen110!)
|
|
||||||
|
|
||||||
## v3.7.0
|
## v3.7.0
|
||||||
|
|
||||||
- Feat: Add markers support (🚨 Requires a SCSS/CSS update 🚨) (thanks @ForeverSc and @fengshuo!)
|
- Feat: Add markers support (🚨 Requires a SCSS/CSS update 🚨) (thanks @ForeverSc and @fengshuo!)
|
||||||
@ -659,10 +604,10 @@ Because we're using the fancy new ES6 syntax, you will need to polyfill for vint
|
|||||||
|
|
||||||
- Vimeo controls fix (fixes #697)
|
- Vimeo controls fix (fixes #697)
|
||||||
- SVG4everybody compatibility fix
|
- SVG4everybody compatibility fix
|
||||||
- Allow Plyr.setup event listeners to be set up as separate event listeners (<https://github.com/sampotts/plyr/pull/703>)
|
- Allow Plyr.setup event listeners to be set up as separate event listeners (https://github.com/sampotts/plyr/pull/703)
|
||||||
- Added title to the layer html template (for custom controls) (<https://github.com/sampotts/plyr/pull/649>)
|
- Added title to the layer html template (for custom controls) (https://github.com/sampotts/plyr/pull/649)
|
||||||
- Target is null bug fix (<https://github.com/sampotts/plyr/pull/617>)
|
- Target is null bug fix (https://github.com/sampotts/plyr/pull/617)
|
||||||
- fix #684 memory leaks issues after destroy (<https://github.com/sampotts/plyr/pull/700>)
|
- fix #684 memory leaks issues after destroy (https://github.com/sampotts/plyr/pull/700)
|
||||||
|
|
||||||
### v2.0.16
|
### v2.0.16
|
||||||
|
|
||||||
@ -682,8 +627,8 @@ Because we're using the fancy new ES6 syntax, you will need to polyfill for vint
|
|||||||
|
|
||||||
### v2.0.12
|
### v2.0.12
|
||||||
|
|
||||||
- Ability to set custom `blankUrl` for source changes (<https://github.com/sampotts/plyr/pull/504>)
|
- Ability to set custom `blankUrl` for source changes (https://github.com/sampotts/plyr/pull/504)
|
||||||
- Ability to set caption button listener (<https://github.com/sampotts/plyr/pull/468>)
|
- Ability to set caption button listener (https://github.com/sampotts/plyr/pull/468)
|
||||||
|
|
||||||
### v2.0.11
|
### v2.0.11
|
||||||
|
|
||||||
|
107
README.md
107
README.md
@ -7,7 +7,7 @@ Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vi
|
|||||||
|
|
||||||
[](https://badge.fury.io/js/plyr) [](https://gitpod.io/#https://github.com/sampotts/plyr) [](https://opencollective.com/plyr)
|
[](https://badge.fury.io/js/plyr) [](https://gitpod.io/#https://github.com/sampotts/plyr) [](https://opencollective.com/plyr)
|
||||||
|
|
||||||
[](https://plyr.io)
|
[](https://plyr.io)
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vi
|
|||||||
- 🤟 **No frameworks** - written in "vanilla" ES6 JavaScript, no jQuery required
|
- 🤟 **No frameworks** - written in "vanilla" ES6 JavaScript, no jQuery required
|
||||||
- 💁♀️ **Sass** - to include in your build processes
|
- 💁♀️ **Sass** - to include in your build processes
|
||||||
|
|
||||||
## Demos
|
### Demos
|
||||||
|
|
||||||
You can try Plyr in Codepen using our minimal templates: [HTML5 video](https://codepen.io/pen?template=bKeqpr), [HTML5 audio](https://codepen.io/pen?template=rKLywR), [YouTube](https://codepen.io/pen?template=GGqbbJ), [Vimeo](https://codepen.io/pen?template=bKeXNq). For Streaming we also have example integrations with: [Dash.js](https://codepen.io/pen?template=GRoogML), [Hls.js](https://codepen.io/pen?template=oyLKQb) and [Shaka Player](https://codepen.io/pen?template=ZRpzZO)
|
You can try Plyr in Codepen using our minimal templates: [HTML5 video](https://codepen.io/pen?template=bKeqpr), [HTML5 audio](https://codepen.io/pen?template=rKLywR), [YouTube](https://codepen.io/pen?template=GGqbbJ), [Vimeo](https://codepen.io/pen?template=bKeXNq). For Streaming we also have example integrations with: [Dash.js](https://codepen.io/pen?template=GRoogML), [Hls.js](https://codepen.io/pen?template=oyLKQb) and [Shaka Player](https://codepen.io/pen?template=ZRpzZO)
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ Or the `<div>` non progressively enhanced method:
|
|||||||
|
|
||||||
You can use Plyr as an ES6 module as follows:
|
You can use Plyr as an ES6 module as follows:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
import Plyr from 'plyr';
|
import Plyr from 'plyr';
|
||||||
|
|
||||||
const player = new Plyr('#player');
|
const player = new Plyr('#player');
|
||||||
@ -132,18 +132,18 @@ Alternatively you can include the `plyr.js` script before the closing `</body>`
|
|||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
See [initialising](#initializing) for more information on advanced setups.
|
See [initialising](#initialising) for more information on advanced setups.
|
||||||
|
|
||||||
You can use our CDN (provided by [Cloudflare](https://www.cloudflare.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills separately as part of your application but to make life easier you can use the polyfilled build.
|
You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills separately as part of your application but to make life easier you can use the polyfilled build.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.plyr.io/3.7.8/plyr.js"></script>
|
<script src="https://cdn.plyr.io/3.7.0/plyr.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
...or...
|
...or...
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>
|
<script src="https://cdn.plyr.io/3.7.0/plyr.polyfilled.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## CSS
|
## CSS
|
||||||
@ -154,24 +154,16 @@ Include the `plyr.css` stylesheet into your `<head>`.
|
|||||||
<link rel="stylesheet" href="path/to/plyr.css" />
|
<link rel="stylesheet" href="path/to/plyr.css" />
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to use our CDN (provided by [Cloudflare](https://www.cloudflare.com/)) for the default CSS, you can use the following:
|
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
|
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.0/plyr.css" />
|
||||||
```
|
```
|
||||||
|
|
||||||
## SVG Sprite
|
## SVG Sprite
|
||||||
|
|
||||||
The SVG sprite is loaded automatically from our CDN (provided by [Cloudflare](https://www.cloudflare.com/)). To change this, see the [options](#options) below. For
|
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
||||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.7.8/plyr.svg`.
|
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.7.0/plyr.svg`.
|
||||||
|
|
||||||
### Self hosting
|
|
||||||
|
|
||||||
If you don't want to create a build system to include Plyr as an npm module, you can use the pre-built files. You have a few options:
|
|
||||||
|
|
||||||
- Download the files from the CDN links above, they're already minified.
|
|
||||||
- Download the files from [unpkg](https://unpkg.com/browse/plyr/dist/) or similar services.
|
|
||||||
- Build the project yourself using `npm i && npm run build`, which installs the dependencies and spits out a build to `dist`.
|
|
||||||
|
|
||||||
# Ads
|
# Ads
|
||||||
|
|
||||||
@ -197,10 +189,11 @@ Here's a list of the properties and what they are used for:
|
|||||||
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||||
| `--plyr-color-main` | The primary UI color. |  `#00b3ff` |
|
| `--plyr-color-main` | The primary UI color. |  `#00b3ff` |
|
||||||
| `--plyr-video-background` | The background color of video and poster wrappers for using alpha channel videos and poster images. | `rgba(0, 0, 0, 1)` |
|
| `--plyr-video-background` | The background color of video and poster wrappers for using alpha channel videos and poster images. | `rgba(0, 0, 0, 1)` |
|
||||||
| `--plyr-focus-visible-color` | The color used for the focus styles when an element is `:focus-visible` (keyboard focused). | `--plyr-color-main` |
|
| `--plyr-tab-focus-color` | The color used for the dotted outline when an element is `:focus-visible` (equivalent) keyboard focus. | `--plyr-color-main` |
|
||||||
| `--plyr-badge-background` | The background color for badges in the menu. |  `#4a5464` |
|
| `--plyr-badge-background` | The background color for badges in the menu. |  `#4a5464` |
|
||||||
| `--plyr-badge-text-color` | The text color for badges. |  `#ffffff` |
|
| `--plyr-badge-text-color` | The text color for badges. |  `#ffffff` |
|
||||||
| `--plyr-badge-border-radius` | The border radius used for badges. | `2px` |
|
| `--plyr-badge-border-radius` | The border radius used for badges. | `2px` |
|
||||||
|
| `--plyr-tab-focus-color` | The color used to highlight tab (keyboard) focus. | `--plyr-color-main` |
|
||||||
| `--plyr-captions-background` | The color for the background of captions. | `rgba(0, 0, 0, 0.8)` |
|
| `--plyr-captions-background` | The color for the background of captions. | `rgba(0, 0, 0, 0.8)` |
|
||||||
| `--plyr-captions-text-color` | The color used for the captions text. |  `#ffffff` |
|
| `--plyr-captions-text-color` | The color used for the captions text. |  `#ffffff` |
|
||||||
| `--plyr-control-icon-size` | The size of the icons used in the controls. | `18px` |
|
| `--plyr-control-icon-size` | The size of the icons used in the controls. | `18px` |
|
||||||
@ -317,7 +310,7 @@ WebVTT captions are supported. To add a caption track, check the HTML example ab
|
|||||||
|
|
||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
### Initializing
|
### Initialising
|
||||||
|
|
||||||
You can specify a range of arguments for the constructor to use:
|
You can specify a range of arguments for the constructor to use:
|
||||||
|
|
||||||
@ -331,17 +324,17 @@ _Note_: If a `NodeList`, `Array`, or jQuery object are passed, the first element
|
|||||||
|
|
||||||
Passing a CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector):
|
Passing a CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector):
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
const player = new Plyr('#player');
|
const player = new Plyr('#player');
|
||||||
```
|
```
|
||||||
|
|
||||||
Passing a [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement):
|
Passing a [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement):
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
const player = new Plyr(document.getElementById('player'));
|
const player = new Plyr(document.getElementById('player'));
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
const player = new Plyr(document.querySelector('.js-player'));
|
const player = new Plyr(document.querySelector('.js-player'));
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -351,13 +344,13 @@ The HTMLElement or string selector can be the target `<video>`, `<audio>`, or `<
|
|||||||
|
|
||||||
You have two choices here. You can either use a simple array loop to map the constructor:
|
You have two choices here. You can either use a simple array loop to map the constructor:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
const players = Array.from(document.querySelectorAll('.js-player')).map((p) => new Plyr(p));
|
const players = Array.from(document.querySelectorAll('.js-player')).map((p) => new Plyr(p));
|
||||||
```
|
```
|
||||||
|
|
||||||
...or use a static method where you can pass a [CSS string selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors), a [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList), an [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) of [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement), or a [JQuery](https://jquery.com) object:
|
...or use a static method where you can pass a [CSS string selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors), a [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList), an [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) of [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement), or a [JQuery](https://jquery.com) object:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
const players = Plyr.setup('.js-player');
|
const players = Plyr.setup('.js-player');
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -367,7 +360,7 @@ Both options will also return an array of instances in the order of they were in
|
|||||||
|
|
||||||
The second argument for the constructor is the [options](#options) object:
|
The second argument for the constructor is the [options](#options) object:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
const player = new Plyr('#player', {
|
const player = new Plyr('#player', {
|
||||||
title: 'Example Title',
|
title: 'Example Title',
|
||||||
});
|
});
|
||||||
@ -382,7 +375,7 @@ Options can be passed as an object to the constructor as above or as JSON in `da
|
|||||||
Note the single quotes encapsulating the JSON and double quotes on the object keys. Only string values need double quotes.
|
Note the single quotes encapsulating the JSON and double quotes on the object keys. Only string values need double quotes.
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
| Option | Type | Default | Description |
|
||||||
| -------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| -------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `enabled` | Boolean | `true` | Completely disable Plyr. This would allow you to do a User Agent check or similar to programmatically enable or disable Plyr for a certain UA. Example below. |
|
| `enabled` | Boolean | `true` | Completely disable Plyr. This would allow you to do a User Agent check or similar to programmatically enable or disable Plyr for a certain UA. Example below. |
|
||||||
| `debug` | Boolean | `false` | Display debugging information in the console |
|
| `debug` | Boolean | `false` | Display debugging information in the console |
|
||||||
| `controls` | Array, Function or Element | `['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']` | If a function is passed, it is assumed your method will return either an element or HTML string for the controls. Three arguments will be passed to your function; `id` (the unique id for the player), `seektime` (the seektime step in seconds), and `title` (the media title). See [CONTROLS.md](CONTROLS.md) for more info on how the html needs to be structured. |
|
| `controls` | Array, Function or Element | `['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']` | If a function is passed, it is assumed your method will return either an element or HTML string for the controls. Three arguments will be passed to your function; `id` (the unique id for the player), `seektime` (the seektime step in seconds), and `title` (the media title). See [CONTROLS.md](CONTROLS.md) for more info on how the html needs to be structured. |
|
||||||
@ -394,7 +387,6 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `blankVideo` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. |
|
| `blankVideo` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. |
|
||||||
| `autoplay`² | Boolean | `false` | Autoplay the media on load. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
| `autoplay`² | Boolean | `false` | Autoplay the media on load. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
||||||
| `autopause`¹ | Boolean | `true` | Only allow one player playing at once. |
|
| `autopause`¹ | Boolean | `true` | Only allow one player playing at once. |
|
||||||
| `playsinline`³ | Boolean | `true` | Allow inline playback on iOS. Note this has no effect on iPadOS. |
|
|
||||||
| `seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind. |
|
| `seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind. |
|
||||||
| `volume` | Number | `1` | A number, between 0 and 1, representing the initial volume of the player. |
|
| `volume` | Number | `1` | A number, between 0 and 1, representing the initial volume of the player. |
|
||||||
| `muted` | Boolean | `false` | Whether to start playback muted. If the `muted` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
| `muted` | Boolean | `false` | Whether to start playback muted. If the `muted` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
||||||
@ -410,7 +402,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
|
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
|
||||||
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
|
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
|
||||||
| `captions` | Object | `{ active: false, language: 'auto', update: false }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). 'auto' uses the browser language. `update`: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in non-selectable language options). |
|
| `captions` | Object | `{ active: false, language: 'auto', update: false }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). 'auto' uses the browser language. `update`: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in non-selectable language options). |
|
||||||
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false, container: null }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution (`true`/`false`/`'force'`). `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) - note this has no effect on iPadOS. `container`: A selector for an ancestor of the player element, allows contextual content to remain visual in fullscreen mode. Non-ancestors are ignored. |
|
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false, container: null }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution (`true`/`false`/`'force'`). `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls). `container`: A selector for an ancestor of the player element, allows contextual content to remain visual in fullscreen mode. Non-ancestors are ignored. |
|
||||||
| `ratio` | String | `null` | Force an aspect ratio for all videos. The format is `'w:h'` - e.g. `'16:9'` or `'4:3'`. If this is not specified then the default for HTML5 and Vimeo is to use the native resolution of the video. As dimensions are not available from YouTube via SDK, 16:9 is forced as a sensible default. |
|
| `ratio` | String | `null` | Force an aspect ratio for all videos. The format is `'w:h'` - e.g. `'16:9'` or `'4:3'`. If this is not specified then the default for HTML5 and Vimeo is to use the native resolution of the video. As dimensions are not available from YouTube via SDK, 16:9 is forced as a sensible default. |
|
||||||
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
||||||
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4] }` | `selected`: The default speed for playback. `options`: The speed options to display in the UI. YouTube and Vimeo will ignore any options outside of the 0.5-2 range, so options outside of this range will be hidden automatically. |
|
| `speed` | Object | `{ selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4] }` | `selected`: The default speed for playback. `options`: The speed options to display in the UI. YouTube and Vimeo will ignore any options outside of the 0.5-2 range, so options outside of this range will be hidden automatically. |
|
||||||
@ -427,14 +419,9 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
1. Vimeo only
|
1. Vimeo only
|
||||||
2. Autoplay is generally not recommended as it is seen as a negative user experience. It is also disabled in many browsers. Before raising issues, do your homework. More info can be found here:
|
2. Autoplay is generally not recommended as it is seen as a negative user experience. It is also disabled in many browsers. Before raising issues, do your homework. More info can be found here:
|
||||||
|
|
||||||
- <https://webkit.org/blog/6784/new-video-policies-for-ios/>
|
- https://webkit.org/blog/6784/new-video-policies-for-ios/
|
||||||
- <https://developers.google.com/web/updates/2017/09/autoplay-policy-changes>
|
- https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
|
||||||
- <https://hacks.mozilla.org/2019/02/firefox-66-to-block-automatically-playing-audible-video-and-audio/>
|
- https://hacks.mozilla.org/2019/02/firefox-66-to-block-automatically-playing-audible-video-and-audio/
|
||||||
|
|
||||||
3. YouTube does not support programatically toggling the native fullscreen player via it's API. This means on iOS you have two options, neither being perfect:
|
|
||||||
|
|
||||||
- Use the fallback/faux fullscreen option which covers the whole viewport (this is the default)
|
|
||||||
- Set `playsinline` to `false` and/or `fullscreen.iosNative` to `true` - either option hides the fullscreen toggle in the UI (because of the above API issue) and means iOS will play the video in it's native player.
|
|
||||||
|
|
||||||
# API
|
# API
|
||||||
|
|
||||||
@ -444,7 +431,7 @@ There are methods, setters and getters on a Plyr object.
|
|||||||
|
|
||||||
The easiest way to access the Plyr object is to set the return value from your call to the constructor to a variable. For example:
|
The easiest way to access the Plyr object is to set the return value from your call to the constructor to a variable. For example:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
const player = new Plyr('#player', {
|
const player = new Plyr('#player', {
|
||||||
/* options */
|
/* options */
|
||||||
});
|
});
|
||||||
@ -452,7 +439,7 @@ const player = new Plyr('#player', {
|
|||||||
|
|
||||||
You can also access the object through any events:
|
You can also access the object through any events:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
element.addEventListener('ready', (event) => {
|
element.addEventListener('ready', (event) => {
|
||||||
const player = event.detail.plyr;
|
const player = event.detail.plyr;
|
||||||
});
|
});
|
||||||
@ -462,7 +449,7 @@ element.addEventListener('ready', (event) => {
|
|||||||
|
|
||||||
Example method use:
|
Example method use:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
player.play(); // Start playback
|
player.play(); // Start playback
|
||||||
player.fullscreen.enter(); // Enter fullscreen
|
player.fullscreen.enter(); // Enter fullscreen
|
||||||
```
|
```
|
||||||
@ -497,14 +484,14 @@ player.fullscreen.enter(); // Enter fullscreen
|
|||||||
|
|
||||||
Example setters:
|
Example setters:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
player.volume = 0.5; // Sets volume at 50%
|
player.volume = 0.5; // Sets volume at 50%
|
||||||
player.currentTime = 10; // Seeks to 10 seconds
|
player.currentTime = 10; // Seeks to 10 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
Example getters:
|
Example getters:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
player.volume; // 0.5;
|
player.volume; // 0.5;
|
||||||
player.currentTime; // 10
|
player.currentTime; // 10
|
||||||
player.fullscreen.active; // false;
|
player.fullscreen.active; // false;
|
||||||
@ -548,7 +535,7 @@ This allows changing the player source and type on the fly.
|
|||||||
|
|
||||||
Video example:
|
Video example:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
player.source = {
|
player.source = {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
title: 'Example title',
|
title: 'Example title',
|
||||||
@ -588,7 +575,7 @@ player.source = {
|
|||||||
|
|
||||||
Audio example:
|
Audio example:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
player.source = {
|
player.source = {
|
||||||
type: 'audio',
|
type: 'audio',
|
||||||
title: 'Example title',
|
title: 'Example title',
|
||||||
@ -607,7 +594,7 @@ player.source = {
|
|||||||
|
|
||||||
YouTube example:
|
YouTube example:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
player.source = {
|
player.source = {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
sources: [
|
sources: [
|
||||||
@ -621,12 +608,12 @@ player.source = {
|
|||||||
|
|
||||||
Vimeo example
|
Vimeo example
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
player.source = {
|
player.source = {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: '76979871',
|
src: '143418951',
|
||||||
provider: 'vimeo',
|
provider: 'vimeo',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -652,7 +639,7 @@ You can listen for events on the target element you setup Plyr on (see example u
|
|||||||
reference to the instance, you can use the `on()` API method or `addEventListener()`. Access to the API can be obtained this way through the `event.detail.plyr`
|
reference to the instance, you can use the `on()` API method or `addEventListener()`. Access to the API can be obtained this way through the `event.detail.plyr`
|
||||||
property. Here's an example:
|
property. Here's an example:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
player.on('ready', (event) => {
|
player.on('ready', (event) => {
|
||||||
const instance = event.detail.plyr;
|
const instance = event.detail.plyr;
|
||||||
});
|
});
|
||||||
@ -774,20 +761,21 @@ Plyr uses ES6 which isn't supported in all browsers quite yet. This means some f
|
|||||||
|
|
||||||
You can use the static method to check for support. For example
|
You can use the static method to check for support. For example
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
const supported = Plyr.supported('video', 'html5');
|
const supported = Plyr.supported('video', 'html5', true);
|
||||||
```
|
```
|
||||||
|
|
||||||
The arguments are:
|
The arguments are:
|
||||||
|
|
||||||
- Media type (`'audio' | 'video'`)
|
- Media type (`audio` or `video`)
|
||||||
- Provider (`'html5' | 'youtube' | 'vimeo'`)
|
- Provider (`html5`, `youtube` or `vimeo`)
|
||||||
|
- Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
|
||||||
|
|
||||||
## Disable support programmatically
|
## Disable support programmatically
|
||||||
|
|
||||||
The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for smartphones, you could use:
|
The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for smartphones, you could use:
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
{
|
{
|
||||||
enabled: !/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
enabled: !/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
||||||
}
|
}
|
||||||
@ -857,8 +845,6 @@ Plyr costs money to run, not only my time. I donate my time for free as I enjoy
|
|||||||
- [pressakey.com | Blog-Magazin für Videospiele](https://pressakey.com)
|
- [pressakey.com | Blog-Magazin für Videospiele](https://pressakey.com)
|
||||||
- [STROLLÿN: Work with a View](https://strollyn.com)
|
- [STROLLÿN: Work with a View](https://strollyn.com)
|
||||||
- [CFDA Runway360](https://runway360.cfda.com/)
|
- [CFDA Runway360](https://runway360.cfda.com/)
|
||||||
- [NKLAV | Filmmaker](https://nklav.com)
|
|
||||||
- [GDI.JS.ORG - Google Drive Index](https://gitlab.com/GoogleDriveIndex/Google-Drive-Index)
|
|
||||||
|
|
||||||
If you want to be added to the list, open a pull request. It'd be awesome to see how you're using Plyr 😎
|
If you want to be added to the list, open a pull request. It'd be awesome to see how you're using Plyr 😎
|
||||||
|
|
||||||
@ -869,8 +855,13 @@ If you want to be added to the list, open a pull request. It'd be awesome to see
|
|||||||
|
|
||||||
# Thanks
|
# Thanks
|
||||||
|
|
||||||
- [Cloudflare](https://www.cloudflare.com/) and [Fastly](https://www.fastly.com/) for providing the CDN services.
|
[](https://www.fastly.com/)
|
||||||
- [Sentry](https://sentry.io/) for error logging service on the demo website.
|
|
||||||
|
Massive thanks to [Fastly](https://www.fastly.com/) for providing the CDN services.
|
||||||
|
|
||||||
|
[](https://sentry.io/)
|
||||||
|
|
||||||
|
Massive thanks to [Sentry](https://sentry.io/) for providing the logging services for the demo site.
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"version": "0.2",
|
"version": "0.2",
|
||||||
"ignorePaths": ["package.json", "dist/*", "demo/node_modules/*"],
|
"ignorePaths": ["package.json", "dist/*", "demo/node_modules/*"],
|
||||||
"dictionaryDefinitions": [],
|
"dictionaryDefinitions": [],
|
||||||
"dictionaries": ["en-gb", "softwareTerms", "html", "css", "typescript"],
|
"dictionaries": [],
|
||||||
"words": [
|
"words": [
|
||||||
"autopause",
|
"autopause",
|
||||||
"autoplay",
|
"autoplay",
|
||||||
@ -13,11 +13,9 @@
|
|||||||
"fastly",
|
"fastly",
|
||||||
"fullscreen",
|
"fullscreen",
|
||||||
"gordita",
|
"gordita",
|
||||||
"loadjs",
|
|
||||||
"magazin",
|
"magazin",
|
||||||
"menuitemradio",
|
"menuitemradio",
|
||||||
"noupe",
|
"noupe",
|
||||||
"otransitionend",
|
|
||||||
"playsinline",
|
"playsinline",
|
||||||
"plyr",
|
"plyr",
|
||||||
"rutheneum",
|
"rutheneum",
|
||||||
|
@ -66,46 +66,42 @@
|
|||||||
<h1>Pl<span>a</span>y<span>e</span>r</h1>
|
<h1>Pl<span>a</span>y<span>e</span>r</h1>
|
||||||
<p>
|
<p>
|
||||||
A simple, accessible and customisable media player for
|
A simple, accessible and customisable media player for
|
||||||
<button type="button" class="link" data-source="video">
|
<button type="button" class="faux-link" data-source="video">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<title>HTML5</title>
|
<title>HTML5</title>
|
||||||
<path
|
<path
|
||||||
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
></path>
|
></path></svg
|
||||||
</svg>
|
>Video</button
|
||||||
Video</button
|
|
||||||
>,
|
>,
|
||||||
<button type="button" class="link" data-source="audio">
|
<button type="button" class="faux-link" data-source="audio">
|
||||||
<svg class="icon">
|
<svg class="icon">
|
||||||
<title>HTML5</title>
|
<title>HTML5</title>
|
||||||
<path
|
<path
|
||||||
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
></path>
|
></path></svg
|
||||||
</svg>
|
>Audio</button
|
||||||
Audio</button
|
|
||||||
>,
|
>,
|
||||||
<button type="button" class="link" data-source="youtube">
|
<button type="button" class="faux-link" data-source="youtube">
|
||||||
<svg class="icon" role="presentation">
|
<svg class="icon" role="presentation">
|
||||||
<title>YouTube</title>
|
<title>YouTube</title>
|
||||||
<path
|
<path
|
||||||
d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
||||||
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
||||||
M6,11V5l5,3L6,11z"
|
M6,11V5l5,3L6,11z"
|
||||||
></path>
|
></path></svg
|
||||||
</svg>
|
>YouTube
|
||||||
YouTube
|
|
||||||
</button>
|
</button>
|
||||||
and
|
and
|
||||||
<button type="button" class="link" data-source="vimeo">
|
<button type="button" class="faux-link" data-source="vimeo">
|
||||||
<svg class="icon" role="presentation">
|
<svg class="icon" role="presentation">
|
||||||
<title>Vimeo</title>
|
<title>Vimeo</title>
|
||||||
<path
|
<path
|
||||||
d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
||||||
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
||||||
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"
|
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"
|
||||||
></path>
|
></path></svg
|
||||||
</svg>
|
>Vimeo
|
||||||
Vimeo
|
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -181,10 +177,7 @@
|
|||||||
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"
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<a
|
<a href="https://itunes.apple.com/au/movie/view-from-a-blue-moon/id1041586323" target="_blank"
|
||||||
href="https://itunes.apple.com/au/movie/view-from-a-blue-moon/id1041586323"
|
|
||||||
target="_blank"
|
|
||||||
class="link"
|
|
||||||
>View From A Blue Moon</a
|
>View From A Blue Moon</a
|
||||||
>
|
>
|
||||||
© Brainfarm
|
© Brainfarm
|
||||||
@ -198,7 +191,7 @@
|
|||||||
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"
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<a href="http://www.kishibashi.com/" target="_blank" class="link"
|
<a href="http://www.kishibashi.com/" target="_blank"
|
||||||
>Kishi Bashi – “It All Began With A Burst”</a
|
>Kishi Bashi – “It All Began With A Burst”</a
|
||||||
>
|
>
|
||||||
© Kishi Bashi
|
© Kishi Bashi
|
||||||
@ -222,7 +215,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
||||||
<small>
|
<small>
|
||||||
<a href="https://vimeo.com/40648169" target="_blank" class="link">Toob “Wavaphon” Music Video</a>
|
<a href="https://vimeo.com/40648169" target="_blank">Toob “Wavaphon” Music Video</a>
|
||||||
on
|
on
|
||||||
<span class="color--vimeo">
|
<span class="color--vimeo">
|
||||||
<svg class="icon" role="presentation">
|
<svg class="icon" role="presentation">
|
||||||
@ -255,7 +248,7 @@
|
|||||||
<a
|
<a
|
||||||
href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts"
|
href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="link js-shr"
|
class="js-shr"
|
||||||
>tweet it</a
|
>tweet it</a
|
||||||
>
|
>
|
||||||
👍
|
👍
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
// Please see README.md in the root or github.com/sampotts/plyr
|
// Please see README.md in the root or github.com/sampotts/plyr
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
|
import './tab-focus';
|
||||||
import 'custom-event-polyfill';
|
import 'custom-event-polyfill';
|
||||||
import 'url-polyfill';
|
import 'url-polyfill';
|
||||||
|
|
||||||
@ -12,13 +13,13 @@ import Shr from 'shr-buttons';
|
|||||||
|
|
||||||
import Plyr from '../../../src/js/plyr';
|
import Plyr from '../../../src/js/plyr';
|
||||||
import sources from './sources';
|
import sources from './sources';
|
||||||
|
import toggleClass from './toggle-class';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const production = 'plyr.io';
|
const production = 'plyr.io';
|
||||||
const isProduction = window.location.host.includes(production);
|
|
||||||
|
|
||||||
// Sentry for demo site (https://plyr.io) only
|
// Sentry for demo site (https://plyr.io) only
|
||||||
if (isProduction) {
|
if (window.location.host === production) {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555',
|
dsn: 'https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555',
|
||||||
whitelistUrls: [production].map((d) => new RegExp(`https://(([a-z0-9])+(.))*${d}`)),
|
whitelistUrls: [production].map((d) => new RegExp(`https://(([a-z0-9])+(.))*${d}`)),
|
||||||
@ -52,10 +53,10 @@ import sources from './sources';
|
|||||||
captions: {
|
captions: {
|
||||||
active: true,
|
active: true,
|
||||||
},
|
},
|
||||||
/* ads: {
|
ads: {
|
||||||
enabled: isProduction,
|
enabled: window.location.host.includes(production),
|
||||||
publisherId: '918848828995742',
|
publisherId: '918848828995742',
|
||||||
}, */
|
},
|
||||||
previewThumbnails: {
|
previewThumbnails: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
src: ['https://cdn.plyr.io/static/demo/thumbs/100p.vtt', 'https://cdn.plyr.io/static/demo/thumbs/240p.vtt'],
|
src: ['https://cdn.plyr.io/static/demo/thumbs/100p.vtt', 'https://cdn.plyr.io/static/demo/thumbs/240p.vtt'],
|
||||||
@ -106,10 +107,10 @@ import sources from './sources';
|
|||||||
|
|
||||||
function render(type) {
|
function render(type) {
|
||||||
// Remove active classes
|
// Remove active classes
|
||||||
Array.from(buttons).forEach((button) => button.parentElement.classList.toggle('active', false));
|
Array.from(buttons).forEach((button) => toggleClass(button.parentElement, 'active', false));
|
||||||
|
|
||||||
// Set active on parent
|
// Set active on parent
|
||||||
document.querySelector(`[data-source="${type}"]`).classList.toggle('active', true);
|
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
|
||||||
|
|
||||||
// Show cite
|
// Show cite
|
||||||
Array.from(document.querySelectorAll('.plyr__cite')).forEach((cite) => {
|
Array.from(document.querySelectorAll('.plyr__cite')).forEach((cite) => {
|
||||||
|
31
demo/src/js/tab-focus.js
Normal file
31
demo/src/js/tab-focus.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Setup tab focus
|
||||||
|
const container = document.getElementById('container');
|
||||||
|
const tabClassName = 'tab-focus';
|
||||||
|
|
||||||
|
// Remove class on blur
|
||||||
|
document.addEventListener('focusout', (event) => {
|
||||||
|
if (!event.target.classList || container.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.target.classList.remove(tabClassName);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add classname to tabbed elements
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.keyCode !== 9) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay the adding of classname until the focus has changed
|
||||||
|
// This event fires before the focusin event
|
||||||
|
setTimeout(() => {
|
||||||
|
const focused = document.activeElement;
|
||||||
|
|
||||||
|
if (!focused || !focused.classList || container.contains(focused)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
focused.classList.add(tabClassName);
|
||||||
|
}, 10);
|
||||||
|
});
|
5
demo/src/js/toggle-class.js
Normal file
5
demo/src/js/toggle-class.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Toggle class on an element
|
||||||
|
const toggleClass = (element, className = '', toggle = false) =>
|
||||||
|
element && element.classList[toggle ? 'add' : 'remove'](className);
|
||||||
|
|
||||||
|
export default toggleClass;
|
@ -1,9 +1,7 @@
|
|||||||
@charset "UTF-8";
|
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr.io Demo Page
|
// Plyr.io Demo Page
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
@charset 'UTF-8';
|
||||||
@import '../../../../src/sass/lib/css-vars';
|
@import '../../../../src/sass/lib/css-vars';
|
||||||
|
|
||||||
$css-vars-use-native: true;
|
$css-vars-use-native: true;
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
@charset "UTF-8";
|
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr.io Error Page
|
// Plyr.io Error Page
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
@charset 'UTF-8';
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
@import '../settings/colors';
|
@import '../settings/colors';
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
.button__count {
|
.button__count {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: $border-radius-medium;
|
border-radius: $border-radius-base;
|
||||||
|
box-shadow: 0 1px 1px rgba(#000, 0.1);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding: ($spacing-base * 0.75);
|
padding: ($spacing-base * 0.75);
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -18,49 +19,45 @@
|
|||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
.button {
|
.button {
|
||||||
--shadow-color: 0deg 0% 20%;
|
background: $color-button-background;
|
||||||
align-items: center;
|
|
||||||
background-color: $color-button-background;
|
|
||||||
background-image: linear-gradient(0deg, transparent, rgba(255, 255, 255, 0.05));
|
|
||||||
border: 1px solid darken($color-button-background, 5);
|
|
||||||
box-shadow: 0 0.8px 1px hsl(var(--shadow-color) / 0.05), 0 1.3px 1.6px -1px hsl(var(--shadow-color) / 0.06),
|
|
||||||
0 2.8px 3.4px -2px hsl(var(--shadow-color) / 0.07);
|
|
||||||
color: $color-button-text;
|
color: $color-button-text;
|
||||||
display: inline-flex;
|
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
gap: 0.25rem;
|
|
||||||
padding-left: ($spacing-base * 1.25);
|
padding-left: ($spacing-base * 1.25);
|
||||||
padding-right: ($spacing-base * 1.25);
|
padding-right: ($spacing-base * 1.25);
|
||||||
text-decoration: none;
|
|
||||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: $color-button-background-hover;
|
background: $color-button-background-hover;
|
||||||
border-color: darken($color-button-background, 7);
|
|
||||||
|
|
||||||
// Remove the underline/border
|
// Remove the underline/border
|
||||||
&::after {
|
&::after {
|
||||||
display: none !important;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 2px 2px rgba(#000, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus-visible {
|
&.tab-focus {
|
||||||
@include focus-visible($color-button-background);
|
@include tab-focus;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
box-shadow: none;
|
|
||||||
top: 1px;
|
top: 1px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
// Button group
|
||||||
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.1));
|
.button--with-count {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
.button .icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,21 +66,19 @@
|
|||||||
.button__count {
|
.button__count {
|
||||||
animation: fade-in 0.2s ease;
|
animation: fade-in 0.2s ease;
|
||||||
background: $color-button-count-background;
|
background: $color-button-count-background;
|
||||||
border: 1px solid $color-gray-100;
|
|
||||||
color: $color-button-count-text;
|
color: $color-button-count-text;
|
||||||
margin-left: ($spacing-base * 0.75);
|
margin-left: ($spacing-base * 0.75);
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
background-color: $color-button-count-background;
|
border: $arrow-size solid transparent;
|
||||||
border: inherit;
|
border-left-width: 0;
|
||||||
border-width: 0 0 1px 1px;
|
border-right-color: $color-button-count-background;
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
height: 0;
|
||||||
height: 8px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 100%;
|
right: 100%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%) translateX(50%) translateX(-1px) rotate(45deg);
|
transform: translateY(-50%);
|
||||||
width: 8px;
|
width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,15 @@
|
|||||||
// Links
|
// Links
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
.link {
|
// Make a <button> look like an <a>
|
||||||
align-items: center;
|
button.faux-link {
|
||||||
border-bottom: 1px dashed currentColor;
|
@extend a; // stylelint-disable-line
|
||||||
|
@include cancel-button-styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Links
|
||||||
|
a {
|
||||||
|
border-bottom: 1px dotted currentColor;
|
||||||
color: $color-link;
|
color: $color-link;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -32,8 +38,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus-visible {
|
&.tab-focus {
|
||||||
@include focus-visible($color-link);
|
@include tab-focus;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no-border::after {
|
&.no-border::after {
|
||||||
|
@ -6,10 +6,8 @@
|
|||||||
|
|
||||||
// Example players
|
// Example players
|
||||||
.plyr {
|
.plyr {
|
||||||
--shadow-color: 197deg 32% 65%;
|
border-radius: $border-radius-large;
|
||||||
border-radius: $border-radius-2x-large;
|
box-shadow: 0 2px 15px rgba(#000, 0.1);
|
||||||
box-shadow: 0 0.5px 0.6px hsl(var(--shadow-color) / 0.36), 0 1.7px 1.9px -0.8px hsl(var(--shadow-color) / 0.36),
|
|
||||||
0 4.3px 4.8px -1.7px hsl(var(--shadow-color) / 0.36), -0.1px 10.6px 11.9px -2.5px hsl(var(--shadow-color) / 0.36);
|
|
||||||
margin: $spacing-base auto;
|
margin: $spacing-base auto;
|
||||||
|
|
||||||
&.plyr--audio {
|
&.plyr--audio {
|
||||||
@ -19,7 +17,6 @@
|
|||||||
|
|
||||||
.plyr__video-wrapper::after {
|
.plyr__video-wrapper::after {
|
||||||
border: 1px solid rgba(#000, 0.15);
|
border: 1px solid rgba(#000, 0.15);
|
||||||
border-bottom-color: rgba(#000, 0.25);
|
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -37,7 +37,6 @@ main {
|
|||||||
aside {
|
aside {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.05);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -59,8 +58,8 @@ aside {
|
|||||||
a {
|
a {
|
||||||
color: $color-twitter;
|
color: $color-twitter;
|
||||||
|
|
||||||
&:focus-visible {
|
&.tab-focus {
|
||||||
@include focus-visible($color-twitter);
|
@include tab-focus($color-twitter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,30 @@
|
|||||||
|
|
||||||
@use 'sass:math';
|
@use 'sass:math';
|
||||||
|
|
||||||
|
// Convert a <button> into an <a>
|
||||||
|
// ---------------------------------------
|
||||||
|
@mixin cancel-button-styles() {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
line-height: $line-height-base;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
text-align: inherit;
|
||||||
|
text-shadow: inherit;
|
||||||
|
user-select: text;
|
||||||
|
vertical-align: baseline;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
// Nicer focus styles
|
// Nicer focus styles
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@mixin focus-visible($color: $focus-default-color) {
|
@mixin tab-focus($color: $tab-focus-default-color) {
|
||||||
outline: 2px dashed $color;
|
box-shadow: 0 0 0 3px rgba($color, 0.35);
|
||||||
outline-offset: 2px;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use rems for font sizing
|
// Use rems for font sizing
|
||||||
|
@ -9,7 +9,3 @@
|
|||||||
*::before {
|
*::before {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
all: unset;
|
|
||||||
}
|
|
||||||
|
@ -39,4 +39,4 @@ $color-button-count-background: #fff;
|
|||||||
$color-button-count-text: $color-gray-600;
|
$color-button-count-text: $color-gray-600;
|
||||||
|
|
||||||
// Focus
|
// Focus
|
||||||
$focus-default-color: $color-brand-primary;
|
$tab-focus-default-color: #fff;
|
||||||
|
@ -6,9 +6,8 @@
|
|||||||
$arrow-size: 5px;
|
$arrow-size: 5px;
|
||||||
|
|
||||||
// Radii
|
// Radii
|
||||||
$border-radius-small: 4px;
|
$border-radius-base: 4px;
|
||||||
$border-radius-medium: 6px;
|
$border-radius-large: 8px;
|
||||||
$border-radius-2x-large: 12px;
|
|
||||||
|
|
||||||
// Background
|
// Background
|
||||||
$page-background: linear-gradient(to left top, $color-background-from, $color-background-to);
|
$page-background: linear-gradient(to left top, $color-background-from, $color-background-to);
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
*:focus-visible {
|
|
||||||
outline: 2px dotted $color-brand-primary;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
45
package.json
45
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "plyr",
|
"name": "plyr",
|
||||||
"version": "3.7.8",
|
"version": "3.7.0",
|
||||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||||
"homepage": "https://plyr.io",
|
"homepage": "https://plyr.io",
|
||||||
"author": "Sam Potts <sam@potts.es>",
|
"author": "Sam Potts <sam@potts.es>",
|
||||||
@ -34,25 +34,24 @@
|
|||||||
"lint": "eslint src/js && npm run remark && stylelint **/*.scss",
|
"lint": "eslint src/js && npm run remark && stylelint **/*.scss",
|
||||||
"lint:fix": "eslint --fix src/js && stylelint **/*.scss --fix",
|
"lint:fix": "eslint --fix src/js && stylelint **/*.scss --fix",
|
||||||
"remark": "remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
|
"remark": "remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
|
||||||
"deploy": "npm run lint && gulp version && gulp build && gulp deploy",
|
"deploy": "yarn lint && gulp version && gulp build && gulp deploy",
|
||||||
"format": "prettier --write \"./{src,demo/src}/**/*.{js,scss}\"",
|
"format": "prettier --write \"./{src,demo/src}/**/*.{js,scss}\"",
|
||||||
"spellcheck": "cspell \"**/*.{js,md,scss,json}\" --no-must-find-files",
|
"spellcheck": "cspell \"**/*.{js,md,scss,json}\" --no-must-find-files",
|
||||||
"start": "gulp"
|
"start": "gulp"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.2",
|
"@babel/core": "^7.17.9",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.18.9",
|
"@babel/preset-env": "^7.16.11",
|
||||||
"@babel/preset-env": "^7.20.2",
|
|
||||||
"@sampotts/eslint-config": "1.1.7",
|
"@sampotts/eslint-config": "1.1.7",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.4",
|
||||||
"aws-sdk": "^2.1256.0",
|
"aws-sdk": "^2.1116.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"browser-sync": "^2.27.10",
|
"browser-sync": "^2.27.9",
|
||||||
"colorette": "2.0.19",
|
"colorette": "2.0.16",
|
||||||
"cspell": "^6.14.2",
|
"cspell": "^5.19.7",
|
||||||
"cssnano": "^5.1.14",
|
"cssnano": "^5.1.7",
|
||||||
"del": "^6.1.1",
|
"del": "^6.0.0",
|
||||||
"eslint": "^7.23.0",
|
"eslint": "^7.23.0",
|
||||||
"fancy-log": "^2.0.0",
|
"fancy-log": "^2.0.0",
|
||||||
"git-branch": "^2.0.1",
|
"git-branch": "^2.0.1",
|
||||||
@ -74,25 +73,25 @@
|
|||||||
"gulp-sourcemaps": "^3.0.0",
|
"gulp-sourcemaps": "^3.0.0",
|
||||||
"gulp-svgstore": "^9.0.0",
|
"gulp-svgstore": "^9.0.0",
|
||||||
"gulp-terser": "^2.1.0",
|
"gulp-terser": "^2.1.0",
|
||||||
"postcss": "^8.4.19",
|
"postcss": "^8.4.12",
|
||||||
"postcss-custom-properties": "^12.1.9",
|
"postcss-custom-properties": "^12.1.7",
|
||||||
"postcss-scss": "^4.0.5",
|
"postcss-scss": "^4.0.3",
|
||||||
"prettier-eslint": "^12.0.0",
|
"prettier-eslint": "^12.0.0",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"remark-cli": "^11.0.0",
|
"remark-cli": "^10.0.1",
|
||||||
"remark-validate-links": "^12.1.0",
|
"remark-validate-links": "^11.0.2",
|
||||||
"rollup": "^3.3.0",
|
"rollup": "^2.70.2",
|
||||||
"rollup-plugin-babel": "^4.4.0",
|
"rollup-plugin-babel": "^4.4.0",
|
||||||
"rollup-plugin-commonjs": "^10.1.0",
|
"rollup-plugin-commonjs": "^10.1.0",
|
||||||
"rollup-plugin-node-resolve": "^5.2.0",
|
"rollup-plugin-node-resolve": "^5.2.0",
|
||||||
"sass": "^1.56.1",
|
"sass": "^1.50.0",
|
||||||
"stylelint": "^14.15.0",
|
"stylelint": "^14.7.1",
|
||||||
"stylelint-config-prettier": "^9.0.4",
|
"stylelint-config-prettier": "^9.0.3",
|
||||||
"stylelint-config-sass-guidelines": "^9.0.1",
|
"stylelint-config-sass-guidelines": "^9.0.1",
|
||||||
"stylelint-selector-bem-pattern": "^2.1.1"
|
"stylelint-selector-bem-pattern": "^2.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.26.1",
|
"core-js": "^3.22.0",
|
||||||
"custom-event-polyfill": "^1.0.7",
|
"custom-event-polyfill": "^1.0.7",
|
||||||
"loadjs": "^4.2.0",
|
"loadjs": "^4.2.0",
|
||||||
"rangetouch": "^2.0.1",
|
"rangetouch": "^2.0.1",
|
||||||
|
13882
pnpm-lock.yaml
generated
13882
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -47,7 +47,6 @@ const captions = {
|
|||||||
// Inject the container
|
// Inject the container
|
||||||
if (!is.element(this.elements.captions)) {
|
if (!is.element(this.elements.captions)) {
|
||||||
this.elements.captions = createElement('div', getAttributesFromSelector(this.config.selectors.captions));
|
this.elements.captions = createElement('div', getAttributesFromSelector(this.config.selectors.captions));
|
||||||
this.elements.captions.setAttribute('dir', 'auto');
|
|
||||||
|
|
||||||
insertAfter(this.elements.captions, this.elements.wrapper);
|
insertAfter(this.elements.captions, this.elements.wrapper);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,8 @@ const defaults = {
|
|||||||
// Only allow one media playing at once (vimeo only)
|
// Only allow one media playing at once (vimeo only)
|
||||||
autopause: true,
|
autopause: true,
|
||||||
|
|
||||||
// Allow inline playback on iOS
|
// Allow inline playback on iOS (this effects YouTube/Vimeo - HTML5 requires the attribute present)
|
||||||
|
// TODO: Remove iosNative fullscreen option in favour of this (logic needs work)
|
||||||
playsinline: true,
|
playsinline: true,
|
||||||
|
|
||||||
// Default time to skip when rewind/fast forward
|
// Default time to skip when rewind/fast forward
|
||||||
@ -60,7 +61,7 @@ const defaults = {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/3.7.8/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.7.0/plyr.svg',
|
||||||
|
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
@ -352,6 +353,7 @@ const defaults = {
|
|||||||
marker: 'plyr__progress__marker',
|
marker: 'plyr__progress__marker',
|
||||||
hidden: 'plyr__sr-only',
|
hidden: 'plyr__sr-only',
|
||||||
hideControls: 'plyr--hide-controls',
|
hideControls: 'plyr--hide-controls',
|
||||||
|
isIos: 'plyr--is-ios',
|
||||||
isTouch: 'plyr--is-touch',
|
isTouch: 'plyr--is-touch',
|
||||||
uiSupported: 'plyr--full-ui',
|
uiSupported: 'plyr--full-ui',
|
||||||
noTransition: 'plyr--no-transition',
|
noTransition: 'plyr--no-transition',
|
||||||
@ -379,6 +381,7 @@ const defaults = {
|
|||||||
supported: 'plyr--airplay-supported',
|
supported: 'plyr--airplay-supported',
|
||||||
active: 'plyr--airplay-active',
|
active: 'plyr--airplay-active',
|
||||||
},
|
},
|
||||||
|
tabFocus: 'plyr__tab-focus',
|
||||||
previewThumbnails: {
|
previewThumbnails: {
|
||||||
// Tooltip thumbs
|
// Tooltip thumbs
|
||||||
thumbContainer: 'plyr__preview-thumb',
|
thumbContainer: 'plyr__preview-thumb',
|
||||||
|
38
src/js/controls.js
vendored
38
src/js/controls.js
vendored
@ -383,7 +383,6 @@ const controls = {
|
|||||||
extend(attributes, {
|
extend(attributes, {
|
||||||
class: `${attributes.class ? attributes.class : ''} ${this.config.classNames.display.time} `.trim(),
|
class: `${attributes.class ? attributes.class : ''} ${this.config.classNames.display.time} `.trim(),
|
||||||
'aria-label': i18n.get(type, this.config),
|
'aria-label': i18n.get(type, this.config),
|
||||||
role: 'timer',
|
|
||||||
}),
|
}),
|
||||||
'00:00',
|
'00:00',
|
||||||
);
|
);
|
||||||
@ -405,7 +404,7 @@ const controls = {
|
|||||||
'keydown keyup',
|
'keydown keyup',
|
||||||
(event) => {
|
(event) => {
|
||||||
// We only care about space and ⬆️ ⬇️️ ➡️
|
// We only care about space and ⬆️ ⬇️️ ➡️
|
||||||
if (![' ', 'ArrowUp', 'ArrowDown', 'ArrowRight'].includes(event.key)) {
|
if (![32, 38, 39, 40].includes(event.which)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,13 +420,13 @@ const controls = {
|
|||||||
const isRadioButton = matches(menuItem, '[role="menuitemradio"]');
|
const isRadioButton = matches(menuItem, '[role="menuitemradio"]');
|
||||||
|
|
||||||
// Show the respective menu
|
// Show the respective menu
|
||||||
if (!isRadioButton && [' ', 'ArrowRight'].includes(event.key)) {
|
if (!isRadioButton && [32, 39].includes(event.which)) {
|
||||||
controls.showMenuPanel.call(this, type, true);
|
controls.showMenuPanel.call(this, type, true);
|
||||||
} else {
|
} else {
|
||||||
let target;
|
let target;
|
||||||
|
|
||||||
if (event.key !== ' ') {
|
if (event.which !== 32) {
|
||||||
if (event.key === 'ArrowDown' || (isRadioButton && event.key === 'ArrowRight')) {
|
if (event.which === 40 || (isRadioButton && event.which === 39)) {
|
||||||
target = menuItem.nextElementSibling;
|
target = menuItem.nextElementSibling;
|
||||||
|
|
||||||
if (!is.element(target)) {
|
if (!is.element(target)) {
|
||||||
@ -451,7 +450,9 @@ const controls = {
|
|||||||
// Enter will fire a `click` event but we still need to manage focus
|
// Enter will fire a `click` event but we still need to manage focus
|
||||||
// So we bind to keyup which fires after and set focus here
|
// So we bind to keyup which fires after and set focus here
|
||||||
on.call(this, menuItem, 'keyup', (event) => {
|
on.call(this, menuItem, 'keyup', (event) => {
|
||||||
if (event.key !== 'Return') return;
|
if (event.which !== 13) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
controls.focusFirstMenuItem.call(this, null, true);
|
controls.focusFirstMenuItem.call(this, null, true);
|
||||||
});
|
});
|
||||||
@ -505,7 +506,7 @@ const controls = {
|
|||||||
menuItem,
|
menuItem,
|
||||||
'click keyup',
|
'click keyup',
|
||||||
(event) => {
|
(event) => {
|
||||||
if (is.keyboardEvent(event) && event.key !== ' ') {
|
if (is.keyboardEvent(event) && event.which !== 32) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -677,7 +678,7 @@ const controls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WebKit only
|
// WebKit only
|
||||||
if (!browser.isWebKit && !browser.isIPadOS) {
|
if (!browser.isWebkit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1106,7 +1107,7 @@ const controls = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Focus the first menu item in a given (or visible) menu
|
// Focus the first menu item in a given (or visible) menu
|
||||||
focusFirstMenuItem(pane, focusVisible = false) {
|
focusFirstMenuItem(pane, tabFocus = false) {
|
||||||
if (this.elements.settings.popup.hidden) {
|
if (this.elements.settings.popup.hidden) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1119,7 +1120,7 @@ const controls = {
|
|||||||
|
|
||||||
const firstItem = target.querySelector('[role^="menuitem"]');
|
const firstItem = target.querySelector('[role^="menuitem"]');
|
||||||
|
|
||||||
setFocus.call(this, firstItem, focusVisible);
|
setFocus.call(this, firstItem, tabFocus);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Show/hide menu
|
// Show/hide menu
|
||||||
@ -1138,7 +1139,7 @@ const controls = {
|
|||||||
|
|
||||||
if (is.boolean(input)) {
|
if (is.boolean(input)) {
|
||||||
show = input;
|
show = input;
|
||||||
} else if (is.keyboardEvent(input) && input.key === 'Escape') {
|
} else if (is.keyboardEvent(input) && input.which === 27) {
|
||||||
show = false;
|
show = false;
|
||||||
} else if (is.event(input)) {
|
} else if (is.event(input)) {
|
||||||
// If Plyr is in a shadowDOM, the event target is set to the component, instead of the
|
// If Plyr is in a shadowDOM, the event target is set to the component, instead of the
|
||||||
@ -1196,7 +1197,7 @@ const controls = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Show a panel in the menu
|
// Show a panel in the menu
|
||||||
showMenuPanel(type = '', focusVisible = false) {
|
showMenuPanel(type = '', tabFocus = false) {
|
||||||
const target = this.elements.container.querySelector(`#plyr-settings-${this.id}-${type}`);
|
const target = this.elements.container.querySelector(`#plyr-settings-${this.id}-${type}`);
|
||||||
|
|
||||||
// Nothing to show, bail
|
// Nothing to show, bail
|
||||||
@ -1247,7 +1248,7 @@ const controls = {
|
|||||||
toggleHidden(target, false);
|
toggleHidden(target, false);
|
||||||
|
|
||||||
// Focus the first item
|
// Focus the first item
|
||||||
controls.focusFirstMenuItem.call(this, target, focusVisible);
|
controls.focusFirstMenuItem.call(this, target, tabFocus);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set the download URL
|
// Set the download URL
|
||||||
@ -1386,7 +1387,7 @@ const controls = {
|
|||||||
// Volume range control
|
// Volume range control
|
||||||
// Ignored on iOS as it's handled globally
|
// Ignored on iOS as it's handled globally
|
||||||
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
|
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
|
||||||
if (control === 'volume' && !browser.isIos && !browser.isIPadOS) {
|
if (control === 'volume' && !browser.isIos) {
|
||||||
// Set the attributes
|
// Set the attributes
|
||||||
const attributes = {
|
const attributes = {
|
||||||
max: 1,
|
max: 1,
|
||||||
@ -1526,7 +1527,10 @@ const controls = {
|
|||||||
pane,
|
pane,
|
||||||
'keydown',
|
'keydown',
|
||||||
(event) => {
|
(event) => {
|
||||||
if (event.key !== 'ArrowLeft') return;
|
// We only care about <-
|
||||||
|
if (event.which !== 37) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent seek
|
// Prevent seek
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -1716,17 +1720,13 @@ const controls = {
|
|||||||
if (!is.empty(this.elements.buttons)) {
|
if (!is.empty(this.elements.buttons)) {
|
||||||
const addProperty = (button) => {
|
const addProperty = (button) => {
|
||||||
const className = this.config.classNames.controlPressed;
|
const className = this.config.classNames.controlPressed;
|
||||||
button.setAttribute('aria-pressed', 'false');
|
|
||||||
|
|
||||||
Object.defineProperty(button, 'pressed', {
|
Object.defineProperty(button, 'pressed', {
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get() {
|
get() {
|
||||||
return hasClass(button, className);
|
return hasClass(button, className);
|
||||||
},
|
},
|
||||||
set(pressed = false) {
|
set(pressed = false) {
|
||||||
toggleClass(button, className, pressed);
|
toggleClass(button, className, pressed);
|
||||||
button.setAttribute('aria-pressed', pressed ? 'true' : 'false');
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -57,10 +57,12 @@ class Fullscreen {
|
|||||||
|
|
||||||
// Update the UI
|
// Update the UI
|
||||||
this.update();
|
this.update();
|
||||||
|
|
||||||
|
// this.toggle = this.toggle.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if native supported
|
// Determine if native supported
|
||||||
static get nativeSupported() {
|
static get native() {
|
||||||
return !!(
|
return !!(
|
||||||
document.fullscreenEnabled ||
|
document.fullscreenEnabled ||
|
||||||
document.webkitFullscreenEnabled ||
|
document.webkitFullscreenEnabled ||
|
||||||
@ -70,14 +72,16 @@ class Fullscreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we're actually using native
|
// If we're actually using native
|
||||||
get useNative() {
|
get usingNative() {
|
||||||
return Fullscreen.nativeSupported && !this.forceFallback;
|
return Fullscreen.native && !this.forceFallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the prefix for handlers
|
// Get the prefix for handlers
|
||||||
static get prefix() {
|
static get prefix() {
|
||||||
// No prefix
|
// No prefix
|
||||||
if (is.function(document.exitFullscreen)) return '';
|
if (is.function(document.exitFullscreen)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Check for fullscreen support by vendor prefix
|
// Check for fullscreen support by vendor prefix
|
||||||
let value = '';
|
let value = '';
|
||||||
@ -99,30 +103,24 @@ class Fullscreen {
|
|||||||
return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
|
return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if fullscreen is supported
|
// Determine if fullscreen is enabled
|
||||||
get supported() {
|
get enabled() {
|
||||||
return [
|
return (
|
||||||
// Fullscreen is enabled in config
|
(Fullscreen.native || this.player.config.fullscreen.fallback) &&
|
||||||
this.player.config.fullscreen.enabled,
|
this.player.config.fullscreen.enabled &&
|
||||||
// Must be a video
|
this.player.supported.ui &&
|
||||||
this.player.isVideo,
|
this.player.isVideo
|
||||||
// Either native is supported or fallback enabled
|
);
|
||||||
Fullscreen.nativeSupported || this.player.config.fullscreen.fallback,
|
|
||||||
// YouTube has no way to trigger fullscreen, so on devices with no native support, playsinline
|
|
||||||
// must be enabled and iosNative fullscreen must be disabled to offer the fullscreen fallback
|
|
||||||
!this.player.isYouTube ||
|
|
||||||
Fullscreen.nativeSupported ||
|
|
||||||
!browser.isIos ||
|
|
||||||
(this.player.config.playsinline && !this.player.config.fullscreen.iosNative),
|
|
||||||
].every(Boolean);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get active state
|
// Get active state
|
||||||
get active() {
|
get active() {
|
||||||
if (!this.supported) return false;
|
if (!this.enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Fallback using classname
|
// Fallback using classname
|
||||||
if (!Fullscreen.nativeSupported || this.forceFallback) {
|
if (!Fullscreen.native || this.forceFallback) {
|
||||||
return hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
|
return hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,11 +135,13 @@ class Fullscreen {
|
|||||||
get target() {
|
get target() {
|
||||||
return browser.isIos && this.player.config.fullscreen.iosNative
|
return browser.isIos && this.player.config.fullscreen.iosNative
|
||||||
? this.player.media
|
? this.player.media
|
||||||
: this.player.elements.fullscreen ?? this.player.elements.container;
|
: this.player.elements.fullscreen || this.player.elements.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = () => {
|
onChange = () => {
|
||||||
if (!this.supported) return;
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Update toggle button
|
// Update toggle button
|
||||||
const button = this.player.elements.buttons.fullscreen;
|
const button = this.player.elements.buttons.fullscreen;
|
||||||
@ -159,8 +159,8 @@ class Fullscreen {
|
|||||||
// Store or restore scroll position
|
// Store or restore scroll position
|
||||||
if (toggle) {
|
if (toggle) {
|
||||||
this.scrollPosition = {
|
this.scrollPosition = {
|
||||||
x: window.scrollX ?? 0,
|
x: window.scrollX || 0,
|
||||||
y: window.scrollY ?? 0,
|
y: window.scrollY || 0,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
|
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
|
||||||
@ -188,7 +188,10 @@ class Fullscreen {
|
|||||||
|
|
||||||
if (toggle) {
|
if (toggle) {
|
||||||
this.cleanupViewport = !hasProperty;
|
this.cleanupViewport = !hasProperty;
|
||||||
if (!hasProperty) viewport.content += `,${property}`;
|
|
||||||
|
if (!hasProperty) {
|
||||||
|
viewport.content += `,${property}`;
|
||||||
|
}
|
||||||
} else if (this.cleanupViewport) {
|
} else if (this.cleanupViewport) {
|
||||||
viewport.content = viewport.content
|
viewport.content = viewport.content
|
||||||
.split(',')
|
.split(',')
|
||||||
@ -203,8 +206,10 @@ class Fullscreen {
|
|||||||
|
|
||||||
// Trap focus inside container
|
// Trap focus inside container
|
||||||
trapFocus = (event) => {
|
trapFocus = (event) => {
|
||||||
// Bail if iOS/iPadOS, not active, not the tab key
|
// Bail if iOS, not active, not the tab key
|
||||||
if (browser.isIos || browser.isIPadOS || !this.active || event.key !== 'Tab') return;
|
if (browser.isIos || !this.active || event.key !== 'Tab' || event.keyCode !== 9) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the current focused element
|
// Get the current focused element
|
||||||
const focused = document.activeElement;
|
const focused = document.activeElement;
|
||||||
@ -225,12 +230,16 @@ class Fullscreen {
|
|||||||
|
|
||||||
// Update UI
|
// Update UI
|
||||||
update = () => {
|
update = () => {
|
||||||
if (this.supported) {
|
if (this.enabled) {
|
||||||
let mode;
|
let mode;
|
||||||
|
|
||||||
if (this.forceFallback) mode = 'Fallback (forced)';
|
if (this.forceFallback) {
|
||||||
else if (Fullscreen.nativeSupported) mode = 'Native';
|
mode = 'Fallback (forced)';
|
||||||
else mode = 'Fallback';
|
} else if (Fullscreen.native) {
|
||||||
|
mode = 'Native';
|
||||||
|
} else {
|
||||||
|
mode = 'Fallback';
|
||||||
|
}
|
||||||
|
|
||||||
this.player.debug.log(`${mode} fullscreen enabled`);
|
this.player.debug.log(`${mode} fullscreen enabled`);
|
||||||
} else {
|
} else {
|
||||||
@ -238,12 +247,14 @@ class Fullscreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add styling hook to show button
|
// Add styling hook to show button
|
||||||
toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.supported);
|
toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make an element fullscreen
|
// Make an element fullscreen
|
||||||
enter = () => {
|
enter = () => {
|
||||||
if (!this.supported) return;
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// iOS native fullscreen doesn't need the request step
|
// iOS native fullscreen doesn't need the request step
|
||||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
@ -252,7 +263,7 @@ class Fullscreen {
|
|||||||
} else {
|
} else {
|
||||||
this.target.webkitEnterFullscreen();
|
this.target.webkitEnterFullscreen();
|
||||||
}
|
}
|
||||||
} else if (!Fullscreen.nativeSupported || this.forceFallback) {
|
} else if (!Fullscreen.native || this.forceFallback) {
|
||||||
this.toggleFallback(true);
|
this.toggleFallback(true);
|
||||||
} else if (!this.prefix) {
|
} else if (!this.prefix) {
|
||||||
this.target.requestFullscreen({ navigationUI: 'hide' });
|
this.target.requestFullscreen({ navigationUI: 'hide' });
|
||||||
@ -263,17 +274,15 @@ class Fullscreen {
|
|||||||
|
|
||||||
// Bail from fullscreen
|
// Bail from fullscreen
|
||||||
exit = () => {
|
exit = () => {
|
||||||
if (!this.supported) return;
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// iOS native fullscreen
|
// iOS native fullscreen
|
||||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
if (this.player.isVimeo) {
|
this.target.webkitExitFullscreen();
|
||||||
this.player.embed.exitFullscreen();
|
|
||||||
} else {
|
|
||||||
this.target.webkitEnterFullscreen();
|
|
||||||
}
|
|
||||||
silencePromise(this.player.play());
|
silencePromise(this.player.play());
|
||||||
} else if (!Fullscreen.nativeSupported || this.forceFallback) {
|
} else if (!Fullscreen.native || this.forceFallback) {
|
||||||
this.toggleFallback(false);
|
this.toggleFallback(false);
|
||||||
} else if (!this.prefix) {
|
} else if (!this.prefix) {
|
||||||
(document.cancelFullScreen || document.exitFullscreen).call(document);
|
(document.cancelFullScreen || document.exitFullscreen).call(document);
|
||||||
@ -285,8 +294,11 @@ class Fullscreen {
|
|||||||
|
|
||||||
// Toggle state
|
// Toggle state
|
||||||
toggle = () => {
|
toggle = () => {
|
||||||
if (!this.active) this.enter();
|
if (!this.active) {
|
||||||
else this.exit();
|
this.enter();
|
||||||
|
} else {
|
||||||
|
this.exit();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ class Listeners {
|
|||||||
|
|
||||||
this.handleKey = this.handleKey.bind(this);
|
this.handleKey = this.handleKey.bind(this);
|
||||||
this.toggleMenu = this.toggleMenu.bind(this);
|
this.toggleMenu = this.toggleMenu.bind(this);
|
||||||
|
this.setTabFocus = this.setTabFocus.bind(this);
|
||||||
this.firstTouch = this.firstTouch.bind(this);
|
this.firstTouch = this.firstTouch.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,25 +29,25 @@ class Listeners {
|
|||||||
handleKey(event) {
|
handleKey(event) {
|
||||||
const { player } = this;
|
const { player } = this;
|
||||||
const { elements } = player;
|
const { elements } = player;
|
||||||
const { key, type, altKey, ctrlKey, metaKey, shiftKey } = event;
|
const code = event.keyCode ? event.keyCode : event.which;
|
||||||
const pressed = type === 'keydown';
|
const pressed = event.type === 'keydown';
|
||||||
const repeat = pressed && key === this.lastKey;
|
const repeat = pressed && code === this.lastKey;
|
||||||
|
|
||||||
// Bail if a modifier key is set
|
// Bail if a modifier key is set
|
||||||
if (altKey || ctrlKey || metaKey || shiftKey) {
|
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the event is bubbled from the media element
|
// If the event is bubbled from the media element
|
||||||
// Firefox doesn't get the key for whatever reason
|
// Firefox doesn't get the keycode for whatever reason
|
||||||
if (!key) {
|
if (!is.number(code)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek by increment
|
// Seek by the number keys
|
||||||
const seekByIncrement = (increment) => {
|
const seekByKey = () => {
|
||||||
// Divide the max duration into 10th's and times by the number value
|
// Divide the max duration into 10th's and times by the number value
|
||||||
player.currentTime = (player.duration / 10) * increment;
|
player.currentTime = (player.duration / 10) * (code - 48);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle the key on keydown
|
// Handle the key on keydown
|
||||||
@ -64,114 +65,113 @@ class Listeners {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === ' ' && matches(focused, 'button, [role^="menuitem"]')) {
|
if (event.which === 32 && matches(focused, 'button, [role^="menuitem"]')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Which keys should we prevent default
|
// Which keycodes should we prevent default
|
||||||
const preventDefault = [
|
const preventDefault = [32, 37, 38, 39, 40, 48, 49, 50, 51, 52, 53, 54, 56, 57, 67, 70, 73, 75, 76, 77, 79];
|
||||||
' ',
|
|
||||||
'ArrowLeft',
|
|
||||||
'ArrowUp',
|
|
||||||
'ArrowRight',
|
|
||||||
'ArrowDown',
|
|
||||||
|
|
||||||
'0',
|
// If the code is found prevent default (e.g. prevent scrolling for arrows)
|
||||||
'1',
|
if (preventDefault.includes(code)) {
|
||||||
'2',
|
|
||||||
'3',
|
|
||||||
'4',
|
|
||||||
'5',
|
|
||||||
'6',
|
|
||||||
'7',
|
|
||||||
'8',
|
|
||||||
'9',
|
|
||||||
|
|
||||||
'c',
|
|
||||||
'f',
|
|
||||||
'k',
|
|
||||||
'l',
|
|
||||||
'm',
|
|
||||||
];
|
|
||||||
|
|
||||||
// If the key is found prevent default (e.g. prevent scrolling for arrows)
|
|
||||||
if (preventDefault.includes(key)) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (key) {
|
switch (code) {
|
||||||
case '0':
|
case 48:
|
||||||
case '1':
|
case 49:
|
||||||
case '2':
|
case 50:
|
||||||
case '3':
|
case 51:
|
||||||
case '4':
|
case 52:
|
||||||
case '5':
|
case 53:
|
||||||
case '6':
|
case 54:
|
||||||
case '7':
|
case 55:
|
||||||
case '8':
|
case 56:
|
||||||
case '9':
|
case 57:
|
||||||
|
// 0-9
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
seekByIncrement(parseInt(key, 10));
|
seekByKey();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ' ':
|
case 32:
|
||||||
case 'k':
|
case 75:
|
||||||
|
// Space and K key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
silencePromise(player.togglePlay());
|
silencePromise(player.togglePlay());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ArrowUp':
|
case 38:
|
||||||
|
// Arrow up
|
||||||
player.increaseVolume(0.1);
|
player.increaseVolume(0.1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ArrowDown':
|
case 40:
|
||||||
|
// Arrow down
|
||||||
player.decreaseVolume(0.1);
|
player.decreaseVolume(0.1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'm':
|
case 77:
|
||||||
|
// M key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
player.muted = !player.muted;
|
player.muted = !player.muted;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ArrowRight':
|
case 39:
|
||||||
|
// Arrow forward
|
||||||
player.forward();
|
player.forward();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ArrowLeft':
|
case 37:
|
||||||
|
// Arrow back
|
||||||
player.rewind();
|
player.rewind();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'f':
|
case 70:
|
||||||
|
// F key
|
||||||
player.fullscreen.toggle();
|
player.fullscreen.toggle();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'c':
|
case 67:
|
||||||
|
// C key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
player.toggleCaptions();
|
player.toggleCaptions();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'l':
|
case 76:
|
||||||
|
// L key
|
||||||
player.loop = !player.loop;
|
player.loop = !player.loop;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/* case 73:
|
||||||
|
this.setLoop('start');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 76:
|
||||||
|
this.setLoop();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 79:
|
||||||
|
this.setLoop('end');
|
||||||
|
break; */
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape is handle natively when in full screen
|
// Escape is handle natively when in full screen
|
||||||
// So we only need to worry about non native
|
// So we only need to worry about non native
|
||||||
if (key === 'Escape' && !player.fullscreen.usingNative && player.fullscreen.active) {
|
if (code === 27 && !player.fullscreen.usingNative && player.fullscreen.active) {
|
||||||
player.fullscreen.toggle();
|
player.fullscreen.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store last key for next cycle
|
// Store last code for next cycle
|
||||||
this.lastKey = key;
|
this.lastKey = code;
|
||||||
} else {
|
} else {
|
||||||
this.lastKey = null;
|
this.lastKey = null;
|
||||||
}
|
}
|
||||||
@ -193,6 +193,56 @@ class Listeners {
|
|||||||
toggleClass(elements.container, player.config.classNames.isTouch, true);
|
toggleClass(elements.container, player.config.classNames.isTouch, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setTabFocus = (event) => {
|
||||||
|
const { player } = this;
|
||||||
|
const { elements } = player;
|
||||||
|
|
||||||
|
clearTimeout(this.focusTimer);
|
||||||
|
|
||||||
|
// Ignore any key other than tab
|
||||||
|
if (event.type === 'keydown' && event.which !== 9) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store reference to event timeStamp
|
||||||
|
if (event.type === 'keydown') {
|
||||||
|
this.lastKeyDown = event.timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove current classes
|
||||||
|
const removeCurrent = () => {
|
||||||
|
const className = player.config.classNames.tabFocus;
|
||||||
|
const current = getElements.call(player, `.${className}`);
|
||||||
|
toggleClass(current, className, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine if a key was pressed to trigger this event
|
||||||
|
const wasKeyDown = event.timeStamp - this.lastKeyDown <= 20;
|
||||||
|
|
||||||
|
// Ignore focus events if a key was pressed prior
|
||||||
|
if (event.type === 'focus' && !wasKeyDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all current
|
||||||
|
removeCurrent();
|
||||||
|
|
||||||
|
// Delay the adding of classname until the focus has changed
|
||||||
|
// This event fires before the focusin event
|
||||||
|
if (event.type !== 'focusout') {
|
||||||
|
this.focusTimer = setTimeout(() => {
|
||||||
|
const focused = document.activeElement;
|
||||||
|
|
||||||
|
// Ignore if current focus element isn't inside the player
|
||||||
|
if (!elements.container.contains(focused)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Global window & document listeners
|
// Global window & document listeners
|
||||||
global = (toggle = true) => {
|
global = (toggle = true) => {
|
||||||
const { player } = this;
|
const { player } = this;
|
||||||
@ -207,6 +257,9 @@ class Listeners {
|
|||||||
|
|
||||||
// Detect touch by events
|
// Detect touch by events
|
||||||
once.call(player, document.body, 'touchstart', this.firstTouch);
|
once.call(player, document.body, 'touchstart', this.firstTouch);
|
||||||
|
|
||||||
|
// Tab focus detection
|
||||||
|
toggleListener.call(player, document.body, 'keydown focus blur focusout', this.setTabFocus, toggle, false, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Container listeners
|
// Container listeners
|
||||||
@ -608,12 +661,15 @@ class Listeners {
|
|||||||
elements.buttons.settings,
|
elements.buttons.settings,
|
||||||
'keyup',
|
'keyup',
|
||||||
(event) => {
|
(event) => {
|
||||||
if (![' ', 'Enter'].includes(event.key)) {
|
const code = event.which;
|
||||||
|
|
||||||
|
// We only care about space and return
|
||||||
|
if (![13, 32].includes(code)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because return triggers a click anyway, all we need to do is set focus
|
// Because return triggers a click anyway, all we need to do is set focus
|
||||||
if (event.key === 'Enter') {
|
if (code === 13) {
|
||||||
controls.focusFirstMenuItem.call(player, null, true);
|
controls.focusFirstMenuItem.call(player, null, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -633,7 +689,7 @@ class Listeners {
|
|||||||
|
|
||||||
// Escape closes menu
|
// Escape closes menu
|
||||||
this.bind(elements.settings.menu, 'keydown', (event) => {
|
this.bind(elements.settings.menu, 'keydown', (event) => {
|
||||||
if (event.key === 'Escape') {
|
if (event.which === 27) {
|
||||||
controls.toggleMenu.call(player, event);
|
controls.toggleMenu.call(player, event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -648,9 +704,10 @@ class Listeners {
|
|||||||
// Pause while seeking
|
// Pause while seeking
|
||||||
this.bind(elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', (event) => {
|
this.bind(elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', (event) => {
|
||||||
const seek = event.currentTarget;
|
const seek = event.currentTarget;
|
||||||
|
const code = event.keyCode ? event.keyCode : event.which;
|
||||||
const attribute = 'play-on-seeked';
|
const attribute = 'play-on-seeked';
|
||||||
|
|
||||||
if (is.keyboardEvent(event) && !['ArrowLeft', 'ArrowRight'].includes(event.key)) {
|
if (is.keyboardEvent(event) && code !== 39 && code !== 37) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -742,7 +799,7 @@ class Listeners {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Polyfill for lower fill in <input type="range"> for webkit
|
// Polyfill for lower fill in <input type="range"> for webkit
|
||||||
if (browser.isWebKit) {
|
if (browser.isWebkit) {
|
||||||
Array.from(getElements.call(player, 'input[type="range"]')).forEach((element) => {
|
Array.from(getElements.call(player, 'input[type="range"]')).forEach((element) => {
|
||||||
this.bind(element, 'input', (event) => controls.updateRangeFill.call(player, event.target));
|
this.bind(element, 'input', (event) => controls.updateRangeFill.call(player, event.target));
|
||||||
});
|
});
|
||||||
@ -826,7 +883,7 @@ class Listeners {
|
|||||||
elements.inputs.volume,
|
elements.inputs.volume,
|
||||||
'wheel',
|
'wheel',
|
||||||
(event) => {
|
(event) => {
|
||||||
// Detect "natural" scroll - supported on OS X Safari only
|
// Detect "natural" scroll - suppored on OS X Safari only
|
||||||
// Other browsers on OS X will be inverted until support improves
|
// Other browsers on OS X will be inverted until support improves
|
||||||
const inverted = event.webkitDirectionInvertedFromDevice;
|
const inverted = event.webkitDirectionInvertedFromDevice;
|
||||||
// Get delta from event. Invert if `inverted` is true
|
// Get delta from event. Invert if `inverted` is true
|
||||||
|
@ -240,7 +240,7 @@ class Ads {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called whenever the ads are ready inside the AdDisplayContainer
|
* This method is called whenever the ads are ready inside the AdDisplayContainer
|
||||||
* @param {Event} event - adsManagerLoadedEvent
|
* @param {Event} adsManagerLoadedEvent
|
||||||
*/
|
*/
|
||||||
onAdsManagerLoaded = (event) => {
|
onAdsManagerLoaded = (event) => {
|
||||||
// Load could occur after a source change (race condition)
|
// Load could occur after a source change (race condition)
|
||||||
@ -581,7 +581,6 @@ class Ads {
|
|||||||
/**
|
/**
|
||||||
* Handles callbacks after an ad event was invoked
|
* Handles callbacks after an ad event was invoked
|
||||||
* @param {String} event - Event type
|
* @param {String} event - Event type
|
||||||
* @param args
|
|
||||||
*/
|
*/
|
||||||
trigger = (event, ...args) => {
|
trigger = (event, ...args) => {
|
||||||
const handlers = this.events[event];
|
const handlers = this.events[event];
|
||||||
|
@ -2,7 +2,6 @@ import { createElement } from '../utils/elements';
|
|||||||
import { once } from '../utils/events';
|
import { once } from '../utils/events';
|
||||||
import fetch from '../utils/fetch';
|
import fetch from '../utils/fetch';
|
||||||
import is from '../utils/is';
|
import is from '../utils/is';
|
||||||
import { clamp } from '../utils/numbers';
|
|
||||||
import { formatTime } from '../utils/time';
|
import { formatTime } from '../utils/time';
|
||||||
|
|
||||||
// Arg: vttDataString example: "WEBVTT\n\n1\n00:00:05.000 --> 00:00:10.000\n1080p-00001.jpg"
|
// Arg: vttDataString example: "WEBVTT\n\n1\n00:00:05.000 --> 00:00:10.000\n1080p-00001.jpg"
|
||||||
@ -110,7 +109,9 @@ class PreviewThumbnails {
|
|||||||
this.player.elements.display.seekTooltip.hidden = this.enabled;
|
this.player.elements.display.seekTooltip.hidden = this.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.enabled) return;
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.getThumbnails().then(() => {
|
this.getThumbnails().then(() => {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
@ -123,9 +124,6 @@ class PreviewThumbnails {
|
|||||||
// Check to see if thumb container size was specified manually in CSS
|
// Check to see if thumb container size was specified manually in CSS
|
||||||
this.determineContainerAutoSizing();
|
this.determineContainerAutoSizing();
|
||||||
|
|
||||||
// Set up listeners
|
|
||||||
this.listeners();
|
|
||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -207,12 +205,18 @@ class PreviewThumbnails {
|
|||||||
};
|
};
|
||||||
|
|
||||||
startMove = (event) => {
|
startMove = (event) => {
|
||||||
if (!this.loaded) return;
|
if (!this.loaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!is.event(event) || !['touchmove', 'mousemove'].includes(event.type)) return;
|
if (!is.event(event) || !['touchmove', 'mousemove'].includes(event.type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Wait until media has a duration
|
// Wait until media has a duration
|
||||||
if (!this.player.media.duration) return;
|
if (!this.player.media.duration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.type === 'touchmove') {
|
if (event.type === 'touchmove') {
|
||||||
// Calculate seek hover position as approx video seconds
|
// Calculate seek hover position as approx video seconds
|
||||||
@ -558,7 +562,11 @@ class PreviewThumbnails {
|
|||||||
};
|
};
|
||||||
|
|
||||||
get currentImageContainer() {
|
get currentImageContainer() {
|
||||||
return this.mouseDown ? this.elements.scrubbing.container : this.elements.thumb.imageContainer;
|
if (this.mouseDown) {
|
||||||
|
return this.elements.scrubbing.container;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.elements.thumb.imageContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
get usingSprites() {
|
get usingSprites() {
|
||||||
@ -591,7 +599,11 @@ class PreviewThumbnails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get currentImageElement() {
|
get currentImageElement() {
|
||||||
return this.mouseDown ? this.currentScrubbingImageElement : this.currentThumbnailImageElement;
|
if (this.mouseDown) {
|
||||||
|
return this.currentScrubbingImageElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.currentThumbnailImageElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
set currentImageElement(element) {
|
set currentImageElement(element) {
|
||||||
@ -631,39 +643,46 @@ class PreviewThumbnails {
|
|||||||
|
|
||||||
// Set the size to be about a quarter of the size of video. Unless option dynamicSize === false, in which case it needs to be set in CSS
|
// Set the size to be about a quarter of the size of video. Unless option dynamicSize === false, in which case it needs to be set in CSS
|
||||||
setThumbContainerSizeAndPos = () => {
|
setThumbContainerSizeAndPos = () => {
|
||||||
const { imageContainer } = this.elements.thumb;
|
|
||||||
|
|
||||||
if (!this.sizeSpecifiedInCSS) {
|
if (!this.sizeSpecifiedInCSS) {
|
||||||
const thumbWidth = Math.floor(this.thumbContainerHeight * this.thumbAspectRatio);
|
const thumbWidth = Math.floor(this.thumbContainerHeight * this.thumbAspectRatio);
|
||||||
imageContainer.style.height = `${this.thumbContainerHeight}px`;
|
this.elements.thumb.imageContainer.style.height = `${this.thumbContainerHeight}px`;
|
||||||
imageContainer.style.width = `${thumbWidth}px`;
|
this.elements.thumb.imageContainer.style.width = `${thumbWidth}px`;
|
||||||
} else if (imageContainer.clientHeight > 20 && imageContainer.clientWidth < 20) {
|
} else if (
|
||||||
const thumbWidth = Math.floor(imageContainer.clientHeight * this.thumbAspectRatio);
|
this.elements.thumb.imageContainer.clientHeight > 20 &&
|
||||||
imageContainer.style.width = `${thumbWidth}px`;
|
this.elements.thumb.imageContainer.clientWidth < 20
|
||||||
} else if (imageContainer.clientHeight < 20 && imageContainer.clientWidth > 20) {
|
) {
|
||||||
const thumbHeight = Math.floor(imageContainer.clientWidth / this.thumbAspectRatio);
|
const thumbWidth = Math.floor(this.elements.thumb.imageContainer.clientHeight * this.thumbAspectRatio);
|
||||||
imageContainer.style.height = `${thumbHeight}px`;
|
this.elements.thumb.imageContainer.style.width = `${thumbWidth}px`;
|
||||||
|
} else if (
|
||||||
|
this.elements.thumb.imageContainer.clientHeight < 20 &&
|
||||||
|
this.elements.thumb.imageContainer.clientWidth > 20
|
||||||
|
) {
|
||||||
|
const thumbHeight = Math.floor(this.elements.thumb.imageContainer.clientWidth / this.thumbAspectRatio);
|
||||||
|
this.elements.thumb.imageContainer.style.height = `${thumbHeight}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setThumbContainerPos();
|
this.setThumbContainerPos();
|
||||||
};
|
};
|
||||||
|
|
||||||
setThumbContainerPos = () => {
|
setThumbContainerPos = () => {
|
||||||
const scrubberRect = this.player.elements.progress.getBoundingClientRect();
|
const seekbarRect = this.player.elements.progress.getBoundingClientRect();
|
||||||
const containerRect = this.player.elements.container.getBoundingClientRect();
|
const plyrRect = this.player.elements.container.getBoundingClientRect();
|
||||||
const { container } = this.elements.thumb;
|
const { container } = this.elements.thumb;
|
||||||
// Find the lowest and highest desired left-position, so we don't slide out the side of the video container
|
// Find the lowest and highest desired left-position, so we don't slide out the side of the video container
|
||||||
const min = containerRect.left - scrubberRect.left + 10;
|
const minVal = plyrRect.left - seekbarRect.left + 10;
|
||||||
const max = containerRect.right - scrubberRect.left - container.clientWidth - 10;
|
const maxVal = plyrRect.right - seekbarRect.left - container.clientWidth - 10;
|
||||||
// Set preview container position to: mousepos, minus seekbar.left, minus half of previewContainer.clientWidth
|
// Set preview container position to: mousepos, minus seekbar.left, minus half of previewContainer.clientWidth
|
||||||
const position = this.mousePosX - scrubberRect.left - container.clientWidth / 2;
|
let previewPos = this.mousePosX - seekbarRect.left - container.clientWidth / 2;
|
||||||
const clamped = clamp(position, min, max);
|
|
||||||
|
|
||||||
// Move the popover position
|
if (previewPos < minVal) {
|
||||||
container.style.left = `${clamped}px`;
|
previewPos = minVal;
|
||||||
|
}
|
||||||
|
|
||||||
// The arrow can follow the cursor
|
if (previewPos > maxVal) {
|
||||||
container.style.setProperty('--preview-arrow-offset', `${position - clamped}px`);
|
previewPos = maxVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.style.left = `${previewPos}px`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Can't use 100% width, in case the video is a different aspect ratio to the video container
|
// Can't use 100% width, in case the video is a different aspect ratio to the video container
|
||||||
@ -678,7 +697,9 @@ class PreviewThumbnails {
|
|||||||
|
|
||||||
// Sprites need to be offset to the correct location
|
// Sprites need to be offset to the correct location
|
||||||
setImageSizeAndOffset = (previewImage, frame) => {
|
setImageSizeAndOffset = (previewImage, frame) => {
|
||||||
if (!this.usingSprites) return;
|
if (!this.usingSprites) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Find difference between height and preview container height
|
// Find difference between height and preview container height
|
||||||
const multiplier = this.thumbContainerHeight / frame.h;
|
const multiplier = this.thumbContainerHeight / frame.h;
|
||||||
|
@ -113,7 +113,7 @@ const vimeo = {
|
|||||||
autoplay: player.autoplay,
|
autoplay: player.autoplay,
|
||||||
muted: player.muted,
|
muted: player.muted,
|
||||||
gesture: 'media',
|
gesture: 'media',
|
||||||
playsinline: player.config.playsinline,
|
playsinline: !this.config.fullscreen.iosNative,
|
||||||
// hash has to be added to iframe-URL
|
// hash has to be added to iframe-URL
|
||||||
...hashParam,
|
...hashParam,
|
||||||
...frameParams,
|
...frameParams,
|
||||||
@ -265,7 +265,7 @@ const vimeo = {
|
|||||||
set(input) {
|
set(input) {
|
||||||
const toggle = is.boolean(input) ? input : false;
|
const toggle = is.boolean(input) ? input : false;
|
||||||
|
|
||||||
player.embed.setMuted(toggle ? true : player.config.muted).then(() => {
|
player.embed.setVolume(toggle ? 0 : player.config.volume).then(() => {
|
||||||
muted = toggle;
|
muted = toggle;
|
||||||
triggerEvent.call(player, player.media, 'volumechange');
|
triggerEvent.call(player, player.media, 'volumechange');
|
||||||
});
|
});
|
||||||
|
@ -131,7 +131,7 @@ const youtube = {
|
|||||||
const posterSrc = (s) => `https://i.ytimg.com/vi/${videoId}/${s}default.jpg`;
|
const posterSrc = (s) => `https://i.ytimg.com/vi/${videoId}/${s}default.jpg`;
|
||||||
|
|
||||||
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
|
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
|
||||||
loadImage(posterSrc('maxres'), 121) // Highest quality and un-padded
|
loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
|
||||||
.catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
|
.catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
|
||||||
.catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
|
.catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
|
||||||
.then((image) => ui.setPoster.call(player, image.src))
|
.then((image) => ui.setPoster.call(player, image.src))
|
||||||
@ -161,7 +161,7 @@ const youtube = {
|
|||||||
// Disable keyboard as we handle it
|
// Disable keyboard as we handle it
|
||||||
disablekb: 1,
|
disablekb: 1,
|
||||||
// Allow iOS inline playback
|
// Allow iOS inline playback
|
||||||
playsinline: player.config.playsinline && !player.config.fullscreen.iosNative ? 1 : 0,
|
playsinline: !player.config.fullscreen.iosNative ? 1 : 0,
|
||||||
// Captions are flaky on YouTube
|
// Captions are flaky on YouTube
|
||||||
cc_load_policy: player.captions.active ? 1 : 0,
|
cc_load_policy: player.captions.active ? 1 : 0,
|
||||||
cc_lang_pref: player.config.captions.language,
|
cc_lang_pref: player.config.captions.language,
|
||||||
@ -183,7 +183,7 @@ const youtube = {
|
|||||||
100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
|
100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
|
||||||
101: 'The owner of the requested video does not allow it to be played in embedded players.',
|
101: 'The owner of the requested video does not allow it to be played in embedded players.',
|
||||||
150: 'The owner of the requested video does not allow it to be played in embedded players.',
|
150: 'The owner of the requested video does not allow it to be played in embedded players.',
|
||||||
}[code] || 'An unknown error occurred';
|
}[code] || 'An unknown error occured';
|
||||||
|
|
||||||
player.media.error = { code, message };
|
player.media.error = { code, message };
|
||||||
|
|
||||||
|
23
src/js/plyr.d.ts
vendored
23
src/js/plyr.d.ts
vendored
@ -212,7 +212,7 @@ declare class Plyr {
|
|||||||
airplay(): void;
|
airplay(): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the preview thumbnails for the current source.
|
* Sets the preview thubmnails for the current source.
|
||||||
*/
|
*/
|
||||||
setPreviewThumbnails(source: Plyr.PreviewThumbnailsOptions): void;
|
setPreviewThumbnails(source: Plyr.PreviewThumbnailsOptions): void;
|
||||||
|
|
||||||
@ -272,8 +272,8 @@ declare namespace Plyr {
|
|||||||
controlsshown: PlyrEvent;
|
controlsshown: PlyrEvent;
|
||||||
ready: PlyrEvent;
|
ready: PlyrEvent;
|
||||||
};
|
};
|
||||||
// For retrocompatibility, we keep StandardEvent
|
// For retrocompatibility, we keep StandadEvent
|
||||||
type StandardEvent = keyof Plyr.StandardEventMap;
|
type StandadEvent = keyof Plyr.StandardEventMap;
|
||||||
type Html5EventMap = {
|
type Html5EventMap = {
|
||||||
loadstart: PlyrEvent;
|
loadstart: PlyrEvent;
|
||||||
loadeddata: PlyrEvent;
|
loadeddata: PlyrEvent;
|
||||||
@ -341,7 +341,7 @@ declare namespace Plyr {
|
|||||||
* id (the unique id for the player), seektime (the seektime step in seconds), and title (the media title). See CONTROLS.md for more info on how the html needs to be structured.
|
* id (the unique id for the player), seektime (the seektime step in seconds), and title (the media title). See CONTROLS.md for more info on how the html needs to be structured.
|
||||||
* Defaults to ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']
|
* Defaults to ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']
|
||||||
*/
|
*/
|
||||||
controls?: string | string[] | ((id: string, seektime: number, title: string) => unknown) | Element;
|
controls?: string[] | ((id: string, seektime: number, title: string) => unknown) | Element;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If you're using the default controls are used then you can specify which settings to show in the menu
|
* If you're using the default controls are used then you can specify which settings to show in the menu
|
||||||
@ -523,11 +523,6 @@ declare namespace Plyr {
|
|||||||
* Media Metadata Options.
|
* Media Metadata Options.
|
||||||
*/
|
*/
|
||||||
mediaMetadata?: MediaMetadataOptions;
|
mediaMetadata?: MediaMetadataOptions;
|
||||||
|
|
||||||
/**
|
|
||||||
* Markers Options
|
|
||||||
*/
|
|
||||||
markers?: MarkersOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QualityOptions {
|
interface QualityOptions {
|
||||||
@ -599,16 +594,6 @@ declare namespace Plyr {
|
|||||||
artwork?: MediaMetadataArtwork[];
|
artwork?: MediaMetadataArtwork[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MarkersPoints {
|
|
||||||
time: number;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MarkersOptions {
|
|
||||||
enabled: boolean;
|
|
||||||
points: MarkersPoints[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Elements {
|
export interface Elements {
|
||||||
buttons: {
|
buttons: {
|
||||||
airplay?: HTMLButtonElement;
|
airplay?: HTMLButtonElement;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr
|
// Plyr
|
||||||
// plyr.js v3.7.8
|
// plyr.js v3.7.0
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@ -246,7 +246,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for support again but with type
|
// Check for support again but with type
|
||||||
this.supported = support.check(this.type, this.provider);
|
this.supported = support.check(this.type, this.provider, this.config.playsinline);
|
||||||
|
|
||||||
// If no support for even API, bail
|
// If no support for even API, bail
|
||||||
if (!this.supported.api) {
|
if (!this.supported.api) {
|
||||||
@ -267,7 +267,7 @@ class Plyr {
|
|||||||
|
|
||||||
// Wrap media
|
// Wrap media
|
||||||
if (!is.element(this.elements.container)) {
|
if (!is.element(this.elements.container)) {
|
||||||
this.elements.container = createElement('div');
|
this.elements.container = createElement('div', { tabindex: 0 });
|
||||||
wrap(this.media, this.elements.container);
|
wrap(this.media, this.elements.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,7 +649,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set playback speed
|
* Set playback speed
|
||||||
* @param {Number} input - the speed of playback (0.5-2.0)
|
* @param {Number} speed - the speed of playback (0.5-2.0)
|
||||||
*/
|
*/
|
||||||
set speed(input) {
|
set speed(input) {
|
||||||
let speed = null;
|
let speed = null;
|
||||||
@ -933,7 +933,8 @@ class Plyr {
|
|||||||
* @param {Boolean} input - Whether to autoplay or not
|
* @param {Boolean} input - Whether to autoplay or not
|
||||||
*/
|
*/
|
||||||
set autoplay(input) {
|
set autoplay(input) {
|
||||||
this.config.autoplay = is.boolean(input) ? input : this.config.autoplay;
|
const toggle = is.boolean(input) ? input : this.config.autoplay;
|
||||||
|
this.config.autoplay = toggle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -953,7 +954,7 @@ class Plyr {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the caption track by index
|
* Set the caption track by index
|
||||||
* @param {Number} input - Caption index
|
* @param {Number} - Caption index
|
||||||
*/
|
*/
|
||||||
set currentTrack(input) {
|
set currentTrack(input) {
|
||||||
captions.set.call(this, input, false);
|
captions.set.call(this, input, false);
|
||||||
@ -971,7 +972,7 @@ class Plyr {
|
|||||||
/**
|
/**
|
||||||
* Set the wanted language for captions
|
* Set the wanted language for captions
|
||||||
* Since tracks can be added later it won't update the actual caption track until there is a matching track
|
* Since tracks can be added later it won't update the actual caption track until there is a matching track
|
||||||
* @param {String} input - Two character ISO language code (e.g. EN, FR, PT, etc)
|
* @param {String} - Two character ISO language code (e.g. EN, FR, PT, etc)
|
||||||
*/
|
*/
|
||||||
set language(input) {
|
set language(input) {
|
||||||
captions.setLanguage.call(this, input, false);
|
captions.setLanguage.call(this, input, false);
|
||||||
@ -1032,7 +1033,7 @@ class Plyr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the preview thumbnails for the current source
|
* Sets the preview thubmnails for the current source
|
||||||
*/
|
*/
|
||||||
setPreviewThumbnails(thumbnailSource) {
|
setPreviewThumbnails(thumbnailSource) {
|
||||||
if (this.previewThumbnails && this.previewThumbnails.loaded) {
|
if (this.previewThumbnails && this.previewThumbnails.loaded) {
|
||||||
@ -1239,9 +1240,10 @@ class Plyr {
|
|||||||
* Check for support
|
* Check for support
|
||||||
* @param {String} type - Player type (audio/video)
|
* @param {String} type - Player type (audio/video)
|
||||||
* @param {String} provider - Provider (html5/youtube/vimeo)
|
* @param {String} provider - Provider (html5/youtube/vimeo)
|
||||||
|
* @param {Boolean} inline - Where player has `playsinline` sttribute
|
||||||
*/
|
*/
|
||||||
static supported(type, provider) {
|
static supported(type, provider, inline) {
|
||||||
return support.check(type, provider);
|
return support.check(type, provider, inline);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr Polyfilled Build
|
// Plyr Polyfilled Build
|
||||||
// plyr.js v3.7.8
|
// plyr.js v3.7.0
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
@ -24,9 +24,10 @@ const support = {
|
|||||||
|
|
||||||
// Check for support
|
// Check for support
|
||||||
// Basic functionality vs full UI
|
// Basic functionality vs full UI
|
||||||
check(type, provider) {
|
check(type, provider, playsinline) {
|
||||||
|
const canPlayInline = browser.isIPhone && playsinline && support.playsinline;
|
||||||
const api = support[type] || provider !== 'html5';
|
const api = support[type] || provider !== 'html5';
|
||||||
const ui = api && support.rangeInput;
|
const ui = api && support.rangeInput && (type !== 'video' || !browser.isIPhone || canPlayInline);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
api,
|
api,
|
||||||
@ -37,9 +38,6 @@ const support = {
|
|||||||
// Picture-in-picture support
|
// Picture-in-picture support
|
||||||
// Safari & Chrome only currently
|
// Safari & Chrome only currently
|
||||||
pip: (() => {
|
pip: (() => {
|
||||||
// While iPhone's support picture-in-picture for some apps, seemingly Safari isn't one of them
|
|
||||||
// It will throw the following error when trying to enter picture-in-picture
|
|
||||||
// `NotSupportedError: The Picture-in-Picture mode is not supported.`
|
|
||||||
if (browser.isIPhone) {
|
if (browser.isIPhone) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import captions from './captions';
|
import captions from './captions';
|
||||||
import controls from './controls';
|
import controls from './controls';
|
||||||
import support from './support';
|
import support from './support';
|
||||||
|
import browser from './utils/browser';
|
||||||
import { getElement, toggleClass } from './utils/elements';
|
import { getElement, toggleClass } from './utils/elements';
|
||||||
import { ready, triggerEvent } from './utils/events';
|
import { ready, triggerEvent } from './utils/events';
|
||||||
import i18n from './utils/i18n';
|
import i18n from './utils/i18n';
|
||||||
@ -97,6 +98,9 @@ const ui = {
|
|||||||
// Check for airplay support
|
// Check for airplay support
|
||||||
toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5);
|
toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5);
|
||||||
|
|
||||||
|
// Add iOS class
|
||||||
|
toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
|
||||||
|
|
||||||
// Add touch class
|
// Add touch class
|
||||||
toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
|
toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);
|
||||||
|
|
||||||
|
@ -3,19 +3,14 @@
|
|||||||
// Unfortunately, due to mixed support, UA sniffing is required
|
// Unfortunately, due to mixed support, UA sniffing is required
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
const isIE = Boolean(window.document.documentMode);
|
const browser = {
|
||||||
const isEdge = /Edge/g.test(navigator.userAgent);
|
isIE: Boolean(window.document.documentMode),
|
||||||
const isWebKit = 'WebkitAppearance' in document.documentElement.style && !/Edge/g.test(navigator.userAgent);
|
isEdge: window.navigator.userAgent.includes('Edge'),
|
||||||
const isIPhone = /iPhone|iPod/gi.test(navigator.userAgent) && navigator.maxTouchPoints > 1;
|
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
|
||||||
// navigator.platform may be deprecated but this check is still required
|
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
|
||||||
const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
|
isIos:
|
||||||
const isIos = /iPad|iPhone|iPod/gi.test(navigator.userAgent) && navigator.maxTouchPoints > 1;
|
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) ||
|
||||||
|
/(iPad|iPhone|iPod)/gi.test(navigator.platform),
|
||||||
export default {
|
|
||||||
isIE,
|
|
||||||
isEdge,
|
|
||||||
isWebKit,
|
|
||||||
isIPhone,
|
|
||||||
isIPadOS,
|
|
||||||
isIos,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default browser;
|
||||||
|
@ -37,7 +37,9 @@ export function wrap(elements, wrapper) {
|
|||||||
|
|
||||||
// Set attributes
|
// Set attributes
|
||||||
export function setAttributes(element, attributes) {
|
export function setAttributes(element, attributes) {
|
||||||
if (!is.element(element) || is.empty(attributes)) return;
|
if (!is.element(element) || is.empty(attributes)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Assume null and undefined attributes should be left out,
|
// Assume null and undefined attributes should be left out,
|
||||||
// Setting them would otherwise convert them to "null" and "undefined"
|
// Setting them would otherwise convert them to "null" and "undefined"
|
||||||
@ -65,16 +67,20 @@ export function createElement(type, attributes, text) {
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert an element after another
|
// Inaert an element after another
|
||||||
export function insertAfter(element, target) {
|
export function insertAfter(element, target) {
|
||||||
if (!is.element(element) || !is.element(target)) return;
|
if (!is.element(element) || !is.element(target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
target.parentNode.insertBefore(element, target.nextSibling);
|
target.parentNode.insertBefore(element, target.nextSibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert a DocumentFragment
|
// Insert a DocumentFragment
|
||||||
export function insertElement(type, parent, attributes, text) {
|
export function insertElement(type, parent, attributes, text) {
|
||||||
if (!is.element(parent)) return;
|
if (!is.element(parent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
parent.appendChild(createElement(type, attributes, text));
|
parent.appendChild(createElement(type, attributes, text));
|
||||||
}
|
}
|
||||||
@ -95,7 +101,9 @@ export function removeElement(element) {
|
|||||||
|
|
||||||
// Remove all child elements
|
// Remove all child elements
|
||||||
export function emptyElement(element) {
|
export function emptyElement(element) {
|
||||||
if (!is.element(element)) return;
|
if (!is.element(element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let { length } = element.childNodes;
|
let { length } = element.childNodes;
|
||||||
|
|
||||||
@ -107,7 +115,9 @@ export function emptyElement(element) {
|
|||||||
|
|
||||||
// Replace element
|
// Replace element
|
||||||
export function replaceElement(newChild, oldChild) {
|
export function replaceElement(newChild, oldChild) {
|
||||||
if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) return null;
|
if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
oldChild.parentNode.replaceChild(newChild, oldChild);
|
oldChild.parentNode.replaceChild(newChild, oldChild);
|
||||||
|
|
||||||
@ -121,7 +131,9 @@ export function getAttributesFromSelector(sel, existingAttributes) {
|
|||||||
// '#test' to { id: 'test' }
|
// '#test' to { id: 'test' }
|
||||||
// '[data-test="test"]' to { 'data-test': 'test' }
|
// '[data-test="test"]' to { 'data-test': 'test' }
|
||||||
|
|
||||||
if (!is.string(sel) || is.empty(sel)) return {};
|
if (!is.string(sel) || is.empty(sel)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const attributes = {};
|
const attributes = {};
|
||||||
const existing = extend({}, existingAttributes);
|
const existing = extend({}, existingAttributes);
|
||||||
@ -169,7 +181,9 @@ export function getAttributesFromSelector(sel, existingAttributes) {
|
|||||||
|
|
||||||
// Toggle hidden
|
// Toggle hidden
|
||||||
export function toggleHidden(element, hidden) {
|
export function toggleHidden(element, hidden) {
|
||||||
if (!is.element(element)) return;
|
if (!is.element(element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let hide = hidden;
|
let hide = hidden;
|
||||||
|
|
||||||
@ -254,9 +268,16 @@ export function getElement(selector) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set focus and tab focus class
|
// Set focus and tab focus class
|
||||||
export function setFocus(element = null, focusVisible = false) {
|
export function setFocus(element = null, tabFocus = false) {
|
||||||
if (!is.element(element)) return;
|
if (!is.element(element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Set regular focus
|
// Set regular focus
|
||||||
element.focus({ preventScroll: true, focusVisible });
|
element.focus({ preventScroll: true });
|
||||||
|
|
||||||
|
// If we want to mimic keyboard focus via tab
|
||||||
|
if (tabFocus) {
|
||||||
|
toggleClass(element, this.config.classNames.tabFocus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ const isObject = (input) => getConstructor(input) === Object;
|
|||||||
const isNumber = (input) => getConstructor(input) === Number && !Number.isNaN(input);
|
const isNumber = (input) => getConstructor(input) === Number && !Number.isNaN(input);
|
||||||
const isString = (input) => getConstructor(input) === String;
|
const isString = (input) => getConstructor(input) === String;
|
||||||
const isBoolean = (input) => getConstructor(input) === Boolean;
|
const isBoolean = (input) => getConstructor(input) === Boolean;
|
||||||
const isFunction = (input) => typeof input === 'function';
|
const isFunction = (input) => getConstructor(input) === Function;
|
||||||
const isArray = (input) => Array.isArray(input);
|
const isArray = (input) => Array.isArray(input);
|
||||||
const isWeakMap = (input) => instanceOf(input, WeakMap);
|
const isWeakMap = (input) => instanceOf(input, WeakMap);
|
||||||
const isNodeList = (input) => instanceOf(input, NodeList);
|
const isNodeList = (input) => instanceOf(input, NodeList);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* @param {Number} input
|
* @param {Number} input
|
||||||
* @param {Number} min The lower boundary of the output range
|
* @param {Number} min The lower boundary of the output range
|
||||||
* @param {Number} max The upper boundary of the output range
|
* @param {Number} max The upper boundary of the output range
|
||||||
* @returns A number within the bounds of min and max
|
* @returns A number in the range [min, max]
|
||||||
* @type Number
|
* @type Number
|
||||||
*/
|
*/
|
||||||
export function clamp(input = 0, min = 0, max = 255) {
|
export function clamp(input = 0, min = 0, max = 255) {
|
||||||
|
@ -11,9 +11,11 @@ export function generateId(prefix) {
|
|||||||
|
|
||||||
// Format string
|
// Format string
|
||||||
export function format(input, ...args) {
|
export function format(input, ...args) {
|
||||||
if (is.empty(input)) return input;
|
if (is.empty(input)) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
return input.toString().replace(/{(\d+)}/g, (_, i) => args[i].toString());
|
return input.toString().replace(/{(\d+)}/g, (match, i) => args[i].toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get percentage
|
// Get percentage
|
||||||
@ -25,7 +27,7 @@ export function getPercentage(current, max) {
|
|||||||
return ((current / max) * 100).toFixed(2);
|
return ((current / max) * 100).toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace all occurrences of a string in a string
|
// Replace all occurances of a string in a string
|
||||||
export const replaceAll = (input = '', find = '', replace = '') =>
|
export const replaceAll = (input = '', find = '', replace = '') =>
|
||||||
input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
|
|
||||||
|
@ -28,8 +28,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tab focus
|
// Tab focus
|
||||||
&:focus-visible {
|
&.plyr__tab-focus {
|
||||||
@include plyr-focus-visible;
|
@include plyr-tab-focus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
&__container {
|
&__container {
|
||||||
animation: plyr-popup 0.2s ease;
|
animation: plyr-popup 0.2s ease;
|
||||||
background: $plyr-menu-background;
|
background: $plyr-menu-background;
|
||||||
border-radius: $plyr-menu-radius;
|
border-radius: 4px;
|
||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
box-shadow: $plyr-menu-shadow;
|
box-shadow: $plyr-menu-shadow;
|
||||||
color: $plyr-menu-color;
|
color: $plyr-menu-color;
|
||||||
@ -100,7 +100,7 @@
|
|||||||
right: calc((#{$plyr-control-padding} * 1.5) - #{$plyr-menu-item-arrow-size});
|
right: calc((#{$plyr-control-padding} * 1.5) - #{$plyr-menu-item-arrow-size});
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus-visible::after,
|
&.plyr__tab-focus::after,
|
||||||
&:hover::after {
|
&:hover::after {
|
||||||
border-left-color: currentColor;
|
border-left-color: currentColor;
|
||||||
}
|
}
|
||||||
@ -132,7 +132,7 @@
|
|||||||
top: 100%;
|
top: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus-visible::after,
|
&.plyr__tab-focus::after,
|
||||||
&:hover::after {
|
&:hover::after {
|
||||||
border-right-color: currentColor;
|
border-right-color: currentColor;
|
||||||
}
|
}
|
||||||
@ -181,7 +181,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus-visible::before,
|
&.plyr__tab-focus::before,
|
||||||
&:hover::before {
|
&:hover::before {
|
||||||
background: rgba($plyr-color-gray-900, 0.1);
|
background: rgba($plyr-color-gray-900, 0.1);
|
||||||
}
|
}
|
||||||
@ -192,7 +192,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: calc((#{$plyr-control-padding} - 2px) * -1);
|
margin-right: calc((#{$plyr-control-padding} - 2) * -1);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-left: calc(#{$plyr-control-padding} * 3.5);
|
padding-left: calc(#{$plyr-control-padding} * 3.5);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -27,6 +27,7 @@ $plyr-progress-offset: $plyr-range-thumb-height;
|
|||||||
left: 0;
|
left: 0;
|
||||||
max-width: 120px;
|
max-width: 120px;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
white-space: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,17 +83,17 @@
|
|||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus-visible {
|
&.plyr__tab-focus {
|
||||||
&::-webkit-slider-runnable-track {
|
&::-webkit-slider-runnable-track {
|
||||||
@include plyr-focus-visible;
|
@include plyr-tab-focus;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-moz-range-track {
|
&::-moz-range-track {
|
||||||
@include plyr-focus-visible;
|
@include plyr-tab-focus;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-ms-track {
|
&::-ms-track {
|
||||||
@include plyr-focus-visible;
|
@include plyr-tab-focus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
// Displaying
|
// Displaying
|
||||||
.plyr .plyr__control:hover .plyr__tooltip,
|
.plyr .plyr__control:hover .plyr__tooltip,
|
||||||
.plyr .plyr__control:focus-visible .plyr__tooltip,
|
.plyr .plyr__control.plyr__tab-focus .plyr__tooltip,
|
||||||
.plyr__tooltip--visible {
|
.plyr__tooltip--visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translate(-50%, 0) scale(1);
|
transform: translate(-50%, 0) scale(1);
|
||||||
@ -82,7 +82,7 @@
|
|||||||
.plyr__controls > .plyr__control:first-child + .plyr__control,
|
.plyr__controls > .plyr__control:first-child + .plyr__control,
|
||||||
.plyr__controls > .plyr__control:last-child {
|
.plyr__controls > .plyr__control:last-child {
|
||||||
&:hover .plyr__tooltip,
|
&:hover .plyr__tooltip,
|
||||||
&:focus-visible .plyr__tooltip,
|
&.plyr__tab-focus .plyr__tooltip,
|
||||||
.plyr__tooltip--visible {
|
.plyr__tooltip--visible {
|
||||||
transform: translate(0, 0) scale(1);
|
transform: translate(0, 0) scale(1);
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,21 @@
|
|||||||
.plyr__volume {
|
.plyr__volume {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
max-width: 110px;
|
||||||
|
min-width: 80px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: 20%;
|
||||||
|
|
||||||
input[type='range'] {
|
input[type='range'] {
|
||||||
margin-left: calc(#{$plyr-control-spacing} / 2);
|
margin-left: calc(#{$plyr-control-spacing} / 2);
|
||||||
margin-right: calc(#{$plyr-control-spacing} / 2);
|
margin-right: calc(#{$plyr-control-spacing} / 2);
|
||||||
max-width: 90px;
|
|
||||||
min-width: 60px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto size on iOS as there's no slider
|
||||||
|
.plyr--is-ios .plyr__volume {
|
||||||
|
min-width: 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
// Nicer focus styles
|
// Nicer focus styles
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@mixin plyr-focus-visible($color: $plyr-focus-visible-color) {
|
@mixin plyr-tab-focus($color: $plyr-tab-focus-color) {
|
||||||
outline: 2px dashed $color;
|
outline: $color dotted 3px;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
bottom: calc(#{$plyr-preview-arrow-size} * -1);
|
bottom: calc(#{$plyr-preview-arrow-size} * -1);
|
||||||
content: '';
|
content: '';
|
||||||
height: 0;
|
height: 0;
|
||||||
left: calc(50% + var(--preview-arrow-offset));
|
left: 50%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
width: 0;
|
width: 0;
|
||||||
@ -46,27 +46,15 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
|
||||||
img,
|
img {
|
||||||
&::after {
|
height: 100%; // Non sprite images are 100%. Sprites will have their size applied by JavaScript
|
||||||
height: 100%;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
|
max-height: none;
|
||||||
|
max-width: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
|
||||||
border-radius: inherit;
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(#000, 15%);
|
|
||||||
content: '';
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
// Non sprite images are 100%. Sprites will have their size applied by JavaScript
|
|
||||||
max-height: none;
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek time text
|
// Seek time text
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
$plyr-preview-padding: $plyr-tooltip-padding !default;
|
$plyr-preview-padding: $plyr-tooltip-padding !default;
|
||||||
$plyr-preview-background: $plyr-tooltip-background !default;
|
$plyr-preview-background: $plyr-tooltip-background !default;
|
||||||
$plyr-preview-radius: $plyr-menu-radius !default;
|
$plyr-preview-radius: 6px !default;
|
||||||
$plyr-preview-shadow: $plyr-tooltip-shadow !default;
|
$plyr-preview-shadow: $plyr-tooltip-shadow !default;
|
||||||
$plyr-preview-arrow-size: $plyr-tooltip-arrow-size !default;
|
$plyr-preview-arrow-size: $plyr-tooltip-arrow-size !default;
|
||||||
$plyr-preview-image-background: $plyr-color-gray-200 !default;
|
$plyr-preview-image-background: $plyr-color-gray-200 !default;
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
@charset "UTF-8";
|
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr styles
|
// Plyr styles
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// TODO: Review use of BEM classnames
|
// TODO: Review use of BEM classnames
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
@charset 'UTF-8';
|
||||||
@import 'lib/css-vars';
|
@import 'lib/css-vars';
|
||||||
|
|
||||||
$css-vars-use-native: true;
|
$css-vars-use-native: true;
|
||||||
|
@ -6,7 +6,7 @@ $plyr-control-icon-size: var(--plyr-control-icon-size, 18px) !default;
|
|||||||
$plyr-control-spacing: var(--plyr-control-spacing, 10px) !default;
|
$plyr-control-spacing: var(--plyr-control-spacing, 10px) !default;
|
||||||
$plyr-control-padding: calc(#{$plyr-control-spacing} * 0.7);
|
$plyr-control-padding: calc(#{$plyr-control-spacing} * 0.7);
|
||||||
$plyr-control-padding: var(--plyr-control-padding, $plyr-control-padding) !default;
|
$plyr-control-padding: var(--plyr-control-padding, $plyr-control-padding) !default;
|
||||||
$plyr-control-radius: var(--plyr-control-radius, 4px) !default;
|
$plyr-control-radius: var(--plyr-control-radius, 3px) !default;
|
||||||
$plyr-control-toggle-checked-background: var(
|
$plyr-control-toggle-checked-background: var(
|
||||||
--plyr-control-toggle-checked-background,
|
--plyr-control-toggle-checked-background,
|
||||||
var(--plyr-color-main, $plyr-color-main)
|
var(--plyr-color-main, $plyr-color-main)
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
// Cosmetic
|
// Cosmetic
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$plyr-focus-visible-color: var(--plyr-focus-visible-color, var(--plyr-color-main, $plyr-color-main)) !default;
|
$plyr-tab-focus-color: var(--plyr-tab-focus-color, var(--plyr-color-main, $plyr-color-main)) !default;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$plyr-menu-background: var(--plyr-menu-background, rgba(#fff, 0.9)) !default;
|
$plyr-menu-background: var(--plyr-menu-background, rgba(#fff, 0.9)) !default;
|
||||||
$plyr-menu-radius: var(--plyr-menu-radius, 8px) !default;
|
$plyr-menu-radius: var(--plyr-menu-radius, 4px) !default;
|
||||||
$plyr-menu-color: var(--plyr-menu-color, $plyr-color-gray-700) !default;
|
$plyr-menu-color: var(--plyr-menu-color, $plyr-color-gray-700) !default;
|
||||||
$plyr-menu-shadow: var(--plyr-menu-shadow, 0 1px 2px rgba(#000, 0.15)) !default;
|
$plyr-menu-shadow: var(--plyr-menu-shadow, 0 1px 2px rgba(#000, 0.15)) !default;
|
||||||
$plyr-menu-arrow-size: var(--plyr-menu-arrow-size, 4px) !default;
|
$plyr-menu-arrow-size: var(--plyr-menu-arrow-size, 4px) !default;
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
// Tooltips
|
// Tooltips
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$plyr-tooltip-background: var(--plyr-tooltip-background, #fff) !default;
|
$plyr-tooltip-background: var(--plyr-tooltip-background, rgba(#fff, 0.9)) !default;
|
||||||
$plyr-tooltip-color: var(--plyr-tooltip-color, $plyr-color-gray-700) !default;
|
$plyr-tooltip-color: var(--plyr-tooltip-color, $plyr-color-gray-700) !default;
|
||||||
$plyr-tooltip-padding: calc(#{$plyr-control-spacing} / 2);
|
$plyr-tooltip-padding: calc(#{$plyr-control-spacing} / 2);
|
||||||
$plyr-tooltip-padding: var(--plyr-tooltip-padding, $plyr-tooltip-padding) !default;
|
$plyr-tooltip-padding: var(--plyr-tooltip-padding, $plyr-tooltip-padding) !default;
|
||||||
$plyr-tooltip-arrow-size: var(--plyr-tooltip-arrow-size, 4px) !default;
|
$plyr-tooltip-arrow-size: var(--plyr-tooltip-arrow-size, 4px) !default;
|
||||||
$plyr-tooltip-radius: var(--plyr-tooltip-radius, 5px) !default;
|
$plyr-tooltip-radius: var(--plyr-tooltip-radius, 3px) !default;
|
||||||
$plyr-tooltip-shadow: var(--plyr-tooltip-shadow, 0 1px 2px rgba(0, 0, 0, 0.15)) !default;
|
$plyr-tooltip-shadow: var(--plyr-tooltip-shadow, 0 1px 2px rgba(0, 0, 0, 0.15)) !default;
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
@include plyr-fullscreen-active;
|
@include plyr-fullscreen-active;
|
||||||
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
display: block;
|
||||||
left: 0;
|
left: 0;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
// Control elements
|
// Control elements
|
||||||
.plyr--audio .plyr__control {
|
.plyr--audio .plyr__control {
|
||||||
&:focus-visible,
|
&.plyr__tab-focus,
|
||||||
&:hover,
|
&:hover,
|
||||||
&[aria-expanded='true'] {
|
&[aria-expanded='true'] {
|
||||||
background: $plyr-audio-control-background-hover;
|
background: $plyr-audio-control-background-hover;
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
// Container
|
// Container
|
||||||
.plyr--video {
|
.plyr--video {
|
||||||
|
background: var(--plyr-video-background, $plyr-video-background);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&.plyr--menu-open {
|
&.plyr--menu-open {
|
||||||
@ -15,7 +16,6 @@
|
|||||||
|
|
||||||
.plyr__video-wrapper {
|
.plyr__video-wrapper {
|
||||||
background: var(--plyr-video-background, $plyr-video-background);
|
background: var(--plyr-video-background, $plyr-video-background);
|
||||||
border-radius: inherit;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -87,7 +87,8 @@ $embed-padding: (math.div(100, 16) * 9);
|
|||||||
|
|
||||||
// Control elements
|
// Control elements
|
||||||
.plyr--video .plyr__control {
|
.plyr--video .plyr__control {
|
||||||
&:focus-visible,
|
// Hover and tab focus
|
||||||
|
&.plyr__tab-focus,
|
||||||
&:hover,
|
&:hover,
|
||||||
&[aria-expanded='true'] {
|
&[aria-expanded='true'] {
|
||||||
background: $plyr-video-control-background-hover;
|
background: $plyr-video-control-background-hover;
|
||||||
|
@ -118,7 +118,7 @@ Object.entries(build.js).forEach(([filename, entry]) => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
plugins: ['@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-optional-chaining'],
|
plugins: ['@babel/plugin-proposal-class-properties'],
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
exclude: [/\/core-js\//],
|
exclude: [/\/core-js\//],
|
||||||
}),
|
}),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user