Fix merge conflicts
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
indent_size = 4
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
PLEASE USE OUR SPECIFIC ISSUE TEMPLATES for bug reports, features and improvement suggestions.
|
PLEASE USE OUR SPECIFIC ISSUE TEMPLATES for bug reports, features and improvement suggestions.
|
||||||
|
|
||||||
Our issue tracker is not for support questions. If you need help, follow our support instructions: https://github.com/sampotts/plyr/blob/master/contributing.md#support
|
Our issue tracker is not for support questions. If you need help, follow our support instructions: https://github.com/sampotts/plyr/blob/master/CONTRIBUTING.md#support
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
tasks:
|
tasks:
|
||||||
- init: npm install && npm i gulp -g
|
- before: npm install && npm i gulp -g
|
||||||
command: gulp
|
command: gulp
|
||||||
ports:
|
ports:
|
||||||
- port: 3000
|
- port: 3000
|
||||||
|
|||||||
+5
-5
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"tabWidth": 4,
|
"tabWidth": 2,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"printWidth": 120
|
"printWidth": 120
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-22
@@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"plugins": ["stylelint-selector-bem-pattern", "stylelint-scss"],
|
"plugins": ["stylelint-selector-bem-pattern", "stylelint-scss"],
|
||||||
"extends": ["stylelint-config-recommended", "stylelint-config-sass-guidelines", "stylelint-config-prettier"],
|
"extends": ["stylelint-config-recommended", "stylelint-config-sass-guidelines", "stylelint-config-prettier"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"selector-class-pattern": null,
|
"selector-class-pattern": null,
|
||||||
"selector-no-qualifying-type": [
|
"selector-no-qualifying-type": [
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
"ignore": ["attribute", "class"]
|
"ignore": ["attribute", "class"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"string-no-newline": null,
|
"string-no-newline": null,
|
||||||
"indentation": 4,
|
"indentation": 2,
|
||||||
"string-quotes": "single",
|
"string-quotes": "single",
|
||||||
"max-nesting-depth": 2,
|
"max-nesting-depth": 2,
|
||||||
"plugin/selector-bem-pattern": {
|
"plugin/selector-bem-pattern": {
|
||||||
"preset": "bem",
|
"preset": "bem",
|
||||||
"componentName": "(([a-z0-9]+(?!-$)-?)+)",
|
"componentName": "(([a-z0-9]+(?!-$)-?)+)",
|
||||||
"componentSelectors": {
|
"componentSelectors": {
|
||||||
"initial": "\\.{componentName}(((__|--)(([a-z0-9\\[\\]'=]+(?!-$)-?)+))+)?$"
|
"initial": "\\.{componentName}(((__|--)(([a-z0-9\\[\\]'=]+(?!-$)-?)+))+)?$"
|
||||||
},
|
},
|
||||||
"ignoreSelectors": [".*\\.has-.*", ".*\\.is-.*"]
|
"ignoreSelectors": [".*\\.has-.*", ".*\\.is-.*"]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1183
File diff suppressed because it is too large
Load Diff
@@ -7,15 +7,17 @@ We welcome bug reports, feature requests and pull requests. If you want to help
|
|||||||
Before asking questions, read our [documentation](https://github.com/sampotts/plyr) and [FAQ](https://github.com/sampotts/plyr/wiki/FAQ).
|
Before asking questions, read our [documentation](https://github.com/sampotts/plyr) and [FAQ](https://github.com/sampotts/plyr/wiki/FAQ).
|
||||||
|
|
||||||
If these doesn't answer your question
|
If these doesn't answer your question
|
||||||
* Use [Stack Overflow](https://stackoverflow.com/) for questions that doesn't directly involve Plyr. This includes for example how to use Javascript, CSS or HTML5 media in general, and how to use other frameworks, libraries and technology.
|
|
||||||
* Use [our Slack](https://bit.ly/plyr-chat) if you need help using Plyr or have questions about Plyr.
|
- Use [Stack Overflow](https://stackoverflow.com/) for questions that doesn't directly involve Plyr. This includes for example how to use Javascript, CSS or HTML5 media in general, and how to use other frameworks, libraries and technology.
|
||||||
|
- Use [our Slack](https://bit.ly/plyr-chat) if you need help using Plyr or have questions about Plyr.
|
||||||
|
|
||||||
## Commenting
|
## Commenting
|
||||||
|
|
||||||
When commenting, keep a civil tone and stay on topic. Don't ask for [support](#support), or post "+1" or "I agree" type of comments. Use the emojis instead.
|
When commenting, keep a civil tone and stay on topic. Don't ask for [support](#support), or post "+1" or "I agree" type of comments. Use the emojis instead.
|
||||||
|
|
||||||
Asking for the status on issues is discouraged. Unless someone has explicitly said in an issue that it's work in progress, most likely that means no one is working on it. We have a lot to do, and it may not be a top priority for us.
|
Asking for the status on issues is discouraged. Unless someone has explicitly said in an issue that it's work in progress, most likely that means no one is working on it. We have a lot to do, and it may not be a top priority for us.
|
||||||
|
|
||||||
We *may* moderate discussions. We do this to avoid threads being "hijacked", to avoid confusion in case the content is misleading or outdated, and to avoid bothering people with github notifications.
|
We _may_ moderate discussions. We do this to avoid threads being "hijacked", to avoid confusion in case the content is misleading or outdated, and to avoid bothering people with github notifications.
|
||||||
|
|
||||||
## Creating issues
|
## Creating issues
|
||||||
|
|
||||||
@@ -23,24 +25,30 @@ Please follow the instructions in our issue templates. Don't use github issues t
|
|||||||
|
|
||||||
## Contributing features and documentation
|
## Contributing features and documentation
|
||||||
|
|
||||||
* If you want to add a feature or make critical changes, you may want to ensure that this is something we also want (so you don't waste your time). Ask us about this in the corresponding issue if there is one, or on [our Slack](https://bit.ly/plyr-chat) otherwise.
|
- If you want to add a feature or make critical changes, you may want to ensure that this is something we also want (so you don't waste your time). Ask us about this in the corresponding issue if there is one, or on [our Slack](https://bit.ly/plyr-chat) otherwise.
|
||||||
|
|
||||||
* Fork Plyr, and create a new branch in your fork, based on the **develop** branch
|
- Fork Plyr, and create a new branch in your fork, based on the **develop** branch
|
||||||
|
|
||||||
* To test locally, you can use the demo site. First make sure you have installed the dependencies with `npm install` or `yarn`. Run `gulp` to build and it will run a local web server for development and watch for any changes.
|
- To test locally, you can use the demo site. First make sure you have installed the dependencies with `npm install` or `yarn`. Run `gulp` to build and it will run a local web server for development and watch for any changes.
|
||||||
|
|
||||||
### Online one-click setup
|
### Online one-click setup for contributing
|
||||||
|
|
||||||
Alternatively can also use Gitpod, a free online Visual Studio Code-like IDE. With a single click it will automatically launch a ready-to-code workspace with all the dependencies pre-installed, gulp watching for changes and the web server running, so that you can start coding straightaway.
|
You can use Gitpod (a free online VS Code-like IDE) for contributing. With a single click it will launch a workspace and automatically:
|
||||||
|
|
||||||
|
- clone the plyr repo.
|
||||||
|
- install the dependencies.
|
||||||
|
- run `gulp` to the start the server.
|
||||||
|
|
||||||
|
So that you can start straight away.
|
||||||
|
|
||||||
[](https://gitpod.io/from-referrer/)
|
[](https://gitpod.io/from-referrer/)
|
||||||
|
|
||||||
* Develop and test your modifications.
|
- Develop and test your modifications.
|
||||||
|
|
||||||
* Preferably commit your changes as independent logical chunks, with meaningful messages. Make sure you do not commit unnecessary files or changes, such as the build output, or logging and breakpoints you added for testing.
|
- Preferably commit your changes as independent logical chunks, with meaningful messages. Make sure you do not commit unnecessary files or changes, such as the build output, or logging and breakpoints you added for testing.
|
||||||
|
|
||||||
* If your modifications changes the documented behavior or add new features, document these changes in readme.md.
|
- If your modifications changes the documented behavior or add new features, document these changes in [README.md](README.md).
|
||||||
|
|
||||||
* When finished, push the changes to your GitHub repository and send a pull request to **develop**. Describe what your PR does.
|
- When finished, push the changes to your GitHub repository and send a pull request to **develop**. Describe what your PR does.
|
||||||
|
|
||||||
* If the Travis build fails, or if you get a code review with change requests, you can fix these by pushing new or rebased commits to the branch.
|
- If the Travis build fails, or if you get a code review with change requests, you can fix these by pushing new or rebased commits to the branch.
|
||||||
+275
-179
@@ -8,26 +8,26 @@ Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vi
|
|||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- 📼 **HTML Video & Audio, YouTube & Vimeo** - support for the major formats
|
- 📼 **HTML Video & Audio, YouTube & Vimeo** - support for the major formats
|
||||||
- 💪 **Accessible** - full support for VTT captions and screen readers
|
- 💪 **Accessible** - full support for VTT captions and screen readers
|
||||||
- 🔧 **[Customizable](#html)** - make the player look how you want with the markup you want
|
- 🔧 **[Customizable](#html)** - make the player look how you want with the markup you want
|
||||||
- 😎 **Clean HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
|
- 😎 **Clean HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
|
||||||
`<span>` or `<a href="#">` button hacks
|
`<span>` or `<a href="#">` button hacks
|
||||||
- 📱 **Responsive** - works with any screen size
|
- 📱 **Responsive** - works with any screen size
|
||||||
- 💵 **[Monetization](#ads)** - make money from your videos
|
- 💵 **[Monetization](#ads)** - make money from your videos
|
||||||
- 📹 **[Streaming](#demos)** - support for hls.js, Shaka and dash.js streaming playback
|
- 📹 **[Streaming](#demos)** - support for hls.js, Shaka and dash.js streaming playback
|
||||||
- 🎛 **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
- 🎛 **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
||||||
- 🎤 **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
|
- 🎤 **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
|
||||||
- 🔎 **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes
|
- 🔎 **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes
|
||||||
- ⌨️ **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
|
- ⌨️ **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
|
||||||
- 🖥 **Picture-in-Picture** - supports picture-in-picture mode
|
- 🖥 **Picture-in-Picture** - supports picture-in-picture mode
|
||||||
- 📱 **Playsinline** - supports the `playsinline` attribute
|
- 📱 **Playsinline** - supports the `playsinline` attribute
|
||||||
- 🏎 **Speed controls** - adjust speed on the fly
|
- 🏎 **Speed controls** - adjust speed on the fly
|
||||||
- 📖 **Multiple captions** - support for multiple caption tracks
|
- 📖 **Multiple captions** - support for multiple caption tracks
|
||||||
- 🌎 **i18n support** - support for internationalization of controls
|
- 🌎 **i18n support** - support for internationalization of controls
|
||||||
- 👌 **[Preview thumbnails](#preview-thumbnails)** - support for displaying preview thumbnails
|
- 👌 **[Preview thumbnails](#preview-thumbnails)** - support for displaying preview thumbnails
|
||||||
- 🤟 **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
|
||||||
|
|
||||||
@@ -42,21 +42,23 @@ Plyr extends upon the standard [HTML5 media element](https://developer.mozilla.o
|
|||||||
### HTML5 Video
|
### HTML5 Video
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<video poster="/path/to/poster.jpg" id="player" playsinline controls>
|
<video id="player" playsinline controls data-poster="/path/to/poster.jpg">
|
||||||
<source src="/path/to/video.mp4" type="video/mp4" />
|
<source src="/path/to/video.mp4" type="video/mp4" />
|
||||||
<source src="/path/to/video.webm" type="video/webm" />
|
<source src="/path/to/video.webm" type="video/webm" />
|
||||||
|
|
||||||
<!-- Captions are optional -->
|
<!-- Captions are optional -->
|
||||||
<track kind="captions" label="English captions" src="/path/to/captions.vtt" srclang="en" default />
|
<track kind="captions" label="English captions" src="/path/to/captions.vtt" srclang="en" default />
|
||||||
</video>
|
</video>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Note**: The poster image should be specified using `data-poster`. This is to prevent it [being downloaded twice](https://github.com/sampotts/plyr/issues/1531). If you're sure the image will be cached, you can still use the `poster` attribute for true progressive enhancement.
|
||||||
|
|
||||||
### HTML5 Audio
|
### HTML5 Audio
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<audio id="player" controls>
|
<audio id="player" controls>
|
||||||
<source src="/path/to/audio.mp3" type="audio/mp3" />
|
<source src="/path/to/audio.mp3" type="audio/mp3" />
|
||||||
<source src="/path/to/audio.ogg" type="audio/ogg" />
|
<source src="/path/to/audio.ogg" type="audio/ogg" />
|
||||||
</audio>
|
</audio>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -68,12 +70,12 @@ We recommend [progressive enhancement](https://www.smashingmagazine.com/2009/04/
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<div class="plyr__video-embed" id="player">
|
<div class="plyr__video-embed" id="player">
|
||||||
<iframe
|
<iframe
|
||||||
src="https://www.youtube.com/embed/bTqVqk7FSmY?origin=https://plyr.io&iv_load_policy=3&modestbranding=1&playsinline=1&showinfo=0&rel=0&enablejsapi=1"
|
src="https://www.youtube.com/embed/bTqVqk7FSmY?origin=https://plyr.io&iv_load_policy=3&modestbranding=1&playsinline=1&showinfo=0&rel=0&enablejsapi=1"
|
||||||
allowfullscreen
|
allowfullscreen
|
||||||
allowtransparency
|
allowtransparency
|
||||||
allow="autoplay"
|
allow="autoplay"
|
||||||
></iframe>
|
></iframe>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -93,12 +95,12 @@ Much the same as YouTube above.
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<div class="plyr__video-embed" id="player">
|
<div class="plyr__video-embed" id="player">
|
||||||
<iframe
|
<iframe
|
||||||
src="https://player.vimeo.com/video/76979871?loop=false&byline=false&portrait=false&title=false&speed=true&transparent=0&gesture=media"
|
src="https://player.vimeo.com/video/76979871?loop=false&byline=false&portrait=false&title=false&speed=true&transparent=0&gesture=media"
|
||||||
allowfullscreen
|
allowfullscreen
|
||||||
allowtransparency
|
allowtransparency
|
||||||
allow="autoplay"
|
allow="autoplay"
|
||||||
></iframe>
|
></iframe>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -123,7 +125,7 @@ Alternatively you can include the `plyr.js` script before the closing `</body>`
|
|||||||
```html
|
```html
|
||||||
<script src="path/to/plyr.js"></script>
|
<script src="path/to/plyr.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const player = new Plyr('#player');
|
const player = new Plyr('#player');
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -132,13 +134,13 @@ See [initialising](#initialising) for more information on advanced setups.
|
|||||||
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 seperately 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 seperately 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.5.10/plyr.js"></script>
|
<script src="https://cdn.plyr.io/3.6.1/plyr.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
...or...
|
...or...
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.plyr.io/3.5.10/plyr.polyfilled.js"></script>
|
<script src="https://cdn.plyr.io/3.6.1/plyr.polyfilled.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## CSS
|
## CSS
|
||||||
@@ -152,30 +154,127 @@ Include the `plyr.css` stylsheet into your `<head>`.
|
|||||||
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.10/plyr.css" />
|
<link rel="stylesheet" href="https://cdn.plyr.io/3.6.1/plyr.css" />
|
||||||
```
|
```
|
||||||
|
|
||||||
## SVG Sprite
|
## SVG Sprite
|
||||||
|
|
||||||
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
||||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.5.10/plyr.svg`.
|
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.6.1/plyr.svg`.
|
||||||
|
|
||||||
# Ads
|
# Ads
|
||||||
|
|
||||||
Plyr has partnered up with [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy:
|
Plyr has partnered up with [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy:
|
||||||
|
|
||||||
- [Sign up for a vi.ai account](https://vi.ai/publisher-video-monetization/?aid=plyrio)
|
- [Sign up for a vi.ai account](https://vi.ai/publisher-video-monetization/?aid=plyrio)
|
||||||
- Grab your publisher ID from the code snippet
|
- Grab your publisher ID from the code snippet
|
||||||
- Enable ads in the [config options](#options) and enter your publisher ID
|
- Enable ads in the [config options](#options) and enter your publisher ID
|
||||||
|
|
||||||
Any questions regarding the ads can be sent straight to vi.ai and any issues with rendering raised through GitHub issues.
|
Any questions regarding the ads can be sent straight to vi.ai and any issues with rendering raised through GitHub issues.
|
||||||
|
|
||||||
|
If you do not wish to use Vi, you can set your own `ads.tagUrl` [option](#options).
|
||||||
|
|
||||||
# Advanced
|
# Advanced
|
||||||
|
|
||||||
## SASS
|
## Customizing the CSS
|
||||||
|
|
||||||
You can use `bundle.scss` file included in `/src` as part of your build and change variables to suit your design. The SASS require you to
|
If you want to change any design tokens used for the rendering of the player, you can do so using [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties).
|
||||||
use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you should be already!) as all declarations use the W3C definitions.
|
|
||||||
|
Here's a list of the properties and what they are used for:
|
||||||
|
|
||||||
|
| Name | Description | Default / Fallback |
|
||||||
|
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||||
|
| `--plyr-color-main` | The primary UI color. |  `#00b3ff` |
|
||||||
|
| `--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-text-color` | The text color for badges. |  `#ffffff` |
|
||||||
|
| `--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-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-spacing` | The space between controls (sometimes used in a multiple - e.g. `10px / 2 = 5px`). | `10px` |
|
||||||
|
| `--plyr-control-padding` | The padding inside controls. | `--plyr-control-spacing * 0.7` (`7px`) |
|
||||||
|
| `--plyr-control-radius` | The border radius used on controls. | `3px` |
|
||||||
|
| `--plyr-control-toggle-checked-background` | The background color used for checked menu items. | `--plyr-color-main` |
|
||||||
|
| `--plyr-video-controls-background` | The background for the video controls. | `linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.75))` |
|
||||||
|
| `--plyr-video-control-color` | The text/icon color for video controls. |  `#ffffff` |
|
||||||
|
| `--plyr-video-control-color-hover` | The text/icon color used when video controls are `:hover`, `:focus` and `:focus-visible` (equivalent). |  `#ffffff` |
|
||||||
|
| `--plyr-video-control-background-hover` | The background color used when video controls are `:hover`, `:focus` and `:focus-visible` (equivalent). | `--plyr-color-main` |
|
||||||
|
| `--plyr-audio-controls-background` | The background for the audio controls. |  `#ffffff` |
|
||||||
|
| `--plyr-audio-control-color` | The text/icon color for audio controls. |  `#4a5464` |
|
||||||
|
| `--plyr-audio-control-color-hover` | The text/icon color used when audio controls are `:hover`, `:focus` and `:focus-visible` (equivalent). |  `#ffffff` |
|
||||||
|
| `--plyr-audio-control-background-hover` | The background color used when video controls are `:hover`, `:focus` and `:focus-visible` (equivalent). | `--plyr-color-main` |
|
||||||
|
| `--plyr-menu-background` | The background color for menus. | `rgba(255, 255, 255, 0.9)` |
|
||||||
|
| `--plyr-menu-color` | The text/icon color for menu items. |  `#4a5464` |
|
||||||
|
| `--plyr-menu-shadow` | The shadow used on menus. | `0 1px 2px rgba(0, 0, 0, 0.15)` |
|
||||||
|
| `--plyr-menu-radius` | The border radius on the menu. | `4px` |
|
||||||
|
| `--plyr-menu-arrow-size` | The size of the arrow on the bottom of the menu. | `6px` |
|
||||||
|
| `--plyr-menu-item-arrow-color` | The color of the arrows in the menu. |  `#728197` |
|
||||||
|
| `--plyr-menu-item-arrow-size` | The size of the arrows in the menu. | `4px` |
|
||||||
|
| `--plyr-menu-border-color` | The border color for the bottom of the back button in the top of the sub menu pages. |  `#dcdfe5` |
|
||||||
|
| `--plyr-menu-border-shadow-color` | The shadow below the border of the back button in the top of the sub menu pages. |  `#ffffff` |
|
||||||
|
| `--plyr-progress-loading-size` | The size of the stripes in the loading state in the scrubber. | `25px` |
|
||||||
|
| `--plyr-progress-loading-background` | The background color on the loading state in the scrubber. | `rgba(35, 40, 47, 0.6)` |
|
||||||
|
| `--plyr-video-progress-buffered-background` | The fill color for the buffer indication in the scrubber for video. | `rgba(255, 255, 255, 0.25)` |
|
||||||
|
| `--plyr-audio-progress-buffered-background` | The fill color for the buffer indication in the scrubber for audio. | `rgba(193, 200, 209, 0.6)` |
|
||||||
|
| `--plyr-range-thumb-height` | The height of the scrubber handle/thumb. | `13px` |
|
||||||
|
| `--plyr-range-thumb-background` | The background of the scrubber handle/thumb. |  `#ffffff` |
|
||||||
|
| `--plyr-range-thumb-shadow` | The shadow of the scrubber handle/thumb. | `0 1px 1px rgba(215, 26, 18, 0.15), 0 0 0 1px rgba(215, 26, 18, 0.2)` |
|
||||||
|
| `--plyr-range-thumb-active-shadow-width` | The width of the shadow when the scrubber handle/thumb is `:active` (pressed). | `3px` |
|
||||||
|
| `--plyr-range-track-height` | The height of the scrubber/progress track. | `5px` |
|
||||||
|
| `--plyr-range-fill-background` | The fill color of the scrubber/progress. | `--plyr-color-main` |
|
||||||
|
| `--plyr-video-range-track-background` | The background of the scrubber/progress. | `--plyr-video-progress-buffered-background` |
|
||||||
|
| `--plyr-video-range-thumb-active-shadow-color` | The color of the shadow when the video scrubber handle/thumb is `:active` (pressed). | `rgba(255, 255, 255, 0.5)` |
|
||||||
|
| `--plyr-audio-range-track-background` | The background of the scrubber/progress. | `--plyr-video-progress-buffered-background` |
|
||||||
|
| `--plyr-audio-range-thumb-active-shadow-color` | The color of the shadow when the audio scrubber handle/thumb is `:active` (pressed). | `rgba(215, 26, 18, 0.1)` |
|
||||||
|
| `--plyr-tooltip-background` | The background color for tooltips. | `rgba(255, 255, 255, 0.9)` |
|
||||||
|
| `--plyr-tooltip-color` | The text color for tooltips. |  `#4a5464` |
|
||||||
|
| `--plyr-tooltip-padding` | The padding for tooltips. | `calc(var(--plyr-control-spacing) / 2))` |
|
||||||
|
| `--plyr-tooltip-arrow-size` | The size of the arrow under tooltips. | `4px` |
|
||||||
|
| `--plyr-tooltip-radius` | The border radius on tooltips. | `3px` |
|
||||||
|
| `--plyr-tooltip-shadow` | The shadow on tooltips. | `0 1px 2px rgba(0, 0, 0, 0.15)` |
|
||||||
|
| `--plyr-font-family` | The font family used in the player. | |
|
||||||
|
| `--plyr-font-size-base` | The base font size. Mainly used for captions. | `15px` |
|
||||||
|
| `--plyr-font-size-small` | The smaller font size. Mainly used for captions. | `13px` |
|
||||||
|
| `--plyr-font-size-large` | The larger font size. Mainly used for captions. | `18px` |
|
||||||
|
| `--plyr-font-size-xlarge` | The even larger font size. Mainly used for captions. | `21px` |
|
||||||
|
| `--plyr-font-size-time` | The font size for the time. | `--plyr-font-size-small` |
|
||||||
|
| `--plyr-font-size-menu` | The font size used in the menu. | `--plyr-font-size-small` |
|
||||||
|
| `--plyr-font-size-badge` | The font size used for badges. | `9px` |
|
||||||
|
| `--plyr-font-weight-regular` | The regular font weight. | `400` |
|
||||||
|
| `--plyr-font-weight-bold` | The bold font weight. | `600` |
|
||||||
|
| `--plyr-line-height` | The line height used within the player. | `1.7` |
|
||||||
|
| `--plyr-font-smoothing` | Whether to enable font antialiasing within the player. | `false` |
|
||||||
|
|
||||||
|
You can set them in your CSS for all players:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--plyr-color-main: #1ac266;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...or for a specific class name:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.player {
|
||||||
|
--plyr-color-main: #1ac266;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...or in your HTML:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<video class="player" style="--plyr-color-main: #1ac266;">
|
||||||
|
...
|
||||||
|
</vieo>
|
||||||
|
```
|
||||||
|
|
||||||
|
### SASS
|
||||||
|
|
||||||
|
You can use `plyr.scss` file included in `/src/sass` as part of your build and change variables to suit your design. The SASS requires you to
|
||||||
|
use [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) (you should be already!) as all declarations use the W3C definitions.
|
||||||
|
|
||||||
The HTML markup uses the BEM methodology with `plyr` as the block, e.g. `.plyr__controls`. You can change the class hooks in the options to match any custom CSS
|
The HTML markup uses the BEM methodology with `plyr` as the block, e.g. `.plyr__controls`. You can change the class hooks in the options to match any custom CSS
|
||||||
you write. Check out the JavaScript source for more on this.
|
you write. Check out the JavaScript source for more on this.
|
||||||
@@ -213,9 +312,9 @@ WebVTT captions are supported. To add a caption track, check the HTML example ab
|
|||||||
|
|
||||||
You can specify a range of arguments for the constructor to use:
|
You can specify a range of arguments for the constructor to use:
|
||||||
|
|
||||||
- A [CSS string selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors)
|
- A [CSS string selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors)
|
||||||
- A [`HTMLElement`](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
|
- A [`HTMLElement`](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
|
||||||
- A [jQuery](https://jquery.com) object
|
- A [jQuery](https://jquery.com) object
|
||||||
|
|
||||||
_Note_: If a `NodeList`, `Array`, or jQuery object are passed, the first element will be used for setup. To setup multiple players, see [multiple players](#multiple-players) below.
|
_Note_: If a `NodeList`, `Array`, or jQuery object are passed, the first element will be used for setup. To setup multiple players, see [multiple players](#multiple-players) below.
|
||||||
|
|
||||||
@@ -261,7 +360,7 @@ The second argument for the constructor is the [options](#options) object:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const player = new Plyr('#player', {
|
const player = new Plyr('#player', {
|
||||||
title: 'Example Title',
|
title: 'Example Title',
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -277,7 +376,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
|||||||
| -------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| -------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `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. |
|
||||||
| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If the default controls are used, you can specify which settings to show in the menu |
|
| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If the default controls are used, you can specify which settings to show in the menu |
|
||||||
| `i18n` | Object | See [defaults.js](/src/js/config/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
|
| `i18n` | Object | See [defaults.js](/src/js/config/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
|
||||||
| `loadSprite` | Boolean | `true` | Load the SVG sprite specified as the `iconUrl` option (if a URL). If `false`, it is assumed you are handling sprite loading yourself. |
|
| `loadSprite` | Boolean | `true` | Load the SVG sprite specified as the `iconUrl` option (if a URL). If `false`, it is assumed you are handling sprite loading yourself. |
|
||||||
@@ -301,13 +400,13 @@ 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 unselectable 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 unselectable language options). |
|
||||||
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `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) |
|
| `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] }` | `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] }` | `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. |
|
||||||
| `quality` | Object | `{ default: 576, options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240] }` | `default` is the default quality level (if it exists in your sources). `options` are the options to display. This is used to filter the available sources. |
|
| `quality` | Object | `{ default: 576, options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240] }` | `default` is the default quality level (if it exists in your sources). `options` are the options to display. This is used to filter the available sources. |
|
||||||
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
||||||
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable advertisements. `publisherId`: Your unique [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) publisher ID. |
|
| `ads` | Object | `{ enabled: false, publisherId: '', tagUrl: '' }` | `enabled`: Whether to enable advertisements. `publisherId`: Your unique [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) publisher ID. `tagUrl` is a URL for a custom VAST tag if you're not using Vi. |
|
||||||
| `urls` | Object | See source. | If you wish to override any API URLs then you can do so here. You can also set a custom download URL for the download button. |
|
| `urls` | Object | See source. | If you wish to override any API URLs then you can do so here. You can also set a custom download URL for the download button. |
|
||||||
| `vimeo` | Object | `{ byline: false, portrait: false, title: false, speed: true, transparent: false }` | See [Vimeo embed options](https://github.com/vimeo/player.js/#embed-options). Some are set automatically based on other config options, namely: `loop`, `autoplay`, `muted`, `gesture`, `playsinline` |
|
| `vimeo` | Object | `{ byline: false, portrait: false, title: false, speed: true, transparent: false }` | See [Vimeo embed options](https://github.com/vimeo/player.js/#embed-options). Some are set automatically based on other config options, namely: `loop`, `autoplay`, `muted`, `gesture`, `playsinline` |
|
||||||
| `youtube` | Object | `{ noCookie: false, rel: 0, showinfo: 0, iv_load_policy: 3, modestbranding: 1 }` | See [YouTube embed options](https://developers.google.com/youtube/player_parameters#Parameters). The only custom option is `noCookie` to use an alternative to YouTube that doesn't use cookies (useful for GDPR, etc). Some are set automatically based on other config options, namely: `autoplay`, `hl`, `controls`, `disablekb`, `playsinline`, `cc_load_policy`, `cc_lang_pref`, `widget_referrer` |
|
| `youtube` | Object | `{ noCookie: false, rel: 0, showinfo: 0, iv_load_policy: 3, modestbranding: 1 }` | See [YouTube embed options](https://developers.google.com/youtube/player_parameters#Parameters). The only custom option is `noCookie` to use an alternative to YouTube that doesn't use cookies (useful for GDPR, etc). Some are set automatically based on other config options, namely: `autoplay`, `hl`, `controls`, `disablekb`, `playsinline`, `cc_load_policy`, `cc_lang_pref`, `widget_referrer` |
|
||||||
@@ -316,9 +415,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/
|
||||||
|
|
||||||
# API
|
# API
|
||||||
|
|
||||||
@@ -330,7 +429,7 @@ The easiest way to access the Plyr object is to set the return value from your c
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const player = new Plyr('#player', {
|
const player = new Plyr('#player', {
|
||||||
/* options */
|
/* options */
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -338,7 +437,7 @@ You can also access the object through any events:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
element.addEventListener('ready', event => {
|
element.addEventListener('ready', event => {
|
||||||
const player = event.detail.plyr;
|
const player = event.detail.plyr;
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -351,30 +450,30 @@ player.play(); // Start playback
|
|||||||
player.fullscreen.enter(); // Enter fullscreen
|
player.fullscreen.enter(); // Enter fullscreen
|
||||||
```
|
```
|
||||||
|
|
||||||
| Method | Parameters | Description |
|
| Method | Parameters | Description |
|
||||||
| ------------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------- |
|
| -------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||||
| `play()`¹ | - | Start playback. |
|
| `play()`¹ | - | Start playback. |
|
||||||
| `pause()` | - | Pause playback. |
|
| `pause()` | - | Pause playback. |
|
||||||
| `togglePlay(toggle)` | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. |
|
| `togglePlay(toggle)`¹ | Boolean | Toggle playback, if no parameters are passed, it will toggle based on current status. |
|
||||||
| `stop()` | - | Stop playback and reset to start. |
|
| `stop()` | - | Stop playback and reset to start. |
|
||||||
| `restart()` | - | Restart playback. |
|
| `restart()` | - | Restart playback. |
|
||||||
| `rewind(seekTime)` | Number | Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
| `rewind(seekTime)` | Number | Rewind playback by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
||||||
| `forward(seekTime)` | Number | Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
| `forward(seekTime)` | Number | Fast forward by the specified seek time. If no parameter is passed, the default seek time will be used. |
|
||||||
| `increaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
| `increaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
||||||
| `decreaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
| `decreaseVolume(step)` | Number | Increase volume by the specified step. If no parameter is passed, the default step will be used. |
|
||||||
| `toggleCaptions(toggle)` | Boolean | Toggle captions display. If no parameter is passed, it will toggle based on current status. |
|
| `toggleCaptions(toggle)` | Boolean | Toggle captions display. If no parameter is passed, it will toggle based on current status. |
|
||||||
| `fullscreen.enter()` | - | Enter fullscreen. If fullscreen is not supported, a fallback "full window/viewport" is used instead. |
|
| `fullscreen.enter()` | - | Enter fullscreen. If fullscreen is not supported, a fallback "full window/viewport" is used instead. |
|
||||||
| `fullscreen.exit()` | - | Exit fullscreen. |
|
| `fullscreen.exit()` | - | Exit fullscreen. |
|
||||||
| `fullscreen.toggle()` | - | Toggle fullscreen. |
|
| `fullscreen.toggle()` | - | Toggle fullscreen. |
|
||||||
| `airplay()` | - | Trigger the airplay dialog on supported devices. |
|
| `airplay()` | - | Trigger the airplay dialog on supported devices. |
|
||||||
| `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. |
|
| `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. |
|
||||||
| `on(event, function)` | String, Function | Add an event listener for the specified event. |
|
| `on(event, function)` | String, Function | Add an event listener for the specified event. |
|
||||||
| `once(event, function)` | String, Function | Add an event listener for the specified event once. |
|
| `once(event, function)` | String, Function | Add an event listener for the specified event once. |
|
||||||
| `off(event, function)` | String, Function | Remove an event listener for the specified event. |
|
| `off(event, function)` | String, Function | Remove an event listener for the specified event. |
|
||||||
| `supports(type)` | String | Check support for a mime type. |
|
| `supports(type)` | String | Check support for a mime type. |
|
||||||
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
|
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
|
||||||
|
|
||||||
1. For HTML5 players, `play()` will return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) in _some_ browsers - WebKit and Mozilla [according to MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) at time of writing.
|
1. For HTML5 players, `play()` will return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for most browsers - e.g. Chrome, Firefox, Opera, Safari and Edge [according to MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) at time of writing.
|
||||||
|
|
||||||
## Getters and Setters
|
## Getters and Setters
|
||||||
|
|
||||||
@@ -432,39 +531,39 @@ Video example:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
player.source = {
|
player.source = {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
title: 'Example title',
|
title: 'Example title',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: '/path/to/movie.mp4',
|
src: '/path/to/movie.mp4',
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
size: 720,
|
size: 720,
|
||||||
},
|
|
||||||
{
|
|
||||||
src: '/path/to/movie.webm',
|
|
||||||
type: 'video/webm',
|
|
||||||
size: 1080,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
poster: '/path/to/poster.jpg',
|
|
||||||
previewThumbnails: {
|
|
||||||
src: '/path/to/thumbnails.vtt'
|
|
||||||
},
|
},
|
||||||
tracks: [
|
{
|
||||||
{
|
src: '/path/to/movie.webm',
|
||||||
kind: 'captions',
|
type: 'video/webm',
|
||||||
label: 'English',
|
size: 1080,
|
||||||
srclang: 'en',
|
},
|
||||||
src: '/path/to/captions.en.vtt',
|
],
|
||||||
default: true,
|
poster: '/path/to/poster.jpg',
|
||||||
},
|
previewThumbnails: {
|
||||||
{
|
src: '/path/to/thumbnails.vtt',
|
||||||
kind: 'captions',
|
},
|
||||||
label: 'French',
|
tracks: [
|
||||||
srclang: 'fr',
|
{
|
||||||
src: '/path/to/captions.fr.vtt',
|
kind: 'captions',
|
||||||
},
|
label: 'English',
|
||||||
],
|
srclang: 'en',
|
||||||
|
src: '/path/to/captions.en.vtt',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 'captions',
|
||||||
|
label: 'French',
|
||||||
|
srclang: 'fr',
|
||||||
|
src: '/path/to/captions.fr.vtt',
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -472,18 +571,18 @@ Audio example:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
player.source = {
|
player.source = {
|
||||||
type: 'audio',
|
type: 'audio',
|
||||||
title: 'Example title',
|
title: 'Example title',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: '/path/to/audio.mp3',
|
src: '/path/to/audio.mp3',
|
||||||
type: 'audio/mp3',
|
type: 'audio/mp3',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: '/path/to/audio.ogg',
|
src: '/path/to/audio.ogg',
|
||||||
type: 'audio/ogg',
|
type: 'audio/ogg',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -491,29 +590,27 @@ YouTube example:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
player.source = {
|
player.source = {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: 'bTqVqk7FSmY',
|
src: 'bTqVqk7FSmY',
|
||||||
provider: 'youtube',
|
provider: 'youtube',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
_Note_: `src` can be the video ID or URL
|
|
||||||
|
|
||||||
Vimeo example
|
Vimeo example
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
player.source = {
|
player.source = {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: '143418951',
|
src: '143418951',
|
||||||
provider: 'vimeo',
|
provider: 'vimeo',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -538,7 +635,7 @@ property. Here's an example:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
player.on('ready', event => {
|
player.on('ready', event => {
|
||||||
const instance = event.detail.plyr;
|
const instance = event.detail.plyr;
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -597,8 +694,8 @@ YouTube and Vimeo are currently supported and function much like a HTML5 video.
|
|||||||
to access the API's directly. You can do so via the `embed` property of your player object - e.g. `player.embed`. You can then use the relevant methods from the
|
to access the API's directly. You can do so via the `embed` property of your player object - e.g. `player.embed`. You can then use the relevant methods from the
|
||||||
third party APIs. More info on the respective API's here:
|
third party APIs. More info on the respective API's here:
|
||||||
|
|
||||||
- [YouTube iframe API Reference](https://developers.google.com/youtube/iframe_api_reference)
|
- [YouTube iframe API Reference](https://developers.google.com/youtube/iframe_api_reference)
|
||||||
- [Vimeo player.js Reference](https://github.com/vimeo/player.js)
|
- [Vimeo player.js Reference](https://github.com/vimeo/player.js)
|
||||||
|
|
||||||
_Note_: Not all API methods may work 100%. Your mileage may vary. It's better to use the Plyr API where possible.
|
_Note_: Not all API methods may work 100%. Your mileage may vary. It's better to use the Plyr API where possible.
|
||||||
|
|
||||||
@@ -664,9 +761,9 @@ const supported = Plyr.supported('video', 'html5', true);
|
|||||||
|
|
||||||
The arguments are:
|
The arguments are:
|
||||||
|
|
||||||
- Media type (`audio` or `video`)
|
- Media type (`audio` or `video`)
|
||||||
- Provider (`html5`, `youtube` or `vimeo`)
|
- Provider (`html5`, `youtube` or `vimeo`)
|
||||||
- Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
|
- Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
|
||||||
|
|
||||||
## Disable support programmatically
|
## Disable support programmatically
|
||||||
|
|
||||||
@@ -674,7 +771,7 @@ The `enabled` option can be used to disable certain User Agents. For example, if
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
{
|
{
|
||||||
enabled: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
enabled: !/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -707,42 +804,42 @@ Plyr is developed by [@sam_potts](https://twitter.com/sam_potts) / [sampotts.me]
|
|||||||
|
|
||||||
Plyr costs money to run, not only my time. I donate my time for free as I enjoy building Plyr but unfortunately have to pay for domains, hosting, and more. Any help with costs is appreciated...
|
Plyr costs money to run, not only my time. I donate my time for free as I enjoy building Plyr but unfortunately have to pay for domains, hosting, and more. Any help with costs is appreciated...
|
||||||
|
|
||||||
- [Donate via Patreon](https://www.patreon.com/plyr)
|
- [Donate via Patreon](https://www.patreon.com/plyr)
|
||||||
- [Donate via PayPal](https://www.paypal.me/pottsy/20usd)
|
- [Donate via PayPal](https://www.paypal.me/pottsy/20usd)
|
||||||
|
|
||||||
# Mentions
|
# Mentions
|
||||||
|
|
||||||
- [ProductHunt](https://www.producthunt.com/tech/plyr)
|
- [ProductHunt](https://www.producthunt.com/tech/plyr)
|
||||||
- [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
|
- [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
|
||||||
- [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
- [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
||||||
- [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
|
- [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
|
||||||
- [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
|
- [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
|
||||||
- [Front End Focus #177](https://frontendfoc.us/issues/177)
|
- [Front End Focus #177](https://frontendfoc.us/issues/177)
|
||||||
- [Hacker News](https://news.ycombinator.com/item?id=9136774)
|
- [Hacker News](https://news.ycombinator.com/item?id=9136774)
|
||||||
- [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
|
- [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
|
||||||
- [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
|
- [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
|
||||||
- [The Treehouse Show #131](https://teamtreehouse.com/library/episode-131-origami-react-responsive-hero-images)
|
- [The Treehouse Show #131](https://teamtreehouse.com/library/episode-131-origami-react-responsive-hero-images)
|
||||||
- [noupe.com](http://www.noupe.com/design/html5-plyr-is-a-responsive-and-accessible-video-player-94389.html)
|
- [noupe.com](http://www.noupe.com/design/html5-plyr-is-a-responsive-and-accessible-video-player-94389.html)
|
||||||
|
|
||||||
# Used by
|
# Used by
|
||||||
|
|
||||||
- [Selz.com](https://selz.com)
|
- [Selz.com](https://selz.com)
|
||||||
- [Peugeot.fr](http://www.peugeot.fr/marque-et-technologie/technologies/peugeot-i-cockpit.html)
|
- [Peugeot.fr](http://www.peugeot.fr/marque-et-technologie/technologies/peugeot-i-cockpit.html)
|
||||||
- [Peugeot.de](http://www.peugeot.de/modelle/modellberater/208-3-turer/fotos-videos.html)
|
- [Peugeot.de](http://www.peugeot.de/modelle/modellberater/208-3-turer/fotos-videos.html)
|
||||||
- [TomTom.com](http://prioritydriving.tomtom.com/)
|
- [TomTom.com](http://prioritydriving.tomtom.com/)
|
||||||
- [DIGBMX](http://digbmx.com/)
|
- [DIGBMX](http://digbmx.com/)
|
||||||
- [Grime Archive](https://grimearchive.com/)
|
- [Grime Archive](https://grimearchive.com/)
|
||||||
- [koel - A personal music streaming server that works.](http://koel.phanan.net/)
|
- [koel - A personal music streaming server that works.](http://koel.phanan.net/)
|
||||||
- [Oscar Radio](http://oscar-radio.xyz/)
|
- [Oscar Radio](http://oscar-radio.xyz/)
|
||||||
- [Sparkk TV](https://www.sparkktv.com/)
|
- [Sparkk TV](https://www.sparkktv.com/)
|
||||||
- [@halfhalftravel](https://www.halfhalftravel.com/)
|
- [@halfhalftravel](https://www.halfhalftravel.com/)
|
||||||
|
|
||||||
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 😎
|
||||||
|
|
||||||
# Useful links and credits
|
# Useful links and credits
|
||||||
|
|
||||||
- [PayPal's Accessible HTML5 Video Player (which Plyr was originally ported from)](https://github.com/paypal/accessible-html5-video-player)
|
- [PayPal's Accessible HTML5 Video Player (which Plyr was originally ported from)](https://github.com/paypal/accessible-html5-video-player)
|
||||||
- [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
- [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
||||||
|
|
||||||
# Thanks
|
# Thanks
|
||||||
|
|
||||||
@@ -777,7 +874,6 @@ Support this project with your organization. Your logo will show up here with a
|
|||||||
<a href="https://opencollective.com/plyr/organization/0/website"><img src="https://opencollective.com/plyr/organization/0/avatar.svg"></a>
|
<a href="https://opencollective.com/plyr/organization/0/website"><img src="https://opencollective.com/plyr/organization/0/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/plyr/organization/1/website"><img src="https://opencollective.com/plyr/organization/1/avatar.svg"></a><a href="https://opencollective.com/plyr/organization/2/website"><img src="https://opencollective.com/plyr/organization/2/avatar.svg"></a>
|
<a href="https://opencollective.com/plyr/organization/1/website"><img src="https://opencollective.com/plyr/organization/1/avatar.svg"></a><a href="https://opencollective.com/plyr/organization/2/website"><img src="https://opencollective.com/plyr/organization/2/avatar.svg"></a>
|
||||||
|
|
||||||
|
|
||||||
# Copyright and License
|
# Copyright and License
|
||||||
|
|
||||||
[The MIT license](license.md)
|
[The MIT license](LICENSE.md)
|
||||||
-1153
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+9253
-5613
File diff suppressed because it is too large
Load Diff
Vendored
+17
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+243
-253
@@ -1,278 +1,268 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title>
|
<title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title>
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content="A simple HTML5 media player with custom controls and WebVTT captions."
|
content="A simple HTML5 media player with custom controls and WebVTT captions."
|
||||||
/>
|
/>
|
||||||
<meta name="author" content="Sam Potts" />
|
<meta name="author" content="Sam Potts" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
<!-- Icons -->
|
<!-- Icons -->
|
||||||
<link rel="icon" href="https://cdn.plyr.io/static/icons/favicon.ico" />
|
<link rel="icon" href="https://cdn.plyr.io/static/icons/favicon.ico" />
|
||||||
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/32x32.png" sizes="32x32" />
|
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/32x32.png" sizes="32x32" />
|
||||||
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16" />
|
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png" />
|
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png" />
|
||||||
|
|
||||||
<!-- Open Graph -->
|
<!-- Open Graph -->
|
||||||
<meta
|
<meta property="og:title" content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player" />
|
||||||
property="og:title"
|
<meta property="og:site_name" content="Plyr" />
|
||||||
content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player"
|
<meta property="og:url" content="https://plyr.io" />
|
||||||
/>
|
<meta property="og:image" content="https://cdn.plyr.io/static/icons/1200x630.png" />
|
||||||
<meta property="og:site_name" content="Plyr" />
|
|
||||||
<meta property="og:url" content="https://plyr.io" />
|
|
||||||
<meta property="og:image" content="https://cdn.plyr.io/static/icons/1200x630.png" />
|
|
||||||
|
|
||||||
<!-- Twitter -->
|
<!-- Twitter -->
|
||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary" />
|
||||||
<meta name="twitter:site" content="@sam_potts" />
|
<meta name="twitter:site" content="@sam_potts" />
|
||||||
<meta name="twitter:creator" content="@sam_potts" />
|
<meta name="twitter:creator" content="@sam_potts" />
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
|
||||||
<!-- Docs styles -->
|
<!-- Docs styles -->
|
||||||
<link rel="stylesheet" href="dist/demo.css" />
|
<link rel="stylesheet" href="dist/demo.css" />
|
||||||
|
|
||||||
<!-- Preload -->
|
<!-- Preload -->
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
as="font"
|
as="font"
|
||||||
crossorigin
|
crossorigin
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2"
|
href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
as="font"
|
as="font"
|
||||||
crossorigin
|
crossorigin
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2"
|
href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Google Analytics-->
|
<!-- Google Analytics-->
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-132699580-1"></script>
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-132699580-1"></script>
|
||||||
<script>
|
<script>
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
function gtag() {
|
function gtag() {
|
||||||
dataLayer.push(arguments);
|
dataLayer.push(arguments);
|
||||||
}
|
}
|
||||||
gtag('js', new Date());
|
gtag('js', new Date());
|
||||||
gtag('config', 'UA-132699580-1');
|
gtag('config', 'UA-132699580-1');
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<header>
|
<header>
|
||||||
<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="faux-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></svg
|
></path></svg
|
||||||
>Video</button
|
>Video</button
|
||||||
>,
|
>,
|
||||||
<button type="button" class="faux-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></svg
|
></path></svg
|
||||||
>Audio</button
|
>Audio</button
|
||||||
>,
|
>,
|
||||||
<button type="button" class="faux-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></svg
|
></path></svg
|
||||||
>YouTube
|
>YouTube
|
||||||
</button>
|
</button>
|
||||||
and
|
and
|
||||||
<button type="button" class="faux-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></svg
|
></path></svg
|
||||||
>Vimeo
|
>Vimeo
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Premium video monetization from
|
Premium video monetization from
|
||||||
<a href="https://vi.ai/publisher-video-monetization/?aid=plyrio" target="_blank" class="no-border">
|
<a href="https://vi.ai/publisher-video-monetization/?aid=plyrio" target="_blank" class="no-border">
|
||||||
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi" />
|
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi" />
|
||||||
<span class="sr-only">ai.vi</span>
|
<span class="sr-only">ai.vi</span>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="call-to-action">
|
<div class="call-to-action">
|
||||||
<a href="https://github.com/sampotts/plyr" target="_blank" class="button js-shr">
|
<a href="https://github.com/sampotts/plyr" target="_blank" class="button js-shr">
|
||||||
<svg class="icon" role="presentation">
|
<svg class="icon" role="presentation">
|
||||||
<title>GitHub</title>
|
<title>GitHub</title>
|
||||||
<path
|
<path
|
||||||
d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
|
d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
|
||||||
C5.9,15.9,6,15.6,6,15.4c0-0.2,0-0.7,0-1.4C3.8,14.5,3.3,13,3.3,13c-0.4-0.9-0.9-1.2-0.9-1.2c-0.7-0.5,0.1-0.5,0.1-0.5
|
C5.9,15.9,6,15.6,6,15.4c0-0.2,0-0.7,0-1.4C3.8,14.5,3.3,13,3.3,13c-0.4-0.9-0.9-1.2-0.9-1.2c-0.7-0.5,0.1-0.5,0.1-0.5
|
||||||
c0.8,0.1,1.2,0.8,1.2,0.8C4.4,13.4,5.6,13,6,12.8c0.1-0.5,0.3-0.9,0.5-1.1c-1.8-0.2-3.6-0.9-3.6-4c0-0.9,0.3-1.6,0.8-2.1
|
c0.8,0.1,1.2,0.8,1.2,0.8C4.4,13.4,5.6,13,6,12.8c0.1-0.5,0.3-0.9,0.5-1.1c-1.8-0.2-3.6-0.9-3.6-4c0-0.9,0.3-1.6,0.8-2.1
|
||||||
c-0.1-0.2-0.4-1,0.1-2.1c0,0,0.7-0.2,2.2,0.8c0.6-0.2,1.3-0.3,2-0.3c0.7,0,1.4,0.1,2,0.3c1.5-1,2.2-0.8,2.2-0.8
|
c-0.1-0.2-0.4-1,0.1-2.1c0,0,0.7-0.2,2.2,0.8c0.6-0.2,1.3-0.3,2-0.3c0.7,0,1.4,0.1,2,0.3c1.5-1,2.2-0.8,2.2-0.8
|
||||||
c0.4,1.1,0.2,1.9,0.1,2.1c0.5,0.6,0.8,1.3,0.8,2.1c0,3.1-1.9,3.7-3.7,3.9C9.7,12,10,12.5,10,13.2c0,1.1,0,1.9,0,2.2
|
c0.4,1.1,0.2,1.9,0.1,2.1c0.5,0.6,0.8,1.3,0.8,2.1c0,3.1-1.9,3.7-3.7,3.9C9.7,12,10,12.5,10,13.2c0,1.1,0,1.9,0,2.2
|
||||||
c0,0.2,0.1,0.5,0.6,0.4c3.2-1.1,5.5-4.1,5.5-7.6C16,3.8,12.4,0.2,8,0.2z"
|
c0,0.2,0.1,0.5,0.6,0.4c3.2-1.1,5.5-4.1,5.5-7.6C16,3.8,12.4,0.2,8,0.2z"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
Download on GitHub
|
Download on GitHub
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
<main>
|
||||||
|
<div id="container">
|
||||||
|
<video
|
||||||
|
controls
|
||||||
|
crossorigin
|
||||||
|
playsinline
|
||||||
|
data-poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg"
|
||||||
|
id="player"
|
||||||
|
>
|
||||||
|
<!-- Video files -->
|
||||||
|
<source
|
||||||
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
size="576"
|
||||||
|
/>
|
||||||
|
<source
|
||||||
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
size="720"
|
||||||
|
/>
|
||||||
|
<source
|
||||||
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4"
|
||||||
|
type="video/mp4"
|
||||||
|
size="1080"
|
||||||
|
/>
|
||||||
|
|
||||||
<main>
|
<!-- Caption files -->
|
||||||
<div id="container">
|
<track
|
||||||
<video
|
kind="captions"
|
||||||
controls
|
label="English"
|
||||||
crossorigin
|
srclang="en"
|
||||||
playsinline
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
|
||||||
poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg"
|
default
|
||||||
id="player"
|
/>
|
||||||
>
|
<track
|
||||||
<!-- Video files -->
|
kind="captions"
|
||||||
<source
|
label="Français"
|
||||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4"
|
srclang="fr"
|
||||||
type="video/mp4"
|
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"
|
||||||
size="576"
|
/>
|
||||||
/>
|
|
||||||
<source
|
|
||||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4"
|
|
||||||
type="video/mp4"
|
|
||||||
size="720"
|
|
||||||
/>
|
|
||||||
<source
|
|
||||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4"
|
|
||||||
type="video/mp4"
|
|
||||||
size="1080"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Caption files -->
|
<!-- Fallback for browsers that don't support the <video> element -->
|
||||||
<track
|
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" download>Download</a>
|
||||||
kind="captions"
|
</video>
|
||||||
label="English"
|
|
||||||
srclang="en"
|
|
||||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
|
|
||||||
default
|
|
||||||
/>
|
|
||||||
<track
|
|
||||||
kind="captions"
|
|
||||||
label="Français"
|
|
||||||
srclang="fr"
|
|
||||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Fallback for browsers that don't support the <video> element -->
|
|
||||||
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4" download
|
|
||||||
>Download</a
|
|
||||||
>
|
|
||||||
</video>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li class="plyr__cite plyr__cite--video" hidden>
|
|
||||||
<small>
|
|
||||||
<svg class="icon">
|
|
||||||
<title>HTML5</title>
|
|
||||||
<path
|
|
||||||
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<a
|
|
||||||
href="https://itunes.apple.com/au/movie/view-from-a-blue-moon/id1041586323"
|
|
||||||
target="_blank"
|
|
||||||
>View From A Blue Moon</a
|
|
||||||
>
|
|
||||||
© Brainfarm
|
|
||||||
</small>
|
|
||||||
</li>
|
|
||||||
<li class="plyr__cite plyr__cite--audio" hidden>
|
|
||||||
<small>
|
|
||||||
<svg class="icon" title="HTML5">
|
|
||||||
<title>HTML5</title>
|
|
||||||
<path
|
|
||||||
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
<a href="http://www.kishibashi.com/" target="_blank"
|
|
||||||
>Kishi Bashi – “It All Began With A Burst”</a
|
|
||||||
>
|
|
||||||
© Kishi Bashi
|
|
||||||
</small>
|
|
||||||
</li>
|
|
||||||
<li class="plyr__cite plyr__cite--youtube" hidden>
|
|
||||||
<small>
|
|
||||||
<a href="https://www.youtube.com/watch?v=bTqVqk7FSmY" target="_blank"
|
|
||||||
>View From A Blue Moon</a
|
|
||||||
>
|
|
||||||
on
|
|
||||||
<span class="color--youtube">
|
|
||||||
<svg class="icon" role="presentation">
|
|
||||||
<title>YouTube</title>
|
|
||||||
<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
|
|
||||||
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"
|
|
||||||
></path></svg
|
|
||||||
>YouTube
|
|
||||||
</span>
|
|
||||||
</small>
|
|
||||||
</li>
|
|
||||||
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
|
||||||
<small>
|
|
||||||
<a href="https://vimeo.com/40648169" target="_blank">Toob “Wavaphon” Music Video</a>
|
|
||||||
on
|
|
||||||
<span class="color--vimeo">
|
|
||||||
<svg class="icon" role="presentation">
|
|
||||||
<title>Vimeo</title>
|
|
||||||
<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
|
|
||||||
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"
|
|
||||||
></path></svg
|
|
||||||
>Vimeo
|
|
||||||
</span>
|
|
||||||
</small>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</main>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside>
|
<ul>
|
||||||
<svg class="icon">
|
<li class="plyr__cite plyr__cite--video" hidden>
|
||||||
<title>Twitter</title>
|
<small>
|
||||||
|
<svg class="icon">
|
||||||
|
<title>HTML5</title>
|
||||||
<path
|
<path
|
||||||
d="M16,3c-0.6,0.3-1.2,0.4-1.9,0.5c0.7-0.4,1.2-1,1.4-1.8c-0.6,0.4-1.3,0.6-2.1,0.8c-0.6-0.6-1.5-1-2.4-1
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<a href="https://itunes.apple.com/au/movie/view-from-a-blue-moon/id1041586323" target="_blank"
|
||||||
|
>View From A Blue Moon</a
|
||||||
|
>
|
||||||
|
© Brainfarm
|
||||||
|
</small>
|
||||||
|
</li>
|
||||||
|
<li class="plyr__cite plyr__cite--audio" hidden>
|
||||||
|
<small>
|
||||||
|
<svg class="icon" title="HTML5">
|
||||||
|
<title>HTML5</title>
|
||||||
|
<path
|
||||||
|
d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<a href="http://www.kishibashi.com/" target="_blank"
|
||||||
|
>Kishi Bashi – “It All Began With A Burst”</a
|
||||||
|
>
|
||||||
|
© Kishi Bashi
|
||||||
|
</small>
|
||||||
|
</li>
|
||||||
|
<li class="plyr__cite plyr__cite--youtube" hidden>
|
||||||
|
<small>
|
||||||
|
<a href="https://www.youtube.com/watch?v=bTqVqk7FSmY" target="_blank">View From A Blue Moon</a>
|
||||||
|
on
|
||||||
|
<span class="color--youtube">
|
||||||
|
<svg class="icon" role="presentation">
|
||||||
|
<title>YouTube</title>
|
||||||
|
<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
|
||||||
|
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"
|
||||||
|
></path></svg
|
||||||
|
>YouTube
|
||||||
|
</span>
|
||||||
|
</small>
|
||||||
|
</li>
|
||||||
|
<li class="plyr__cite plyr__cite--vimeo" hidden>
|
||||||
|
<small>
|
||||||
|
<a href="https://vimeo.com/40648169" target="_blank">Toob “Wavaphon” Music Video</a>
|
||||||
|
on
|
||||||
|
<span class="color--vimeo">
|
||||||
|
<svg class="icon" role="presentation">
|
||||||
|
<title>Vimeo</title>
|
||||||
|
<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
|
||||||
|
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"
|
||||||
|
></path></svg
|
||||||
|
>Vimeo
|
||||||
|
</span>
|
||||||
|
</small>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<svg class="icon">
|
||||||
|
<title>Twitter</title>
|
||||||
|
<path
|
||||||
|
d="M16,3c-0.6,0.3-1.2,0.4-1.9,0.5c0.7-0.4,1.2-1,1.4-1.8c-0.6,0.4-1.3,0.6-2.1,0.8c-0.6-0.6-1.5-1-2.4-1
|
||||||
C9.3,1.5,7.8,3,7.8,4.8c0,0.3,0,0.5,0.1,0.7C5.2,5.4,2.7,4.1,1.1,2.1c-0.3,0.5-0.4,1-0.4,1.7c0,1.1,0.6,2.1,1.5,2.7
|
C9.3,1.5,7.8,3,7.8,4.8c0,0.3,0,0.5,0.1,0.7C5.2,5.4,2.7,4.1,1.1,2.1c-0.3,0.5-0.4,1-0.4,1.7c0,1.1,0.6,2.1,1.5,2.7
|
||||||
c-0.5,0-1-0.2-1.5-0.4c0,0,0,0,0,0c0,1.6,1.1,2.9,2.6,3.2C3,9.4,2.7,9.4,2.4,9.4c-0.2,0-0.4,0-0.6-0.1c0.4,1.3,1.6,2.3,3.1,2.3
|
c-0.5,0-1-0.2-1.5-0.4c0,0,0,0,0,0c0,1.6,1.1,2.9,2.6,3.2C3,9.4,2.7,9.4,2.4,9.4c-0.2,0-0.4,0-0.6-0.1c0.4,1.3,1.6,2.3,3.1,2.3
|
||||||
c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"
|
c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<p>
|
<p>
|
||||||
If you think Plyr's good,
|
If you think Plyr's good,
|
||||||
<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="js-shr"
|
class="js-shr"
|
||||||
>tweet it</a
|
>tweet it</a
|
||||||
>
|
>
|
||||||
👍
|
👍
|
||||||
</p>
|
</p>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<script src="dist/demo.js" crossorigin="anonymous"></script>
|
<script src="dist/demo.js" crossorigin="anonymous"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+12
-12
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "plyr-demo",
|
"name": "plyr-demo",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Demo for Plyr",
|
"description": "Demo for Plyr",
|
||||||
"homepage": "https://plyr.io",
|
"homepage": "https://plyr.io",
|
||||||
"author": "Sam Potts <sam@potts.es>",
|
"author": "Sam Potts <sam@potts.es>",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.6.4",
|
"@sentry/browser": "^5.15.5",
|
||||||
"custom-event-polyfill": "^1.0.7",
|
"core-js": "^3.6.5",
|
||||||
"raven-js": "^3.27.2",
|
"custom-event-polyfill": "^1.0.7",
|
||||||
"shr-buttons": "2.0.3",
|
"shr-buttons": "2.0.3",
|
||||||
"url-polyfill": "^1.1.8"
|
"url-polyfill": "^1.1.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+126
-137
@@ -1,14 +1,14 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr.io demo
|
// Plyr.io demo
|
||||||
// This code is purely for the https://plyr.io website
|
// This code is purely for the https://plyr.io website
|
||||||
// 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 './tab-focus';
|
||||||
import 'custom-event-polyfill';
|
import 'custom-event-polyfill';
|
||||||
import 'url-polyfill';
|
import 'url-polyfill';
|
||||||
|
|
||||||
import Raven from 'raven-js';
|
import * as Sentry from '@sentry/browser';
|
||||||
import Shr from 'shr-buttons';
|
import Shr from 'shr-buttons';
|
||||||
|
|
||||||
import Plyr from '../../../src/js/plyr';
|
import Plyr from '../../../src/js/plyr';
|
||||||
@@ -16,145 +16,134 @@ import sources from './sources';
|
|||||||
import toggleClass from './toggle-class';
|
import toggleClass from './toggle-class';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const { host } = window.location;
|
const production = 'plyr.io';
|
||||||
const env = {
|
|
||||||
prod: host === 'plyr.io',
|
|
||||||
dev: host === 'dev.plyr.io',
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
// Sentry for demo site (https://plyr.io) only
|
||||||
Raven.context(() => {
|
if (window.location.host === production) {
|
||||||
const selector = '#player';
|
Sentry.init({
|
||||||
|
dsn: 'https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555',
|
||||||
|
whitelistUrls: [production].map(d => new RegExp(`https://(([a-z0-9])+(.))*${d}`)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Setup share buttons
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
Shr.setup('.js-shr', {
|
const selector = '#player';
|
||||||
count: {
|
|
||||||
className: 'button__count',
|
|
||||||
},
|
|
||||||
wrapper: {
|
|
||||||
className: 'button--with-count',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup the player
|
// Setup share buttons
|
||||||
const player = new Plyr(selector, {
|
Shr.setup('.js-shr', {
|
||||||
debug: true,
|
count: {
|
||||||
title: 'View From A Blue Moon',
|
className: 'button__count',
|
||||||
iconUrl: 'dist/demo.svg',
|
},
|
||||||
keyboard: {
|
wrapper: {
|
||||||
global: true,
|
className: 'button--with-count',
|
||||||
},
|
},
|
||||||
tooltips: {
|
|
||||||
controls: true,
|
|
||||||
},
|
|
||||||
captions: {
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
ads: {
|
|
||||||
enabled: env.prod || env.dev,
|
|
||||||
publisherId: '918848828995742',
|
|
||||||
},
|
|
||||||
previewThumbnails: {
|
|
||||||
enabled: true,
|
|
||||||
src: [
|
|
||||||
'https://cdn.plyr.io/static/demo/thumbs/100p.vtt',
|
|
||||||
'https://cdn.plyr.io/static/demo/thumbs/240p.vtt',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
vimeo: {
|
|
||||||
// Prevent Vimeo blocking plyr.io demo site
|
|
||||||
referrerPolicy: 'no-referrer',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expose for tinkering in the console
|
|
||||||
window.player = player;
|
|
||||||
|
|
||||||
// Setup type toggle
|
|
||||||
const buttons = document.querySelectorAll('[data-source]');
|
|
||||||
const types = Object.keys(sources);
|
|
||||||
const historySupport = Boolean(window.history && window.history.pushState);
|
|
||||||
let currentType = window.location.hash.substring(1);
|
|
||||||
const hasCurrentType = !currentType.length;
|
|
||||||
|
|
||||||
function render(type) {
|
|
||||||
// Remove active classes
|
|
||||||
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
|
|
||||||
|
|
||||||
// Set active on parent
|
|
||||||
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
|
|
||||||
|
|
||||||
// Show cite
|
|
||||||
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
cite.hidden = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelector(`.plyr__cite--${type}`).hidden = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a new source
|
|
||||||
function setSource(type, init) {
|
|
||||||
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
|
|
||||||
if (
|
|
||||||
!types.includes(type) ||
|
|
||||||
(!init && type === currentType) ||
|
|
||||||
(!currentType.length && type === 'video')
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the new source
|
|
||||||
player.source = sources[type];
|
|
||||||
|
|
||||||
// Set the current type for next time
|
|
||||||
currentType = type;
|
|
||||||
|
|
||||||
render(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind to each button
|
|
||||||
Array.from(buttons).forEach(button => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
const type = button.getAttribute('data-source');
|
|
||||||
|
|
||||||
setSource(type);
|
|
||||||
|
|
||||||
if (historySupport) {
|
|
||||||
window.history.pushState({ type }, '', `#${type}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// List for backwards/forwards
|
|
||||||
window.addEventListener('popstate', event => {
|
|
||||||
if (event.state && Object.keys(event.state).includes('type')) {
|
|
||||||
setSource(event.state.type);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If there's no current type set, assume video
|
|
||||||
if (hasCurrentType) {
|
|
||||||
currentType = 'video';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace current history state
|
|
||||||
if (historySupport && types.includes(currentType)) {
|
|
||||||
window.history.replaceState({ type: currentType }, '', hasCurrentType ? '' : `#${currentType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's not video, load the source
|
|
||||||
if (currentType !== 'video') {
|
|
||||||
setSource(currentType, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
render(currentType);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Raven / Sentry
|
// Setup the player
|
||||||
// For demo site (https://plyr.io) only
|
const player = new Plyr(selector, {
|
||||||
if (env.prod) {
|
debug: true,
|
||||||
Raven.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
title: 'View From A Blue Moon',
|
||||||
|
iconUrl: 'dist/demo.svg',
|
||||||
|
keyboard: {
|
||||||
|
global: true,
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
controls: true,
|
||||||
|
},
|
||||||
|
captions: {
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
ads: {
|
||||||
|
enabled: window.location.host.includes(production),
|
||||||
|
publisherId: '918848828995742',
|
||||||
|
},
|
||||||
|
previewThumbnails: {
|
||||||
|
enabled: true,
|
||||||
|
src: ['https://cdn.plyr.io/static/demo/thumbs/100p.vtt', 'https://cdn.plyr.io/static/demo/thumbs/240p.vtt'],
|
||||||
|
},
|
||||||
|
vimeo: {
|
||||||
|
// Prevent Vimeo blocking plyr.io demo site
|
||||||
|
referrerPolicy: 'no-referrer',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Expose for tinkering in the console
|
||||||
|
window.player = player;
|
||||||
|
|
||||||
|
// Setup type toggle
|
||||||
|
const buttons = document.querySelectorAll('[data-source]');
|
||||||
|
const types = Object.keys(sources);
|
||||||
|
const historySupport = Boolean(window.history && window.history.pushState);
|
||||||
|
let currentType = window.location.hash.substring(1);
|
||||||
|
const hasInitialType = currentType.length;
|
||||||
|
|
||||||
|
function render(type) {
|
||||||
|
// Remove active classes
|
||||||
|
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
|
||||||
|
|
||||||
|
// Set active on parent
|
||||||
|
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
|
||||||
|
|
||||||
|
// Show cite
|
||||||
|
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
cite.hidden = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector(`.plyr__cite--${type}`).hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set a new source
|
||||||
|
function setSource(type, init) {
|
||||||
|
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
|
||||||
|
if (!types.includes(type) || (!init && type === currentType) || (!currentType.length && type === 'video')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new source
|
||||||
|
player.source = sources[type];
|
||||||
|
|
||||||
|
// Set the current type for next time
|
||||||
|
currentType = type;
|
||||||
|
|
||||||
|
render(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind to each button
|
||||||
|
Array.from(buttons).forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const type = button.getAttribute('data-source');
|
||||||
|
|
||||||
|
setSource(type);
|
||||||
|
|
||||||
|
if (historySupport) {
|
||||||
|
window.history.pushState({ type }, '', `#${type}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// List for backwards/forwards
|
||||||
|
window.addEventListener('popstate', event => {
|
||||||
|
if (event.state && Object.keys(event.state).includes('type')) {
|
||||||
|
setSource(event.state.type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there's no current type set, assume video
|
||||||
|
if (!hasInitialType) {
|
||||||
|
currentType = 'video';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace current history state
|
||||||
|
if (historySupport && types.includes(currentType)) {
|
||||||
|
window.history.replaceState({ type: currentType }, '', hasInitialType ? `#${currentType}` : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not video, load the source
|
||||||
|
if (currentType !== 'video') {
|
||||||
|
setSource(currentType, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(currentType);
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
+76
-73
@@ -1,78 +1,81 @@
|
|||||||
const sources = {
|
const sources = {
|
||||||
video: {
|
video: {
|
||||||
type: 'video',
|
type: 'video',
|
||||||
title: 'View From A Blue Moon',
|
title: 'View From A Blue Moon',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
size: 576,
|
size: 576,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
size: 720,
|
size: 720,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
size: 1080,
|
size: 1080,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
size: 1440,
|
size: 1440,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
||||||
tracks: [
|
tracks: [
|
||||||
{
|
{
|
||||||
kind: 'captions',
|
kind: 'captions',
|
||||||
label: 'English',
|
label: 'English',
|
||||||
srclang: 'en',
|
srclang: 'en',
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: 'captions',
|
kind: 'captions',
|
||||||
label: 'French',
|
label: 'French',
|
||||||
srclang: 'fr',
|
srclang: 'fr',
|
||||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
|
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
previewThumbnails: {
|
||||||
audio: {
|
src: ['https://cdn.plyr.io/static/demo/thumbs/100p.vtt', 'https://cdn.plyr.io/static/demo/thumbs/240p.vtt'],
|
||||||
type: 'audio',
|
|
||||||
title: 'Kishi Bashi – “It All Began With A Burst”',
|
|
||||||
sources: [
|
|
||||||
{
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
|
|
||||||
type: 'audio/mp3',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
|
|
||||||
type: 'audio/ogg',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
youtube: {
|
|
||||||
type: 'video',
|
|
||||||
sources: [
|
|
||||||
{
|
|
||||||
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
|
||||||
provider: 'youtube',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
vimeo: {
|
|
||||||
type: 'video',
|
|
||||||
sources: [
|
|
||||||
{
|
|
||||||
src: 'https://vimeo.com/40648169',
|
|
||||||
provider: 'vimeo',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
audio: {
|
||||||
|
type: 'audio',
|
||||||
|
title: 'Kishi Bashi – “It All Began With A Burst”',
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
|
||||||
|
type: 'audio/mp3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
|
||||||
|
type: 'audio/ogg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
youtube: {
|
||||||
|
type: 'video',
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
||||||
|
provider: 'youtube',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
vimeo: {
|
||||||
|
type: 'video',
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
src: 'https://vimeo.com/40648169',
|
||||||
|
provider: 'vimeo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default sources;
|
export default sources;
|
||||||
|
|||||||
+17
-17
@@ -4,28 +4,28 @@ const tabClassName = 'tab-focus';
|
|||||||
|
|
||||||
// Remove class on blur
|
// Remove class on blur
|
||||||
document.addEventListener('focusout', event => {
|
document.addEventListener('focusout', event => {
|
||||||
if (!event.target.classList || container.contains(event.target)) {
|
if (!event.target.classList || container.contains(event.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.target.classList.remove(tabClassName);
|
event.target.classList.remove(tabClassName);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add classname to tabbed elements
|
// Add classname to tabbed elements
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('keydown', event => {
|
||||||
if (event.keyCode !== 9) {
|
if (event.keyCode !== 9) {
|
||||||
return;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay the adding of classname until the focus has changed
|
focused.classList.add(tabClassName);
|
||||||
// This event fires before the focusin event
|
}, 10);
|
||||||
setTimeout(() => {
|
|
||||||
const focused = document.activeElement;
|
|
||||||
|
|
||||||
if (!focused || !focused.classList || container.contains(focused)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
focused.classList.add(tabClassName);
|
|
||||||
}, 10);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Toggle class on an element
|
// Toggle class on an element
|
||||||
const toggleClass = (element, className = '', toggle = false) =>
|
const toggleClass = (element, className = '', toggle = false) =>
|
||||||
element && element.classList[toggle ? 'add' : 'remove'](className);
|
element && element.classList[toggle ? 'add' : 'remove'](className);
|
||||||
|
|
||||||
export default toggleClass;
|
export default toggleClass;
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@charset 'UTF-8';
|
@charset 'UTF-8';
|
||||||
|
|
||||||
|
@import '../../../../src/sass/lib/css-vars';
|
||||||
|
$css-vars-use-native: true;
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
@import '../settings/breakpoints';
|
@import '../settings/breakpoints';
|
||||||
@import '../settings/colors';
|
@import '../settings/colors';
|
||||||
|
|||||||
@@ -5,80 +5,80 @@
|
|||||||
// Shared
|
// Shared
|
||||||
.button,
|
.button,
|
||||||
.button__count {
|
.button__count {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: $border-radius-base;
|
border-radius: $border-radius-base;
|
||||||
box-shadow: 0 1px 1px rgba(#000, 0.1);
|
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;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
.button {
|
.button {
|
||||||
background: $color-button-background;
|
background: $color-button-background;
|
||||||
color: $color-button-text;
|
color: $color-button-text;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
padding-left: ($spacing-base * 1.25);
|
padding-left: ($spacing-base * 1.25);
|
||||||
padding-right: ($spacing-base * 1.25);
|
padding-right: ($spacing-base * 1.25);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background: $color-button-background-hover;
|
background: $color-button-background-hover;
|
||||||
|
|
||||||
// Remove the underline/border
|
// Remove the underline/border
|
||||||
&::after {
|
&::after {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 2px 2px rgba(#000, 0.1);
|
box-shadow: 0 2px 2px rgba(#000, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.tab-focus {
|
&.tab-focus {
|
||||||
@include tab-focus();
|
@include tab-focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
top: 1px;
|
top: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Button group
|
// Button group
|
||||||
.button--with-count {
|
.button--with-count {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
||||||
.button .icon {
|
.button .icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count bubble
|
// Count bubble
|
||||||
.button__count {
|
.button__count {
|
||||||
animation: fadein 0.2s ease;
|
animation: fadein 0.2s ease;
|
||||||
background: $color-button-count-background;
|
background: $color-button-count-background;
|
||||||
color: $color-button-count-text;
|
color: $color-button-count-text;
|
||||||
margin-left: ($spacing-base * 0.75);
|
margin-left: ($spacing-base * 0.75);
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
border: $arrow-size solid transparent;
|
border: $arrow-size solid transparent;
|
||||||
border-left-width: 0;
|
border-left-width: 0;
|
||||||
border-right-color: $color-button-count-background;
|
border-right-color: $color-button-count-background;
|
||||||
content: '';
|
content: '';
|
||||||
height: 0;
|
height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 100%;
|
right: 100%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,28 +3,28 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
header {
|
header {
|
||||||
padding-bottom: $spacing-base;
|
padding-bottom: $spacing-base;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
h1 span {
|
h1 span {
|
||||||
animation: shrinkHide 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 2s forwards;
|
animation: shrinkHide 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 2s forwards;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-weight: $font-weight-light;
|
font-weight: $font-weight-light;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.call-to-action {
|
.call-to-action {
|
||||||
margin-top: ($spacing-base * 1.5);
|
margin-top: ($spacing-base * 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (min-width: $screen-md) {
|
@media only screen and (min-width: $screen-md) {
|
||||||
margin-right: ($spacing-base * 3);
|
margin-right: ($spacing-base * 3);
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
padding-bottom: ($spacing-base * 2);
|
padding-bottom: ($spacing-base * 2);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
p:first-of-type {
|
p:first-of-type {
|
||||||
@include font-size($font-size-base + 1);
|
@include font-size($font-size-base + 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,20 @@
|
|||||||
|
|
||||||
// Base size icon styles
|
// Base size icon styles
|
||||||
.icon {
|
.icon {
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
height: $icon-size;
|
height: $icon-size;
|
||||||
vertical-align: -3px;
|
vertical-align: -3px;
|
||||||
width: $icon-size;
|
width: $icon-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Within elements
|
// Within elements
|
||||||
a svg,
|
a svg,
|
||||||
button svg,
|
button svg,
|
||||||
label svg {
|
label svg {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a .icon,
|
a .icon,
|
||||||
.btn .icon {
|
.btn .icon {
|
||||||
margin-right: ($spacing-base / 2);
|
margin-right: ($spacing-base / 2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,45 +4,45 @@
|
|||||||
|
|
||||||
// Make a <button> look like an <a>
|
// Make a <button> look like an <a>
|
||||||
button.faux-link {
|
button.faux-link {
|
||||||
@extend a; // stylelint-disable-line
|
@extend a; // stylelint-disable-line
|
||||||
@include cancel-button-styles();
|
@include cancel-button-styles();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Links
|
// Links
|
||||||
a {
|
a {
|
||||||
border-bottom: 1px dotted currentColor;
|
border-bottom: 1px dotted currentColor;
|
||||||
color: $color-link;
|
color: $color-link;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background: currentColor;
|
||||||
|
content: '';
|
||||||
|
height: 1px;
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
transition: width 0.2s ease;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
outline: 0;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
background: currentColor;
|
width: 100%;
|
||||||
content: '';
|
|
||||||
height: 1px;
|
|
||||||
left: 50%;
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
transition: width 0.2s ease;
|
|
||||||
width: 0;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover,
|
&.tab-focus {
|
||||||
&:focus {
|
@include tab-focus();
|
||||||
border-bottom-color: transparent;
|
}
|
||||||
outline: 0;
|
|
||||||
|
|
||||||
&::after {
|
&.no-border::after {
|
||||||
width: 100%;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&.tab-focus {
|
|
||||||
@include tab-focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
&.no-border::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
// Lists
|
// Lists
|
||||||
ul,
|
ul,
|
||||||
li {
|
li {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
img,
|
img,
|
||||||
video,
|
video,
|
||||||
audio {
|
audio {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: $spacing-base;
|
margin-bottom: $spacing-base;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,33 +4,33 @@
|
|||||||
|
|
||||||
// Example players
|
// Example players
|
||||||
.plyr {
|
.plyr {
|
||||||
border-radius: $border-radius-base;
|
border-radius: $border-radius-base;
|
||||||
box-shadow: 0 2px 15px rgba(#000, 0.1);
|
box-shadow: 0 2px 15px rgba(#000, 0.1);
|
||||||
margin: $spacing-base auto;
|
margin: $spacing-base auto;
|
||||||
|
|
||||||
&.plyr--audio {
|
&.plyr--audio {
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plyr__video-wrapper::after {
|
.plyr__video-wrapper::after {
|
||||||
border: 1px solid rgba(#000, 0.15);
|
border: 1px solid rgba(#000, 0.15);
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
content: '';
|
content: '';
|
||||||
left: 0;
|
left: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style full supported player
|
// Style full supported player
|
||||||
.plyr__cite {
|
.plyr__cite {
|
||||||
color: $color-gray-5;
|
color: $color-gray-500;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: ceil($spacing-base / 6);
|
margin-right: ceil($spacing-base / 6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,60 +4,60 @@
|
|||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background: $page-background;
|
background: $page-background;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding-bottom: 1px; // Collapsing margins
|
padding-bottom: 1px; // Collapsing margins
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
aside {
|
aside {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: $spacing-base;
|
padding: $spacing-base;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
fill: $color-twitter;
|
fill: $color-twitter;
|
||||||
margin-right: ($spacing-base / 2);
|
margin-right: ($spacing-base / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $color-twitter;
|
color: $color-twitter;
|
||||||
|
|
||||||
&.tab-focus {
|
&.tab-focus {
|
||||||
@include tab-focus($color-twitter);
|
@include tab-focus($color-twitter);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,26 +5,26 @@
|
|||||||
// Error page
|
// Error page
|
||||||
html.error,
|
html.error,
|
||||||
.error body {
|
.error body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.error {
|
html.error {
|
||||||
background: $page-background;
|
background: $page-background;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error body {
|
.error body {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error main {
|
.error main {
|
||||||
padding: $spacing-base;
|
padding: $spacing-base;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@include font-size($font-size-large);
|
@include font-size($font-size-large);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,17 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: $spacing-base;
|
padding: $spacing-base;
|
||||||
|
|
||||||
@media only screen and (min-width: $screen-md) {
|
@media only screen and (min-width: $screen-md) {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: $container-max-width;
|
max-width: $container-max-width;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,24 @@
|
|||||||
|
|
||||||
// Fade
|
// Fade
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes shrinkHide {
|
@keyframes shrinkHide {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
width: 38px;
|
width: 38px;
|
||||||
}
|
}
|
||||||
20% {
|
20% {
|
||||||
width: 45px;
|
width: 45px;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,46 +3,46 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-light;
|
font-weight: $font-weight-light;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-light.woff2') format('woff2'),
|
src: url('https://cdn.plyr.io/static/fonts/gordita-light.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/gordita-light.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/gordita-light.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-regular;
|
font-weight: $font-weight-regular;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-regular.woff2') format('woff2'),
|
src: url('https://cdn.plyr.io/static/fonts/gordita-regular.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/gordita-regular.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/gordita-regular.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-medium.woff2') format('woff2'),
|
src: url('https://cdn.plyr.io/static/fonts/gordita-medium.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/gordita-medium.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/gordita-medium.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-bold.woff2') format('woff2'),
|
src: url('https://cdn.plyr.io/static/fonts/gordita-bold.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/gordita-bold.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/gordita-bold.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-family: 'Gordita';
|
font-family: 'Gordita';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: $font-weight-black;
|
font-weight: $font-weight-black;
|
||||||
src: url('https://cdn.plyr.io/static/fonts/gordita-black.woff2') format('woff2'),
|
src: url('https://cdn.plyr.io/static/fonts/gordita-black.woff2') format('woff2'),
|
||||||
url('https://cdn.plyr.io/static/fonts/gordita-black.woff') format('woff');
|
url('https://cdn.plyr.io/static/fonts/gordita-black.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,50 +5,50 @@
|
|||||||
// Convert a <button> into an <a>
|
// Convert a <button> into an <a>
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@mixin cancel-button-styles() {
|
@mixin cancel-button-styles() {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
line-height: $line-height-base;
|
line-height: $line-height-base;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: inherit;
|
text-align: inherit;
|
||||||
text-shadow: inherit;
|
text-shadow: inherit;
|
||||||
-moz-user-select: text; // stylelint-disable-line
|
-moz-user-select: text; // stylelint-disable-line
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nicer focus styles
|
// Nicer focus styles
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@mixin tab-focus($color: $tab-focus-default-color) {
|
@mixin tab-focus($color: $tab-focus-default-color) {
|
||||||
box-shadow: 0 0 0 3px rgba($color, 0.35);
|
box-shadow: 0 0 0 3px rgba($color, 0.35);
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use rems for font sizing
|
// Use rems for font sizing
|
||||||
// Leave <body> at 100%/16px
|
// Leave <body> at 100%/16px
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@function calculate-rem($size) {
|
@function calculate-rem($size) {
|
||||||
$rem: $size / 16;
|
$rem: $size / 16;
|
||||||
@return #{$rem}rem;
|
@return #{$rem}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin font-size($size: $font-size-base) {
|
@mixin font-size($size: $font-size-base) {
|
||||||
font-size: $size * 1px; // Fallback in px
|
font-size: $size * 1px; // Fallback in px
|
||||||
font-size: calculate-rem($size);
|
font-size: calculate-rem($size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Font smoothing
|
// Font smoothing
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
@mixin font-smoothing($enabled: true) {
|
@mixin font-smoothing($enabled: true) {
|
||||||
@if $enabled {
|
@if $enabled {
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
} @else {
|
} @else {
|
||||||
-moz-osx-font-smoothing: auto;
|
-moz-osx-font-smoothing: auto;
|
||||||
-webkit-font-smoothing: subpixel-antialiased;
|
-webkit-font-smoothing: subpixel-antialiased;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+74
-74
@@ -10,9 +10,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
html {
|
html {
|
||||||
line-height: 1.15; /* 1 */
|
line-height: 1.15; /* 1 */
|
||||||
-ms-text-size-adjust: 100%; /* 2 */
|
-ms-text-size-adjust: 100%; /* 2 */
|
||||||
-webkit-text-size-adjust: 100%; /* 2 */
|
-webkit-text-size-adjust: 100%; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sections
|
/* Sections
|
||||||
@@ -23,7 +23,7 @@ html {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,7 +36,7 @@ footer,
|
|||||||
header,
|
header,
|
||||||
nav,
|
nav,
|
||||||
section {
|
section {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,8 +45,8 @@ section {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
margin: 0.67em 0;
|
margin: 0.67em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Grouping content
|
/* Grouping content
|
||||||
@@ -60,8 +60,8 @@ h1 {
|
|||||||
figcaption,
|
figcaption,
|
||||||
figure,
|
figure,
|
||||||
main {
|
main {
|
||||||
/* 1 */
|
/* 1 */
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +69,7 @@ main {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
figure {
|
figure {
|
||||||
margin: 1em 40px;
|
margin: 1em 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,9 +78,9 @@ figure {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
box-sizing: content-box; /* 1 */
|
box-sizing: content-box; /* 1 */
|
||||||
height: 0; /* 1 */
|
height: 0; /* 1 */
|
||||||
overflow: visible; /* 2 */
|
overflow: visible; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,8 +89,8 @@ hr {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
font-family: monospace, monospace; /* 1 */
|
font-family: monospace, monospace; /* 1 */
|
||||||
font-size: 1em; /* 2 */
|
font-size: 1em; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Text-level semantics
|
/* Text-level semantics
|
||||||
@@ -102,8 +102,8 @@ pre {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
a {
|
a {
|
||||||
background-color: transparent; /* 1 */
|
background-color: transparent; /* 1 */
|
||||||
-webkit-text-decoration-skip: objects; /* 2 */
|
-webkit-text-decoration-skip: objects; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,9 +112,9 @@ a {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
abbr[title] {
|
abbr[title] {
|
||||||
border-bottom: none; /* 1 */
|
border-bottom: none; /* 1 */
|
||||||
text-decoration: underline; /* 2 */
|
text-decoration: underline; /* 2 */
|
||||||
text-decoration: underline dotted; /* 2 */
|
text-decoration: underline dotted; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,7 +123,7 @@ abbr[title] {
|
|||||||
|
|
||||||
b,
|
b,
|
||||||
strong {
|
strong {
|
||||||
font-weight: inherit;
|
font-weight: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,7 +132,7 @@ strong {
|
|||||||
|
|
||||||
b,
|
b,
|
||||||
strong {
|
strong {
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,8 +143,8 @@ strong {
|
|||||||
code,
|
code,
|
||||||
kbd,
|
kbd,
|
||||||
samp {
|
samp {
|
||||||
font-family: monospace, monospace; /* 1 */
|
font-family: monospace, monospace; /* 1 */
|
||||||
font-size: 1em; /* 2 */
|
font-size: 1em; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,7 +152,7 @@ samp {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
dfn {
|
dfn {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,8 +160,8 @@ dfn {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
mark {
|
mark {
|
||||||
background-color: #ff0;
|
background-color: #ff0;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -169,7 +169,7 @@ mark {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
small {
|
small {
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -179,18 +179,18 @@ small {
|
|||||||
|
|
||||||
sub,
|
sub,
|
||||||
sup {
|
sup {
|
||||||
font-size: 75%;
|
font-size: 75%;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub {
|
sub {
|
||||||
bottom: -0.25em;
|
bottom: -0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
sup {
|
sup {
|
||||||
top: -0.5em;
|
top: -0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Embedded content
|
/* Embedded content
|
||||||
@@ -202,7 +202,7 @@ sup {
|
|||||||
|
|
||||||
audio,
|
audio,
|
||||||
video {
|
video {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,8 +210,8 @@ video {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
audio:not([controls]) {
|
audio:not([controls]) {
|
||||||
display: none;
|
display: none;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -219,7 +219,7 @@ audio:not([controls]) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border-style: none;
|
border-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -227,7 +227,7 @@ img {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
svg:not(:root) {
|
svg:not(:root) {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Forms
|
/* Forms
|
||||||
@@ -243,10 +243,10 @@ input,
|
|||||||
optgroup,
|
optgroup,
|
||||||
select,
|
select,
|
||||||
textarea {
|
textarea {
|
||||||
font-family: sans-serif; /* 1 */
|
font-family: sans-serif; /* 1 */
|
||||||
font-size: 100%; /* 1 */
|
font-size: 100%; /* 1 */
|
||||||
line-height: 1.15; /* 1 */
|
line-height: 1.15; /* 1 */
|
||||||
margin: 0; /* 2 */
|
margin: 0; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -256,8 +256,8 @@ textarea {
|
|||||||
|
|
||||||
button,
|
button,
|
||||||
input {
|
input {
|
||||||
/* 1 */
|
/* 1 */
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -267,8 +267,8 @@ input {
|
|||||||
|
|
||||||
button,
|
button,
|
||||||
select {
|
select {
|
||||||
/* 1 */
|
/* 1 */
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -281,7 +281,7 @@ button,
|
|||||||
html [type='button'],
|
html [type='button'],
|
||||||
[type='reset'],
|
[type='reset'],
|
||||||
[type='submit'] {
|
[type='submit'] {
|
||||||
-webkit-appearance: button; /* 2 */
|
-webkit-appearance: button; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -292,8 +292,8 @@ button::-moz-focus-inner,
|
|||||||
[type='button']::-moz-focus-inner,
|
[type='button']::-moz-focus-inner,
|
||||||
[type='reset']::-moz-focus-inner,
|
[type='reset']::-moz-focus-inner,
|
||||||
[type='submit']::-moz-focus-inner {
|
[type='submit']::-moz-focus-inner {
|
||||||
border-style: none;
|
border-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -304,7 +304,7 @@ button:-moz-focusring,
|
|||||||
[type='button']:-moz-focusring,
|
[type='button']:-moz-focusring,
|
||||||
[type='reset']:-moz-focusring,
|
[type='reset']:-moz-focusring,
|
||||||
[type='submit']:-moz-focusring {
|
[type='submit']:-moz-focusring {
|
||||||
outline: 1px dotted ButtonText;
|
outline: 1px dotted ButtonText;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -312,7 +312,7 @@ button:-moz-focusring,
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
padding: 0.35em 0.75em 0.625em;
|
padding: 0.35em 0.75em 0.625em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -323,12 +323,12 @@ fieldset {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
legend {
|
legend {
|
||||||
box-sizing: border-box; /* 1 */
|
box-sizing: border-box; /* 1 */
|
||||||
color: inherit; /* 2 */
|
color: inherit; /* 2 */
|
||||||
display: table; /* 1 */
|
display: table; /* 1 */
|
||||||
max-width: 100%; /* 1 */
|
max-width: 100%; /* 1 */
|
||||||
padding: 0; /* 3 */
|
padding: 0; /* 3 */
|
||||||
white-space: normal; /* 1 */
|
white-space: normal; /* 1 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -337,8 +337,8 @@ legend {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
progress {
|
progress {
|
||||||
display: inline-block; /* 1 */
|
display: inline-block; /* 1 */
|
||||||
vertical-align: baseline; /* 2 */
|
vertical-align: baseline; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -346,7 +346,7 @@ progress {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -356,8 +356,8 @@ textarea {
|
|||||||
|
|
||||||
[type='checkbox'],
|
[type='checkbox'],
|
||||||
[type='radio'] {
|
[type='radio'] {
|
||||||
box-sizing: border-box; /* 1 */
|
box-sizing: border-box; /* 1 */
|
||||||
padding: 0; /* 2 */
|
padding: 0; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -366,7 +366,7 @@ textarea {
|
|||||||
|
|
||||||
[type='number']::-webkit-inner-spin-button,
|
[type='number']::-webkit-inner-spin-button,
|
||||||
[type='number']::-webkit-outer-spin-button {
|
[type='number']::-webkit-outer-spin-button {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -375,8 +375,8 @@ textarea {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
[type='search'] {
|
[type='search'] {
|
||||||
-webkit-appearance: textfield; /* 1 */
|
-webkit-appearance: textfield; /* 1 */
|
||||||
outline-offset: -2px; /* 2 */
|
outline-offset: -2px; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -385,7 +385,7 @@ textarea {
|
|||||||
|
|
||||||
[type='search']::-webkit-search-cancel-button,
|
[type='search']::-webkit-search-cancel-button,
|
||||||
[type='search']::-webkit-search-decoration {
|
[type='search']::-webkit-search-decoration {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -394,8 +394,8 @@ textarea {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
::-webkit-file-upload-button {
|
||||||
-webkit-appearance: button; /* 1 */
|
-webkit-appearance: button; /* 1 */
|
||||||
font: inherit; /* 2 */
|
font: inherit; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Interactive
|
/* Interactive
|
||||||
@@ -408,7 +408,7 @@ textarea {
|
|||||||
|
|
||||||
details,
|
details,
|
||||||
menu {
|
menu {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -416,7 +416,7 @@ menu {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
summary {
|
summary {
|
||||||
display: list-item;
|
display: list-item;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scripting
|
/* Scripting
|
||||||
@@ -427,7 +427,7 @@ summary {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -435,7 +435,7 @@ canvas {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
template {
|
template {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hidden
|
/* Hidden
|
||||||
@@ -446,5 +446,5 @@ template {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
[hidden] {
|
[hidden] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
*,
|
*,
|
||||||
*::after,
|
*::after,
|
||||||
*::before {
|
*::before {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,22 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
// Grayscale
|
// Grayscale
|
||||||
$color-gray-9: hsl(210, 15%, 16%);
|
$color-gray-900: hsl(210, 15%, 16%);
|
||||||
$color-gray-8: lighten($color-gray-9, 9%);
|
$color-gray-800: lighten($color-gray-900, 9%);
|
||||||
$color-gray-7: lighten($color-gray-8, 9%);
|
$color-gray-700: lighten($color-gray-800, 9%);
|
||||||
$color-gray-6: lighten($color-gray-7, 9%);
|
$color-gray-600: lighten($color-gray-700, 9%);
|
||||||
$color-gray-5: lighten($color-gray-6, 9%);
|
$color-gray-500: lighten($color-gray-600, 9%);
|
||||||
$color-gray-4: lighten($color-gray-5, 9%);
|
$color-gray-400: lighten($color-gray-500, 9%);
|
||||||
$color-gray-3: lighten($color-gray-4, 9%);
|
$color-gray-300: lighten($color-gray-400, 9%);
|
||||||
$color-gray-2: lighten($color-gray-3, 9%);
|
$color-gray-200: lighten($color-gray-300, 9%);
|
||||||
$color-gray-1: lighten($color-gray-2, 9%);
|
$color-gray-100: lighten($color-gray-200, 9%);
|
||||||
$color-gray-0: lighten($color-gray-1, 9%);
|
$color-gray-50: lighten($color-gray-100, 9%);
|
||||||
|
|
||||||
// Branding
|
// Branding
|
||||||
$color-brand-primary: hsl(198, 100%, 50%);
|
$color-brand-primary: hsl(198, 100%, 50%);
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
$color-text: $color-gray-7;
|
$color-text: $color-gray-700;
|
||||||
$color-headings: $color-brand-primary;
|
$color-headings: $color-brand-primary;
|
||||||
|
|
||||||
// Brands
|
// Brands
|
||||||
@@ -36,7 +36,7 @@ $color-button-background: $color-brand-primary;
|
|||||||
$color-button-text: #fff;
|
$color-button-text: #fff;
|
||||||
$color-button-background-hover: hsl(198, 100%, 55%);
|
$color-button-background-hover: hsl(198, 100%, 55%);
|
||||||
$color-button-count-background: #fff;
|
$color-button-count-background: #fff;
|
||||||
$color-button-count-text: $color-gray-6;
|
$color-button-count-text: $color-gray-600;
|
||||||
|
|
||||||
// Focus
|
// Focus
|
||||||
$tab-focus-default-color: #fff;
|
$tab-focus-default-color: #fff;
|
||||||
|
|||||||
@@ -2,24 +2,17 @@
|
|||||||
// Plyr Settings
|
// Plyr Settings
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
// Font
|
@include css-vars(
|
||||||
$plyr-font-family: inherit;
|
(
|
||||||
|
--plyr-color-main: $color-brand-primary,
|
||||||
// Sizes
|
--plyr-font-size-base: 13px,
|
||||||
$plyr-font-size-base: 13px;
|
--plyr-font-size-small: 12px,
|
||||||
$plyr-font-size-small: 12px;
|
--plyr-font-size-time: 11px,
|
||||||
$plyr-font-size-time: 11px;
|
--plyr-font-size-badges: 9px,
|
||||||
$plyr-font-size-badges: 9px;
|
--plyr-font-size-menu: var(--plyr-font-size-base),
|
||||||
|
--plyr-font-weight-regular: 500,
|
||||||
// Other
|
--plyr-font-weight-bold: 600,
|
||||||
$plyr-font-smoothing: true;
|
--plyr-font-size-captions-medium: 18px,
|
||||||
|
--plyr-font-size-captions-large: 21px,
|
||||||
// Colors
|
)
|
||||||
$plyr-color-main: $color-brand-primary;
|
);
|
||||||
|
|
||||||
// Captions
|
|
||||||
$plyr-font-size-captions-base: $plyr-font-size-base;
|
|
||||||
$plyr-font-size-captions-small: $plyr-font-size-small;
|
|
||||||
$plyr-font-size-captions-medium: 18px;
|
|
||||||
$plyr-font-size-captions-large: 21px;
|
|
||||||
$plyr-font-size-menu: $plyr-font-size-base;
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
$font-sans-serif: 'Gordita', 'Avenir', 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
$font-sans-serif: 'Gordita', 'Avenir', 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||||
'Segoe UI Symbol';
|
'Segoe UI Symbol';
|
||||||
|
|
||||||
$font-size-base: 15;
|
$font-size-base: 15;
|
||||||
$font-size-small: 13;
|
$font-size-small: 13;
|
||||||
|
|||||||
@@ -4,31 +4,31 @@
|
|||||||
|
|
||||||
// Set to 100% for rem sizing
|
// Set to 100% for rem sizing
|
||||||
html {
|
html {
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@include font-smoothing();
|
@include font-smoothing();
|
||||||
@include font-size($font-size-base);
|
@include font-size($font-size-base);
|
||||||
color: $color-text;
|
color: $color-text;
|
||||||
font-family: $font-sans-serif;
|
font-family: $font-sans-serif;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
line-height: $line-height-base;
|
line-height: $line-height-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
input,
|
input,
|
||||||
select,
|
select,
|
||||||
textarea {
|
textarea {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
p,
|
p,
|
||||||
small {
|
small {
|
||||||
margin: 0 0 ($spacing-base * 1.5);
|
margin: 0 0 ($spacing-base * 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
small {
|
small {
|
||||||
@include font-size($font-size-small);
|
@include font-size($font-size-small);
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@include font-size($font-size-h1);
|
@include font-size($font-size-h1);
|
||||||
color: $color-headings;
|
color: $color-headings;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
letter-spacing: $letter-spacing-headings;
|
letter-spacing: $letter-spacing-headings;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
margin: 0 0 ($spacing-base * 1.5);
|
margin: 0 0 ($spacing-base * 1.5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
.no-border {
|
.no-border {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,18 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
[hidden] {
|
[hidden] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide only visually, but have it available for screen readers: h5bp.com/v
|
// Hide only visually, but have it available for screen readers: h5bp.com/v
|
||||||
.sr-only {
|
.sr-only {
|
||||||
border: 0;
|
border: 0;
|
||||||
clip: rect(0 0 0 0);
|
clip: rect(0 0 0 0);
|
||||||
height: 1px;
|
height: 1px;
|
||||||
margin: -1px;
|
margin: -1px;
|
||||||
opacity: 0.001;
|
opacity: 0.001;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-13
@@ -2,27 +2,79 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
core-js@^3.1.4:
|
"@sentry/browser@^5.15.5":
|
||||||
version "3.1.4"
|
version "5.15.5"
|
||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.4.tgz#3a2837fc48e582e1ae25907afcd6cf03b0cc7a07"
|
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.15.5.tgz#d9a51f1388581067b50d30ed9b1aed2cbb333a36"
|
||||||
integrity sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==
|
integrity sha512-rqDvjk/EvogfdbZ4TiEpxM/lwpPKmq23z9YKEO4q81+1SwJNua53H60dOk9HpRU8nOJ1g84TMKT2Ov8H7sqDWA==
|
||||||
|
dependencies:
|
||||||
|
"@sentry/core" "5.15.5"
|
||||||
|
"@sentry/types" "5.15.5"
|
||||||
|
"@sentry/utils" "5.15.5"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
|
"@sentry/core@5.15.5":
|
||||||
|
version "5.15.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.15.5.tgz#40ea79bff5272d3fbbeeb4a98cdc59e1adbd2c92"
|
||||||
|
integrity sha512-enxBLv5eibBMqcWyr+vApqeix8uqkfn0iGsD3piKvoMXCgKsrfMwlb/qo9Ox0lKr71qIlZVt+9/A2vZohdgnlg==
|
||||||
|
dependencies:
|
||||||
|
"@sentry/hub" "5.15.5"
|
||||||
|
"@sentry/minimal" "5.15.5"
|
||||||
|
"@sentry/types" "5.15.5"
|
||||||
|
"@sentry/utils" "5.15.5"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
|
"@sentry/hub@5.15.5":
|
||||||
|
version "5.15.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.15.5.tgz#f5abbcdbe656a70e2ff02c02a5a4cffa0f125935"
|
||||||
|
integrity sha512-zX9o49PcNIVMA4BZHe//GkbQ4Jx+nVofqU/Il32/IbwKhcpPlhGX3c1sOVQo4uag3cqd/JuQsk+DML9TKkN0Lw==
|
||||||
|
dependencies:
|
||||||
|
"@sentry/types" "5.15.5"
|
||||||
|
"@sentry/utils" "5.15.5"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
|
"@sentry/minimal@5.15.5":
|
||||||
|
version "5.15.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.15.5.tgz#a0e4e071f01d9c4d808094ae7203f6c4cca9348a"
|
||||||
|
integrity sha512-zQkkJ1l9AjmU/Us5IrOTzu7bic4sTPKCatptXvLSTfyKW7N6K9MPIIFeSpZf9o1yM2sRYdK7GV08wS2eCT3JYw==
|
||||||
|
dependencies:
|
||||||
|
"@sentry/hub" "5.15.5"
|
||||||
|
"@sentry/types" "5.15.5"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
|
"@sentry/types@5.15.5":
|
||||||
|
version "5.15.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.15.5.tgz#16c97e464cf09bbd1d2e8ce90d130e781709076e"
|
||||||
|
integrity sha512-F9A5W7ucgQLJUG4LXw1ZIy4iLevrYZzbeZ7GJ09aMlmXH9PqGThm1t5LSZlVpZvUfQ2rYA8NU6BdKJSt7B5LPw==
|
||||||
|
|
||||||
|
"@sentry/utils@5.15.5":
|
||||||
|
version "5.15.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.15.5.tgz#dec1d4c79037c4da08b386f5d34409234dcbfb15"
|
||||||
|
integrity sha512-Nl9gl/MGnzSkuKeo3QaefoD/OJrFLB8HmwQ7HUbTXb6E7yyEzNKAQMHXGkwNAjbdYyYbd42iABP6Y5F/h39NtA==
|
||||||
|
dependencies:
|
||||||
|
"@sentry/types" "5.15.5"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
|
core-js@^3.6.5:
|
||||||
|
version "3.6.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
|
||||||
|
integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
|
||||||
|
|
||||||
custom-event-polyfill@^1.0.7:
|
custom-event-polyfill@^1.0.7:
|
||||||
version "1.0.7"
|
version "1.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee"
|
resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee"
|
||||||
integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==
|
integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==
|
||||||
|
|
||||||
raven-js@^3.27.2:
|
|
||||||
version "3.27.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.27.2.tgz#6c33df952026cd73820aa999122b7b7737a66775"
|
|
||||||
integrity sha512-mFWQcXnhRFEQe5HeFroPaEghlnqy7F5E2J3Fsab189ondqUzcjwSVi7el7F36cr6PvQYXoZ1P2F5CSF2/azeMQ==
|
|
||||||
|
|
||||||
shr-buttons@2.0.3:
|
shr-buttons@2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/shr-buttons/-/shr-buttons-2.0.3.tgz#2ffd021fc3d789e1510ce2736b938bd09ea1da5a"
|
resolved "https://registry.yarnpkg.com/shr-buttons/-/shr-buttons-2.0.3.tgz#2ffd021fc3d789e1510ce2736b938bd09ea1da5a"
|
||||||
integrity sha512-sPAgHiw4uaIt9TnxTfyZEedDChcldSVtnBHE44cpe/mSC7rqm4IEKZRLYqnVlTcGM+FSDNBPUNpSf50Q2ntd+w==
|
integrity sha512-sPAgHiw4uaIt9TnxTfyZEedDChcldSVtnBHE44cpe/mSC7rqm4IEKZRLYqnVlTcGM+FSDNBPUNpSf50Q2ntd+w==
|
||||||
|
|
||||||
url-polyfill@^1.1.5:
|
tslib@^1.9.3:
|
||||||
version "1.1.5"
|
version "1.11.1"
|
||||||
resolved "https://registry.yarnpkg.com/url-polyfill/-/url-polyfill-1.1.5.tgz#bec79b72b5407dba6d8cced2e32e4ab273aa9fb1"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
|
||||||
integrity sha512-9XjIJ6nwrU+nGd8t90Ze0Zs7t8A+SU0gqsqPttj6j3zAVe5q0HFcuv37nDBdVSPpi4aTHTfbUF/i+ZVD+o2EbA==
|
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
|
||||||
|
|
||||||
|
url-polyfill@^1.1.8:
|
||||||
|
version "1.1.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/url-polyfill/-/url-polyfill-1.1.8.tgz#21eb58ad61192f52b77dcac8ab5293ae7bc67060"
|
||||||
|
integrity sha512-Ey61F4FEqhcu1vHSOMmjl0Vd/RPRLEjMj402qszD/dhMBrVfoUsnIj8KSZo2yj+eIlxJGKFdnm6ES+7UzMgZ3Q==
|
||||||
|
|||||||
+10
-10
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"cdn": {
|
"cdn": {
|
||||||
"bucket": "plyr",
|
"bucket": "plyr",
|
||||||
"domain": "cdn.plyr.io",
|
"domain": "cdn.plyr.io",
|
||||||
"region": "us-east-1"
|
"region": "us-east-1"
|
||||||
},
|
},
|
||||||
"demo": {
|
"demo": {
|
||||||
"bucket": "plyr.io",
|
"bucket": "plyr.io",
|
||||||
"domain": "plyr.io",
|
"domain": "plyr.io",
|
||||||
"region": "us-west-1"
|
"region": "us-west-1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+339
-171
@@ -75,6 +75,42 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _objectWithoutPropertiesLoose(source, excluded) {
|
||||||
|
if (source == null) return {};
|
||||||
|
var target = {};
|
||||||
|
var sourceKeys = Object.keys(source);
|
||||||
|
var key, i;
|
||||||
|
|
||||||
|
for (i = 0; i < sourceKeys.length; i++) {
|
||||||
|
key = sourceKeys[i];
|
||||||
|
if (excluded.indexOf(key) >= 0) continue;
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _objectWithoutProperties(source, excluded) {
|
||||||
|
if (source == null) return {};
|
||||||
|
|
||||||
|
var target = _objectWithoutPropertiesLoose(source, excluded);
|
||||||
|
|
||||||
|
var key, i;
|
||||||
|
|
||||||
|
if (Object.getOwnPropertySymbols) {
|
||||||
|
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
|
||||||
|
|
||||||
|
for (i = 0; i < sourceSymbolKeys.length; i++) {
|
||||||
|
key = sourceSymbolKeys[i];
|
||||||
|
if (excluded.indexOf(key) >= 0) continue;
|
||||||
|
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
function _slicedToArray(arr, i) {
|
function _slicedToArray(arr, i) {
|
||||||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
||||||
}
|
}
|
||||||
@@ -437,7 +473,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var isPromise = function isPromise(input) {
|
var isPromise = function isPromise(input) {
|
||||||
return instanceOf$1(input, Promise);
|
return instanceOf$1(input, Promise) && isFunction$1(input.then);
|
||||||
};
|
};
|
||||||
|
|
||||||
var isEmpty$1 = function isEmpty(input) {
|
var isEmpty$1 = function isEmpty(input) {
|
||||||
@@ -779,12 +815,33 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
} // Element matches selector
|
} // Element matches selector
|
||||||
|
|
||||||
function matches$1(element, selector) {
|
function matches$1(element, selector) {
|
||||||
|
var _Element = Element,
|
||||||
|
prototype = _Element.prototype;
|
||||||
|
|
||||||
function match() {
|
function match() {
|
||||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
var method = match;
|
var method = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
|
||||||
|
return method.call(element, selector);
|
||||||
|
} // Closest ancestor element matching selector (also tests element itself)
|
||||||
|
|
||||||
|
function closest(element, selector) {
|
||||||
|
var _Element2 = Element,
|
||||||
|
prototype = _Element2.prototype; // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
|
||||||
|
|
||||||
|
function closestElement() {
|
||||||
|
var el = this;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (matches$1.matches(el, selector)) return el;
|
||||||
|
el = el.parentElement || el.parentNode;
|
||||||
|
} while (el !== null && el.nodeType === 1);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var method = prototype.closest || closestElement;
|
||||||
return method.call(element, selector);
|
return method.call(element, selector);
|
||||||
} // Find all elements
|
} // Find all elements
|
||||||
|
|
||||||
@@ -1056,6 +1113,19 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
}).then(function () {});
|
}).then(function () {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Silence a Promise-like object.
|
||||||
|
* This is useful for avoiding non-harmful, but potentially confusing "uncaught
|
||||||
|
* play promise" rejection error messages.
|
||||||
|
* @param {Object} value An object that may or may not be `Promise`-like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function silencePromise(value) {
|
||||||
|
if (is$1.promise(value)) {
|
||||||
|
value.then(null, function () {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateRatio(input) {
|
function validateRatio(input) {
|
||||||
if (!is$1.array(input) && (!is$1.string(input) || !input.includes(':'))) {
|
if (!is$1.array(input) && (!is$1.string(input) || !input.includes(':'))) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1124,8 +1194,8 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
var padding = 100 / w * h;
|
var padding = 100 / w * h;
|
||||||
wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||||
|
|
||||||
if (this.isVimeo && this.supported.ui) {
|
if (this.isVimeo && !this.config.vimeo.premium && this.supported.ui) {
|
||||||
var height = 240;
|
var height = 100 / this.media.offsetWidth * parseInt(window.getComputedStyle(this.media).paddingBottom, 10);
|
||||||
var offset = (height - padding) / (height / 50);
|
var offset = (height - padding) / (height / 50);
|
||||||
this.media.style.transform = "translateY(-".concat(offset, "%)");
|
this.media.style.transform = "translateY(-".concat(offset, "%)");
|
||||||
} else if (this.isHTML5) {
|
} else if (this.isHTML5) {
|
||||||
@@ -1232,7 +1302,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
player.currentTime = currentTime; // Resume playing
|
player.currentTime = currentTime; // Resume playing
|
||||||
|
|
||||||
if (!paused) {
|
if (!paused) {
|
||||||
player.play();
|
silencePromise(player.play());
|
||||||
}
|
}
|
||||||
}); // Load new source
|
}); // Load new source
|
||||||
|
|
||||||
@@ -1281,7 +1351,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
});
|
});
|
||||||
} // Get the closest value in an array
|
} // Get the closest value in an array
|
||||||
|
|
||||||
function closest(array, value) {
|
function closest$1(array, value) {
|
||||||
if (!is$1.array(array) || !array.length) {
|
if (!is$1.array(array) || !array.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1319,19 +1389,19 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
return (current / max * 100).toFixed(2);
|
return (current / max * 100).toFixed(2);
|
||||||
} // Replace all occurances of a string in a string
|
} // Replace all occurances of a string in a string
|
||||||
|
|
||||||
function replaceAll() {
|
var replaceAll = function replaceAll() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
||||||
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
||||||
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
} // Convert to title case
|
}; // Convert to title case
|
||||||
|
|
||||||
function toTitleCase() {
|
var toTitleCase = function toTitleCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
return input.toString().replace(/\w\S*/g, function (text) {
|
return input.toString().replace(/\w\S*/g, function (text) {
|
||||||
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
||||||
});
|
});
|
||||||
} // Convert string to pascalCase
|
}; // Convert string to pascalCase
|
||||||
|
|
||||||
function toPascalCase() {
|
function toPascalCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
@@ -2429,39 +2499,39 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// Set the looping options
|
// Set the looping options
|
||||||
|
|
||||||
/* setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
// Menu required
|
// Menu required
|
||||||
if (!is.element(this.elements.settings.panels.loop)) {
|
if (!is.element(this.elements.settings.panels.loop)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
||||||
// Show the pane and tab
|
// Show the pane and tab
|
||||||
toggleHidden(this.elements.settings.buttons.loop, false);
|
toggleHidden(this.elements.settings.buttons.loop, false);
|
||||||
toggleHidden(this.elements.settings.panels.loop, false);
|
toggleHidden(this.elements.settings.panels.loop, false);
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !is.empty(this.loop.options);
|
const toggle = !is.empty(this.loop.options);
|
||||||
controls.toggleMenuButton.call(this, 'loop', toggle);
|
controls.toggleMenuButton.call(this, 'loop', toggle);
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
emptyElement(list);
|
emptyElement(list);
|
||||||
options.forEach(option => {
|
options.forEach(option => {
|
||||||
const item = createElement('li');
|
const item = createElement('li');
|
||||||
const button = createElement(
|
const button = createElement(
|
||||||
'button',
|
'button',
|
||||||
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: this.config.classNames.control,
|
class: this.config.classNames.control,
|
||||||
'data-plyr-loop-action': option,
|
'data-plyr-loop-action': option,
|
||||||
}),
|
}),
|
||||||
i18n.get(option, this.config)
|
i18n.get(option, this.config)
|
||||||
);
|
);
|
||||||
if (['start', 'end'].includes(option)) {
|
if (['start', 'end'].includes(option)) {
|
||||||
const badge = controls.createBadge.call(this, '00:00');
|
const badge = controls.createBadge.call(this, '00:00');
|
||||||
button.appendChild(badge);
|
button.appendChild(badge);
|
||||||
}
|
}
|
||||||
item.appendChild(button);
|
item.appendChild(button);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
}, */
|
}, */
|
||||||
// Get current selected caption language
|
// Get current selected caption language
|
||||||
// TODO: rework this to user the getter in the API?
|
// TODO: rework this to user the getter in the API?
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
@@ -3263,9 +3333,15 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
meta.set(track, {
|
meta.set(track, {
|
||||||
default: track.mode === 'showing'
|
default: track.mode === 'showing'
|
||||||
}); // Turn off native caption rendering to avoid double captions
|
}); // Turn off native caption rendering to avoid double captions
|
||||||
|
// Note: mode='hidden' forces a track to download. To ensure every track
|
||||||
|
// isn't downloaded at once, only 'showing' tracks should be reassigned
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
|
||||||
track.mode = 'hidden'; // Add event listener for cue changes
|
if (track.mode === 'showing') {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
track.mode = 'hidden';
|
||||||
|
} // Add event listener for cue changes
|
||||||
|
|
||||||
|
|
||||||
on.call(_this, track, 'cuechange', function () {
|
on.call(_this, track, 'cuechange', function () {
|
||||||
return captions.updateCues.call(_this);
|
return captions.updateCues.call(_this);
|
||||||
@@ -3289,6 +3365,8 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// Toggle captions display
|
// Toggle captions display
|
||||||
// Used internally for the toggleCaptions method, with the passive option forced to false
|
// Used internally for the toggleCaptions method, with the passive option forced to false
|
||||||
toggle: function toggle(input) {
|
toggle: function toggle(input) {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
||||||
|
|
||||||
// If there's no full support
|
// If there's no full support
|
||||||
@@ -3335,7 +3413,15 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
controls.updateSetting.call(this, 'captions'); // Trigger event (not used internally)
|
controls.updateSetting.call(this, 'captions'); // Trigger event (not used internally)
|
||||||
|
|
||||||
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
||||||
}
|
} // Wait for the call stack to clear before setting mode='hidden'
|
||||||
|
// on the active track - forcing the browser to download it
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
if (active && _this2.captions.toggled) {
|
||||||
|
_this2.captions.currentTrackNode.mode = 'hidden';
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
// Set captions by track index
|
// Set captions by track index
|
||||||
// Used internally for the currentTrack setter with the passive option forced to false
|
// Used internally for the currentTrack setter with the passive option forced to false
|
||||||
@@ -3416,7 +3502,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// If update is false it will also ignore tracks without metadata
|
// If update is false it will also ignore tracks without metadata
|
||||||
// This is used to "freeze" the language options when captions.update is false
|
// This is used to "freeze" the language options when captions.update is false
|
||||||
getTracks: function getTracks() {
|
getTracks: function getTracks() {
|
||||||
var _this2 = this;
|
var _this3 = this;
|
||||||
|
|
||||||
var update = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
var update = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||||||
// Handle media or textTracks missing or null
|
// Handle media or textTracks missing or null
|
||||||
@@ -3424,20 +3510,20 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
||||||
|
|
||||||
return tracks.filter(function (track) {
|
return tracks.filter(function (track) {
|
||||||
return !_this2.isHTML5 || update || _this2.captions.meta.has(track);
|
return !_this3.isHTML5 || update || _this3.captions.meta.has(track);
|
||||||
}).filter(function (track) {
|
}).filter(function (track) {
|
||||||
return ['captions', 'subtitles'].includes(track.kind);
|
return ['captions', 'subtitles'].includes(track.kind);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// Match tracks based on languages and get the first
|
// Match tracks based on languages and get the first
|
||||||
findTrack: function findTrack(languages) {
|
findTrack: function findTrack(languages) {
|
||||||
var _this3 = this;
|
var _this4 = this;
|
||||||
|
|
||||||
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||||
var tracks = captions.getTracks.call(this);
|
var tracks = captions.getTracks.call(this);
|
||||||
|
|
||||||
var sortIsDefault = function sortIsDefault(track) {
|
var sortIsDefault = function sortIsDefault(track) {
|
||||||
return Number((_this3.captions.meta.get(track) || {}).default);
|
return Number((_this4.captions.meta.get(track) || {}).default);
|
||||||
};
|
};
|
||||||
|
|
||||||
var sorted = Array.from(tracks).sort(function (a, b) {
|
var sorted = Array.from(tracks).sort(function (a, b) {
|
||||||
@@ -3570,7 +3656,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/3.5.10/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.6.1/plyr.svg',
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
// Quality default
|
// Quality default
|
||||||
@@ -3618,6 +3704,9 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
fallback: true,
|
fallback: true,
|
||||||
// Fallback using full viewport/window
|
// Fallback using full viewport/window
|
||||||
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
|
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
|
||||||
|
// Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode
|
||||||
|
// Non-ancestors of the player element will be ignored
|
||||||
|
// container: null, // defaults to the player element
|
||||||
|
|
||||||
},
|
},
|
||||||
// Local storage
|
// Local storage
|
||||||
@@ -3855,16 +3944,16 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
title: false,
|
title: false,
|
||||||
speed: true,
|
speed: true,
|
||||||
transparent: false,
|
transparent: false,
|
||||||
// These settings require a pro or premium account to work
|
// Whether the owner of the video has a Pro or Business account
|
||||||
sidedock: false,
|
// (which allows us to properly hide controls without CSS hacks, etc)
|
||||||
controls: false,
|
premium: false,
|
||||||
// Custom settings from Plyr
|
// Custom settings from Plyr
|
||||||
referrerPolicy: null // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
referrerPolicy: null // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
||||||
|
|
||||||
},
|
},
|
||||||
// YouTube plugin
|
// YouTube plugin
|
||||||
youtube: {
|
youtube: {
|
||||||
noCookie: false,
|
noCookie: true,
|
||||||
// Whether to use an alternative version of YouTube without cookies
|
// Whether to use an alternative version of YouTube without cookies
|
||||||
rel: 0,
|
rel: 0,
|
||||||
// No related vids
|
// No related vids
|
||||||
@@ -3974,7 +4063,10 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
y: 0
|
y: 0
|
||||||
}; // Force the use of 'full window/browser' rather than fullscreen
|
}; // Force the use of 'full window/browser' rather than fullscreen
|
||||||
|
|
||||||
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Register event listeners
|
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Get the fullscreen element
|
||||||
|
// Checks container is an ancestor, defaults to null
|
||||||
|
|
||||||
|
this.player.elements.fullscreen = player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container); // Register event listeners
|
||||||
// Handle event (incase user presses escape etc)
|
// Handle event (incase user presses escape etc)
|
||||||
|
|
||||||
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
|
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
|
||||||
@@ -4147,7 +4239,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
this.target.webkitExitFullscreen();
|
this.target.webkitExitFullscreen();
|
||||||
this.player.play();
|
silencePromise(this.player.play());
|
||||||
} else if (!Fullscreen.native || this.forceFallback) {
|
} else if (!Fullscreen.native || this.forceFallback) {
|
||||||
this.toggleFallback(false);
|
this.toggleFallback(false);
|
||||||
} else if (!this.prefix) {
|
} else if (!this.prefix) {
|
||||||
@@ -4194,13 +4286,13 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var element = !this.prefix ? document.fullscreenElement : document["".concat(this.prefix).concat(this.property, "Element")];
|
var element = !this.prefix ? document.fullscreenElement : document["".concat(this.prefix).concat(this.property, "Element")];
|
||||||
return element === this.target;
|
return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target;
|
||||||
} // Get target element
|
} // Get target element
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
key: "target",
|
key: "target",
|
||||||
get: function get() {
|
get: function get() {
|
||||||
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
|
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.fullscreen || this.player.elements.container;
|
||||||
}
|
}
|
||||||
}], [{
|
}], [{
|
||||||
key: "native",
|
key: "native",
|
||||||
@@ -4262,7 +4354,6 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
|
||||||
var ui = {
|
var ui = {
|
||||||
addStyleHook: function addStyleHook() {
|
addStyleHook: function addStyleHook() {
|
||||||
toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
|
toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
|
||||||
@@ -4397,12 +4488,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
} // Set property synchronously to respect the call order
|
} // Set property synchronously to respect the call order
|
||||||
|
|
||||||
|
|
||||||
this.media.setAttribute('poster', poster); // HTML5 uses native poster attribute
|
this.media.setAttribute('data-poster', poster); // Wait until ui is ready
|
||||||
|
|
||||||
if (this.isHTML5) {
|
|
||||||
return Promise.resolve(poster);
|
|
||||||
} // Wait until ui is ready
|
|
||||||
|
|
||||||
|
|
||||||
return ready.call(this) // Load image
|
return ready.call(this) // Load image
|
||||||
.then(function () {
|
.then(function () {
|
||||||
@@ -4478,6 +4564,26 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));
|
this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// Migrate any custom properties from the media to the parent
|
||||||
|
migrateStyles: function migrateStyles() {
|
||||||
|
var _this5 = this;
|
||||||
|
|
||||||
|
// Loop through values (as they are the keys when the object is spread 🤔)
|
||||||
|
Object.values(_objectSpread2({}, this.media.style)) // We're only fussed about Plyr specific properties
|
||||||
|
.filter(function (key) {
|
||||||
|
return !is$1.empty(key) && key.startsWith('--plyr');
|
||||||
|
}).forEach(function (key) {
|
||||||
|
// Set on the container
|
||||||
|
_this5.elements.container.style.setProperty(key, _this5.media.style.getPropertyValue(key)); // Clean up from media element
|
||||||
|
|
||||||
|
|
||||||
|
_this5.media.style.removeProperty(key);
|
||||||
|
}); // Remove attribute if empty
|
||||||
|
|
||||||
|
if (is$1.empty(this.media.style)) {
|
||||||
|
this.media.removeAttribute('style');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4572,7 +4678,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
case 75:
|
case 75:
|
||||||
// Space and K key
|
// Space and K key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
player.togglePlay();
|
silencePromise(player.togglePlay());
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -4686,15 +4792,17 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
removeCurrent(); // Delay the adding of classname until the focus has changed
|
removeCurrent(); // Delay the adding of classname until the focus has changed
|
||||||
// This event fires before the focusin event
|
// This event fires before the focusin event
|
||||||
|
|
||||||
this.focusTimer = setTimeout(function () {
|
if (event.type !== 'focusout') {
|
||||||
var focused = document.activeElement; // Ignore if current focus element isn't inside the player
|
this.focusTimer = setTimeout(function () {
|
||||||
|
var focused = document.activeElement; // Ignore if current focus element isn't inside the player
|
||||||
|
|
||||||
if (!elements.container.contains(focused)) {
|
if (!elements.container.contains(focused)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
|
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
|
||||||
}, 10);
|
}, 10);
|
||||||
|
}
|
||||||
} // Global window & document listeners
|
} // Global window & document listeners
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
@@ -4712,7 +4820,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
once.call(player, document.body, 'touchstart', this.firstTouch); // Tab focus detection
|
once.call(player, document.body, 'touchstart', this.firstTouch); // Tab focus detection
|
||||||
|
|
||||||
toggleListener.call(player, document.body, 'keydown focus blur', this.setTabFocus, toggle, false, true);
|
toggleListener.call(player, document.body, 'keydown focus blur focusout', this.setTabFocus, toggle, false, true);
|
||||||
} // Container listeners
|
} // Container listeners
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
@@ -4755,7 +4863,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
}); // Set a gutter for Vimeo
|
}); // Set a gutter for Vimeo
|
||||||
|
|
||||||
var setGutter = function setGutter(ratio, padding, toggle) {
|
var setGutter = function setGutter(ratio, padding, toggle) {
|
||||||
if (!player.isVimeo) {
|
if (!player.isVimeo || player.config.vimeo.premium) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4812,7 +4920,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
ratio = _setPlayerSize.ratio; // Set Vimeo gutter
|
ratio = _setPlayerSize.ratio; // Set Vimeo gutter
|
||||||
|
|
||||||
|
|
||||||
setGutter(ratio, padding, isEnter); // If not using native fullscreen, we need to check for resizes of viewport
|
setGutter(ratio, padding, isEnter); // If not using native browser fullscreen API, we need to check for resizes of viewport
|
||||||
|
|
||||||
if (!usingNative) {
|
if (!usingNative) {
|
||||||
if (isEnter) {
|
if (isEnter) {
|
||||||
@@ -4890,9 +4998,13 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
if (player.ended) {
|
if (player.ended) {
|
||||||
_this.proxy(event, player.restart, 'restart');
|
_this.proxy(event, player.restart, 'restart');
|
||||||
|
|
||||||
_this.proxy(event, player.play, 'play');
|
_this.proxy(event, function () {
|
||||||
|
silencePromise(player.play());
|
||||||
|
}, 'play');
|
||||||
} else {
|
} else {
|
||||||
_this.proxy(event, player.togglePlay, 'play');
|
_this.proxy(event, function () {
|
||||||
|
silencePromise(player.togglePlay());
|
||||||
|
}, 'play');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} // Disable right click
|
} // Disable right click
|
||||||
@@ -4990,7 +5102,9 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
if (elements.buttons.play) {
|
if (elements.buttons.play) {
|
||||||
Array.from(elements.buttons.play).forEach(function (button) {
|
Array.from(elements.buttons.play).forEach(function (button) {
|
||||||
_this3.bind(button, 'click', player.togglePlay, 'play');
|
_this3.bind(button, 'click', function () {
|
||||||
|
silencePromise(player.togglePlay());
|
||||||
|
}, 'play');
|
||||||
});
|
});
|
||||||
} // Pause
|
} // Pause
|
||||||
|
|
||||||
@@ -5087,7 +5201,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
if (play && done) {
|
if (play && done) {
|
||||||
seek.removeAttribute(attribute);
|
seek.removeAttribute(attribute);
|
||||||
player.play();
|
silencePromise(player.play());
|
||||||
} else if (!done && player.playing) {
|
} else if (!done && player.playing) {
|
||||||
seek.setAttribute(attribute, '');
|
seek.setAttribute(attribute, '');
|
||||||
player.pause();
|
player.pause();
|
||||||
@@ -5185,7 +5299,18 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
|
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
|
||||||
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
||||||
}); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
|
}); // Also update controls.hover state for any non-player children of fullscreen element (as above)
|
||||||
|
|
||||||
|
if (elements.fullscreen) {
|
||||||
|
Array.from(elements.fullscreen.children).filter(function (c) {
|
||||||
|
return !c.contains(elements.container);
|
||||||
|
}).forEach(function (child) {
|
||||||
|
_this3.bind(child, 'mouseenter mouseleave', function (event) {
|
||||||
|
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
|
||||||
|
|
||||||
|
|
||||||
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
|
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
|
||||||
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
||||||
@@ -5601,15 +5726,28 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
var player = this;
|
var player = this;
|
||||||
var config = player.config.vimeo; // Get Vimeo params for the iframe
|
var config = player.config.vimeo;
|
||||||
|
|
||||||
var params = buildUrlParams(extend({}, {
|
var premium = config.premium,
|
||||||
|
referrerPolicy = config.referrerPolicy,
|
||||||
|
frameParams = _objectWithoutProperties(config, ["premium", "referrerPolicy"]); // If the owner has a pro or premium account then we can hide controls etc
|
||||||
|
|
||||||
|
|
||||||
|
if (premium) {
|
||||||
|
Object.assign(frameParams, {
|
||||||
|
controls: false,
|
||||||
|
sidedock: false
|
||||||
|
});
|
||||||
|
} // Get Vimeo params for the iframe
|
||||||
|
|
||||||
|
|
||||||
|
var params = buildUrlParams(_objectSpread2({
|
||||||
loop: player.config.loop.active,
|
loop: player.config.loop.active,
|
||||||
autoplay: player.autoplay,
|
autoplay: player.autoplay,
|
||||||
muted: player.muted,
|
muted: player.muted,
|
||||||
gesture: 'media',
|
gesture: 'media',
|
||||||
playsinline: !this.config.fullscreen.iosNative
|
playsinline: !this.config.fullscreen.iosNative
|
||||||
}, config)); // Get the source URL or ID
|
}, frameParams)); // Get the source URL or ID
|
||||||
|
|
||||||
var source = player.media.getAttribute('src'); // Get from <div> if needed
|
var source = player.media.getAttribute('src'); // Get from <div> if needed
|
||||||
|
|
||||||
@@ -5623,22 +5761,27 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
var src = format(player.config.urls.vimeo.iframe, id, params);
|
var src = format(player.config.urls.vimeo.iframe, id, params);
|
||||||
iframe.setAttribute('src', src);
|
iframe.setAttribute('src', src);
|
||||||
iframe.setAttribute('allowfullscreen', '');
|
iframe.setAttribute('allowfullscreen', '');
|
||||||
iframe.setAttribute('allowtransparency', '');
|
iframe.setAttribute('allow', 'autoplay,fullscreen,picture-in-picture'); // Set the referrer policy if required
|
||||||
iframe.setAttribute('allow', 'autoplay'); // Set the referrer policy if required
|
|
||||||
|
|
||||||
if (!is$1.empty(config.referrerPolicy)) {
|
if (!is$1.empty(referrerPolicy)) {
|
||||||
iframe.setAttribute('referrerPolicy', config.referrerPolicy);
|
iframe.setAttribute('referrerPolicy', referrerPolicy);
|
||||||
} // Get poster, if already set
|
} // Inject the package
|
||||||
|
|
||||||
|
|
||||||
var poster = player.poster; // Inject the package
|
var poster = player.poster;
|
||||||
|
|
||||||
|
if (premium) {
|
||||||
|
iframe.setAttribute('data-poster', poster);
|
||||||
|
player.media = replaceElement(iframe, player.media);
|
||||||
|
} else {
|
||||||
|
var wrapper = createElement('div', {
|
||||||
|
class: player.config.classNames.embedContainer,
|
||||||
|
'data-poster': poster
|
||||||
|
});
|
||||||
|
wrapper.appendChild(iframe);
|
||||||
|
player.media = replaceElement(wrapper, player.media);
|
||||||
|
} // Get poster image
|
||||||
|
|
||||||
var wrapper = createElement('div', {
|
|
||||||
poster: poster,
|
|
||||||
class: player.config.classNames.embedContainer
|
|
||||||
});
|
|
||||||
wrapper.appendChild(iframe);
|
|
||||||
player.media = replaceElement(wrapper, player.media); // Get poster image
|
|
||||||
|
|
||||||
fetch(format(player.config.urls.vimeo.api, id), 'json').then(function (response) {
|
fetch(format(player.config.urls.vimeo.api, id), 'json').then(function (response) {
|
||||||
if (is$1.empty(response)) {
|
if (is$1.empty(response)) {
|
||||||
@@ -5722,6 +5865,9 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
player.embed.setPlaybackRate(input).then(function () {
|
player.embed.setPlaybackRate(input).then(function () {
|
||||||
speed = input;
|
speed = input;
|
||||||
triggerEvent.call(player, player.media, 'ratechange');
|
triggerEvent.call(player, player.media, 'ratechange');
|
||||||
|
}).catch(function () {
|
||||||
|
// Cannot set Playback Rate, Video is probably not on Pro account
|
||||||
|
player.options.speed = [1];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}); // Volume
|
}); // Volume
|
||||||
@@ -6008,7 +6154,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
var container = createElement('div', {
|
var container = createElement('div', {
|
||||||
id: id,
|
id: id,
|
||||||
poster: poster
|
'data-poster': poster
|
||||||
});
|
});
|
||||||
player.media = replaceElement(container, player.media); // Id to poster wrapper
|
player.media = replaceElement(container, player.media); // Id to poster wrapper
|
||||||
|
|
||||||
@@ -6327,14 +6473,12 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
class: this.config.classNames.video
|
class: this.config.classNames.video
|
||||||
}); // Wrap the video in a container
|
}); // Wrap the video in a container
|
||||||
|
|
||||||
wrap(this.media, this.elements.wrapper); // Faux poster container
|
wrap(this.media, this.elements.wrapper); // Poster image container
|
||||||
|
|
||||||
if (this.isEmbed) {
|
this.elements.poster = createElement('div', {
|
||||||
this.elements.poster = createElement('div', {
|
class: this.config.classNames.poster
|
||||||
class: this.config.classNames.poster
|
});
|
||||||
});
|
this.elements.wrapper.appendChild(this.elements.poster);
|
||||||
this.elements.wrapper.appendChild(this.elements.poster);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
@@ -6461,6 +6605,8 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
* mobile devices, this initialization is done as the result of a user action.
|
* mobile devices, this initialization is done as the result of a user action.
|
||||||
*/
|
*/
|
||||||
value: function setupIMA() {
|
value: function setupIMA() {
|
||||||
|
var _this4 = this;
|
||||||
|
|
||||||
// Create the container for our advertisements
|
// Create the container for our advertisements
|
||||||
this.elements.container = createElement('div', {
|
this.elements.container = createElement('div', {
|
||||||
class: this.player.config.classNames.ads
|
class: this.player.config.classNames.ads
|
||||||
@@ -6473,7 +6619,16 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline); // We assume the adContainer is the video container of the plyr element that will house the ads
|
google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline); // We assume the adContainer is the video container of the plyr element that will house the ads
|
||||||
|
|
||||||
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media); // Request video ads to be pre-loaded
|
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media); // Create ads loader
|
||||||
|
|
||||||
|
this.loader = new google.ima.AdsLoader(this.elements.displayContainer); // Listen and respond to ads loaded and error events
|
||||||
|
|
||||||
|
this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, function (event) {
|
||||||
|
return _this4.onAdsManagerLoaded(event);
|
||||||
|
}, false);
|
||||||
|
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, function (error) {
|
||||||
|
return _this4.onAdError(error);
|
||||||
|
}, false); // Request video ads to be pre-loaded
|
||||||
|
|
||||||
this.requestAds();
|
this.requestAds();
|
||||||
}
|
}
|
||||||
@@ -6484,21 +6639,10 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
}, {
|
}, {
|
||||||
key: "requestAds",
|
key: "requestAds",
|
||||||
value: function requestAds() {
|
value: function requestAds() {
|
||||||
var _this4 = this;
|
|
||||||
|
|
||||||
var container = this.player.elements.container;
|
var container = this.player.elements.container;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create ads loader
|
// Request video ads
|
||||||
this.loader = new google.ima.AdsLoader(this.elements.displayContainer); // Listen and respond to ads loaded and error events
|
|
||||||
|
|
||||||
this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, function (event) {
|
|
||||||
return _this4.onAdsManagerLoaded(event);
|
|
||||||
}, false);
|
|
||||||
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, function (error) {
|
|
||||||
return _this4.onAdError(error);
|
|
||||||
}, false); // Request video ads
|
|
||||||
|
|
||||||
var request = new google.ima.AdsRequest();
|
var request = new google.ima.AdsRequest();
|
||||||
request.adTagUrl = this.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK
|
request.adTagUrl = this.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK
|
||||||
// to select the correct creative if multiple are returned
|
// to select the correct creative if multiple are returned
|
||||||
@@ -6677,7 +6821,13 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// };
|
// };
|
||||||
// TODO: So there is still this thing where a video should only be allowed to start
|
// TODO: So there is still this thing where a video should only be allowed to start
|
||||||
// playing when the IMA SDK is ready or has failed
|
// playing when the IMA SDK is ready or has failed
|
||||||
this.loadAds();
|
if (this.player.ended) {
|
||||||
|
this.loadAds();
|
||||||
|
} else {
|
||||||
|
// The SDK won't allow new ads to be called without receiving a contentComplete()
|
||||||
|
this.loader.contentComplete();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
||||||
@@ -6813,7 +6963,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
this.playing = false; // Play video
|
this.playing = false; // Play video
|
||||||
|
|
||||||
this.player.media.play();
|
silencePromise(this.player.media.play());
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Pause our video
|
* Pause our video
|
||||||
@@ -6870,7 +7020,9 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
_this11.on('loaded', resolve);
|
_this11.on('loaded', resolve);
|
||||||
|
|
||||||
_this11.player.debug.log(_this11.manager);
|
_this11.player.debug.log(_this11.manager);
|
||||||
}); // Now request some new advertisements
|
}); // Now that the manager has been destroyed set it to also be un-initialized
|
||||||
|
|
||||||
|
_this11.initialized = false; // Now request some new advertisements
|
||||||
|
|
||||||
_this11.requestAds();
|
_this11.requestAds();
|
||||||
}).catch(function () {});
|
}).catch(function () {});
|
||||||
@@ -7114,15 +7266,10 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
if (is$1.empty(src)) {
|
if (is$1.empty(src)) {
|
||||||
throw new Error('Missing previewThumbnails.src config attribute');
|
throw new Error('Missing previewThumbnails.src config attribute');
|
||||||
} // If string, convert into single-element list
|
} // Resolve promise
|
||||||
|
|
||||||
|
|
||||||
var urls = is$1.string(src) ? [src] : src; // Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
var sortAndResolve = function sortAndResolve() {
|
||||||
|
|
||||||
var promises = urls.map(function (u) {
|
|
||||||
return _this2.getThumbnail(u);
|
|
||||||
});
|
|
||||||
Promise.all(promises).then(function () {
|
|
||||||
// Sort smallest to biggest (e.g., [120p, 480p, 1080p])
|
// Sort smallest to biggest (e.g., [120p, 480p, 1080p])
|
||||||
_this2.thumbnails.sort(function (x, y) {
|
_this2.thumbnails.sort(function (x, y) {
|
||||||
return x.height - y.height;
|
return x.height - y.height;
|
||||||
@@ -7131,7 +7278,25 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
_this2.player.debug.log('Preview thumbnails', _this2.thumbnails);
|
_this2.player.debug.log('Preview thumbnails', _this2.thumbnails);
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
}; // Via callback()
|
||||||
|
|
||||||
|
|
||||||
|
if (is$1.function(src)) {
|
||||||
|
src(function (thumbnails) {
|
||||||
|
_this2.thumbnails = thumbnails;
|
||||||
|
sortAndResolve();
|
||||||
|
});
|
||||||
|
} // VTT urls
|
||||||
|
else {
|
||||||
|
// If string, convert into single-element list
|
||||||
|
var urls = is$1.string(src) ? [src] : src; // Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
||||||
|
|
||||||
|
var promises = urls.map(function (u) {
|
||||||
|
return _this2.getThumbnail(u);
|
||||||
|
}); // Resolve
|
||||||
|
|
||||||
|
Promise.all(promises).then(sortAndResolve);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} // Process individual VTT file
|
} // Process individual VTT file
|
||||||
|
|
||||||
@@ -7919,6 +8084,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
this.elements = {
|
this.elements = {
|
||||||
container: null,
|
container: null,
|
||||||
|
fullscreen: null,
|
||||||
captions: null,
|
captions: null,
|
||||||
buttons: {},
|
buttons: {},
|
||||||
display: {},
|
display: {},
|
||||||
@@ -8093,9 +8259,11 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
tabindex: 0
|
tabindex: 0
|
||||||
});
|
});
|
||||||
wrap(this.media, this.elements.container);
|
wrap(this.media, this.elements.container);
|
||||||
} // Add style hook
|
} // Migrate custom properties from media to container (so they work 😉)
|
||||||
|
|
||||||
|
|
||||||
|
ui.migrateStyles.call(this); // Add style hook
|
||||||
|
|
||||||
ui.addStyleHook.call(this); // Setup media
|
ui.addStyleHook.call(this); // Setup media
|
||||||
|
|
||||||
media.setup.call(this); // Listen for events if debugging
|
media.setup.call(this); // Listen for events if debugging
|
||||||
@@ -8104,10 +8272,12 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
|
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
|
||||||
_this.debug.log("event: ".concat(event.type));
|
_this.debug.log("event: ".concat(event.type));
|
||||||
});
|
});
|
||||||
} // Setup interface
|
} // Setup fullscreen
|
||||||
// If embed but not fully supported, build interface now to avoid flash of controls
|
|
||||||
|
|
||||||
|
|
||||||
|
this.fullscreen = new Fullscreen(this); // Setup interface
|
||||||
|
// If embed but not fully supported, build interface now to avoid flash of controls
|
||||||
|
|
||||||
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
|
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
|
||||||
ui.build.call(this);
|
ui.build.call(this);
|
||||||
} // Container listeners
|
} // Container listeners
|
||||||
@@ -8115,9 +8285,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
this.listeners.container(); // Global listeners
|
this.listeners.container(); // Global listeners
|
||||||
|
|
||||||
this.listeners.global(); // Setup fullscreen
|
this.listeners.global(); // Setup ads if provided
|
||||||
|
|
||||||
this.fullscreen = new Fullscreen(this); // Setup ads if provided
|
|
||||||
|
|
||||||
if (this.config.ads.enabled) {
|
if (this.config.ads.enabled) {
|
||||||
this.ads = new Ads(this);
|
this.ads = new Ads(this);
|
||||||
@@ -8126,7 +8294,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
if (this.isHTML5 && this.config.autoplay) {
|
if (this.isHTML5 && this.config.autoplay) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
return _this.play();
|
return silencePromise(_this.play());
|
||||||
}, 10);
|
}, 10);
|
||||||
} // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
} // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
||||||
|
|
||||||
@@ -8163,7 +8331,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
this.ads.managerPromise.then(function () {
|
this.ads.managerPromise.then(function () {
|
||||||
return _this2.ads.play();
|
return _this2.ads.play();
|
||||||
}).catch(function () {
|
}).catch(function () {
|
||||||
return _this2.media.play();
|
return silencePromise(_this2.media.play());
|
||||||
});
|
});
|
||||||
} // Return the promise (for HTML5)
|
} // Return the promise (for HTML5)
|
||||||
|
|
||||||
@@ -8816,7 +8984,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
var updateStorage = true;
|
var updateStorage = true;
|
||||||
|
|
||||||
if (!options.includes(quality)) {
|
if (!options.includes(quality)) {
|
||||||
var value = closest(options, quality);
|
var value = closest$1(options, quality);
|
||||||
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
|
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
|
||||||
quality = value; // Don't update storage if quality is not supported
|
quality = value; // Don't update storage if quality is not supported
|
||||||
|
|
||||||
@@ -8855,41 +9023,41 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
this.media.loop = toggle; // Set default to be a true toggle
|
this.media.loop = toggle; // Set default to be a true toggle
|
||||||
|
|
||||||
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'start':
|
case 'start':
|
||||||
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
||||||
this.config.loop.end = null;
|
this.config.loop.end = null;
|
||||||
}
|
}
|
||||||
this.config.loop.start = this.currentTime;
|
this.config.loop.start = this.currentTime;
|
||||||
// this.config.loop.indicator.start = this.elements.display.played.value;
|
// this.config.loop.indicator.start = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
if (this.config.loop.start >= this.currentTime) {
|
if (this.config.loop.start >= this.currentTime) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
this.config.loop.end = this.currentTime;
|
this.config.loop.end = this.currentTime;
|
||||||
// this.config.loop.indicator.end = this.elements.display.played.value;
|
// this.config.loop.indicator.end = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = this.duration - 2;
|
|
||||||
this.config.loop.indicator.start = 0;
|
|
||||||
this.config.loop.indicator.end = 100;
|
|
||||||
break;
|
|
||||||
case 'toggle':
|
|
||||||
if (this.config.loop.active) {
|
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = null;
|
|
||||||
} else {
|
|
||||||
this.config.loop.start = 0;
|
this.config.loop.start = 0;
|
||||||
this.config.loop.end = this.duration - 2;
|
this.config.loop.end = this.duration - 2;
|
||||||
}
|
this.config.loop.indicator.start = 0;
|
||||||
break;
|
this.config.loop.indicator.end = 100;
|
||||||
default:
|
break;
|
||||||
this.config.loop.start = 0;
|
case 'toggle':
|
||||||
this.config.loop.end = null;
|
if (this.config.loop.active) {
|
||||||
break;
|
this.config.loop.start = 0;
|
||||||
} */
|
this.config.loop.end = null;
|
||||||
|
} else {
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = this.duration - 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = null;
|
||||||
|
break;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get current loop state
|
* Get current loop state
|
||||||
@@ -8961,7 +9129,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.media.getAttribute('poster');
|
return this.media.getAttribute('poster') || this.media.getAttribute('data-poster');
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the current aspect ratio in use
|
* Get the current aspect ratio in use
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+339
-171
@@ -69,6 +69,42 @@ function _objectSpread2(target) {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _objectWithoutPropertiesLoose(source, excluded) {
|
||||||
|
if (source == null) return {};
|
||||||
|
var target = {};
|
||||||
|
var sourceKeys = Object.keys(source);
|
||||||
|
var key, i;
|
||||||
|
|
||||||
|
for (i = 0; i < sourceKeys.length; i++) {
|
||||||
|
key = sourceKeys[i];
|
||||||
|
if (excluded.indexOf(key) >= 0) continue;
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _objectWithoutProperties(source, excluded) {
|
||||||
|
if (source == null) return {};
|
||||||
|
|
||||||
|
var target = _objectWithoutPropertiesLoose(source, excluded);
|
||||||
|
|
||||||
|
var key, i;
|
||||||
|
|
||||||
|
if (Object.getOwnPropertySymbols) {
|
||||||
|
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
|
||||||
|
|
||||||
|
for (i = 0; i < sourceSymbolKeys.length; i++) {
|
||||||
|
key = sourceSymbolKeys[i];
|
||||||
|
if (excluded.indexOf(key) >= 0) continue;
|
||||||
|
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
function _slicedToArray(arr, i) {
|
function _slicedToArray(arr, i) {
|
||||||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
||||||
}
|
}
|
||||||
@@ -431,7 +467,7 @@ var isTrack = function isTrack(input) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var isPromise = function isPromise(input) {
|
var isPromise = function isPromise(input) {
|
||||||
return instanceOf$1(input, Promise);
|
return instanceOf$1(input, Promise) && isFunction$1(input.then);
|
||||||
};
|
};
|
||||||
|
|
||||||
var isEmpty$1 = function isEmpty(input) {
|
var isEmpty$1 = function isEmpty(input) {
|
||||||
@@ -773,12 +809,33 @@ function hasClass(element, className) {
|
|||||||
} // Element matches selector
|
} // Element matches selector
|
||||||
|
|
||||||
function matches$1(element, selector) {
|
function matches$1(element, selector) {
|
||||||
|
var _Element = Element,
|
||||||
|
prototype = _Element.prototype;
|
||||||
|
|
||||||
function match() {
|
function match() {
|
||||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
var method = match;
|
var method = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
|
||||||
|
return method.call(element, selector);
|
||||||
|
} // Closest ancestor element matching selector (also tests element itself)
|
||||||
|
|
||||||
|
function closest(element, selector) {
|
||||||
|
var _Element2 = Element,
|
||||||
|
prototype = _Element2.prototype; // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
|
||||||
|
|
||||||
|
function closestElement() {
|
||||||
|
var el = this;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (matches$1.matches(el, selector)) return el;
|
||||||
|
el = el.parentElement || el.parentNode;
|
||||||
|
} while (el !== null && el.nodeType === 1);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var method = prototype.closest || closestElement;
|
||||||
return method.call(element, selector);
|
return method.call(element, selector);
|
||||||
} // Find all elements
|
} // Find all elements
|
||||||
|
|
||||||
@@ -1050,6 +1107,19 @@ function ready() {
|
|||||||
}).then(function () {});
|
}).then(function () {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Silence a Promise-like object.
|
||||||
|
* This is useful for avoiding non-harmful, but potentially confusing "uncaught
|
||||||
|
* play promise" rejection error messages.
|
||||||
|
* @param {Object} value An object that may or may not be `Promise`-like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function silencePromise(value) {
|
||||||
|
if (is$1.promise(value)) {
|
||||||
|
value.then(null, function () {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateRatio(input) {
|
function validateRatio(input) {
|
||||||
if (!is$1.array(input) && (!is$1.string(input) || !input.includes(':'))) {
|
if (!is$1.array(input) && (!is$1.string(input) || !input.includes(':'))) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1118,8 +1188,8 @@ function setAspectRatio(input) {
|
|||||||
var padding = 100 / w * h;
|
var padding = 100 / w * h;
|
||||||
wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||||
|
|
||||||
if (this.isVimeo && this.supported.ui) {
|
if (this.isVimeo && !this.config.vimeo.premium && this.supported.ui) {
|
||||||
var height = 240;
|
var height = 100 / this.media.offsetWidth * parseInt(window.getComputedStyle(this.media).paddingBottom, 10);
|
||||||
var offset = (height - padding) / (height / 50);
|
var offset = (height - padding) / (height / 50);
|
||||||
this.media.style.transform = "translateY(-".concat(offset, "%)");
|
this.media.style.transform = "translateY(-".concat(offset, "%)");
|
||||||
} else if (this.isHTML5) {
|
} else if (this.isHTML5) {
|
||||||
@@ -1226,7 +1296,7 @@ var html5 = {
|
|||||||
player.currentTime = currentTime; // Resume playing
|
player.currentTime = currentTime; // Resume playing
|
||||||
|
|
||||||
if (!paused) {
|
if (!paused) {
|
||||||
player.play();
|
silencePromise(player.play());
|
||||||
}
|
}
|
||||||
}); // Load new source
|
}); // Load new source
|
||||||
|
|
||||||
@@ -1275,7 +1345,7 @@ function dedupe(array) {
|
|||||||
});
|
});
|
||||||
} // Get the closest value in an array
|
} // Get the closest value in an array
|
||||||
|
|
||||||
function closest(array, value) {
|
function closest$1(array, value) {
|
||||||
if (!is$1.array(array) || !array.length) {
|
if (!is$1.array(array) || !array.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1313,19 +1383,19 @@ function getPercentage(current, max) {
|
|||||||
return (current / max * 100).toFixed(2);
|
return (current / max * 100).toFixed(2);
|
||||||
} // Replace all occurances of a string in a string
|
} // Replace all occurances of a string in a string
|
||||||
|
|
||||||
function replaceAll() {
|
var replaceAll = function replaceAll() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
||||||
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
||||||
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
} // Convert to title case
|
}; // Convert to title case
|
||||||
|
|
||||||
function toTitleCase() {
|
var toTitleCase = function toTitleCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
return input.toString().replace(/\w\S*/g, function (text) {
|
return input.toString().replace(/\w\S*/g, function (text) {
|
||||||
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
||||||
});
|
});
|
||||||
} // Convert string to pascalCase
|
}; // Convert string to pascalCase
|
||||||
|
|
||||||
function toPascalCase() {
|
function toPascalCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
@@ -2423,39 +2493,39 @@ var controls = {
|
|||||||
// Set the looping options
|
// Set the looping options
|
||||||
|
|
||||||
/* setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
// Menu required
|
// Menu required
|
||||||
if (!is.element(this.elements.settings.panels.loop)) {
|
if (!is.element(this.elements.settings.panels.loop)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
||||||
// Show the pane and tab
|
// Show the pane and tab
|
||||||
toggleHidden(this.elements.settings.buttons.loop, false);
|
toggleHidden(this.elements.settings.buttons.loop, false);
|
||||||
toggleHidden(this.elements.settings.panels.loop, false);
|
toggleHidden(this.elements.settings.panels.loop, false);
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !is.empty(this.loop.options);
|
const toggle = !is.empty(this.loop.options);
|
||||||
controls.toggleMenuButton.call(this, 'loop', toggle);
|
controls.toggleMenuButton.call(this, 'loop', toggle);
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
emptyElement(list);
|
emptyElement(list);
|
||||||
options.forEach(option => {
|
options.forEach(option => {
|
||||||
const item = createElement('li');
|
const item = createElement('li');
|
||||||
const button = createElement(
|
const button = createElement(
|
||||||
'button',
|
'button',
|
||||||
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: this.config.classNames.control,
|
class: this.config.classNames.control,
|
||||||
'data-plyr-loop-action': option,
|
'data-plyr-loop-action': option,
|
||||||
}),
|
}),
|
||||||
i18n.get(option, this.config)
|
i18n.get(option, this.config)
|
||||||
);
|
);
|
||||||
if (['start', 'end'].includes(option)) {
|
if (['start', 'end'].includes(option)) {
|
||||||
const badge = controls.createBadge.call(this, '00:00');
|
const badge = controls.createBadge.call(this, '00:00');
|
||||||
button.appendChild(badge);
|
button.appendChild(badge);
|
||||||
}
|
}
|
||||||
item.appendChild(button);
|
item.appendChild(button);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
}, */
|
}, */
|
||||||
// Get current selected caption language
|
// Get current selected caption language
|
||||||
// TODO: rework this to user the getter in the API?
|
// TODO: rework this to user the getter in the API?
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
@@ -3257,9 +3327,15 @@ var captions = {
|
|||||||
meta.set(track, {
|
meta.set(track, {
|
||||||
default: track.mode === 'showing'
|
default: track.mode === 'showing'
|
||||||
}); // Turn off native caption rendering to avoid double captions
|
}); // Turn off native caption rendering to avoid double captions
|
||||||
|
// Note: mode='hidden' forces a track to download. To ensure every track
|
||||||
|
// isn't downloaded at once, only 'showing' tracks should be reassigned
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
|
||||||
track.mode = 'hidden'; // Add event listener for cue changes
|
if (track.mode === 'showing') {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
track.mode = 'hidden';
|
||||||
|
} // Add event listener for cue changes
|
||||||
|
|
||||||
|
|
||||||
on.call(_this, track, 'cuechange', function () {
|
on.call(_this, track, 'cuechange', function () {
|
||||||
return captions.updateCues.call(_this);
|
return captions.updateCues.call(_this);
|
||||||
@@ -3283,6 +3359,8 @@ var captions = {
|
|||||||
// Toggle captions display
|
// Toggle captions display
|
||||||
// Used internally for the toggleCaptions method, with the passive option forced to false
|
// Used internally for the toggleCaptions method, with the passive option forced to false
|
||||||
toggle: function toggle(input) {
|
toggle: function toggle(input) {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
||||||
|
|
||||||
// If there's no full support
|
// If there's no full support
|
||||||
@@ -3329,7 +3407,15 @@ var captions = {
|
|||||||
controls.updateSetting.call(this, 'captions'); // Trigger event (not used internally)
|
controls.updateSetting.call(this, 'captions'); // Trigger event (not used internally)
|
||||||
|
|
||||||
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
||||||
}
|
} // Wait for the call stack to clear before setting mode='hidden'
|
||||||
|
// on the active track - forcing the browser to download it
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
if (active && _this2.captions.toggled) {
|
||||||
|
_this2.captions.currentTrackNode.mode = 'hidden';
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
// Set captions by track index
|
// Set captions by track index
|
||||||
// Used internally for the currentTrack setter with the passive option forced to false
|
// Used internally for the currentTrack setter with the passive option forced to false
|
||||||
@@ -3410,7 +3496,7 @@ var captions = {
|
|||||||
// If update is false it will also ignore tracks without metadata
|
// If update is false it will also ignore tracks without metadata
|
||||||
// This is used to "freeze" the language options when captions.update is false
|
// This is used to "freeze" the language options when captions.update is false
|
||||||
getTracks: function getTracks() {
|
getTracks: function getTracks() {
|
||||||
var _this2 = this;
|
var _this3 = this;
|
||||||
|
|
||||||
var update = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
var update = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||||||
// Handle media or textTracks missing or null
|
// Handle media or textTracks missing or null
|
||||||
@@ -3418,20 +3504,20 @@ var captions = {
|
|||||||
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
||||||
|
|
||||||
return tracks.filter(function (track) {
|
return tracks.filter(function (track) {
|
||||||
return !_this2.isHTML5 || update || _this2.captions.meta.has(track);
|
return !_this3.isHTML5 || update || _this3.captions.meta.has(track);
|
||||||
}).filter(function (track) {
|
}).filter(function (track) {
|
||||||
return ['captions', 'subtitles'].includes(track.kind);
|
return ['captions', 'subtitles'].includes(track.kind);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// Match tracks based on languages and get the first
|
// Match tracks based on languages and get the first
|
||||||
findTrack: function findTrack(languages) {
|
findTrack: function findTrack(languages) {
|
||||||
var _this3 = this;
|
var _this4 = this;
|
||||||
|
|
||||||
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||||
var tracks = captions.getTracks.call(this);
|
var tracks = captions.getTracks.call(this);
|
||||||
|
|
||||||
var sortIsDefault = function sortIsDefault(track) {
|
var sortIsDefault = function sortIsDefault(track) {
|
||||||
return Number((_this3.captions.meta.get(track) || {}).default);
|
return Number((_this4.captions.meta.get(track) || {}).default);
|
||||||
};
|
};
|
||||||
|
|
||||||
var sorted = Array.from(tracks).sort(function (a, b) {
|
var sorted = Array.from(tracks).sort(function (a, b) {
|
||||||
@@ -3564,7 +3650,7 @@ var defaults$1 = {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/3.5.10/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.6.1/plyr.svg',
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
// Quality default
|
// Quality default
|
||||||
@@ -3612,6 +3698,9 @@ var defaults$1 = {
|
|||||||
fallback: true,
|
fallback: true,
|
||||||
// Fallback using full viewport/window
|
// Fallback using full viewport/window
|
||||||
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
|
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
|
||||||
|
// Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode
|
||||||
|
// Non-ancestors of the player element will be ignored
|
||||||
|
// container: null, // defaults to the player element
|
||||||
|
|
||||||
},
|
},
|
||||||
// Local storage
|
// Local storage
|
||||||
@@ -3849,16 +3938,16 @@ var defaults$1 = {
|
|||||||
title: false,
|
title: false,
|
||||||
speed: true,
|
speed: true,
|
||||||
transparent: false,
|
transparent: false,
|
||||||
// These settings require a pro or premium account to work
|
// Whether the owner of the video has a Pro or Business account
|
||||||
sidedock: false,
|
// (which allows us to properly hide controls without CSS hacks, etc)
|
||||||
controls: false,
|
premium: false,
|
||||||
// Custom settings from Plyr
|
// Custom settings from Plyr
|
||||||
referrerPolicy: null // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
referrerPolicy: null // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
||||||
|
|
||||||
},
|
},
|
||||||
// YouTube plugin
|
// YouTube plugin
|
||||||
youtube: {
|
youtube: {
|
||||||
noCookie: false,
|
noCookie: true,
|
||||||
// Whether to use an alternative version of YouTube without cookies
|
// Whether to use an alternative version of YouTube without cookies
|
||||||
rel: 0,
|
rel: 0,
|
||||||
// No related vids
|
// No related vids
|
||||||
@@ -3968,7 +4057,10 @@ var Fullscreen = /*#__PURE__*/function () {
|
|||||||
y: 0
|
y: 0
|
||||||
}; // Force the use of 'full window/browser' rather than fullscreen
|
}; // Force the use of 'full window/browser' rather than fullscreen
|
||||||
|
|
||||||
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Register event listeners
|
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Get the fullscreen element
|
||||||
|
// Checks container is an ancestor, defaults to null
|
||||||
|
|
||||||
|
this.player.elements.fullscreen = player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container); // Register event listeners
|
||||||
// Handle event (incase user presses escape etc)
|
// Handle event (incase user presses escape etc)
|
||||||
|
|
||||||
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
|
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
|
||||||
@@ -4141,7 +4233,7 @@ var Fullscreen = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
this.target.webkitExitFullscreen();
|
this.target.webkitExitFullscreen();
|
||||||
this.player.play();
|
silencePromise(this.player.play());
|
||||||
} else if (!Fullscreen.native || this.forceFallback) {
|
} else if (!Fullscreen.native || this.forceFallback) {
|
||||||
this.toggleFallback(false);
|
this.toggleFallback(false);
|
||||||
} else if (!this.prefix) {
|
} else if (!this.prefix) {
|
||||||
@@ -4188,13 +4280,13 @@ var Fullscreen = /*#__PURE__*/function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var element = !this.prefix ? document.fullscreenElement : document["".concat(this.prefix).concat(this.property, "Element")];
|
var element = !this.prefix ? document.fullscreenElement : document["".concat(this.prefix).concat(this.property, "Element")];
|
||||||
return element === this.target;
|
return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target;
|
||||||
} // Get target element
|
} // Get target element
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
key: "target",
|
key: "target",
|
||||||
get: function get() {
|
get: function get() {
|
||||||
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
|
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.fullscreen || this.player.elements.container;
|
||||||
}
|
}
|
||||||
}], [{
|
}], [{
|
||||||
key: "native",
|
key: "native",
|
||||||
@@ -4256,7 +4348,6 @@ function loadImage(src) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
|
||||||
var ui = {
|
var ui = {
|
||||||
addStyleHook: function addStyleHook() {
|
addStyleHook: function addStyleHook() {
|
||||||
toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
|
toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
|
||||||
@@ -4391,12 +4482,7 @@ var ui = {
|
|||||||
} // Set property synchronously to respect the call order
|
} // Set property synchronously to respect the call order
|
||||||
|
|
||||||
|
|
||||||
this.media.setAttribute('poster', poster); // HTML5 uses native poster attribute
|
this.media.setAttribute('data-poster', poster); // Wait until ui is ready
|
||||||
|
|
||||||
if (this.isHTML5) {
|
|
||||||
return Promise.resolve(poster);
|
|
||||||
} // Wait until ui is ready
|
|
||||||
|
|
||||||
|
|
||||||
return ready.call(this) // Load image
|
return ready.call(this) // Load image
|
||||||
.then(function () {
|
.then(function () {
|
||||||
@@ -4472,6 +4558,26 @@ var ui = {
|
|||||||
|
|
||||||
this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));
|
this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// Migrate any custom properties from the media to the parent
|
||||||
|
migrateStyles: function migrateStyles() {
|
||||||
|
var _this5 = this;
|
||||||
|
|
||||||
|
// Loop through values (as they are the keys when the object is spread 🤔)
|
||||||
|
Object.values(_objectSpread2({}, this.media.style)) // We're only fussed about Plyr specific properties
|
||||||
|
.filter(function (key) {
|
||||||
|
return !is$1.empty(key) && key.startsWith('--plyr');
|
||||||
|
}).forEach(function (key) {
|
||||||
|
// Set on the container
|
||||||
|
_this5.elements.container.style.setProperty(key, _this5.media.style.getPropertyValue(key)); // Clean up from media element
|
||||||
|
|
||||||
|
|
||||||
|
_this5.media.style.removeProperty(key);
|
||||||
|
}); // Remove attribute if empty
|
||||||
|
|
||||||
|
if (is$1.empty(this.media.style)) {
|
||||||
|
this.media.removeAttribute('style');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4566,7 +4672,7 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
case 75:
|
case 75:
|
||||||
// Space and K key
|
// Space and K key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
player.togglePlay();
|
silencePromise(player.togglePlay());
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -4680,15 +4786,17 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
removeCurrent(); // Delay the adding of classname until the focus has changed
|
removeCurrent(); // Delay the adding of classname until the focus has changed
|
||||||
// This event fires before the focusin event
|
// This event fires before the focusin event
|
||||||
|
|
||||||
this.focusTimer = setTimeout(function () {
|
if (event.type !== 'focusout') {
|
||||||
var focused = document.activeElement; // Ignore if current focus element isn't inside the player
|
this.focusTimer = setTimeout(function () {
|
||||||
|
var focused = document.activeElement; // Ignore if current focus element isn't inside the player
|
||||||
|
|
||||||
if (!elements.container.contains(focused)) {
|
if (!elements.container.contains(focused)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
|
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
|
||||||
}, 10);
|
}, 10);
|
||||||
|
}
|
||||||
} // Global window & document listeners
|
} // Global window & document listeners
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
@@ -4706,7 +4814,7 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
once.call(player, document.body, 'touchstart', this.firstTouch); // Tab focus detection
|
once.call(player, document.body, 'touchstart', this.firstTouch); // Tab focus detection
|
||||||
|
|
||||||
toggleListener.call(player, document.body, 'keydown focus blur', this.setTabFocus, toggle, false, true);
|
toggleListener.call(player, document.body, 'keydown focus blur focusout', this.setTabFocus, toggle, false, true);
|
||||||
} // Container listeners
|
} // Container listeners
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
@@ -4749,7 +4857,7 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
}); // Set a gutter for Vimeo
|
}); // Set a gutter for Vimeo
|
||||||
|
|
||||||
var setGutter = function setGutter(ratio, padding, toggle) {
|
var setGutter = function setGutter(ratio, padding, toggle) {
|
||||||
if (!player.isVimeo) {
|
if (!player.isVimeo || player.config.vimeo.premium) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4806,7 +4914,7 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
ratio = _setPlayerSize.ratio; // Set Vimeo gutter
|
ratio = _setPlayerSize.ratio; // Set Vimeo gutter
|
||||||
|
|
||||||
|
|
||||||
setGutter(ratio, padding, isEnter); // If not using native fullscreen, we need to check for resizes of viewport
|
setGutter(ratio, padding, isEnter); // If not using native browser fullscreen API, we need to check for resizes of viewport
|
||||||
|
|
||||||
if (!usingNative) {
|
if (!usingNative) {
|
||||||
if (isEnter) {
|
if (isEnter) {
|
||||||
@@ -4884,9 +4992,13 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
if (player.ended) {
|
if (player.ended) {
|
||||||
_this.proxy(event, player.restart, 'restart');
|
_this.proxy(event, player.restart, 'restart');
|
||||||
|
|
||||||
_this.proxy(event, player.play, 'play');
|
_this.proxy(event, function () {
|
||||||
|
silencePromise(player.play());
|
||||||
|
}, 'play');
|
||||||
} else {
|
} else {
|
||||||
_this.proxy(event, player.togglePlay, 'play');
|
_this.proxy(event, function () {
|
||||||
|
silencePromise(player.togglePlay());
|
||||||
|
}, 'play');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} // Disable right click
|
} // Disable right click
|
||||||
@@ -4984,7 +5096,9 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
if (elements.buttons.play) {
|
if (elements.buttons.play) {
|
||||||
Array.from(elements.buttons.play).forEach(function (button) {
|
Array.from(elements.buttons.play).forEach(function (button) {
|
||||||
_this3.bind(button, 'click', player.togglePlay, 'play');
|
_this3.bind(button, 'click', function () {
|
||||||
|
silencePromise(player.togglePlay());
|
||||||
|
}, 'play');
|
||||||
});
|
});
|
||||||
} // Pause
|
} // Pause
|
||||||
|
|
||||||
@@ -5081,7 +5195,7 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
if (play && done) {
|
if (play && done) {
|
||||||
seek.removeAttribute(attribute);
|
seek.removeAttribute(attribute);
|
||||||
player.play();
|
silencePromise(player.play());
|
||||||
} else if (!done && player.playing) {
|
} else if (!done && player.playing) {
|
||||||
seek.setAttribute(attribute, '');
|
seek.setAttribute(attribute, '');
|
||||||
player.pause();
|
player.pause();
|
||||||
@@ -5179,7 +5293,18 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
|
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
|
||||||
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
||||||
}); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
|
}); // Also update controls.hover state for any non-player children of fullscreen element (as above)
|
||||||
|
|
||||||
|
if (elements.fullscreen) {
|
||||||
|
Array.from(elements.fullscreen.children).filter(function (c) {
|
||||||
|
return !c.contains(elements.container);
|
||||||
|
}).forEach(function (child) {
|
||||||
|
_this3.bind(child, 'mouseenter mouseleave', function (event) {
|
||||||
|
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
|
||||||
|
|
||||||
|
|
||||||
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
|
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
|
||||||
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
||||||
@@ -5595,15 +5720,28 @@ var vimeo = {
|
|||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
var player = this;
|
var player = this;
|
||||||
var config = player.config.vimeo; // Get Vimeo params for the iframe
|
var config = player.config.vimeo;
|
||||||
|
|
||||||
var params = buildUrlParams(extend({}, {
|
var premium = config.premium,
|
||||||
|
referrerPolicy = config.referrerPolicy,
|
||||||
|
frameParams = _objectWithoutProperties(config, ["premium", "referrerPolicy"]); // If the owner has a pro or premium account then we can hide controls etc
|
||||||
|
|
||||||
|
|
||||||
|
if (premium) {
|
||||||
|
Object.assign(frameParams, {
|
||||||
|
controls: false,
|
||||||
|
sidedock: false
|
||||||
|
});
|
||||||
|
} // Get Vimeo params for the iframe
|
||||||
|
|
||||||
|
|
||||||
|
var params = buildUrlParams(_objectSpread2({
|
||||||
loop: player.config.loop.active,
|
loop: player.config.loop.active,
|
||||||
autoplay: player.autoplay,
|
autoplay: player.autoplay,
|
||||||
muted: player.muted,
|
muted: player.muted,
|
||||||
gesture: 'media',
|
gesture: 'media',
|
||||||
playsinline: !this.config.fullscreen.iosNative
|
playsinline: !this.config.fullscreen.iosNative
|
||||||
}, config)); // Get the source URL or ID
|
}, frameParams)); // Get the source URL or ID
|
||||||
|
|
||||||
var source = player.media.getAttribute('src'); // Get from <div> if needed
|
var source = player.media.getAttribute('src'); // Get from <div> if needed
|
||||||
|
|
||||||
@@ -5617,22 +5755,27 @@ var vimeo = {
|
|||||||
var src = format(player.config.urls.vimeo.iframe, id, params);
|
var src = format(player.config.urls.vimeo.iframe, id, params);
|
||||||
iframe.setAttribute('src', src);
|
iframe.setAttribute('src', src);
|
||||||
iframe.setAttribute('allowfullscreen', '');
|
iframe.setAttribute('allowfullscreen', '');
|
||||||
iframe.setAttribute('allowtransparency', '');
|
iframe.setAttribute('allow', 'autoplay,fullscreen,picture-in-picture'); // Set the referrer policy if required
|
||||||
iframe.setAttribute('allow', 'autoplay'); // Set the referrer policy if required
|
|
||||||
|
|
||||||
if (!is$1.empty(config.referrerPolicy)) {
|
if (!is$1.empty(referrerPolicy)) {
|
||||||
iframe.setAttribute('referrerPolicy', config.referrerPolicy);
|
iframe.setAttribute('referrerPolicy', referrerPolicy);
|
||||||
} // Get poster, if already set
|
} // Inject the package
|
||||||
|
|
||||||
|
|
||||||
var poster = player.poster; // Inject the package
|
var poster = player.poster;
|
||||||
|
|
||||||
|
if (premium) {
|
||||||
|
iframe.setAttribute('data-poster', poster);
|
||||||
|
player.media = replaceElement(iframe, player.media);
|
||||||
|
} else {
|
||||||
|
var wrapper = createElement('div', {
|
||||||
|
class: player.config.classNames.embedContainer,
|
||||||
|
'data-poster': poster
|
||||||
|
});
|
||||||
|
wrapper.appendChild(iframe);
|
||||||
|
player.media = replaceElement(wrapper, player.media);
|
||||||
|
} // Get poster image
|
||||||
|
|
||||||
var wrapper = createElement('div', {
|
|
||||||
poster: poster,
|
|
||||||
class: player.config.classNames.embedContainer
|
|
||||||
});
|
|
||||||
wrapper.appendChild(iframe);
|
|
||||||
player.media = replaceElement(wrapper, player.media); // Get poster image
|
|
||||||
|
|
||||||
fetch(format(player.config.urls.vimeo.api, id), 'json').then(function (response) {
|
fetch(format(player.config.urls.vimeo.api, id), 'json').then(function (response) {
|
||||||
if (is$1.empty(response)) {
|
if (is$1.empty(response)) {
|
||||||
@@ -5716,6 +5859,9 @@ var vimeo = {
|
|||||||
player.embed.setPlaybackRate(input).then(function () {
|
player.embed.setPlaybackRate(input).then(function () {
|
||||||
speed = input;
|
speed = input;
|
||||||
triggerEvent.call(player, player.media, 'ratechange');
|
triggerEvent.call(player, player.media, 'ratechange');
|
||||||
|
}).catch(function () {
|
||||||
|
// Cannot set Playback Rate, Video is probably not on Pro account
|
||||||
|
player.options.speed = [1];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}); // Volume
|
}); // Volume
|
||||||
@@ -6002,7 +6148,7 @@ var youtube = {
|
|||||||
|
|
||||||
var container = createElement('div', {
|
var container = createElement('div', {
|
||||||
id: id,
|
id: id,
|
||||||
poster: poster
|
'data-poster': poster
|
||||||
});
|
});
|
||||||
player.media = replaceElement(container, player.media); // Id to poster wrapper
|
player.media = replaceElement(container, player.media); // Id to poster wrapper
|
||||||
|
|
||||||
@@ -6321,14 +6467,12 @@ var media = {
|
|||||||
class: this.config.classNames.video
|
class: this.config.classNames.video
|
||||||
}); // Wrap the video in a container
|
}); // Wrap the video in a container
|
||||||
|
|
||||||
wrap(this.media, this.elements.wrapper); // Faux poster container
|
wrap(this.media, this.elements.wrapper); // Poster image container
|
||||||
|
|
||||||
if (this.isEmbed) {
|
this.elements.poster = createElement('div', {
|
||||||
this.elements.poster = createElement('div', {
|
class: this.config.classNames.poster
|
||||||
class: this.config.classNames.poster
|
});
|
||||||
});
|
this.elements.wrapper.appendChild(this.elements.poster);
|
||||||
this.elements.wrapper.appendChild(this.elements.poster);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
@@ -6455,6 +6599,8 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
* mobile devices, this initialization is done as the result of a user action.
|
* mobile devices, this initialization is done as the result of a user action.
|
||||||
*/
|
*/
|
||||||
value: function setupIMA() {
|
value: function setupIMA() {
|
||||||
|
var _this4 = this;
|
||||||
|
|
||||||
// Create the container for our advertisements
|
// Create the container for our advertisements
|
||||||
this.elements.container = createElement('div', {
|
this.elements.container = createElement('div', {
|
||||||
class: this.player.config.classNames.ads
|
class: this.player.config.classNames.ads
|
||||||
@@ -6467,7 +6613,16 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline); // We assume the adContainer is the video container of the plyr element that will house the ads
|
google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline); // We assume the adContainer is the video container of the plyr element that will house the ads
|
||||||
|
|
||||||
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media); // Request video ads to be pre-loaded
|
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media); // Create ads loader
|
||||||
|
|
||||||
|
this.loader = new google.ima.AdsLoader(this.elements.displayContainer); // Listen and respond to ads loaded and error events
|
||||||
|
|
||||||
|
this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, function (event) {
|
||||||
|
return _this4.onAdsManagerLoaded(event);
|
||||||
|
}, false);
|
||||||
|
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, function (error) {
|
||||||
|
return _this4.onAdError(error);
|
||||||
|
}, false); // Request video ads to be pre-loaded
|
||||||
|
|
||||||
this.requestAds();
|
this.requestAds();
|
||||||
}
|
}
|
||||||
@@ -6478,21 +6633,10 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
}, {
|
}, {
|
||||||
key: "requestAds",
|
key: "requestAds",
|
||||||
value: function requestAds() {
|
value: function requestAds() {
|
||||||
var _this4 = this;
|
|
||||||
|
|
||||||
var container = this.player.elements.container;
|
var container = this.player.elements.container;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create ads loader
|
// Request video ads
|
||||||
this.loader = new google.ima.AdsLoader(this.elements.displayContainer); // Listen and respond to ads loaded and error events
|
|
||||||
|
|
||||||
this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, function (event) {
|
|
||||||
return _this4.onAdsManagerLoaded(event);
|
|
||||||
}, false);
|
|
||||||
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, function (error) {
|
|
||||||
return _this4.onAdError(error);
|
|
||||||
}, false); // Request video ads
|
|
||||||
|
|
||||||
var request = new google.ima.AdsRequest();
|
var request = new google.ima.AdsRequest();
|
||||||
request.adTagUrl = this.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK
|
request.adTagUrl = this.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK
|
||||||
// to select the correct creative if multiple are returned
|
// to select the correct creative if multiple are returned
|
||||||
@@ -6671,7 +6815,13 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
// };
|
// };
|
||||||
// TODO: So there is still this thing where a video should only be allowed to start
|
// TODO: So there is still this thing where a video should only be allowed to start
|
||||||
// playing when the IMA SDK is ready or has failed
|
// playing when the IMA SDK is ready or has failed
|
||||||
this.loadAds();
|
if (this.player.ended) {
|
||||||
|
this.loadAds();
|
||||||
|
} else {
|
||||||
|
// The SDK won't allow new ads to be called without receiving a contentComplete()
|
||||||
|
this.loader.contentComplete();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
||||||
@@ -6807,7 +6957,7 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
this.playing = false; // Play video
|
this.playing = false; // Play video
|
||||||
|
|
||||||
this.player.media.play();
|
silencePromise(this.player.media.play());
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Pause our video
|
* Pause our video
|
||||||
@@ -6864,7 +7014,9 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
_this11.on('loaded', resolve);
|
_this11.on('loaded', resolve);
|
||||||
|
|
||||||
_this11.player.debug.log(_this11.manager);
|
_this11.player.debug.log(_this11.manager);
|
||||||
}); // Now request some new advertisements
|
}); // Now that the manager has been destroyed set it to also be un-initialized
|
||||||
|
|
||||||
|
_this11.initialized = false; // Now request some new advertisements
|
||||||
|
|
||||||
_this11.requestAds();
|
_this11.requestAds();
|
||||||
}).catch(function () {});
|
}).catch(function () {});
|
||||||
@@ -7108,15 +7260,10 @@ var PreviewThumbnails = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
if (is$1.empty(src)) {
|
if (is$1.empty(src)) {
|
||||||
throw new Error('Missing previewThumbnails.src config attribute');
|
throw new Error('Missing previewThumbnails.src config attribute');
|
||||||
} // If string, convert into single-element list
|
} // Resolve promise
|
||||||
|
|
||||||
|
|
||||||
var urls = is$1.string(src) ? [src] : src; // Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
var sortAndResolve = function sortAndResolve() {
|
||||||
|
|
||||||
var promises = urls.map(function (u) {
|
|
||||||
return _this2.getThumbnail(u);
|
|
||||||
});
|
|
||||||
Promise.all(promises).then(function () {
|
|
||||||
// Sort smallest to biggest (e.g., [120p, 480p, 1080p])
|
// Sort smallest to biggest (e.g., [120p, 480p, 1080p])
|
||||||
_this2.thumbnails.sort(function (x, y) {
|
_this2.thumbnails.sort(function (x, y) {
|
||||||
return x.height - y.height;
|
return x.height - y.height;
|
||||||
@@ -7125,7 +7272,25 @@ var PreviewThumbnails = /*#__PURE__*/function () {
|
|||||||
_this2.player.debug.log('Preview thumbnails', _this2.thumbnails);
|
_this2.player.debug.log('Preview thumbnails', _this2.thumbnails);
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
}; // Via callback()
|
||||||
|
|
||||||
|
|
||||||
|
if (is$1.function(src)) {
|
||||||
|
src(function (thumbnails) {
|
||||||
|
_this2.thumbnails = thumbnails;
|
||||||
|
sortAndResolve();
|
||||||
|
});
|
||||||
|
} // VTT urls
|
||||||
|
else {
|
||||||
|
// If string, convert into single-element list
|
||||||
|
var urls = is$1.string(src) ? [src] : src; // Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
||||||
|
|
||||||
|
var promises = urls.map(function (u) {
|
||||||
|
return _this2.getThumbnail(u);
|
||||||
|
}); // Resolve
|
||||||
|
|
||||||
|
Promise.all(promises).then(sortAndResolve);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} // Process individual VTT file
|
} // Process individual VTT file
|
||||||
|
|
||||||
@@ -7913,6 +8078,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
this.elements = {
|
this.elements = {
|
||||||
container: null,
|
container: null,
|
||||||
|
fullscreen: null,
|
||||||
captions: null,
|
captions: null,
|
||||||
buttons: {},
|
buttons: {},
|
||||||
display: {},
|
display: {},
|
||||||
@@ -8087,9 +8253,11 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
tabindex: 0
|
tabindex: 0
|
||||||
});
|
});
|
||||||
wrap(this.media, this.elements.container);
|
wrap(this.media, this.elements.container);
|
||||||
} // Add style hook
|
} // Migrate custom properties from media to container (so they work 😉)
|
||||||
|
|
||||||
|
|
||||||
|
ui.migrateStyles.call(this); // Add style hook
|
||||||
|
|
||||||
ui.addStyleHook.call(this); // Setup media
|
ui.addStyleHook.call(this); // Setup media
|
||||||
|
|
||||||
media.setup.call(this); // Listen for events if debugging
|
media.setup.call(this); // Listen for events if debugging
|
||||||
@@ -8098,10 +8266,12 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
|
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
|
||||||
_this.debug.log("event: ".concat(event.type));
|
_this.debug.log("event: ".concat(event.type));
|
||||||
});
|
});
|
||||||
} // Setup interface
|
} // Setup fullscreen
|
||||||
// If embed but not fully supported, build interface now to avoid flash of controls
|
|
||||||
|
|
||||||
|
|
||||||
|
this.fullscreen = new Fullscreen(this); // Setup interface
|
||||||
|
// If embed but not fully supported, build interface now to avoid flash of controls
|
||||||
|
|
||||||
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
|
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
|
||||||
ui.build.call(this);
|
ui.build.call(this);
|
||||||
} // Container listeners
|
} // Container listeners
|
||||||
@@ -8109,9 +8279,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
this.listeners.container(); // Global listeners
|
this.listeners.container(); // Global listeners
|
||||||
|
|
||||||
this.listeners.global(); // Setup fullscreen
|
this.listeners.global(); // Setup ads if provided
|
||||||
|
|
||||||
this.fullscreen = new Fullscreen(this); // Setup ads if provided
|
|
||||||
|
|
||||||
if (this.config.ads.enabled) {
|
if (this.config.ads.enabled) {
|
||||||
this.ads = new Ads(this);
|
this.ads = new Ads(this);
|
||||||
@@ -8120,7 +8288,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
if (this.isHTML5 && this.config.autoplay) {
|
if (this.isHTML5 && this.config.autoplay) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
return _this.play();
|
return silencePromise(_this.play());
|
||||||
}, 10);
|
}, 10);
|
||||||
} // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
} // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
||||||
|
|
||||||
@@ -8157,7 +8325,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
this.ads.managerPromise.then(function () {
|
this.ads.managerPromise.then(function () {
|
||||||
return _this2.ads.play();
|
return _this2.ads.play();
|
||||||
}).catch(function () {
|
}).catch(function () {
|
||||||
return _this2.media.play();
|
return silencePromise(_this2.media.play());
|
||||||
});
|
});
|
||||||
} // Return the promise (for HTML5)
|
} // Return the promise (for HTML5)
|
||||||
|
|
||||||
@@ -8810,7 +8978,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
var updateStorage = true;
|
var updateStorage = true;
|
||||||
|
|
||||||
if (!options.includes(quality)) {
|
if (!options.includes(quality)) {
|
||||||
var value = closest(options, quality);
|
var value = closest$1(options, quality);
|
||||||
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
|
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
|
||||||
quality = value; // Don't update storage if quality is not supported
|
quality = value; // Don't update storage if quality is not supported
|
||||||
|
|
||||||
@@ -8849,41 +9017,41 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
this.media.loop = toggle; // Set default to be a true toggle
|
this.media.loop = toggle; // Set default to be a true toggle
|
||||||
|
|
||||||
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'start':
|
case 'start':
|
||||||
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
||||||
this.config.loop.end = null;
|
this.config.loop.end = null;
|
||||||
}
|
}
|
||||||
this.config.loop.start = this.currentTime;
|
this.config.loop.start = this.currentTime;
|
||||||
// this.config.loop.indicator.start = this.elements.display.played.value;
|
// this.config.loop.indicator.start = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
if (this.config.loop.start >= this.currentTime) {
|
if (this.config.loop.start >= this.currentTime) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
this.config.loop.end = this.currentTime;
|
this.config.loop.end = this.currentTime;
|
||||||
// this.config.loop.indicator.end = this.elements.display.played.value;
|
// this.config.loop.indicator.end = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = this.duration - 2;
|
|
||||||
this.config.loop.indicator.start = 0;
|
|
||||||
this.config.loop.indicator.end = 100;
|
|
||||||
break;
|
|
||||||
case 'toggle':
|
|
||||||
if (this.config.loop.active) {
|
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = null;
|
|
||||||
} else {
|
|
||||||
this.config.loop.start = 0;
|
this.config.loop.start = 0;
|
||||||
this.config.loop.end = this.duration - 2;
|
this.config.loop.end = this.duration - 2;
|
||||||
}
|
this.config.loop.indicator.start = 0;
|
||||||
break;
|
this.config.loop.indicator.end = 100;
|
||||||
default:
|
break;
|
||||||
this.config.loop.start = 0;
|
case 'toggle':
|
||||||
this.config.loop.end = null;
|
if (this.config.loop.active) {
|
||||||
break;
|
this.config.loop.start = 0;
|
||||||
} */
|
this.config.loop.end = null;
|
||||||
|
} else {
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = this.duration - 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = null;
|
||||||
|
break;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get current loop state
|
* Get current loop state
|
||||||
@@ -8955,7 +9123,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.media.getAttribute('poster');
|
return this.media.getAttribute('poster') || this.media.getAttribute('data-poster');
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the current aspect ratio in use
|
* Get the current aspect ratio in use
|
||||||
|
|||||||
Vendored
+348
-173
@@ -269,7 +269,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
(module.exports = function (key, value) {
|
(module.exports = function (key, value) {
|
||||||
return sharedStore[key] || (sharedStore[key] = value !== undefined ? value : {});
|
return sharedStore[key] || (sharedStore[key] = value !== undefined ? value : {});
|
||||||
})('versions', []).push({
|
})('versions', []).push({
|
||||||
version: '3.6.4',
|
version: '3.6.5',
|
||||||
mode: 'global',
|
mode: 'global',
|
||||||
copyright: '© 2020 Denis Pushkarev (zloirock.ru)'
|
copyright: '© 2020 Denis Pushkarev (zloirock.ru)'
|
||||||
});
|
});
|
||||||
@@ -3061,7 +3061,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
var INVALID_PORT = 'Invalid port';
|
var INVALID_PORT = 'Invalid port';
|
||||||
|
|
||||||
var ALPHA = /[A-Za-z]/;
|
var ALPHA = /[A-Za-z]/;
|
||||||
var ALPHANUMERIC = /[\d+\-.A-Za-z]/;
|
var ALPHANUMERIC = /[\d+-.A-Za-z]/;
|
||||||
var DIGIT = /\d/;
|
var DIGIT = /\d/;
|
||||||
var HEX_START = /^(0x|0X)/;
|
var HEX_START = /^(0x|0X)/;
|
||||||
var OCT = /^[0-7]+$/;
|
var OCT = /^[0-7]+$/;
|
||||||
@@ -4123,6 +4123,42 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _objectWithoutPropertiesLoose(source, excluded) {
|
||||||
|
if (source == null) return {};
|
||||||
|
var target = {};
|
||||||
|
var sourceKeys = Object.keys(source);
|
||||||
|
var key, i;
|
||||||
|
|
||||||
|
for (i = 0; i < sourceKeys.length; i++) {
|
||||||
|
key = sourceKeys[i];
|
||||||
|
if (excluded.indexOf(key) >= 0) continue;
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _objectWithoutProperties(source, excluded) {
|
||||||
|
if (source == null) return {};
|
||||||
|
|
||||||
|
var target = _objectWithoutPropertiesLoose(source, excluded);
|
||||||
|
|
||||||
|
var key, i;
|
||||||
|
|
||||||
|
if (Object.getOwnPropertySymbols) {
|
||||||
|
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
|
||||||
|
|
||||||
|
for (i = 0; i < sourceSymbolKeys.length; i++) {
|
||||||
|
key = sourceSymbolKeys[i];
|
||||||
|
if (excluded.indexOf(key) >= 0) continue;
|
||||||
|
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
function _slicedToArray(arr, i) {
|
function _slicedToArray(arr, i) {
|
||||||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
||||||
}
|
}
|
||||||
@@ -6008,7 +6044,13 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
defer = functionBindContext(port.postMessage, port, 1);
|
defer = functionBindContext(port.postMessage, port, 1);
|
||||||
// Browsers with postMessage, skip WebWorkers
|
// Browsers with postMessage, skip WebWorkers
|
||||||
// IE8 has postMessage, but it's sync & typeof its postMessage is 'object'
|
// IE8 has postMessage, but it's sync & typeof its postMessage is 'object'
|
||||||
} else if (global_1.addEventListener && typeof postMessage == 'function' && !global_1.importScripts && !fails(post)) {
|
} else if (
|
||||||
|
global_1.addEventListener &&
|
||||||
|
typeof postMessage == 'function' &&
|
||||||
|
!global_1.importScripts &&
|
||||||
|
!fails(post) &&
|
||||||
|
location.protocol !== 'file:'
|
||||||
|
) {
|
||||||
defer = post;
|
defer = post;
|
||||||
global_1.addEventListener('message', listener, false);
|
global_1.addEventListener('message', listener, false);
|
||||||
// IE8-
|
// IE8-
|
||||||
@@ -6617,7 +6659,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var isPromise = function isPromise(input) {
|
var isPromise = function isPromise(input) {
|
||||||
return instanceOf$1(input, Promise);
|
return instanceOf$1(input, Promise) && isFunction$1(input.then);
|
||||||
};
|
};
|
||||||
|
|
||||||
var isEmpty$1 = function isEmpty(input) {
|
var isEmpty$1 = function isEmpty(input) {
|
||||||
@@ -7009,12 +7051,33 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
} // Element matches selector
|
} // Element matches selector
|
||||||
|
|
||||||
function matches$1(element, selector) {
|
function matches$1(element, selector) {
|
||||||
|
var _Element = Element,
|
||||||
|
prototype = _Element.prototype;
|
||||||
|
|
||||||
function match() {
|
function match() {
|
||||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
var method = match;
|
var method = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
|
||||||
|
return method.call(element, selector);
|
||||||
|
} // Closest ancestor element matching selector (also tests element itself)
|
||||||
|
|
||||||
|
function closest(element, selector) {
|
||||||
|
var _Element2 = Element,
|
||||||
|
prototype = _Element2.prototype; // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
|
||||||
|
|
||||||
|
function closestElement() {
|
||||||
|
var el = this;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (matches$1.matches(el, selector)) return el;
|
||||||
|
el = el.parentElement || el.parentNode;
|
||||||
|
} while (el !== null && el.nodeType === 1);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var method = prototype.closest || closestElement;
|
||||||
return method.call(element, selector);
|
return method.call(element, selector);
|
||||||
} // Find all elements
|
} // Find all elements
|
||||||
|
|
||||||
@@ -7286,6 +7349,19 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
}).then(function () {});
|
}).then(function () {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Silence a Promise-like object.
|
||||||
|
* This is useful for avoiding non-harmful, but potentially confusing "uncaught
|
||||||
|
* play promise" rejection error messages.
|
||||||
|
* @param {Object} value An object that may or may not be `Promise`-like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function silencePromise(value) {
|
||||||
|
if (is$1.promise(value)) {
|
||||||
|
value.then(null, function () {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateRatio(input) {
|
function validateRatio(input) {
|
||||||
if (!is$1.array(input) && (!is$1.string(input) || !input.includes(':'))) {
|
if (!is$1.array(input) && (!is$1.string(input) || !input.includes(':'))) {
|
||||||
return false;
|
return false;
|
||||||
@@ -7354,8 +7430,8 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
var padding = 100 / w * h;
|
var padding = 100 / w * h;
|
||||||
wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||||
|
|
||||||
if (this.isVimeo && this.supported.ui) {
|
if (this.isVimeo && !this.config.vimeo.premium && this.supported.ui) {
|
||||||
var height = 240;
|
var height = 100 / this.media.offsetWidth * parseInt(window.getComputedStyle(this.media).paddingBottom, 10);
|
||||||
var offset = (height - padding) / (height / 50);
|
var offset = (height - padding) / (height / 50);
|
||||||
this.media.style.transform = "translateY(-".concat(offset, "%)");
|
this.media.style.transform = "translateY(-".concat(offset, "%)");
|
||||||
} else if (this.isHTML5) {
|
} else if (this.isHTML5) {
|
||||||
@@ -7461,7 +7537,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
player.currentTime = currentTime; // Resume playing
|
player.currentTime = currentTime; // Resume playing
|
||||||
|
|
||||||
if (!paused) {
|
if (!paused) {
|
||||||
player.play();
|
silencePromise(player.play());
|
||||||
}
|
}
|
||||||
}); // Load new source
|
}); // Load new source
|
||||||
|
|
||||||
@@ -7508,7 +7584,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
});
|
});
|
||||||
} // Get the closest value in an array
|
} // Get the closest value in an array
|
||||||
|
|
||||||
function closest(array, value) {
|
function closest$1(array, value) {
|
||||||
if (!is$1.array(array) || !array.length) {
|
if (!is$1.array(array) || !array.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -7625,19 +7701,19 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
return (current / max * 100).toFixed(2);
|
return (current / max * 100).toFixed(2);
|
||||||
} // Replace all occurances of a string in a string
|
} // Replace all occurances of a string in a string
|
||||||
|
|
||||||
function replaceAll() {
|
var replaceAll = function replaceAll() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
||||||
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
||||||
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
} // Convert to title case
|
}; // Convert to title case
|
||||||
|
|
||||||
function toTitleCase() {
|
var toTitleCase = function toTitleCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
return input.toString().replace(/\w\S*/g, function (text) {
|
return input.toString().replace(/\w\S*/g, function (text) {
|
||||||
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
||||||
});
|
});
|
||||||
} // Convert string to pascalCase
|
}; // Convert string to pascalCase
|
||||||
|
|
||||||
function toPascalCase() {
|
function toPascalCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
@@ -8744,39 +8820,39 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// Set the looping options
|
// Set the looping options
|
||||||
|
|
||||||
/* setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
// Menu required
|
// Menu required
|
||||||
if (!is.element(this.elements.settings.panels.loop)) {
|
if (!is.element(this.elements.settings.panels.loop)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
||||||
// Show the pane and tab
|
// Show the pane and tab
|
||||||
toggleHidden(this.elements.settings.buttons.loop, false);
|
toggleHidden(this.elements.settings.buttons.loop, false);
|
||||||
toggleHidden(this.elements.settings.panels.loop, false);
|
toggleHidden(this.elements.settings.panels.loop, false);
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !is.empty(this.loop.options);
|
const toggle = !is.empty(this.loop.options);
|
||||||
controls.toggleMenuButton.call(this, 'loop', toggle);
|
controls.toggleMenuButton.call(this, 'loop', toggle);
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
emptyElement(list);
|
emptyElement(list);
|
||||||
options.forEach(option => {
|
options.forEach(option => {
|
||||||
const item = createElement('li');
|
const item = createElement('li');
|
||||||
const button = createElement(
|
const button = createElement(
|
||||||
'button',
|
'button',
|
||||||
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: this.config.classNames.control,
|
class: this.config.classNames.control,
|
||||||
'data-plyr-loop-action': option,
|
'data-plyr-loop-action': option,
|
||||||
}),
|
}),
|
||||||
i18n.get(option, this.config)
|
i18n.get(option, this.config)
|
||||||
);
|
);
|
||||||
if (['start', 'end'].includes(option)) {
|
if (['start', 'end'].includes(option)) {
|
||||||
const badge = controls.createBadge.call(this, '00:00');
|
const badge = controls.createBadge.call(this, '00:00');
|
||||||
button.appendChild(badge);
|
button.appendChild(badge);
|
||||||
}
|
}
|
||||||
item.appendChild(button);
|
item.appendChild(button);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
}, */
|
}, */
|
||||||
// Get current selected caption language
|
// Get current selected caption language
|
||||||
// TODO: rework this to user the getter in the API?
|
// TODO: rework this to user the getter in the API?
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
@@ -9578,9 +9654,15 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
meta.set(track, {
|
meta.set(track, {
|
||||||
default: track.mode === 'showing'
|
default: track.mode === 'showing'
|
||||||
}); // Turn off native caption rendering to avoid double captions
|
}); // Turn off native caption rendering to avoid double captions
|
||||||
|
// Note: mode='hidden' forces a track to download. To ensure every track
|
||||||
|
// isn't downloaded at once, only 'showing' tracks should be reassigned
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
|
||||||
track.mode = 'hidden'; // Add event listener for cue changes
|
if (track.mode === 'showing') {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
track.mode = 'hidden';
|
||||||
|
} // Add event listener for cue changes
|
||||||
|
|
||||||
|
|
||||||
on.call(_this, track, 'cuechange', function () {
|
on.call(_this, track, 'cuechange', function () {
|
||||||
return captions.updateCues.call(_this);
|
return captions.updateCues.call(_this);
|
||||||
@@ -9604,6 +9686,8 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// Toggle captions display
|
// Toggle captions display
|
||||||
// Used internally for the toggleCaptions method, with the passive option forced to false
|
// Used internally for the toggleCaptions method, with the passive option forced to false
|
||||||
toggle: function toggle(input) {
|
toggle: function toggle(input) {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
||||||
|
|
||||||
// If there's no full support
|
// If there's no full support
|
||||||
@@ -9650,7 +9734,15 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
controls.updateSetting.call(this, 'captions'); // Trigger event (not used internally)
|
controls.updateSetting.call(this, 'captions'); // Trigger event (not used internally)
|
||||||
|
|
||||||
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
||||||
}
|
} // Wait for the call stack to clear before setting mode='hidden'
|
||||||
|
// on the active track - forcing the browser to download it
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
if (active && _this2.captions.toggled) {
|
||||||
|
_this2.captions.currentTrackNode.mode = 'hidden';
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
// Set captions by track index
|
// Set captions by track index
|
||||||
// Used internally for the currentTrack setter with the passive option forced to false
|
// Used internally for the currentTrack setter with the passive option forced to false
|
||||||
@@ -9731,7 +9823,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// If update is false it will also ignore tracks without metadata
|
// If update is false it will also ignore tracks without metadata
|
||||||
// This is used to "freeze" the language options when captions.update is false
|
// This is used to "freeze" the language options when captions.update is false
|
||||||
getTracks: function getTracks() {
|
getTracks: function getTracks() {
|
||||||
var _this2 = this;
|
var _this3 = this;
|
||||||
|
|
||||||
var update = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
var update = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||||||
// Handle media or textTracks missing or null
|
// Handle media or textTracks missing or null
|
||||||
@@ -9739,20 +9831,20 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
||||||
|
|
||||||
return tracks.filter(function (track) {
|
return tracks.filter(function (track) {
|
||||||
return !_this2.isHTML5 || update || _this2.captions.meta.has(track);
|
return !_this3.isHTML5 || update || _this3.captions.meta.has(track);
|
||||||
}).filter(function (track) {
|
}).filter(function (track) {
|
||||||
return ['captions', 'subtitles'].includes(track.kind);
|
return ['captions', 'subtitles'].includes(track.kind);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// Match tracks based on languages and get the first
|
// Match tracks based on languages and get the first
|
||||||
findTrack: function findTrack(languages) {
|
findTrack: function findTrack(languages) {
|
||||||
var _this3 = this;
|
var _this4 = this;
|
||||||
|
|
||||||
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||||
var tracks = captions.getTracks.call(this);
|
var tracks = captions.getTracks.call(this);
|
||||||
|
|
||||||
var sortIsDefault = function sortIsDefault(track) {
|
var sortIsDefault = function sortIsDefault(track) {
|
||||||
return Number((_this3.captions.meta.get(track) || {}).default);
|
return Number((_this4.captions.meta.get(track) || {}).default);
|
||||||
};
|
};
|
||||||
|
|
||||||
var sorted = Array.from(tracks).sort(function (a, b) {
|
var sorted = Array.from(tracks).sort(function (a, b) {
|
||||||
@@ -9885,7 +9977,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/3.5.10/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.6.1/plyr.svg',
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
// Quality default
|
// Quality default
|
||||||
@@ -9933,6 +10025,9 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
fallback: true,
|
fallback: true,
|
||||||
// Fallback using full viewport/window
|
// Fallback using full viewport/window
|
||||||
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
|
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
|
||||||
|
// Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode
|
||||||
|
// Non-ancestors of the player element will be ignored
|
||||||
|
// container: null, // defaults to the player element
|
||||||
|
|
||||||
},
|
},
|
||||||
// Local storage
|
// Local storage
|
||||||
@@ -10170,16 +10265,16 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
title: false,
|
title: false,
|
||||||
speed: true,
|
speed: true,
|
||||||
transparent: false,
|
transparent: false,
|
||||||
// These settings require a pro or premium account to work
|
// Whether the owner of the video has a Pro or Business account
|
||||||
sidedock: false,
|
// (which allows us to properly hide controls without CSS hacks, etc)
|
||||||
controls: false,
|
premium: false,
|
||||||
// Custom settings from Plyr
|
// Custom settings from Plyr
|
||||||
referrerPolicy: null // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
referrerPolicy: null // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
||||||
|
|
||||||
},
|
},
|
||||||
// YouTube plugin
|
// YouTube plugin
|
||||||
youtube: {
|
youtube: {
|
||||||
noCookie: false,
|
noCookie: true,
|
||||||
// Whether to use an alternative version of YouTube without cookies
|
// Whether to use an alternative version of YouTube without cookies
|
||||||
rel: 0,
|
rel: 0,
|
||||||
// No related vids
|
// No related vids
|
||||||
@@ -10289,7 +10384,10 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
y: 0
|
y: 0
|
||||||
}; // Force the use of 'full window/browser' rather than fullscreen
|
}; // Force the use of 'full window/browser' rather than fullscreen
|
||||||
|
|
||||||
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Register event listeners
|
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Get the fullscreen element
|
||||||
|
// Checks container is an ancestor, defaults to null
|
||||||
|
|
||||||
|
this.player.elements.fullscreen = player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container); // Register event listeners
|
||||||
// Handle event (incase user presses escape etc)
|
// Handle event (incase user presses escape etc)
|
||||||
|
|
||||||
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
|
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
|
||||||
@@ -10462,7 +10560,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
this.target.webkitExitFullscreen();
|
this.target.webkitExitFullscreen();
|
||||||
this.player.play();
|
silencePromise(this.player.play());
|
||||||
} else if (!Fullscreen.native || this.forceFallback) {
|
} else if (!Fullscreen.native || this.forceFallback) {
|
||||||
this.toggleFallback(false);
|
this.toggleFallback(false);
|
||||||
} else if (!this.prefix) {
|
} else if (!this.prefix) {
|
||||||
@@ -10509,13 +10607,13 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var element = !this.prefix ? document.fullscreenElement : document["".concat(this.prefix).concat(this.property, "Element")];
|
var element = !this.prefix ? document.fullscreenElement : document["".concat(this.prefix).concat(this.property, "Element")];
|
||||||
return element === this.target;
|
return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target;
|
||||||
} // Get target element
|
} // Get target element
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
key: "target",
|
key: "target",
|
||||||
get: function get() {
|
get: function get() {
|
||||||
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
|
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.fullscreen || this.player.elements.container;
|
||||||
}
|
}
|
||||||
}], [{
|
}], [{
|
||||||
key: "native",
|
key: "native",
|
||||||
@@ -10724,12 +10822,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
} // Set property synchronously to respect the call order
|
} // Set property synchronously to respect the call order
|
||||||
|
|
||||||
|
|
||||||
this.media.setAttribute('poster', poster); // HTML5 uses native poster attribute
|
this.media.setAttribute('data-poster', poster); // Wait until ui is ready
|
||||||
|
|
||||||
if (this.isHTML5) {
|
|
||||||
return Promise.resolve(poster);
|
|
||||||
} // Wait until ui is ready
|
|
||||||
|
|
||||||
|
|
||||||
return ready.call(this) // Load image
|
return ready.call(this) // Load image
|
||||||
.then(function () {
|
.then(function () {
|
||||||
@@ -10805,6 +10898,26 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));
|
this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// Migrate any custom properties from the media to the parent
|
||||||
|
migrateStyles: function migrateStyles() {
|
||||||
|
var _this5 = this;
|
||||||
|
|
||||||
|
// Loop through values (as they are the keys when the object is spread 🤔)
|
||||||
|
Object.values(_objectSpread2({}, this.media.style)) // We're only fussed about Plyr specific properties
|
||||||
|
.filter(function (key) {
|
||||||
|
return !is$1.empty(key) && key.startsWith('--plyr');
|
||||||
|
}).forEach(function (key) {
|
||||||
|
// Set on the container
|
||||||
|
_this5.elements.container.style.setProperty(key, _this5.media.style.getPropertyValue(key)); // Clean up from media element
|
||||||
|
|
||||||
|
|
||||||
|
_this5.media.style.removeProperty(key);
|
||||||
|
}); // Remove attribute if empty
|
||||||
|
|
||||||
|
if (is$1.empty(this.media.style)) {
|
||||||
|
this.media.removeAttribute('style');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -10899,7 +11012,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
case 75:
|
case 75:
|
||||||
// Space and K key
|
// Space and K key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
player.togglePlay();
|
silencePromise(player.togglePlay());
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -11013,15 +11126,17 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
removeCurrent(); // Delay the adding of classname until the focus has changed
|
removeCurrent(); // Delay the adding of classname until the focus has changed
|
||||||
// This event fires before the focusin event
|
// This event fires before the focusin event
|
||||||
|
|
||||||
this.focusTimer = setTimeout(function () {
|
if (event.type !== 'focusout') {
|
||||||
var focused = document.activeElement; // Ignore if current focus element isn't inside the player
|
this.focusTimer = setTimeout(function () {
|
||||||
|
var focused = document.activeElement; // Ignore if current focus element isn't inside the player
|
||||||
|
|
||||||
if (!elements.container.contains(focused)) {
|
if (!elements.container.contains(focused)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
|
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
|
||||||
}, 10);
|
}, 10);
|
||||||
|
}
|
||||||
} // Global window & document listeners
|
} // Global window & document listeners
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
@@ -11039,7 +11154,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
once.call(player, document.body, 'touchstart', this.firstTouch); // Tab focus detection
|
once.call(player, document.body, 'touchstart', this.firstTouch); // Tab focus detection
|
||||||
|
|
||||||
toggleListener.call(player, document.body, 'keydown focus blur', this.setTabFocus, toggle, false, true);
|
toggleListener.call(player, document.body, 'keydown focus blur focusout', this.setTabFocus, toggle, false, true);
|
||||||
} // Container listeners
|
} // Container listeners
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
@@ -11082,7 +11197,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
}); // Set a gutter for Vimeo
|
}); // Set a gutter for Vimeo
|
||||||
|
|
||||||
var setGutter = function setGutter(ratio, padding, toggle) {
|
var setGutter = function setGutter(ratio, padding, toggle) {
|
||||||
if (!player.isVimeo) {
|
if (!player.isVimeo || player.config.vimeo.premium) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11139,7 +11254,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
ratio = _setPlayerSize.ratio; // Set Vimeo gutter
|
ratio = _setPlayerSize.ratio; // Set Vimeo gutter
|
||||||
|
|
||||||
|
|
||||||
setGutter(ratio, padding, isEnter); // If not using native fullscreen, we need to check for resizes of viewport
|
setGutter(ratio, padding, isEnter); // If not using native browser fullscreen API, we need to check for resizes of viewport
|
||||||
|
|
||||||
if (!usingNative) {
|
if (!usingNative) {
|
||||||
if (isEnter) {
|
if (isEnter) {
|
||||||
@@ -11217,9 +11332,13 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
if (player.ended) {
|
if (player.ended) {
|
||||||
_this.proxy(event, player.restart, 'restart');
|
_this.proxy(event, player.restart, 'restart');
|
||||||
|
|
||||||
_this.proxy(event, player.play, 'play');
|
_this.proxy(event, function () {
|
||||||
|
silencePromise(player.play());
|
||||||
|
}, 'play');
|
||||||
} else {
|
} else {
|
||||||
_this.proxy(event, player.togglePlay, 'play');
|
_this.proxy(event, function () {
|
||||||
|
silencePromise(player.togglePlay());
|
||||||
|
}, 'play');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} // Disable right click
|
} // Disable right click
|
||||||
@@ -11317,7 +11436,9 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
if (elements.buttons.play) {
|
if (elements.buttons.play) {
|
||||||
Array.from(elements.buttons.play).forEach(function (button) {
|
Array.from(elements.buttons.play).forEach(function (button) {
|
||||||
_this3.bind(button, 'click', player.togglePlay, 'play');
|
_this3.bind(button, 'click', function () {
|
||||||
|
silencePromise(player.togglePlay());
|
||||||
|
}, 'play');
|
||||||
});
|
});
|
||||||
} // Pause
|
} // Pause
|
||||||
|
|
||||||
@@ -11414,7 +11535,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
if (play && done) {
|
if (play && done) {
|
||||||
seek.removeAttribute(attribute);
|
seek.removeAttribute(attribute);
|
||||||
player.play();
|
silencePromise(player.play());
|
||||||
} else if (!done && player.playing) {
|
} else if (!done && player.playing) {
|
||||||
seek.setAttribute(attribute, '');
|
seek.setAttribute(attribute, '');
|
||||||
player.pause();
|
player.pause();
|
||||||
@@ -11512,7 +11633,18 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
|
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
|
||||||
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
||||||
}); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
|
}); // Also update controls.hover state for any non-player children of fullscreen element (as above)
|
||||||
|
|
||||||
|
if (elements.fullscreen) {
|
||||||
|
Array.from(elements.fullscreen.children).filter(function (c) {
|
||||||
|
return !c.contains(elements.container);
|
||||||
|
}).forEach(function (child) {
|
||||||
|
_this3.bind(child, 'mouseenter mouseleave', function (event) {
|
||||||
|
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
|
||||||
|
|
||||||
|
|
||||||
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
|
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
|
||||||
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
||||||
@@ -11981,15 +12113,28 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
var player = this;
|
var player = this;
|
||||||
var config = player.config.vimeo; // Get Vimeo params for the iframe
|
var config = player.config.vimeo;
|
||||||
|
|
||||||
var params = buildUrlParams(extend({}, {
|
var premium = config.premium,
|
||||||
|
referrerPolicy = config.referrerPolicy,
|
||||||
|
frameParams = _objectWithoutProperties(config, ["premium", "referrerPolicy"]); // If the owner has a pro or premium account then we can hide controls etc
|
||||||
|
|
||||||
|
|
||||||
|
if (premium) {
|
||||||
|
Object.assign(frameParams, {
|
||||||
|
controls: false,
|
||||||
|
sidedock: false
|
||||||
|
});
|
||||||
|
} // Get Vimeo params for the iframe
|
||||||
|
|
||||||
|
|
||||||
|
var params = buildUrlParams(_objectSpread2({
|
||||||
loop: player.config.loop.active,
|
loop: player.config.loop.active,
|
||||||
autoplay: player.autoplay,
|
autoplay: player.autoplay,
|
||||||
muted: player.muted,
|
muted: player.muted,
|
||||||
gesture: 'media',
|
gesture: 'media',
|
||||||
playsinline: !this.config.fullscreen.iosNative
|
playsinline: !this.config.fullscreen.iosNative
|
||||||
}, config)); // Get the source URL or ID
|
}, frameParams)); // Get the source URL or ID
|
||||||
|
|
||||||
var source = player.media.getAttribute('src'); // Get from <div> if needed
|
var source = player.media.getAttribute('src'); // Get from <div> if needed
|
||||||
|
|
||||||
@@ -12003,22 +12148,27 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
var src = format(player.config.urls.vimeo.iframe, id, params);
|
var src = format(player.config.urls.vimeo.iframe, id, params);
|
||||||
iframe.setAttribute('src', src);
|
iframe.setAttribute('src', src);
|
||||||
iframe.setAttribute('allowfullscreen', '');
|
iframe.setAttribute('allowfullscreen', '');
|
||||||
iframe.setAttribute('allowtransparency', '');
|
iframe.setAttribute('allow', 'autoplay,fullscreen,picture-in-picture'); // Set the referrer policy if required
|
||||||
iframe.setAttribute('allow', 'autoplay'); // Set the referrer policy if required
|
|
||||||
|
|
||||||
if (!is$1.empty(config.referrerPolicy)) {
|
if (!is$1.empty(referrerPolicy)) {
|
||||||
iframe.setAttribute('referrerPolicy', config.referrerPolicy);
|
iframe.setAttribute('referrerPolicy', referrerPolicy);
|
||||||
} // Get poster, if already set
|
} // Inject the package
|
||||||
|
|
||||||
|
|
||||||
var poster = player.poster; // Inject the package
|
var poster = player.poster;
|
||||||
|
|
||||||
|
if (premium) {
|
||||||
|
iframe.setAttribute('data-poster', poster);
|
||||||
|
player.media = replaceElement(iframe, player.media);
|
||||||
|
} else {
|
||||||
|
var wrapper = createElement('div', {
|
||||||
|
class: player.config.classNames.embedContainer,
|
||||||
|
'data-poster': poster
|
||||||
|
});
|
||||||
|
wrapper.appendChild(iframe);
|
||||||
|
player.media = replaceElement(wrapper, player.media);
|
||||||
|
} // Get poster image
|
||||||
|
|
||||||
var wrapper = createElement('div', {
|
|
||||||
poster: poster,
|
|
||||||
class: player.config.classNames.embedContainer
|
|
||||||
});
|
|
||||||
wrapper.appendChild(iframe);
|
|
||||||
player.media = replaceElement(wrapper, player.media); // Get poster image
|
|
||||||
|
|
||||||
fetch(format(player.config.urls.vimeo.api, id), 'json').then(function (response) {
|
fetch(format(player.config.urls.vimeo.api, id), 'json').then(function (response) {
|
||||||
if (is$1.empty(response)) {
|
if (is$1.empty(response)) {
|
||||||
@@ -12102,6 +12252,9 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
player.embed.setPlaybackRate(input).then(function () {
|
player.embed.setPlaybackRate(input).then(function () {
|
||||||
speed = input;
|
speed = input;
|
||||||
triggerEvent.call(player, player.media, 'ratechange');
|
triggerEvent.call(player, player.media, 'ratechange');
|
||||||
|
}).catch(function () {
|
||||||
|
// Cannot set Playback Rate, Video is probably not on Pro account
|
||||||
|
player.options.speed = [1];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}); // Volume
|
}); // Volume
|
||||||
@@ -12386,7 +12539,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
var container = createElement('div', {
|
var container = createElement('div', {
|
||||||
id: id,
|
id: id,
|
||||||
poster: poster
|
'data-poster': poster
|
||||||
});
|
});
|
||||||
player.media = replaceElement(container, player.media); // Id to poster wrapper
|
player.media = replaceElement(container, player.media); // Id to poster wrapper
|
||||||
|
|
||||||
@@ -12704,14 +12857,12 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
class: this.config.classNames.video
|
class: this.config.classNames.video
|
||||||
}); // Wrap the video in a container
|
}); // Wrap the video in a container
|
||||||
|
|
||||||
wrap$1(this.media, this.elements.wrapper); // Faux poster container
|
wrap$1(this.media, this.elements.wrapper); // Poster image container
|
||||||
|
|
||||||
if (this.isEmbed) {
|
this.elements.poster = createElement('div', {
|
||||||
this.elements.poster = createElement('div', {
|
class: this.config.classNames.poster
|
||||||
class: this.config.classNames.poster
|
});
|
||||||
});
|
this.elements.wrapper.appendChild(this.elements.poster);
|
||||||
this.elements.wrapper.appendChild(this.elements.poster);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
@@ -12838,6 +12989,8 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
* mobile devices, this initialization is done as the result of a user action.
|
* mobile devices, this initialization is done as the result of a user action.
|
||||||
*/
|
*/
|
||||||
value: function setupIMA() {
|
value: function setupIMA() {
|
||||||
|
var _this4 = this;
|
||||||
|
|
||||||
// Create the container for our advertisements
|
// Create the container for our advertisements
|
||||||
this.elements.container = createElement('div', {
|
this.elements.container = createElement('div', {
|
||||||
class: this.player.config.classNames.ads
|
class: this.player.config.classNames.ads
|
||||||
@@ -12850,7 +13003,16 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline); // We assume the adContainer is the video container of the plyr element that will house the ads
|
google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline); // We assume the adContainer is the video container of the plyr element that will house the ads
|
||||||
|
|
||||||
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media); // Request video ads to be pre-loaded
|
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media); // Create ads loader
|
||||||
|
|
||||||
|
this.loader = new google.ima.AdsLoader(this.elements.displayContainer); // Listen and respond to ads loaded and error events
|
||||||
|
|
||||||
|
this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, function (event) {
|
||||||
|
return _this4.onAdsManagerLoaded(event);
|
||||||
|
}, false);
|
||||||
|
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, function (error) {
|
||||||
|
return _this4.onAdError(error);
|
||||||
|
}, false); // Request video ads to be pre-loaded
|
||||||
|
|
||||||
this.requestAds();
|
this.requestAds();
|
||||||
}
|
}
|
||||||
@@ -12861,21 +13023,10 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
}, {
|
}, {
|
||||||
key: "requestAds",
|
key: "requestAds",
|
||||||
value: function requestAds() {
|
value: function requestAds() {
|
||||||
var _this4 = this;
|
|
||||||
|
|
||||||
var container = this.player.elements.container;
|
var container = this.player.elements.container;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create ads loader
|
// Request video ads
|
||||||
this.loader = new google.ima.AdsLoader(this.elements.displayContainer); // Listen and respond to ads loaded and error events
|
|
||||||
|
|
||||||
this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, function (event) {
|
|
||||||
return _this4.onAdsManagerLoaded(event);
|
|
||||||
}, false);
|
|
||||||
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, function (error) {
|
|
||||||
return _this4.onAdError(error);
|
|
||||||
}, false); // Request video ads
|
|
||||||
|
|
||||||
var request = new google.ima.AdsRequest();
|
var request = new google.ima.AdsRequest();
|
||||||
request.adTagUrl = this.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK
|
request.adTagUrl = this.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK
|
||||||
// to select the correct creative if multiple are returned
|
// to select the correct creative if multiple are returned
|
||||||
@@ -13054,7 +13205,13 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
// };
|
// };
|
||||||
// TODO: So there is still this thing where a video should only be allowed to start
|
// TODO: So there is still this thing where a video should only be allowed to start
|
||||||
// playing when the IMA SDK is ready or has failed
|
// playing when the IMA SDK is ready or has failed
|
||||||
this.loadAds();
|
if (this.player.ended) {
|
||||||
|
this.loadAds();
|
||||||
|
} else {
|
||||||
|
// The SDK won't allow new ads to be called without receiving a contentComplete()
|
||||||
|
this.loader.contentComplete();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
||||||
@@ -13190,7 +13347,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
this.playing = false; // Play video
|
this.playing = false; // Play video
|
||||||
|
|
||||||
this.player.media.play();
|
silencePromise(this.player.media.play());
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Pause our video
|
* Pause our video
|
||||||
@@ -13247,7 +13404,9 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
_this11.on('loaded', resolve);
|
_this11.on('loaded', resolve);
|
||||||
|
|
||||||
_this11.player.debug.log(_this11.manager);
|
_this11.player.debug.log(_this11.manager);
|
||||||
}); // Now request some new advertisements
|
}); // Now that the manager has been destroyed set it to also be un-initialized
|
||||||
|
|
||||||
|
_this11.initialized = false; // Now request some new advertisements
|
||||||
|
|
||||||
_this11.requestAds();
|
_this11.requestAds();
|
||||||
}).catch(function () {});
|
}).catch(function () {});
|
||||||
@@ -13542,15 +13701,10 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
if (is$1.empty(src)) {
|
if (is$1.empty(src)) {
|
||||||
throw new Error('Missing previewThumbnails.src config attribute');
|
throw new Error('Missing previewThumbnails.src config attribute');
|
||||||
} // If string, convert into single-element list
|
} // Resolve promise
|
||||||
|
|
||||||
|
|
||||||
var urls = is$1.string(src) ? [src] : src; // Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
var sortAndResolve = function sortAndResolve() {
|
||||||
|
|
||||||
var promises = urls.map(function (u) {
|
|
||||||
return _this2.getThumbnail(u);
|
|
||||||
});
|
|
||||||
Promise.all(promises).then(function () {
|
|
||||||
// Sort smallest to biggest (e.g., [120p, 480p, 1080p])
|
// Sort smallest to biggest (e.g., [120p, 480p, 1080p])
|
||||||
_this2.thumbnails.sort(function (x, y) {
|
_this2.thumbnails.sort(function (x, y) {
|
||||||
return x.height - y.height;
|
return x.height - y.height;
|
||||||
@@ -13559,7 +13713,25 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
_this2.player.debug.log('Preview thumbnails', _this2.thumbnails);
|
_this2.player.debug.log('Preview thumbnails', _this2.thumbnails);
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
}; // Via callback()
|
||||||
|
|
||||||
|
|
||||||
|
if (is$1.function(src)) {
|
||||||
|
src(function (thumbnails) {
|
||||||
|
_this2.thumbnails = thumbnails;
|
||||||
|
sortAndResolve();
|
||||||
|
});
|
||||||
|
} // VTT urls
|
||||||
|
else {
|
||||||
|
// If string, convert into single-element list
|
||||||
|
var urls = is$1.string(src) ? [src] : src; // Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
||||||
|
|
||||||
|
var promises = urls.map(function (u) {
|
||||||
|
return _this2.getThumbnail(u);
|
||||||
|
}); // Resolve
|
||||||
|
|
||||||
|
Promise.all(promises).then(sortAndResolve);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} // Process individual VTT file
|
} // Process individual VTT file
|
||||||
|
|
||||||
@@ -14347,6 +14519,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
this.elements = {
|
this.elements = {
|
||||||
container: null,
|
container: null,
|
||||||
|
fullscreen: null,
|
||||||
captions: null,
|
captions: null,
|
||||||
buttons: {},
|
buttons: {},
|
||||||
display: {},
|
display: {},
|
||||||
@@ -14521,9 +14694,11 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
tabindex: 0
|
tabindex: 0
|
||||||
});
|
});
|
||||||
wrap$1(this.media, this.elements.container);
|
wrap$1(this.media, this.elements.container);
|
||||||
} // Add style hook
|
} // Migrate custom properties from media to container (so they work 😉)
|
||||||
|
|
||||||
|
|
||||||
|
ui.migrateStyles.call(this); // Add style hook
|
||||||
|
|
||||||
ui.addStyleHook.call(this); // Setup media
|
ui.addStyleHook.call(this); // Setup media
|
||||||
|
|
||||||
media.setup.call(this); // Listen for events if debugging
|
media.setup.call(this); // Listen for events if debugging
|
||||||
@@ -14532,10 +14707,12 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
|
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
|
||||||
_this.debug.log("event: ".concat(event.type));
|
_this.debug.log("event: ".concat(event.type));
|
||||||
});
|
});
|
||||||
} // Setup interface
|
} // Setup fullscreen
|
||||||
// If embed but not fully supported, build interface now to avoid flash of controls
|
|
||||||
|
|
||||||
|
|
||||||
|
this.fullscreen = new Fullscreen(this); // Setup interface
|
||||||
|
// If embed but not fully supported, build interface now to avoid flash of controls
|
||||||
|
|
||||||
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
|
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
|
||||||
ui.build.call(this);
|
ui.build.call(this);
|
||||||
} // Container listeners
|
} // Container listeners
|
||||||
@@ -14543,9 +14720,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
this.listeners.container(); // Global listeners
|
this.listeners.container(); // Global listeners
|
||||||
|
|
||||||
this.listeners.global(); // Setup fullscreen
|
this.listeners.global(); // Setup ads if provided
|
||||||
|
|
||||||
this.fullscreen = new Fullscreen(this); // Setup ads if provided
|
|
||||||
|
|
||||||
if (this.config.ads.enabled) {
|
if (this.config.ads.enabled) {
|
||||||
this.ads = new Ads(this);
|
this.ads = new Ads(this);
|
||||||
@@ -14554,7 +14729,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
|
|
||||||
if (this.isHTML5 && this.config.autoplay) {
|
if (this.isHTML5 && this.config.autoplay) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
return _this.play();
|
return silencePromise(_this.play());
|
||||||
}, 10);
|
}, 10);
|
||||||
} // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
} // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
||||||
|
|
||||||
@@ -14591,7 +14766,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
this.ads.managerPromise.then(function () {
|
this.ads.managerPromise.then(function () {
|
||||||
return _this2.ads.play();
|
return _this2.ads.play();
|
||||||
}).catch(function () {
|
}).catch(function () {
|
||||||
return _this2.media.play();
|
return silencePromise(_this2.media.play());
|
||||||
});
|
});
|
||||||
} // Return the promise (for HTML5)
|
} // Return the promise (for HTML5)
|
||||||
|
|
||||||
@@ -15244,7 +15419,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
var updateStorage = true;
|
var updateStorage = true;
|
||||||
|
|
||||||
if (!options.includes(quality)) {
|
if (!options.includes(quality)) {
|
||||||
var value = closest(options, quality);
|
var value = closest$1(options, quality);
|
||||||
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
|
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
|
||||||
quality = value; // Don't update storage if quality is not supported
|
quality = value; // Don't update storage if quality is not supported
|
||||||
|
|
||||||
@@ -15283,41 +15458,41 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
this.media.loop = toggle; // Set default to be a true toggle
|
this.media.loop = toggle; // Set default to be a true toggle
|
||||||
|
|
||||||
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'start':
|
case 'start':
|
||||||
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
||||||
this.config.loop.end = null;
|
this.config.loop.end = null;
|
||||||
}
|
}
|
||||||
this.config.loop.start = this.currentTime;
|
this.config.loop.start = this.currentTime;
|
||||||
// this.config.loop.indicator.start = this.elements.display.played.value;
|
// this.config.loop.indicator.start = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
if (this.config.loop.start >= this.currentTime) {
|
if (this.config.loop.start >= this.currentTime) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
this.config.loop.end = this.currentTime;
|
this.config.loop.end = this.currentTime;
|
||||||
// this.config.loop.indicator.end = this.elements.display.played.value;
|
// this.config.loop.indicator.end = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = this.duration - 2;
|
|
||||||
this.config.loop.indicator.start = 0;
|
|
||||||
this.config.loop.indicator.end = 100;
|
|
||||||
break;
|
|
||||||
case 'toggle':
|
|
||||||
if (this.config.loop.active) {
|
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = null;
|
|
||||||
} else {
|
|
||||||
this.config.loop.start = 0;
|
this.config.loop.start = 0;
|
||||||
this.config.loop.end = this.duration - 2;
|
this.config.loop.end = this.duration - 2;
|
||||||
}
|
this.config.loop.indicator.start = 0;
|
||||||
break;
|
this.config.loop.indicator.end = 100;
|
||||||
default:
|
break;
|
||||||
this.config.loop.start = 0;
|
case 'toggle':
|
||||||
this.config.loop.end = null;
|
if (this.config.loop.active) {
|
||||||
break;
|
this.config.loop.start = 0;
|
||||||
} */
|
this.config.loop.end = null;
|
||||||
|
} else {
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = this.duration - 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = null;
|
||||||
|
break;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get current loop state
|
* Get current loop state
|
||||||
@@ -15389,7 +15564,7 @@ typeof navigator === "object" && (function (global, factory) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.media.getAttribute('poster');
|
return this.media.getAttribute('poster') || this.media.getAttribute('data-poster');
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the current aspect ratio in use
|
* Get the current aspect ratio in use
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+348
-173
@@ -263,7 +263,7 @@ var shared = createCommonjsModule(function (module) {
|
|||||||
(module.exports = function (key, value) {
|
(module.exports = function (key, value) {
|
||||||
return sharedStore[key] || (sharedStore[key] = value !== undefined ? value : {});
|
return sharedStore[key] || (sharedStore[key] = value !== undefined ? value : {});
|
||||||
})('versions', []).push({
|
})('versions', []).push({
|
||||||
version: '3.6.4',
|
version: '3.6.5',
|
||||||
mode: 'global',
|
mode: 'global',
|
||||||
copyright: '© 2020 Denis Pushkarev (zloirock.ru)'
|
copyright: '© 2020 Denis Pushkarev (zloirock.ru)'
|
||||||
});
|
});
|
||||||
@@ -3055,7 +3055,7 @@ var INVALID_HOST = 'Invalid host';
|
|||||||
var INVALID_PORT = 'Invalid port';
|
var INVALID_PORT = 'Invalid port';
|
||||||
|
|
||||||
var ALPHA = /[A-Za-z]/;
|
var ALPHA = /[A-Za-z]/;
|
||||||
var ALPHANUMERIC = /[\d+\-.A-Za-z]/;
|
var ALPHANUMERIC = /[\d+-.A-Za-z]/;
|
||||||
var DIGIT = /\d/;
|
var DIGIT = /\d/;
|
||||||
var HEX_START = /^(0x|0X)/;
|
var HEX_START = /^(0x|0X)/;
|
||||||
var OCT = /^[0-7]+$/;
|
var OCT = /^[0-7]+$/;
|
||||||
@@ -4117,6 +4117,42 @@ function _objectSpread2(target) {
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _objectWithoutPropertiesLoose(source, excluded) {
|
||||||
|
if (source == null) return {};
|
||||||
|
var target = {};
|
||||||
|
var sourceKeys = Object.keys(source);
|
||||||
|
var key, i;
|
||||||
|
|
||||||
|
for (i = 0; i < sourceKeys.length; i++) {
|
||||||
|
key = sourceKeys[i];
|
||||||
|
if (excluded.indexOf(key) >= 0) continue;
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _objectWithoutProperties(source, excluded) {
|
||||||
|
if (source == null) return {};
|
||||||
|
|
||||||
|
var target = _objectWithoutPropertiesLoose(source, excluded);
|
||||||
|
|
||||||
|
var key, i;
|
||||||
|
|
||||||
|
if (Object.getOwnPropertySymbols) {
|
||||||
|
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
|
||||||
|
|
||||||
|
for (i = 0; i < sourceSymbolKeys.length; i++) {
|
||||||
|
key = sourceSymbolKeys[i];
|
||||||
|
if (excluded.indexOf(key) >= 0) continue;
|
||||||
|
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
function _slicedToArray(arr, i) {
|
function _slicedToArray(arr, i) {
|
||||||
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
|
||||||
}
|
}
|
||||||
@@ -6002,7 +6038,13 @@ if (!set$1 || !clear) {
|
|||||||
defer = functionBindContext(port.postMessage, port, 1);
|
defer = functionBindContext(port.postMessage, port, 1);
|
||||||
// Browsers with postMessage, skip WebWorkers
|
// Browsers with postMessage, skip WebWorkers
|
||||||
// IE8 has postMessage, but it's sync & typeof its postMessage is 'object'
|
// IE8 has postMessage, but it's sync & typeof its postMessage is 'object'
|
||||||
} else if (global_1.addEventListener && typeof postMessage == 'function' && !global_1.importScripts && !fails(post)) {
|
} else if (
|
||||||
|
global_1.addEventListener &&
|
||||||
|
typeof postMessage == 'function' &&
|
||||||
|
!global_1.importScripts &&
|
||||||
|
!fails(post) &&
|
||||||
|
location.protocol !== 'file:'
|
||||||
|
) {
|
||||||
defer = post;
|
defer = post;
|
||||||
global_1.addEventListener('message', listener, false);
|
global_1.addEventListener('message', listener, false);
|
||||||
// IE8-
|
// IE8-
|
||||||
@@ -6611,7 +6653,7 @@ var isTrack = function isTrack(input) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var isPromise = function isPromise(input) {
|
var isPromise = function isPromise(input) {
|
||||||
return instanceOf$1(input, Promise);
|
return instanceOf$1(input, Promise) && isFunction$1(input.then);
|
||||||
};
|
};
|
||||||
|
|
||||||
var isEmpty$1 = function isEmpty(input) {
|
var isEmpty$1 = function isEmpty(input) {
|
||||||
@@ -7003,12 +7045,33 @@ function hasClass(element, className) {
|
|||||||
} // Element matches selector
|
} // Element matches selector
|
||||||
|
|
||||||
function matches$1(element, selector) {
|
function matches$1(element, selector) {
|
||||||
|
var _Element = Element,
|
||||||
|
prototype = _Element.prototype;
|
||||||
|
|
||||||
function match() {
|
function match() {
|
||||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
var method = match;
|
var method = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
|
||||||
|
return method.call(element, selector);
|
||||||
|
} // Closest ancestor element matching selector (also tests element itself)
|
||||||
|
|
||||||
|
function closest(element, selector) {
|
||||||
|
var _Element2 = Element,
|
||||||
|
prototype = _Element2.prototype; // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
|
||||||
|
|
||||||
|
function closestElement() {
|
||||||
|
var el = this;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (matches$1.matches(el, selector)) return el;
|
||||||
|
el = el.parentElement || el.parentNode;
|
||||||
|
} while (el !== null && el.nodeType === 1);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var method = prototype.closest || closestElement;
|
||||||
return method.call(element, selector);
|
return method.call(element, selector);
|
||||||
} // Find all elements
|
} // Find all elements
|
||||||
|
|
||||||
@@ -7280,6 +7343,19 @@ function ready() {
|
|||||||
}).then(function () {});
|
}).then(function () {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Silence a Promise-like object.
|
||||||
|
* This is useful for avoiding non-harmful, but potentially confusing "uncaught
|
||||||
|
* play promise" rejection error messages.
|
||||||
|
* @param {Object} value An object that may or may not be `Promise`-like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function silencePromise(value) {
|
||||||
|
if (is$1.promise(value)) {
|
||||||
|
value.then(null, function () {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateRatio(input) {
|
function validateRatio(input) {
|
||||||
if (!is$1.array(input) && (!is$1.string(input) || !input.includes(':'))) {
|
if (!is$1.array(input) && (!is$1.string(input) || !input.includes(':'))) {
|
||||||
return false;
|
return false;
|
||||||
@@ -7348,8 +7424,8 @@ function setAspectRatio(input) {
|
|||||||
var padding = 100 / w * h;
|
var padding = 100 / w * h;
|
||||||
wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||||
|
|
||||||
if (this.isVimeo && this.supported.ui) {
|
if (this.isVimeo && !this.config.vimeo.premium && this.supported.ui) {
|
||||||
var height = 240;
|
var height = 100 / this.media.offsetWidth * parseInt(window.getComputedStyle(this.media).paddingBottom, 10);
|
||||||
var offset = (height - padding) / (height / 50);
|
var offset = (height - padding) / (height / 50);
|
||||||
this.media.style.transform = "translateY(-".concat(offset, "%)");
|
this.media.style.transform = "translateY(-".concat(offset, "%)");
|
||||||
} else if (this.isHTML5) {
|
} else if (this.isHTML5) {
|
||||||
@@ -7455,7 +7531,7 @@ var html5 = {
|
|||||||
player.currentTime = currentTime; // Resume playing
|
player.currentTime = currentTime; // Resume playing
|
||||||
|
|
||||||
if (!paused) {
|
if (!paused) {
|
||||||
player.play();
|
silencePromise(player.play());
|
||||||
}
|
}
|
||||||
}); // Load new source
|
}); // Load new source
|
||||||
|
|
||||||
@@ -7502,7 +7578,7 @@ function dedupe(array) {
|
|||||||
});
|
});
|
||||||
} // Get the closest value in an array
|
} // Get the closest value in an array
|
||||||
|
|
||||||
function closest(array, value) {
|
function closest$1(array, value) {
|
||||||
if (!is$1.array(array) || !array.length) {
|
if (!is$1.array(array) || !array.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -7619,19 +7695,19 @@ function getPercentage(current, max) {
|
|||||||
return (current / max * 100).toFixed(2);
|
return (current / max * 100).toFixed(2);
|
||||||
} // Replace all occurances of a string in a string
|
} // Replace all occurances of a string in a string
|
||||||
|
|
||||||
function replaceAll() {
|
var replaceAll = function replaceAll() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
|
||||||
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
||||||
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
} // Convert to title case
|
}; // Convert to title case
|
||||||
|
|
||||||
function toTitleCase() {
|
var toTitleCase = function toTitleCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
return input.toString().replace(/\w\S*/g, function (text) {
|
return input.toString().replace(/\w\S*/g, function (text) {
|
||||||
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
|
||||||
});
|
});
|
||||||
} // Convert string to pascalCase
|
}; // Convert string to pascalCase
|
||||||
|
|
||||||
function toPascalCase() {
|
function toPascalCase() {
|
||||||
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
||||||
@@ -8738,39 +8814,39 @@ var controls = {
|
|||||||
// Set the looping options
|
// Set the looping options
|
||||||
|
|
||||||
/* setLoopMenu() {
|
/* setLoopMenu() {
|
||||||
// Menu required
|
// Menu required
|
||||||
if (!is.element(this.elements.settings.panels.loop)) {
|
if (!is.element(this.elements.settings.panels.loop)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const options = ['start', 'end', 'all', 'reset'];
|
const options = ['start', 'end', 'all', 'reset'];
|
||||||
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
|
||||||
// Show the pane and tab
|
// Show the pane and tab
|
||||||
toggleHidden(this.elements.settings.buttons.loop, false);
|
toggleHidden(this.elements.settings.buttons.loop, false);
|
||||||
toggleHidden(this.elements.settings.panels.loop, false);
|
toggleHidden(this.elements.settings.panels.loop, false);
|
||||||
// Toggle the pane and tab
|
// Toggle the pane and tab
|
||||||
const toggle = !is.empty(this.loop.options);
|
const toggle = !is.empty(this.loop.options);
|
||||||
controls.toggleMenuButton.call(this, 'loop', toggle);
|
controls.toggleMenuButton.call(this, 'loop', toggle);
|
||||||
// Empty the menu
|
// Empty the menu
|
||||||
emptyElement(list);
|
emptyElement(list);
|
||||||
options.forEach(option => {
|
options.forEach(option => {
|
||||||
const item = createElement('li');
|
const item = createElement('li');
|
||||||
const button = createElement(
|
const button = createElement(
|
||||||
'button',
|
'button',
|
||||||
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: this.config.classNames.control,
|
class: this.config.classNames.control,
|
||||||
'data-plyr-loop-action': option,
|
'data-plyr-loop-action': option,
|
||||||
}),
|
}),
|
||||||
i18n.get(option, this.config)
|
i18n.get(option, this.config)
|
||||||
);
|
);
|
||||||
if (['start', 'end'].includes(option)) {
|
if (['start', 'end'].includes(option)) {
|
||||||
const badge = controls.createBadge.call(this, '00:00');
|
const badge = controls.createBadge.call(this, '00:00');
|
||||||
button.appendChild(badge);
|
button.appendChild(badge);
|
||||||
}
|
}
|
||||||
item.appendChild(button);
|
item.appendChild(button);
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
}, */
|
}, */
|
||||||
// Get current selected caption language
|
// Get current selected caption language
|
||||||
// TODO: rework this to user the getter in the API?
|
// TODO: rework this to user the getter in the API?
|
||||||
// Set a list of available captions languages
|
// Set a list of available captions languages
|
||||||
@@ -9572,9 +9648,15 @@ var captions = {
|
|||||||
meta.set(track, {
|
meta.set(track, {
|
||||||
default: track.mode === 'showing'
|
default: track.mode === 'showing'
|
||||||
}); // Turn off native caption rendering to avoid double captions
|
}); // Turn off native caption rendering to avoid double captions
|
||||||
|
// Note: mode='hidden' forces a track to download. To ensure every track
|
||||||
|
// isn't downloaded at once, only 'showing' tracks should be reassigned
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
|
||||||
track.mode = 'hidden'; // Add event listener for cue changes
|
if (track.mode === 'showing') {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
track.mode = 'hidden';
|
||||||
|
} // Add event listener for cue changes
|
||||||
|
|
||||||
|
|
||||||
on.call(_this, track, 'cuechange', function () {
|
on.call(_this, track, 'cuechange', function () {
|
||||||
return captions.updateCues.call(_this);
|
return captions.updateCues.call(_this);
|
||||||
@@ -9598,6 +9680,8 @@ var captions = {
|
|||||||
// Toggle captions display
|
// Toggle captions display
|
||||||
// Used internally for the toggleCaptions method, with the passive option forced to false
|
// Used internally for the toggleCaptions method, with the passive option forced to false
|
||||||
toggle: function toggle(input) {
|
toggle: function toggle(input) {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
||||||
|
|
||||||
// If there's no full support
|
// If there's no full support
|
||||||
@@ -9644,7 +9728,15 @@ var captions = {
|
|||||||
controls.updateSetting.call(this, 'captions'); // Trigger event (not used internally)
|
controls.updateSetting.call(this, 'captions'); // Trigger event (not used internally)
|
||||||
|
|
||||||
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
||||||
}
|
} // Wait for the call stack to clear before setting mode='hidden'
|
||||||
|
// on the active track - forcing the browser to download it
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
if (active && _this2.captions.toggled) {
|
||||||
|
_this2.captions.currentTrackNode.mode = 'hidden';
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
// Set captions by track index
|
// Set captions by track index
|
||||||
// Used internally for the currentTrack setter with the passive option forced to false
|
// Used internally for the currentTrack setter with the passive option forced to false
|
||||||
@@ -9725,7 +9817,7 @@ var captions = {
|
|||||||
// If update is false it will also ignore tracks without metadata
|
// If update is false it will also ignore tracks without metadata
|
||||||
// This is used to "freeze" the language options when captions.update is false
|
// This is used to "freeze" the language options when captions.update is false
|
||||||
getTracks: function getTracks() {
|
getTracks: function getTracks() {
|
||||||
var _this2 = this;
|
var _this3 = this;
|
||||||
|
|
||||||
var update = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
var update = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
||||||
// Handle media or textTracks missing or null
|
// Handle media or textTracks missing or null
|
||||||
@@ -9733,20 +9825,20 @@ var captions = {
|
|||||||
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
||||||
|
|
||||||
return tracks.filter(function (track) {
|
return tracks.filter(function (track) {
|
||||||
return !_this2.isHTML5 || update || _this2.captions.meta.has(track);
|
return !_this3.isHTML5 || update || _this3.captions.meta.has(track);
|
||||||
}).filter(function (track) {
|
}).filter(function (track) {
|
||||||
return ['captions', 'subtitles'].includes(track.kind);
|
return ['captions', 'subtitles'].includes(track.kind);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// Match tracks based on languages and get the first
|
// Match tracks based on languages and get the first
|
||||||
findTrack: function findTrack(languages) {
|
findTrack: function findTrack(languages) {
|
||||||
var _this3 = this;
|
var _this4 = this;
|
||||||
|
|
||||||
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||||
var tracks = captions.getTracks.call(this);
|
var tracks = captions.getTracks.call(this);
|
||||||
|
|
||||||
var sortIsDefault = function sortIsDefault(track) {
|
var sortIsDefault = function sortIsDefault(track) {
|
||||||
return Number((_this3.captions.meta.get(track) || {}).default);
|
return Number((_this4.captions.meta.get(track) || {}).default);
|
||||||
};
|
};
|
||||||
|
|
||||||
var sorted = Array.from(tracks).sort(function (a, b) {
|
var sorted = Array.from(tracks).sort(function (a, b) {
|
||||||
@@ -9879,7 +9971,7 @@ var defaults$1 = {
|
|||||||
// Sprite (for icons)
|
// Sprite (for icons)
|
||||||
loadSprite: true,
|
loadSprite: true,
|
||||||
iconPrefix: 'plyr',
|
iconPrefix: 'plyr',
|
||||||
iconUrl: 'https://cdn.plyr.io/3.5.10/plyr.svg',
|
iconUrl: 'https://cdn.plyr.io/3.6.1/plyr.svg',
|
||||||
// Blank video (used to prevent errors on source change)
|
// Blank video (used to prevent errors on source change)
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
// Quality default
|
// Quality default
|
||||||
@@ -9927,6 +10019,9 @@ var defaults$1 = {
|
|||||||
fallback: true,
|
fallback: true,
|
||||||
// Fallback using full viewport/window
|
// Fallback using full viewport/window
|
||||||
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
|
iosNative: false // Use the native fullscreen in iOS (disables custom controls)
|
||||||
|
// Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode
|
||||||
|
// Non-ancestors of the player element will be ignored
|
||||||
|
// container: null, // defaults to the player element
|
||||||
|
|
||||||
},
|
},
|
||||||
// Local storage
|
// Local storage
|
||||||
@@ -10164,16 +10259,16 @@ var defaults$1 = {
|
|||||||
title: false,
|
title: false,
|
||||||
speed: true,
|
speed: true,
|
||||||
transparent: false,
|
transparent: false,
|
||||||
// These settings require a pro or premium account to work
|
// Whether the owner of the video has a Pro or Business account
|
||||||
sidedock: false,
|
// (which allows us to properly hide controls without CSS hacks, etc)
|
||||||
controls: false,
|
premium: false,
|
||||||
// Custom settings from Plyr
|
// Custom settings from Plyr
|
||||||
referrerPolicy: null // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
referrerPolicy: null // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
||||||
|
|
||||||
},
|
},
|
||||||
// YouTube plugin
|
// YouTube plugin
|
||||||
youtube: {
|
youtube: {
|
||||||
noCookie: false,
|
noCookie: true,
|
||||||
// Whether to use an alternative version of YouTube without cookies
|
// Whether to use an alternative version of YouTube without cookies
|
||||||
rel: 0,
|
rel: 0,
|
||||||
// No related vids
|
// No related vids
|
||||||
@@ -10283,7 +10378,10 @@ var Fullscreen = /*#__PURE__*/function () {
|
|||||||
y: 0
|
y: 0
|
||||||
}; // Force the use of 'full window/browser' rather than fullscreen
|
}; // Force the use of 'full window/browser' rather than fullscreen
|
||||||
|
|
||||||
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Register event listeners
|
this.forceFallback = player.config.fullscreen.fallback === 'force'; // Get the fullscreen element
|
||||||
|
// Checks container is an ancestor, defaults to null
|
||||||
|
|
||||||
|
this.player.elements.fullscreen = player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container); // Register event listeners
|
||||||
// Handle event (incase user presses escape etc)
|
// Handle event (incase user presses escape etc)
|
||||||
|
|
||||||
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
|
on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
|
||||||
@@ -10456,7 +10554,7 @@ var Fullscreen = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
this.target.webkitExitFullscreen();
|
this.target.webkitExitFullscreen();
|
||||||
this.player.play();
|
silencePromise(this.player.play());
|
||||||
} else if (!Fullscreen.native || this.forceFallback) {
|
} else if (!Fullscreen.native || this.forceFallback) {
|
||||||
this.toggleFallback(false);
|
this.toggleFallback(false);
|
||||||
} else if (!this.prefix) {
|
} else if (!this.prefix) {
|
||||||
@@ -10503,13 +10601,13 @@ var Fullscreen = /*#__PURE__*/function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var element = !this.prefix ? document.fullscreenElement : document["".concat(this.prefix).concat(this.property, "Element")];
|
var element = !this.prefix ? document.fullscreenElement : document["".concat(this.prefix).concat(this.property, "Element")];
|
||||||
return element === this.target;
|
return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target;
|
||||||
} // Get target element
|
} // Get target element
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
key: "target",
|
key: "target",
|
||||||
get: function get() {
|
get: function get() {
|
||||||
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
|
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.fullscreen || this.player.elements.container;
|
||||||
}
|
}
|
||||||
}], [{
|
}], [{
|
||||||
key: "native",
|
key: "native",
|
||||||
@@ -10718,12 +10816,7 @@ var ui = {
|
|||||||
} // Set property synchronously to respect the call order
|
} // Set property synchronously to respect the call order
|
||||||
|
|
||||||
|
|
||||||
this.media.setAttribute('poster', poster); // HTML5 uses native poster attribute
|
this.media.setAttribute('data-poster', poster); // Wait until ui is ready
|
||||||
|
|
||||||
if (this.isHTML5) {
|
|
||||||
return Promise.resolve(poster);
|
|
||||||
} // Wait until ui is ready
|
|
||||||
|
|
||||||
|
|
||||||
return ready.call(this) // Load image
|
return ready.call(this) // Load image
|
||||||
.then(function () {
|
.then(function () {
|
||||||
@@ -10799,6 +10892,26 @@ var ui = {
|
|||||||
|
|
||||||
this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));
|
this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// Migrate any custom properties from the media to the parent
|
||||||
|
migrateStyles: function migrateStyles() {
|
||||||
|
var _this5 = this;
|
||||||
|
|
||||||
|
// Loop through values (as they are the keys when the object is spread 🤔)
|
||||||
|
Object.values(_objectSpread2({}, this.media.style)) // We're only fussed about Plyr specific properties
|
||||||
|
.filter(function (key) {
|
||||||
|
return !is$1.empty(key) && key.startsWith('--plyr');
|
||||||
|
}).forEach(function (key) {
|
||||||
|
// Set on the container
|
||||||
|
_this5.elements.container.style.setProperty(key, _this5.media.style.getPropertyValue(key)); // Clean up from media element
|
||||||
|
|
||||||
|
|
||||||
|
_this5.media.style.removeProperty(key);
|
||||||
|
}); // Remove attribute if empty
|
||||||
|
|
||||||
|
if (is$1.empty(this.media.style)) {
|
||||||
|
this.media.removeAttribute('style');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -10893,7 +11006,7 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
case 75:
|
case 75:
|
||||||
// Space and K key
|
// Space and K key
|
||||||
if (!repeat) {
|
if (!repeat) {
|
||||||
player.togglePlay();
|
silencePromise(player.togglePlay());
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -11007,15 +11120,17 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
removeCurrent(); // Delay the adding of classname until the focus has changed
|
removeCurrent(); // Delay the adding of classname until the focus has changed
|
||||||
// This event fires before the focusin event
|
// This event fires before the focusin event
|
||||||
|
|
||||||
this.focusTimer = setTimeout(function () {
|
if (event.type !== 'focusout') {
|
||||||
var focused = document.activeElement; // Ignore if current focus element isn't inside the player
|
this.focusTimer = setTimeout(function () {
|
||||||
|
var focused = document.activeElement; // Ignore if current focus element isn't inside the player
|
||||||
|
|
||||||
if (!elements.container.contains(focused)) {
|
if (!elements.container.contains(focused)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
|
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
|
||||||
}, 10);
|
}, 10);
|
||||||
|
}
|
||||||
} // Global window & document listeners
|
} // Global window & document listeners
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
@@ -11033,7 +11148,7 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
once.call(player, document.body, 'touchstart', this.firstTouch); // Tab focus detection
|
once.call(player, document.body, 'touchstart', this.firstTouch); // Tab focus detection
|
||||||
|
|
||||||
toggleListener.call(player, document.body, 'keydown focus blur', this.setTabFocus, toggle, false, true);
|
toggleListener.call(player, document.body, 'keydown focus blur focusout', this.setTabFocus, toggle, false, true);
|
||||||
} // Container listeners
|
} // Container listeners
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
@@ -11076,7 +11191,7 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
}); // Set a gutter for Vimeo
|
}); // Set a gutter for Vimeo
|
||||||
|
|
||||||
var setGutter = function setGutter(ratio, padding, toggle) {
|
var setGutter = function setGutter(ratio, padding, toggle) {
|
||||||
if (!player.isVimeo) {
|
if (!player.isVimeo || player.config.vimeo.premium) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11133,7 +11248,7 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
ratio = _setPlayerSize.ratio; // Set Vimeo gutter
|
ratio = _setPlayerSize.ratio; // Set Vimeo gutter
|
||||||
|
|
||||||
|
|
||||||
setGutter(ratio, padding, isEnter); // If not using native fullscreen, we need to check for resizes of viewport
|
setGutter(ratio, padding, isEnter); // If not using native browser fullscreen API, we need to check for resizes of viewport
|
||||||
|
|
||||||
if (!usingNative) {
|
if (!usingNative) {
|
||||||
if (isEnter) {
|
if (isEnter) {
|
||||||
@@ -11211,9 +11326,13 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
if (player.ended) {
|
if (player.ended) {
|
||||||
_this.proxy(event, player.restart, 'restart');
|
_this.proxy(event, player.restart, 'restart');
|
||||||
|
|
||||||
_this.proxy(event, player.play, 'play');
|
_this.proxy(event, function () {
|
||||||
|
silencePromise(player.play());
|
||||||
|
}, 'play');
|
||||||
} else {
|
} else {
|
||||||
_this.proxy(event, player.togglePlay, 'play');
|
_this.proxy(event, function () {
|
||||||
|
silencePromise(player.togglePlay());
|
||||||
|
}, 'play');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} // Disable right click
|
} // Disable right click
|
||||||
@@ -11311,7 +11430,9 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
if (elements.buttons.play) {
|
if (elements.buttons.play) {
|
||||||
Array.from(elements.buttons.play).forEach(function (button) {
|
Array.from(elements.buttons.play).forEach(function (button) {
|
||||||
_this3.bind(button, 'click', player.togglePlay, 'play');
|
_this3.bind(button, 'click', function () {
|
||||||
|
silencePromise(player.togglePlay());
|
||||||
|
}, 'play');
|
||||||
});
|
});
|
||||||
} // Pause
|
} // Pause
|
||||||
|
|
||||||
@@ -11408,7 +11529,7 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
if (play && done) {
|
if (play && done) {
|
||||||
seek.removeAttribute(attribute);
|
seek.removeAttribute(attribute);
|
||||||
player.play();
|
silencePromise(player.play());
|
||||||
} else if (!done && player.playing) {
|
} else if (!done && player.playing) {
|
||||||
seek.setAttribute(attribute, '');
|
seek.setAttribute(attribute, '');
|
||||||
player.pause();
|
player.pause();
|
||||||
@@ -11506,7 +11627,18 @@ var Listeners = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
|
this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
|
||||||
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
||||||
}); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
|
}); // Also update controls.hover state for any non-player children of fullscreen element (as above)
|
||||||
|
|
||||||
|
if (elements.fullscreen) {
|
||||||
|
Array.from(elements.fullscreen.children).filter(function (c) {
|
||||||
|
return !c.contains(elements.container);
|
||||||
|
}).forEach(function (child) {
|
||||||
|
_this3.bind(child, 'mouseenter mouseleave', function (event) {
|
||||||
|
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
|
||||||
|
|
||||||
|
|
||||||
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
|
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
|
||||||
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
||||||
@@ -11975,15 +12107,28 @@ var vimeo = {
|
|||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
var player = this;
|
var player = this;
|
||||||
var config = player.config.vimeo; // Get Vimeo params for the iframe
|
var config = player.config.vimeo;
|
||||||
|
|
||||||
var params = buildUrlParams(extend({}, {
|
var premium = config.premium,
|
||||||
|
referrerPolicy = config.referrerPolicy,
|
||||||
|
frameParams = _objectWithoutProperties(config, ["premium", "referrerPolicy"]); // If the owner has a pro or premium account then we can hide controls etc
|
||||||
|
|
||||||
|
|
||||||
|
if (premium) {
|
||||||
|
Object.assign(frameParams, {
|
||||||
|
controls: false,
|
||||||
|
sidedock: false
|
||||||
|
});
|
||||||
|
} // Get Vimeo params for the iframe
|
||||||
|
|
||||||
|
|
||||||
|
var params = buildUrlParams(_objectSpread2({
|
||||||
loop: player.config.loop.active,
|
loop: player.config.loop.active,
|
||||||
autoplay: player.autoplay,
|
autoplay: player.autoplay,
|
||||||
muted: player.muted,
|
muted: player.muted,
|
||||||
gesture: 'media',
|
gesture: 'media',
|
||||||
playsinline: !this.config.fullscreen.iosNative
|
playsinline: !this.config.fullscreen.iosNative
|
||||||
}, config)); // Get the source URL or ID
|
}, frameParams)); // Get the source URL or ID
|
||||||
|
|
||||||
var source = player.media.getAttribute('src'); // Get from <div> if needed
|
var source = player.media.getAttribute('src'); // Get from <div> if needed
|
||||||
|
|
||||||
@@ -11997,22 +12142,27 @@ var vimeo = {
|
|||||||
var src = format(player.config.urls.vimeo.iframe, id, params);
|
var src = format(player.config.urls.vimeo.iframe, id, params);
|
||||||
iframe.setAttribute('src', src);
|
iframe.setAttribute('src', src);
|
||||||
iframe.setAttribute('allowfullscreen', '');
|
iframe.setAttribute('allowfullscreen', '');
|
||||||
iframe.setAttribute('allowtransparency', '');
|
iframe.setAttribute('allow', 'autoplay,fullscreen,picture-in-picture'); // Set the referrer policy if required
|
||||||
iframe.setAttribute('allow', 'autoplay'); // Set the referrer policy if required
|
|
||||||
|
|
||||||
if (!is$1.empty(config.referrerPolicy)) {
|
if (!is$1.empty(referrerPolicy)) {
|
||||||
iframe.setAttribute('referrerPolicy', config.referrerPolicy);
|
iframe.setAttribute('referrerPolicy', referrerPolicy);
|
||||||
} // Get poster, if already set
|
} // Inject the package
|
||||||
|
|
||||||
|
|
||||||
var poster = player.poster; // Inject the package
|
var poster = player.poster;
|
||||||
|
|
||||||
|
if (premium) {
|
||||||
|
iframe.setAttribute('data-poster', poster);
|
||||||
|
player.media = replaceElement(iframe, player.media);
|
||||||
|
} else {
|
||||||
|
var wrapper = createElement('div', {
|
||||||
|
class: player.config.classNames.embedContainer,
|
||||||
|
'data-poster': poster
|
||||||
|
});
|
||||||
|
wrapper.appendChild(iframe);
|
||||||
|
player.media = replaceElement(wrapper, player.media);
|
||||||
|
} // Get poster image
|
||||||
|
|
||||||
var wrapper = createElement('div', {
|
|
||||||
poster: poster,
|
|
||||||
class: player.config.classNames.embedContainer
|
|
||||||
});
|
|
||||||
wrapper.appendChild(iframe);
|
|
||||||
player.media = replaceElement(wrapper, player.media); // Get poster image
|
|
||||||
|
|
||||||
fetch(format(player.config.urls.vimeo.api, id), 'json').then(function (response) {
|
fetch(format(player.config.urls.vimeo.api, id), 'json').then(function (response) {
|
||||||
if (is$1.empty(response)) {
|
if (is$1.empty(response)) {
|
||||||
@@ -12096,6 +12246,9 @@ var vimeo = {
|
|||||||
player.embed.setPlaybackRate(input).then(function () {
|
player.embed.setPlaybackRate(input).then(function () {
|
||||||
speed = input;
|
speed = input;
|
||||||
triggerEvent.call(player, player.media, 'ratechange');
|
triggerEvent.call(player, player.media, 'ratechange');
|
||||||
|
}).catch(function () {
|
||||||
|
// Cannot set Playback Rate, Video is probably not on Pro account
|
||||||
|
player.options.speed = [1];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}); // Volume
|
}); // Volume
|
||||||
@@ -12380,7 +12533,7 @@ var youtube = {
|
|||||||
|
|
||||||
var container = createElement('div', {
|
var container = createElement('div', {
|
||||||
id: id,
|
id: id,
|
||||||
poster: poster
|
'data-poster': poster
|
||||||
});
|
});
|
||||||
player.media = replaceElement(container, player.media); // Id to poster wrapper
|
player.media = replaceElement(container, player.media); // Id to poster wrapper
|
||||||
|
|
||||||
@@ -12698,14 +12851,12 @@ var media = {
|
|||||||
class: this.config.classNames.video
|
class: this.config.classNames.video
|
||||||
}); // Wrap the video in a container
|
}); // Wrap the video in a container
|
||||||
|
|
||||||
wrap$1(this.media, this.elements.wrapper); // Faux poster container
|
wrap$1(this.media, this.elements.wrapper); // Poster image container
|
||||||
|
|
||||||
if (this.isEmbed) {
|
this.elements.poster = createElement('div', {
|
||||||
this.elements.poster = createElement('div', {
|
class: this.config.classNames.poster
|
||||||
class: this.config.classNames.poster
|
});
|
||||||
});
|
this.elements.wrapper.appendChild(this.elements.poster);
|
||||||
this.elements.wrapper.appendChild(this.elements.poster);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
@@ -12832,6 +12983,8 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
* mobile devices, this initialization is done as the result of a user action.
|
* mobile devices, this initialization is done as the result of a user action.
|
||||||
*/
|
*/
|
||||||
value: function setupIMA() {
|
value: function setupIMA() {
|
||||||
|
var _this4 = this;
|
||||||
|
|
||||||
// Create the container for our advertisements
|
// Create the container for our advertisements
|
||||||
this.elements.container = createElement('div', {
|
this.elements.container = createElement('div', {
|
||||||
class: this.player.config.classNames.ads
|
class: this.player.config.classNames.ads
|
||||||
@@ -12844,7 +12997,16 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline); // We assume the adContainer is the video container of the plyr element that will house the ads
|
google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline); // We assume the adContainer is the video container of the plyr element that will house the ads
|
||||||
|
|
||||||
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media); // Request video ads to be pre-loaded
|
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media); // Create ads loader
|
||||||
|
|
||||||
|
this.loader = new google.ima.AdsLoader(this.elements.displayContainer); // Listen and respond to ads loaded and error events
|
||||||
|
|
||||||
|
this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, function (event) {
|
||||||
|
return _this4.onAdsManagerLoaded(event);
|
||||||
|
}, false);
|
||||||
|
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, function (error) {
|
||||||
|
return _this4.onAdError(error);
|
||||||
|
}, false); // Request video ads to be pre-loaded
|
||||||
|
|
||||||
this.requestAds();
|
this.requestAds();
|
||||||
}
|
}
|
||||||
@@ -12855,21 +13017,10 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
}, {
|
}, {
|
||||||
key: "requestAds",
|
key: "requestAds",
|
||||||
value: function requestAds() {
|
value: function requestAds() {
|
||||||
var _this4 = this;
|
|
||||||
|
|
||||||
var container = this.player.elements.container;
|
var container = this.player.elements.container;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create ads loader
|
// Request video ads
|
||||||
this.loader = new google.ima.AdsLoader(this.elements.displayContainer); // Listen and respond to ads loaded and error events
|
|
||||||
|
|
||||||
this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, function (event) {
|
|
||||||
return _this4.onAdsManagerLoaded(event);
|
|
||||||
}, false);
|
|
||||||
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, function (error) {
|
|
||||||
return _this4.onAdError(error);
|
|
||||||
}, false); // Request video ads
|
|
||||||
|
|
||||||
var request = new google.ima.AdsRequest();
|
var request = new google.ima.AdsRequest();
|
||||||
request.adTagUrl = this.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK
|
request.adTagUrl = this.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK
|
||||||
// to select the correct creative if multiple are returned
|
// to select the correct creative if multiple are returned
|
||||||
@@ -13048,7 +13199,13 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
// };
|
// };
|
||||||
// TODO: So there is still this thing where a video should only be allowed to start
|
// TODO: So there is still this thing where a video should only be allowed to start
|
||||||
// playing when the IMA SDK is ready or has failed
|
// playing when the IMA SDK is ready or has failed
|
||||||
this.loadAds();
|
if (this.player.ended) {
|
||||||
|
this.loadAds();
|
||||||
|
} else {
|
||||||
|
// The SDK won't allow new ads to be called without receiving a contentComplete()
|
||||||
|
this.loader.contentComplete();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
||||||
@@ -13184,7 +13341,7 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
this.playing = false; // Play video
|
this.playing = false; // Play video
|
||||||
|
|
||||||
this.player.media.play();
|
silencePromise(this.player.media.play());
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Pause our video
|
* Pause our video
|
||||||
@@ -13241,7 +13398,9 @@ var Ads = /*#__PURE__*/function () {
|
|||||||
_this11.on('loaded', resolve);
|
_this11.on('loaded', resolve);
|
||||||
|
|
||||||
_this11.player.debug.log(_this11.manager);
|
_this11.player.debug.log(_this11.manager);
|
||||||
}); // Now request some new advertisements
|
}); // Now that the manager has been destroyed set it to also be un-initialized
|
||||||
|
|
||||||
|
_this11.initialized = false; // Now request some new advertisements
|
||||||
|
|
||||||
_this11.requestAds();
|
_this11.requestAds();
|
||||||
}).catch(function () {});
|
}).catch(function () {});
|
||||||
@@ -13536,15 +13695,10 @@ var PreviewThumbnails = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
if (is$1.empty(src)) {
|
if (is$1.empty(src)) {
|
||||||
throw new Error('Missing previewThumbnails.src config attribute');
|
throw new Error('Missing previewThumbnails.src config attribute');
|
||||||
} // If string, convert into single-element list
|
} // Resolve promise
|
||||||
|
|
||||||
|
|
||||||
var urls = is$1.string(src) ? [src] : src; // Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
var sortAndResolve = function sortAndResolve() {
|
||||||
|
|
||||||
var promises = urls.map(function (u) {
|
|
||||||
return _this2.getThumbnail(u);
|
|
||||||
});
|
|
||||||
Promise.all(promises).then(function () {
|
|
||||||
// Sort smallest to biggest (e.g., [120p, 480p, 1080p])
|
// Sort smallest to biggest (e.g., [120p, 480p, 1080p])
|
||||||
_this2.thumbnails.sort(function (x, y) {
|
_this2.thumbnails.sort(function (x, y) {
|
||||||
return x.height - y.height;
|
return x.height - y.height;
|
||||||
@@ -13553,7 +13707,25 @@ var PreviewThumbnails = /*#__PURE__*/function () {
|
|||||||
_this2.player.debug.log('Preview thumbnails', _this2.thumbnails);
|
_this2.player.debug.log('Preview thumbnails', _this2.thumbnails);
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
}; // Via callback()
|
||||||
|
|
||||||
|
|
||||||
|
if (is$1.function(src)) {
|
||||||
|
src(function (thumbnails) {
|
||||||
|
_this2.thumbnails = thumbnails;
|
||||||
|
sortAndResolve();
|
||||||
|
});
|
||||||
|
} // VTT urls
|
||||||
|
else {
|
||||||
|
// If string, convert into single-element list
|
||||||
|
var urls = is$1.string(src) ? [src] : src; // Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
|
||||||
|
|
||||||
|
var promises = urls.map(function (u) {
|
||||||
|
return _this2.getThumbnail(u);
|
||||||
|
}); // Resolve
|
||||||
|
|
||||||
|
Promise.all(promises).then(sortAndResolve);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} // Process individual VTT file
|
} // Process individual VTT file
|
||||||
|
|
||||||
@@ -14341,6 +14513,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
this.elements = {
|
this.elements = {
|
||||||
container: null,
|
container: null,
|
||||||
|
fullscreen: null,
|
||||||
captions: null,
|
captions: null,
|
||||||
buttons: {},
|
buttons: {},
|
||||||
display: {},
|
display: {},
|
||||||
@@ -14515,9 +14688,11 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
tabindex: 0
|
tabindex: 0
|
||||||
});
|
});
|
||||||
wrap$1(this.media, this.elements.container);
|
wrap$1(this.media, this.elements.container);
|
||||||
} // Add style hook
|
} // Migrate custom properties from media to container (so they work 😉)
|
||||||
|
|
||||||
|
|
||||||
|
ui.migrateStyles.call(this); // Add style hook
|
||||||
|
|
||||||
ui.addStyleHook.call(this); // Setup media
|
ui.addStyleHook.call(this); // Setup media
|
||||||
|
|
||||||
media.setup.call(this); // Listen for events if debugging
|
media.setup.call(this); // Listen for events if debugging
|
||||||
@@ -14526,10 +14701,12 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
|
on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
|
||||||
_this.debug.log("event: ".concat(event.type));
|
_this.debug.log("event: ".concat(event.type));
|
||||||
});
|
});
|
||||||
} // Setup interface
|
} // Setup fullscreen
|
||||||
// If embed but not fully supported, build interface now to avoid flash of controls
|
|
||||||
|
|
||||||
|
|
||||||
|
this.fullscreen = new Fullscreen(this); // Setup interface
|
||||||
|
// If embed but not fully supported, build interface now to avoid flash of controls
|
||||||
|
|
||||||
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
|
if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
|
||||||
ui.build.call(this);
|
ui.build.call(this);
|
||||||
} // Container listeners
|
} // Container listeners
|
||||||
@@ -14537,9 +14714,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
this.listeners.container(); // Global listeners
|
this.listeners.container(); // Global listeners
|
||||||
|
|
||||||
this.listeners.global(); // Setup fullscreen
|
this.listeners.global(); // Setup ads if provided
|
||||||
|
|
||||||
this.fullscreen = new Fullscreen(this); // Setup ads if provided
|
|
||||||
|
|
||||||
if (this.config.ads.enabled) {
|
if (this.config.ads.enabled) {
|
||||||
this.ads = new Ads(this);
|
this.ads = new Ads(this);
|
||||||
@@ -14548,7 +14723,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
|
|
||||||
if (this.isHTML5 && this.config.autoplay) {
|
if (this.isHTML5 && this.config.autoplay) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
return _this.play();
|
return silencePromise(_this.play());
|
||||||
}, 10);
|
}, 10);
|
||||||
} // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
} // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
||||||
|
|
||||||
@@ -14585,7 +14760,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
this.ads.managerPromise.then(function () {
|
this.ads.managerPromise.then(function () {
|
||||||
return _this2.ads.play();
|
return _this2.ads.play();
|
||||||
}).catch(function () {
|
}).catch(function () {
|
||||||
return _this2.media.play();
|
return silencePromise(_this2.media.play());
|
||||||
});
|
});
|
||||||
} // Return the promise (for HTML5)
|
} // Return the promise (for HTML5)
|
||||||
|
|
||||||
@@ -15238,7 +15413,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
var updateStorage = true;
|
var updateStorage = true;
|
||||||
|
|
||||||
if (!options.includes(quality)) {
|
if (!options.includes(quality)) {
|
||||||
var value = closest(options, quality);
|
var value = closest$1(options, quality);
|
||||||
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
|
this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
|
||||||
quality = value; // Don't update storage if quality is not supported
|
quality = value; // Don't update storage if quality is not supported
|
||||||
|
|
||||||
@@ -15277,41 +15452,41 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
this.media.loop = toggle; // Set default to be a true toggle
|
this.media.loop = toggle; // Set default to be a true toggle
|
||||||
|
|
||||||
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
/* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'start':
|
case 'start':
|
||||||
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
|
||||||
this.config.loop.end = null;
|
this.config.loop.end = null;
|
||||||
}
|
}
|
||||||
this.config.loop.start = this.currentTime;
|
this.config.loop.start = this.currentTime;
|
||||||
// this.config.loop.indicator.start = this.elements.display.played.value;
|
// this.config.loop.indicator.start = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'end':
|
case 'end':
|
||||||
if (this.config.loop.start >= this.currentTime) {
|
if (this.config.loop.start >= this.currentTime) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
this.config.loop.end = this.currentTime;
|
this.config.loop.end = this.currentTime;
|
||||||
// this.config.loop.indicator.end = this.elements.display.played.value;
|
// this.config.loop.indicator.end = this.elements.display.played.value;
|
||||||
break;
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = this.duration - 2;
|
|
||||||
this.config.loop.indicator.start = 0;
|
|
||||||
this.config.loop.indicator.end = 100;
|
|
||||||
break;
|
|
||||||
case 'toggle':
|
|
||||||
if (this.config.loop.active) {
|
|
||||||
this.config.loop.start = 0;
|
|
||||||
this.config.loop.end = null;
|
|
||||||
} else {
|
|
||||||
this.config.loop.start = 0;
|
this.config.loop.start = 0;
|
||||||
this.config.loop.end = this.duration - 2;
|
this.config.loop.end = this.duration - 2;
|
||||||
}
|
this.config.loop.indicator.start = 0;
|
||||||
break;
|
this.config.loop.indicator.end = 100;
|
||||||
default:
|
break;
|
||||||
this.config.loop.start = 0;
|
case 'toggle':
|
||||||
this.config.loop.end = null;
|
if (this.config.loop.active) {
|
||||||
break;
|
this.config.loop.start = 0;
|
||||||
} */
|
this.config.loop.end = null;
|
||||||
|
} else {
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = this.duration - 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.config.loop.start = 0;
|
||||||
|
this.config.loop.end = null;
|
||||||
|
break;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get current loop state
|
* Get current loop state
|
||||||
@@ -15383,7 +15558,7 @@ var Plyr = /*#__PURE__*/function () {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.media.getAttribute('poster');
|
return this.media.getAttribute('poster') || this.media.getAttribute('data-poster');
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the current aspect ratio in use
|
* Get the current aspect ratio in use
|
||||||
|
|||||||
+2
-516
@@ -1,522 +1,8 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Gulp build script
|
// Gulp build script
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
/* eslint no-console: "off" */
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
// ------------------------------------
|
const HubRegistry = require('gulp-hub');
|
||||||
// JavaScript
|
|
||||||
// ------------------------------------
|
|
||||||
const terser = require('gulp-terser');
|
|
||||||
const rollup = require('gulp-better-rollup');
|
|
||||||
const babel = require('rollup-plugin-babel');
|
|
||||||
const commonjs = require('rollup-plugin-commonjs');
|
|
||||||
const resolve = require('rollup-plugin-node-resolve');
|
|
||||||
// ------------------------------------
|
|
||||||
// CSS
|
|
||||||
// ------------------------------------
|
|
||||||
const sass = require('gulp-sass');
|
|
||||||
const clean = require('gulp-clean-css');
|
|
||||||
const prefix = require('gulp-autoprefixer');
|
|
||||||
// ------------------------------------
|
|
||||||
// Images
|
|
||||||
// ------------------------------------
|
|
||||||
const svgstore = require('gulp-svgstore');
|
|
||||||
const imagemin = require('gulp-imagemin');
|
|
||||||
// ------------------------------------
|
|
||||||
// Utils
|
|
||||||
// ------------------------------------
|
|
||||||
const del = require('del');
|
|
||||||
const filter = require('gulp-filter');
|
|
||||||
const header = require('gulp-header');
|
|
||||||
const gitbranch = require('git-branch');
|
|
||||||
const rename = require('gulp-rename');
|
|
||||||
const replace = require('gulp-replace');
|
|
||||||
const ansi = require('ansi-colors');
|
|
||||||
const log = require('fancy-log');
|
|
||||||
const open = require('gulp-open');
|
|
||||||
const plumber = require('gulp-plumber');
|
|
||||||
const size = require('gulp-size');
|
|
||||||
const sourcemaps = require('gulp-sourcemaps');
|
|
||||||
const through = require('through2');
|
|
||||||
const browserSync = require('browser-sync').create();
|
|
||||||
// ------------------------------------
|
|
||||||
// Deployment
|
|
||||||
// ------------------------------------
|
|
||||||
const aws = require('aws-sdk');
|
|
||||||
const publish = require('gulp-awspublish');
|
|
||||||
const FastlyPurge = require('fastly-purge');
|
|
||||||
// ------------------------------------
|
|
||||||
// Configs
|
|
||||||
// ------------------------------------
|
|
||||||
const pkg = require('./package.json');
|
|
||||||
const build = require('./build.json');
|
|
||||||
const deploy = require('./deploy.json');
|
|
||||||
// ------------------------------------
|
|
||||||
// Info from package
|
|
||||||
// ------------------------------------
|
|
||||||
const { browserslist: browsers, version } = pkg;
|
|
||||||
const minSuffix = '.min';
|
|
||||||
|
|
||||||
// Get AWS config
|
gulp.registry(new HubRegistry(['tasks/*.js']));
|
||||||
Object.values(deploy).forEach(target => {
|
|
||||||
Object.assign(target, {
|
|
||||||
publisher: publish.create({
|
|
||||||
region: target.region,
|
|
||||||
params: {
|
|
||||||
Bucket: target.bucket,
|
|
||||||
},
|
|
||||||
credentials: new aws.SharedIniFileCredentials({ profile: 'plyr' }),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Paths
|
|
||||||
const paths = {
|
|
||||||
plyr: {
|
|
||||||
// Source paths
|
|
||||||
src: {
|
|
||||||
sass: path.join(__dirname, 'src/sass/**/*.scss'),
|
|
||||||
js: path.join(__dirname, 'src/js/**/*.js'),
|
|
||||||
sprite: path.join(__dirname, 'src/sprite/*.svg'),
|
|
||||||
},
|
|
||||||
|
|
||||||
// Output paths
|
|
||||||
output: path.join(__dirname, 'dist/'),
|
|
||||||
},
|
|
||||||
demo: {
|
|
||||||
// Source paths
|
|
||||||
src: {
|
|
||||||
sass: path.join(__dirname, 'demo/src/sass/**/*.scss'),
|
|
||||||
js: path.join(__dirname, 'demo/src/js/**/*.js'),
|
|
||||||
},
|
|
||||||
|
|
||||||
// Output paths
|
|
||||||
output: path.join(__dirname, 'demo/dist/'),
|
|
||||||
|
|
||||||
// Demo
|
|
||||||
root: path.join(__dirname, 'demo/'),
|
|
||||||
},
|
|
||||||
upload: [
|
|
||||||
path.join(__dirname, `dist/*${minSuffix}.*`),
|
|
||||||
path.join(__dirname, 'dist/*.css'),
|
|
||||||
path.join(__dirname, 'dist/*.svg'),
|
|
||||||
path.join(__dirname, `demo/dist/*${minSuffix}.*`),
|
|
||||||
path.join(__dirname, 'demo/dist/*.css'),
|
|
||||||
path.join(__dirname, 'demo/dist/*.svg'),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Task arrays
|
|
||||||
const tasks = {
|
|
||||||
css: [],
|
|
||||||
js: [],
|
|
||||||
sprite: [],
|
|
||||||
clean: 'clean',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Size plugin
|
|
||||||
const sizeOptions = { showFiles: true, gzip: true };
|
|
||||||
|
|
||||||
// Clean out /dist
|
|
||||||
gulp.task(tasks.clean, done => {
|
|
||||||
const dirs = [paths.plyr.output, paths.demo.output].map(dir => path.join(dir, '**/*'));
|
|
||||||
|
|
||||||
// Don't delete the mp4
|
|
||||||
dirs.push(`!${path.join(paths.plyr.output, '**/*.mp4')}`);
|
|
||||||
|
|
||||||
del(dirs);
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
// JavaScript
|
|
||||||
Object.entries(build.js).forEach(([filename, entry]) => {
|
|
||||||
const { dist, formats, namespace, polyfill, src } = entry;
|
|
||||||
|
|
||||||
formats.forEach(format => {
|
|
||||||
const name = `js:${filename}:${format}`;
|
|
||||||
const extension = format === 'es' ? 'mjs' : 'js';
|
|
||||||
tasks.js.push(name);
|
|
||||||
|
|
||||||
gulp.task(name, () =>
|
|
||||||
gulp
|
|
||||||
.src(src)
|
|
||||||
.pipe(plumber())
|
|
||||||
.pipe(sourcemaps.init())
|
|
||||||
.pipe(
|
|
||||||
rollup(
|
|
||||||
{
|
|
||||||
plugins: [
|
|
||||||
resolve(),
|
|
||||||
commonjs(),
|
|
||||||
babel({
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
'@babel/env',
|
|
||||||
{
|
|
||||||
// debug: true,
|
|
||||||
useBuiltIns: polyfill ? 'usage' : false,
|
|
||||||
corejs: polyfill ? 3 : undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
babelrc: false,
|
|
||||||
exclude: [/\/core-js\//],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: namespace,
|
|
||||||
format,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.pipe(header('typeof navigator === "object" && ')) // "Support" SSR (#935)
|
|
||||||
.pipe(
|
|
||||||
rename({
|
|
||||||
extname: `.${extension}`,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(dist))
|
|
||||||
.pipe(filter(`**/*.${extension}`))
|
|
||||||
.pipe(terser())
|
|
||||||
.pipe(rename({ suffix: minSuffix }))
|
|
||||||
.pipe(size(sizeOptions))
|
|
||||||
.pipe(sourcemaps.write(''))
|
|
||||||
.pipe(gulp.dest(dist)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// CSS
|
|
||||||
Object.entries(build.css).forEach(([filename, entry]) => {
|
|
||||||
const { dist, src } = entry;
|
|
||||||
const name = `css:${filename}`;
|
|
||||||
tasks.css.push(name);
|
|
||||||
|
|
||||||
gulp.task(name, () =>
|
|
||||||
gulp
|
|
||||||
.src(src)
|
|
||||||
.pipe(plumber())
|
|
||||||
.pipe(sass())
|
|
||||||
.pipe(
|
|
||||||
prefix(browsers, {
|
|
||||||
cascade: false,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.pipe(clean())
|
|
||||||
.pipe(size(sizeOptions))
|
|
||||||
.pipe(gulp.dest(dist)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// SVG Sprites
|
|
||||||
Object.entries(build.sprite).forEach(([filename, entry]) => {
|
|
||||||
const { dist, src } = entry;
|
|
||||||
const name = `sprite:${filename}`;
|
|
||||||
tasks.sprite.push(name);
|
|
||||||
|
|
||||||
gulp.task(name, () =>
|
|
||||||
gulp
|
|
||||||
.src(src)
|
|
||||||
.pipe(plumber())
|
|
||||||
.pipe(
|
|
||||||
imagemin([
|
|
||||||
imagemin.svgo({
|
|
||||||
plugins: [{ removeViewBox: false }],
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
.pipe(svgstore())
|
|
||||||
.pipe(rename({ basename: path.parse(filename).name }))
|
|
||||||
.pipe(size(sizeOptions))
|
|
||||||
.pipe(gulp.dest(dist)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build all JS
|
|
||||||
gulp.task('js', () => gulp.parallel(...tasks.js));
|
|
||||||
|
|
||||||
// Watch for file changes
|
|
||||||
gulp.task('watch', () => {
|
|
||||||
// Plyr core
|
|
||||||
gulp.watch(paths.plyr.src.js, gulp.parallel(...tasks.js));
|
|
||||||
gulp.watch(paths.plyr.src.sass, gulp.parallel(...tasks.css));
|
|
||||||
gulp.watch(paths.plyr.src.sprite, gulp.parallel(...tasks.sprite));
|
|
||||||
|
|
||||||
// Demo
|
|
||||||
gulp.watch(paths.demo.src.js, gulp.parallel(...tasks.js));
|
|
||||||
gulp.watch(paths.demo.src.sass, gulp.parallel(...tasks.css));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Serve via browser sync
|
|
||||||
gulp.task('serve', () =>
|
|
||||||
browserSync.init({
|
|
||||||
server: {
|
|
||||||
baseDir: paths.demo.root,
|
|
||||||
},
|
|
||||||
notify: false,
|
|
||||||
watch: true,
|
|
||||||
ghostMode: false,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build distribution
|
|
||||||
gulp.task('build', gulp.series(tasks.clean, gulp.parallel(...tasks.js, ...tasks.css, ...tasks.sprite)));
|
|
||||||
|
|
||||||
// Default gulp task
|
|
||||||
gulp.task('default', gulp.series('build', gulp.parallel('serve', 'watch')));
|
|
||||||
|
|
||||||
// Publish a version to CDN and demo
|
|
||||||
// --------------------------------------------
|
|
||||||
// Get deployment config
|
|
||||||
let credentials = {};
|
|
||||||
try {
|
|
||||||
credentials = require('./credentials.json'); //eslint-disable-line
|
|
||||||
} catch (e) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get branch info
|
|
||||||
const branch = {
|
|
||||||
current: gitbranch.sync(),
|
|
||||||
master: 'master',
|
|
||||||
beta: 'beta',
|
|
||||||
};
|
|
||||||
|
|
||||||
const maxAge = 31536000; // 1 year
|
|
||||||
const options = {
|
|
||||||
cdn: {
|
|
||||||
headers: {
|
|
||||||
'Cache-Control': `max-age=${maxAge}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
demo: {
|
|
||||||
uploadPath: branch.current === branch.beta ? '/beta' : null,
|
|
||||||
headers: {
|
|
||||||
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
symlinks(ver, filename) {
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
// http://stackoverflow.com/questions/2272835/amazon-s3-object-redirect
|
|
||||||
'x-amz-website-redirect-location': `/${ver}/${filename}`,
|
|
||||||
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const regex =
|
|
||||||
'(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*).(?:0|[1-9][0-9]*)(?:-[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?';
|
|
||||||
const semver = new RegExp(`v${regex}`, 'gi');
|
|
||||||
const localPath = new RegExp('(../)?dist', 'gi');
|
|
||||||
const versionPath = `https://${deploy.cdn.domain}/${version}`;
|
|
||||||
const cdnpath = new RegExp(`${deploy.cdn.domain}/${regex}/`, 'gi');
|
|
||||||
|
|
||||||
const renameFile = rename(p => {
|
|
||||||
p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line
|
|
||||||
p.dirname = p.dirname.replace('.', version); // eslint-disable-line
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check we're on the correct branch to deploy
|
|
||||||
const canDeploy = () => {
|
|
||||||
const allowed = [branch.master, branch.beta];
|
|
||||||
|
|
||||||
if (!allowed.includes(branch.current)) {
|
|
||||||
console.error(`Must be on ${allowed.join(', ')} to publish! (current: ${branch.current})`);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
gulp.task('version', done => {
|
|
||||||
if (!canDeploy()) {
|
|
||||||
done();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { domain } = deploy.cdn;
|
|
||||||
|
|
||||||
log(`Uploading ${ansi.green.bold(version)} to ${ansi.cyan(domain)}...`);
|
|
||||||
|
|
||||||
// Replace versioned URLs in source
|
|
||||||
const files = ['plyr.js', 'plyr.polyfilled.js', 'config/defaults.js'];
|
|
||||||
|
|
||||||
return gulp
|
|
||||||
.src(
|
|
||||||
files.map(file => path.join(__dirname, `src/js/${file}`)),
|
|
||||||
{ base: '.' },
|
|
||||||
)
|
|
||||||
.pipe(replace(semver, `v${version}`))
|
|
||||||
.pipe(replace(cdnpath, `${domain}/${version}/`))
|
|
||||||
.pipe(gulp.dest('./'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Publish version to CDN bucket
|
|
||||||
gulp.task('cdn', done => {
|
|
||||||
if (!canDeploy()) {
|
|
||||||
done();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { domain, publisher } = deploy.cdn;
|
|
||||||
|
|
||||||
if (!publisher) {
|
|
||||||
throw new Error('No publisher instance. Check AWS configuration.');
|
|
||||||
}
|
|
||||||
|
|
||||||
log(`Uploading ${ansi.green.bold(pkg.version)} to ${ansi.cyan(domain)}...`);
|
|
||||||
|
|
||||||
// Upload to CDN
|
|
||||||
return (
|
|
||||||
gulp
|
|
||||||
.src(paths.upload)
|
|
||||||
.pipe(renameFile)
|
|
||||||
// Remove min suffix from source map URL
|
|
||||||
.pipe(
|
|
||||||
replace(
|
|
||||||
/sourceMappingURL=([\w-?.]+)/,
|
|
||||||
(match, filename) => `sourceMappingURL=${filename.replace(minSuffix, '')}`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.pipe(size(sizeOptions))
|
|
||||||
.pipe(replace(localPath, versionPath))
|
|
||||||
.pipe(publisher.publish(options.cdn.headers))
|
|
||||||
.pipe(publish.reporter())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Purge the fastly cache incase any 403/404 are cached
|
|
||||||
gulp.task('purge', () => {
|
|
||||||
if (!Object.keys(credentials).includes('fastly')) {
|
|
||||||
throw new Error('Fastly credentials required to purge cache.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { fastly } = credentials;
|
|
||||||
const list = [];
|
|
||||||
|
|
||||||
return gulp
|
|
||||||
.src(paths.upload)
|
|
||||||
.pipe(
|
|
||||||
through.obj((file, enc, cb) => {
|
|
||||||
const filename = file.path.split('/').pop();
|
|
||||||
list.push(`${versionPath}/${filename.replace(minSuffix, '')}`);
|
|
||||||
cb(null);
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.on('end', () => {
|
|
||||||
const purge = new FastlyPurge(fastly.token);
|
|
||||||
|
|
||||||
list.forEach(url => {
|
|
||||||
log(`Purging ${ansi.cyan(url)}...`);
|
|
||||||
|
|
||||||
purge.url(url, (error, result) => {
|
|
||||||
if (error) {
|
|
||||||
log.error(error);
|
|
||||||
} else if (result) {
|
|
||||||
log(result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Publish to demo bucket
|
|
||||||
gulp.task('demo', done => {
|
|
||||||
if (!canDeploy()) {
|
|
||||||
done();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { publisher } = deploy.demo;
|
|
||||||
const { domain } = deploy.cdn;
|
|
||||||
|
|
||||||
if (!publisher) {
|
|
||||||
throw new Error('No publisher instance. Check AWS configuration.');
|
|
||||||
}
|
|
||||||
|
|
||||||
log(`Uploading ${ansi.green.bold(pkg.version)} to ${ansi.cyan(domain)}...`);
|
|
||||||
|
|
||||||
// Replace versioned files in readme.md
|
|
||||||
gulp.src([`${__dirname}/readme.md`])
|
|
||||||
.pipe(replace(cdnpath, `${domain}/${version}/`))
|
|
||||||
.pipe(gulp.dest(__dirname));
|
|
||||||
|
|
||||||
// Replace local file paths with remote paths in demo HTML
|
|
||||||
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
|
|
||||||
const index = `${paths.demo.root}index.html`;
|
|
||||||
const error = `${paths.demo.root}error.html`;
|
|
||||||
const pages = [index];
|
|
||||||
|
|
||||||
if (branch.current === branch.master) {
|
|
||||||
pages.push(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return gulp
|
|
||||||
.src(pages)
|
|
||||||
.pipe(replace(localPath, versionPath))
|
|
||||||
.pipe(
|
|
||||||
rename(p => {
|
|
||||||
if (options.demo.uploadPath) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
p.dirname += options.demo.uploadPath;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.pipe(publisher.publish(options.demo.headers))
|
|
||||||
.pipe(publish.reporter());
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('error', done => {
|
|
||||||
// Only update CDN for master (prod)
|
|
||||||
if (!canDeploy() || branch.current !== branch.master) {
|
|
||||||
done();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { publisher } = deploy.cdn;
|
|
||||||
|
|
||||||
if (!publisher) {
|
|
||||||
throw new Error('No publisher instance. Check AWS configuration.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace local file paths with remote paths in demo HTML
|
|
||||||
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
|
|
||||||
// Upload error.html to cdn
|
|
||||||
return gulp
|
|
||||||
.src(`${paths.demo.root}error.html`)
|
|
||||||
.pipe(replace(localPath, versionPath))
|
|
||||||
.pipe(publisher.publish(options.demo.headers))
|
|
||||||
.pipe(publish.reporter());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Open the demo site to check it's ok
|
|
||||||
gulp.task('open', () => {
|
|
||||||
const { domain } = deploy.demo;
|
|
||||||
|
|
||||||
return gulp.src(__filename).pipe(
|
|
||||||
open({
|
|
||||||
uri: `https://${domain}/${branch.current === branch.beta ? 'beta' : ''}`,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Do everything
|
|
||||||
gulp.task(
|
|
||||||
'deploy',
|
|
||||||
gulp.series(
|
|
||||||
'version',
|
|
||||||
tasks.clean,
|
|
||||||
gulp.parallel(...tasks.js, ...tasks.css, ...tasks.sprite),
|
|
||||||
'cdn',
|
|
||||||
'demo',
|
|
||||||
'purge',
|
|
||||||
'open',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|||||||
+98
-96
@@ -1,98 +1,100 @@
|
|||||||
{
|
{
|
||||||
"name": "plyr",
|
"name": "plyr",
|
||||||
"version": "3.5.10",
|
"version": "3.6.2",
|
||||||
"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>",
|
||||||
"main": "dist/plyr.js",
|
"main": "dist/plyr.js",
|
||||||
"types": "src/js/plyr.d.ts",
|
"types": "src/js/plyr.d.ts",
|
||||||
"module": "dist/plyr.min.mjs",
|
"module": "dist/plyr.min.mjs",
|
||||||
"jsnext:main": "dist/plyr.min.mjs",
|
"jsnext:main": "dist/plyr.min.mjs",
|
||||||
"browser": "dist/plyr.min.js",
|
"browser": "dist/plyr.min.js",
|
||||||
"sass": "src/sass/plyr.scss",
|
"sass": "src/sass/plyr.scss",
|
||||||
"style": "dist/plyr.css",
|
"style": "dist/plyr.css",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"HTML5 Video",
|
"HTML5 Video",
|
||||||
"HTML5 Audio",
|
"HTML5 Audio",
|
||||||
"Media Player",
|
"Media Player",
|
||||||
"DASH",
|
"DASH",
|
||||||
"Shaka",
|
"Shaka",
|
||||||
"WordPress",
|
"WordPress",
|
||||||
"HLS"
|
"HLS"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/sampotts/plyr.git"
|
"url": "git://github.com/sampotts/plyr.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/sampotts/plyr/issues"
|
"url": "https://github.com/sampotts/plyr/issues"
|
||||||
},
|
},
|
||||||
"browserslist": "> 1%",
|
"browserslist": "> 1%",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "gulp build",
|
"build": "gulp build",
|
||||||
"lint": "eslint src/js && npm run-script remark",
|
"lint": "eslint src/js && npm run-script remark",
|
||||||
"lint:fix": "eslint --fix src/js",
|
"lint:fix": "eslint --fix src/js",
|
||||||
"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": "yarn lint && gulp deploy"
|
"deploy": "yarn lint && gulp version && gulp build && gulp deploy",
|
||||||
},
|
"format": "prettier --write \"./{src,demo/src}/**/*.{js,scss}\""
|
||||||
"devDependencies": {
|
},
|
||||||
"ansi-colors": "^4.1.1",
|
"devDependencies": {
|
||||||
"aws-sdk": "^2.647.0",
|
"ansi-colors": "^4.1.1",
|
||||||
"@babel/core": "^7.9.0",
|
"autoprefixer": "^9.7.6",
|
||||||
"@babel/preset-env": "^7.9.0",
|
"aws-sdk": "^2.668.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"@babel/core": "^7.9.6",
|
||||||
"browser-sync": "^2.26.7",
|
"@babel/preset-env": "^7.9.6",
|
||||||
"del": "^5.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint": "^6.8.0",
|
"browser-sync": "^2.26.7",
|
||||||
"eslint-config-airbnb-base": "^14.1.0",
|
"del": "^5.1.0",
|
||||||
"eslint-config-prettier": "^6.10.1",
|
"eslint": "^6.8.0",
|
||||||
"eslint-plugin-import": "^2.20.1",
|
"eslint-config-airbnb-base": "^14.1.0",
|
||||||
"eslint-plugin-simple-import-sort": "^5.0.2",
|
"eslint-config-prettier": "^6.11.0",
|
||||||
"fancy-log": "^1.3.3",
|
"eslint-plugin-import": "^2.20.2",
|
||||||
"fastly-purge": "^1.0.1",
|
"eslint-plugin-simple-import-sort": "^5.0.3",
|
||||||
"git-branch": "^2.0.1",
|
"fancy-log": "^1.3.3",
|
||||||
"gulp": "^4.0.2",
|
"fastly-purge": "^1.0.1",
|
||||||
"gulp-autoprefixer": "^7.0.1",
|
"git-branch": "^2.0.1",
|
||||||
"gulp-awspublish": "^4.1.1",
|
"gulp": "^4.0.2",
|
||||||
"gulp-better-rollup": "^4.0.1",
|
"gulp-awspublish": "^4.1.1",
|
||||||
"gulp-clean-css": "^4.3.0",
|
"gulp-better-rollup": "^4.0.1",
|
||||||
"gulp-filter": "^6.0.0",
|
"gulp-filter": "^6.0.0",
|
||||||
"gulp-header": "^2.0.9",
|
"gulp-header": "^2.0.9",
|
||||||
"gulp-imagemin": "^7.1.0",
|
"gulp-hub": "^4.2.0",
|
||||||
"gulp-open": "^3.0.1",
|
"gulp-imagemin": "^7.1.0",
|
||||||
"gulp-plumber": "^1.2.1",
|
"gulp-open": "^3.0.1",
|
||||||
"gulp-postcss": "^8.0.0",
|
"gulp-plumber": "^1.2.1",
|
||||||
"gulp-rename": "^2.0.0",
|
"gulp-postcss": "^8.0.0",
|
||||||
"gulp-replace": "^1.0.0",
|
"gulp-rename": "^2.0.0",
|
||||||
"gulp-sass": "^4.0.2",
|
"gulp-replace": "^1.0.0",
|
||||||
"gulp-size": "^3.0.0",
|
"gulp-sass": "^4.1.0",
|
||||||
"gulp-sourcemaps": "^2.6.5",
|
"gulp-size": "^3.0.0",
|
||||||
"gulp-svgstore": "^7.0.1",
|
"gulp-sourcemaps": "^2.6.5",
|
||||||
"gulp-terser": "^1.2.0",
|
"gulp-svgstore": "^7.0.1",
|
||||||
"postcss-custom-properties": "^9.1.1",
|
"gulp-terser": "^1.2.0",
|
||||||
"prettier-eslint": "^9.0.1",
|
"postcss-clean": "^1.1.0",
|
||||||
"prettier-stylelint": "^0.4.2",
|
"postcss-custom-properties": "^9.1.1",
|
||||||
"remark-cli": "^7.0.1",
|
"prettier-eslint": "^9.0.1",
|
||||||
"remark-validate-links": "^10.0.0",
|
"prettier-stylelint": "^0.4.2",
|
||||||
"rollup": "^2.2.0",
|
"remark-cli": "^8.0.0",
|
||||||
"rollup-plugin-babel": "^4.4.0",
|
"remark-validate-links": "^10.0.0",
|
||||||
"rollup-plugin-commonjs": "^10.1.0",
|
"rollup": "^2.7.6",
|
||||||
"rollup-plugin-node-resolve": "^5.2.0",
|
"rollup-plugin-babel": "^4.4.0",
|
||||||
"stylelint": "^13.2.1",
|
"rollup-plugin-commonjs": "^10.1.0",
|
||||||
"stylelint-config-prettier": "^8.0.1",
|
"rollup-plugin-node-resolve": "^5.2.0",
|
||||||
"stylelint-config-recommended": "^3.0.0",
|
"stylelint": "^13.3.3",
|
||||||
"stylelint-config-sass-guidelines": "^7.0.0",
|
"stylelint-config-prettier": "^8.0.1",
|
||||||
"stylelint-order": "^4.0.0",
|
"stylelint-config-recommended": "^3.0.0",
|
||||||
"stylelint-scss": "^3.16.0",
|
"stylelint-config-sass-guidelines": "^7.0.0",
|
||||||
"stylelint-selector-bem-pattern": "^2.1.0",
|
"stylelint-order": "^4.0.0",
|
||||||
"through2": "^3.0.1"
|
"stylelint-scss": "^3.17.1",
|
||||||
},
|
"stylelint-selector-bem-pattern": "^2.1.0",
|
||||||
"dependencies": {
|
"through2": "^3.0.1"
|
||||||
"core-js": "^3.6.4",
|
},
|
||||||
"custom-event-polyfill": "^1.0.7",
|
"dependencies": {
|
||||||
"loadjs": "^4.2.0",
|
"core-js": "^3.6.5",
|
||||||
"rangetouch": "^2.0.1",
|
"custom-event-polyfill": "^1.0.7",
|
||||||
"url-polyfill": "^1.1.8"
|
"loadjs": "^4.2.0",
|
||||||
}
|
"rangetouch": "^2.0.1",
|
||||||
|
"url-polyfill": "^1.1.8"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-34
@@ -1,37 +1,37 @@
|
|||||||
{
|
{
|
||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
"path": "."
|
"path": "."
|
||||||
}
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"search.exclude": {
|
|
||||||
"**/node_modules": true,
|
|
||||||
"**/dist": true
|
|
||||||
},
|
|
||||||
|
|
||||||
// Linting
|
|
||||||
"stylelint.enable": true,
|
|
||||||
"css.validate": false,
|
|
||||||
"scss.validate": false,
|
|
||||||
"javascript.validate.enable": false,
|
|
||||||
|
|
||||||
// Formatting
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
||||||
"editor.tabSize": 4,
|
|
||||||
"editor.insertSpaces": true,
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
|
|
||||||
// Trim on save
|
|
||||||
"files.trimTrailingWhitespace": true,
|
|
||||||
|
|
||||||
// Special file associations
|
|
||||||
"files.associations": {
|
|
||||||
".eslintrc": "jsonc"
|
|
||||||
},
|
|
||||||
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.fixAll": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"search.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/dist": true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Linting
|
||||||
|
"stylelint.enable": true,
|
||||||
|
"css.validate": false,
|
||||||
|
"scss.validate": false,
|
||||||
|
"javascript.validate.enable": false,
|
||||||
|
|
||||||
|
// Formatting
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.insertSpaces": true,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
|
||||||
|
// Trim on save
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
|
||||||
|
// Special file associations
|
||||||
|
"files.associations": {
|
||||||
|
".eslintrc": "jsonc"
|
||||||
|
},
|
||||||
|
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+328
-311
@@ -8,12 +8,12 @@ import support from './support';
|
|||||||
import { dedupe } from './utils/arrays';
|
import { dedupe } from './utils/arrays';
|
||||||
import browser from './utils/browser';
|
import browser from './utils/browser';
|
||||||
import {
|
import {
|
||||||
createElement,
|
createElement,
|
||||||
emptyElement,
|
emptyElement,
|
||||||
getAttributesFromSelector,
|
getAttributesFromSelector,
|
||||||
insertAfter,
|
insertAfter,
|
||||||
removeElement,
|
removeElement,
|
||||||
toggleClass,
|
toggleClass,
|
||||||
} from './utils/elements';
|
} from './utils/elements';
|
||||||
import { on, triggerEvent } from './utils/events';
|
import { on, triggerEvent } from './utils/events';
|
||||||
import fetch from './utils/fetch';
|
import fetch from './utils/fetch';
|
||||||
@@ -23,368 +23,385 @@ import { getHTML } from './utils/strings';
|
|||||||
import { parseUrl } from './utils/urls';
|
import { parseUrl } from './utils/urls';
|
||||||
|
|
||||||
const captions = {
|
const captions = {
|
||||||
// Setup captions
|
// Setup captions
|
||||||
setup() {
|
setup() {
|
||||||
// Requires UI support
|
// Requires UI support
|
||||||
if (!this.supported.ui) {
|
if (!this.supported.ui) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only Vimeo and HTML5 video supported at this point
|
// Only Vimeo and HTML5 video supported at this point
|
||||||
if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
|
if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
|
||||||
// Clear menu and hide
|
// Clear menu and hide
|
||||||
if (
|
if (
|
||||||
is.array(this.config.controls) &&
|
is.array(this.config.controls) &&
|
||||||
this.config.controls.includes('settings') &&
|
this.config.controls.includes('settings') &&
|
||||||
this.config.settings.includes('captions')
|
this.config.settings.includes('captions')
|
||||||
) {
|
) {
|
||||||
controls.setCaptionsMenu.call(this);
|
controls.setCaptionsMenu.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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));
|
||||||
|
|
||||||
insertAfter(this.elements.captions, this.elements.wrapper);
|
insertAfter(this.elements.captions, this.elements.wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix IE captions if CORS is used
|
// Fix IE captions if CORS is used
|
||||||
// Fetch captions and inject as blobs instead (data URIs not supported!)
|
// Fetch captions and inject as blobs instead (data URIs not supported!)
|
||||||
if (browser.isIE && window.URL) {
|
if (browser.isIE && window.URL) {
|
||||||
const elements = this.media.querySelectorAll('track');
|
const elements = this.media.querySelectorAll('track');
|
||||||
|
|
||||||
Array.from(elements).forEach(track => {
|
Array.from(elements).forEach(track => {
|
||||||
const src = track.getAttribute('src');
|
const src = track.getAttribute('src');
|
||||||
const url = parseUrl(src);
|
const url = parseUrl(src);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
url !== null &&
|
url !== null &&
|
||||||
url.hostname !== window.location.href.hostname &&
|
url.hostname !== window.location.href.hostname &&
|
||||||
['http:', 'https:'].includes(url.protocol)
|
['http:', 'https:'].includes(url.protocol)
|
||||||
) {
|
) {
|
||||||
fetch(src, 'blob')
|
fetch(src, 'blob')
|
||||||
.then(blob => {
|
.then(blob => {
|
||||||
track.setAttribute('src', window.URL.createObjectURL(blob));
|
track.setAttribute('src', window.URL.createObjectURL(blob));
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
removeElement(track);
|
removeElement(track);
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get and set initial data
|
// Get and set initial data
|
||||||
// The "preferred" options are not realized unless / until the wanted language has a match
|
// The "preferred" options are not realized unless / until the wanted language has a match
|
||||||
// * languages: Array of user's browser languages.
|
// * languages: Array of user's browser languages.
|
||||||
// * language: The language preferred by user settings or config
|
// * language: The language preferred by user settings or config
|
||||||
// * active: The state preferred by user settings or config
|
// * active: The state preferred by user settings or config
|
||||||
// * toggled: The real captions state
|
// * toggled: The real captions state
|
||||||
|
|
||||||
const browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];
|
const browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];
|
||||||
const languages = dedupe(browserLanguages.map(language => language.split('-')[0]));
|
const languages = dedupe(browserLanguages.map(language => language.split('-')[0]));
|
||||||
let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
|
let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
|
||||||
|
|
||||||
// Use first browser language when language is 'auto'
|
// Use first browser language when language is 'auto'
|
||||||
if (language === 'auto') {
|
if (language === 'auto') {
|
||||||
[language] = languages;
|
[language] = languages;
|
||||||
}
|
}
|
||||||
|
|
||||||
let active = this.storage.get('captions');
|
let active = this.storage.get('captions');
|
||||||
if (!is.boolean(active)) {
|
if (!is.boolean(active)) {
|
||||||
({ active } = this.config.captions);
|
({ active } = this.config.captions);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(this.captions, {
|
Object.assign(this.captions, {
|
||||||
toggled: false,
|
toggled: false,
|
||||||
active,
|
active,
|
||||||
language,
|
language,
|
||||||
languages,
|
languages,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch changes to textTracks and update captions menu
|
||||||
|
if (this.isHTML5) {
|
||||||
|
const trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack';
|
||||||
|
on.call(this, this.media.textTracks, trackEvents, captions.update.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update available languages in list next tick (the event must not be triggered before the listeners)
|
||||||
|
setTimeout(captions.update.bind(this), 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update available language options in settings based on tracks
|
||||||
|
update() {
|
||||||
|
const tracks = captions.getTracks.call(this, true);
|
||||||
|
// Get the wanted language
|
||||||
|
const { active, language, meta, currentTrackNode } = this.captions;
|
||||||
|
const languageExists = Boolean(tracks.find(track => track.language === language));
|
||||||
|
|
||||||
|
// Handle tracks (add event listener and "pseudo"-default)
|
||||||
|
if (this.isHTML5 && this.isVideo) {
|
||||||
|
tracks
|
||||||
|
.filter(track => !meta.get(track))
|
||||||
|
.forEach(track => {
|
||||||
|
this.debug.log('Track added', track);
|
||||||
|
|
||||||
|
// Attempt to store if the original dom element was "default"
|
||||||
|
meta.set(track, {
|
||||||
|
default: track.mode === 'showing',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Turn off native caption rendering to avoid double captions
|
||||||
|
// Note: mode='hidden' forces a track to download. To ensure every track
|
||||||
|
// isn't downloaded at once, only 'showing' tracks should be reassigned
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
if (track.mode === 'showing') {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
track.mode = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listener for cue changes
|
||||||
|
on.call(this, track, 'cuechange', () => captions.updateCues.call(this));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Watch changes to textTracks and update captions menu
|
// Update language first time it matches, or if the previous matching track was removed
|
||||||
if (this.isHTML5) {
|
if ((languageExists && this.language !== language) || !tracks.includes(currentTrackNode)) {
|
||||||
const trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack';
|
captions.setLanguage.call(this, language);
|
||||||
on.call(this, this.media.textTracks, trackEvents, captions.update.bind(this));
|
captions.toggle.call(this, active && languageExists);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update available languages in list next tick (the event must not be triggered before the listeners)
|
// Enable or disable captions based on track length
|
||||||
setTimeout(captions.update.bind(this), 0);
|
toggleClass(this.elements.container, this.config.classNames.captions.enabled, !is.empty(tracks));
|
||||||
},
|
|
||||||
|
|
||||||
// Update available language options in settings based on tracks
|
// Update available languages in list
|
||||||
update() {
|
if (
|
||||||
const tracks = captions.getTracks.call(this, true);
|
is.array(this.config.controls) &&
|
||||||
// Get the wanted language
|
this.config.controls.includes('settings') &&
|
||||||
const { active, language, meta, currentTrackNode } = this.captions;
|
this.config.settings.includes('captions')
|
||||||
const languageExists = Boolean(tracks.find(track => track.language === language));
|
) {
|
||||||
|
controls.setCaptionsMenu.call(this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Handle tracks (add event listener and "pseudo"-default)
|
// Toggle captions display
|
||||||
if (this.isHTML5 && this.isVideo) {
|
// Used internally for the toggleCaptions method, with the passive option forced to false
|
||||||
tracks
|
toggle(input, passive = true) {
|
||||||
.filter(track => !meta.get(track))
|
// If there's no full support
|
||||||
.forEach(track => {
|
if (!this.supported.ui) {
|
||||||
this.debug.log('Track added', track);
|
return;
|
||||||
// Attempt to store if the original dom element was "default"
|
}
|
||||||
meta.set(track, {
|
|
||||||
default: track.mode === 'showing',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Turn off native caption rendering to avoid double captions
|
const { toggled } = this.captions; // Current state
|
||||||
// eslint-disable-next-line no-param-reassign
|
const activeClass = this.config.classNames.captions.active;
|
||||||
track.mode = 'hidden';
|
// Get the next state
|
||||||
|
// If the method is called without parameter, toggle based on current value
|
||||||
|
const active = is.nullOrUndefined(input) ? !toggled : input;
|
||||||
|
|
||||||
// Add event listener for cue changes
|
// Update state and trigger event
|
||||||
on.call(this, track, 'cuechange', () => captions.updateCues.call(this));
|
if (active !== toggled) {
|
||||||
});
|
// When passive, don't override user preferences
|
||||||
}
|
if (!passive) {
|
||||||
|
this.captions.active = active;
|
||||||
|
this.storage.set({ captions: active });
|
||||||
|
}
|
||||||
|
|
||||||
// Update language first time it matches, or if the previous matching track was removed
|
// Force language if the call isn't passive and there is no matching language to toggle to
|
||||||
if ((languageExists && this.language !== language) || !tracks.includes(currentTrackNode)) {
|
if (!this.language && active && !passive) {
|
||||||
captions.setLanguage.call(this, language);
|
|
||||||
captions.toggle.call(this, active && languageExists);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable or disable captions based on track length
|
|
||||||
toggleClass(this.elements.container, this.config.classNames.captions.enabled, !is.empty(tracks));
|
|
||||||
|
|
||||||
// Update available languages in list
|
|
||||||
if ((is.array(this.config.controls) && this.config.controls.includes('settings'))
|
|
||||||
&& this.config.settings.includes('captions')) {
|
|
||||||
controls.setCaptionsMenu.call(this);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Toggle captions display
|
|
||||||
// Used internally for the toggleCaptions method, with the passive option forced to false
|
|
||||||
toggle(input, passive = true) {
|
|
||||||
// If there's no full support
|
|
||||||
if (!this.supported.ui) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { toggled } = this.captions; // Current state
|
|
||||||
const activeClass = this.config.classNames.captions.active;
|
|
||||||
// Get the next state
|
|
||||||
// If the method is called without parameter, toggle based on current value
|
|
||||||
const active = is.nullOrUndefined(input) ? !toggled : input;
|
|
||||||
|
|
||||||
// Update state and trigger event
|
|
||||||
if (active !== toggled) {
|
|
||||||
// When passive, don't override user preferences
|
|
||||||
if (!passive) {
|
|
||||||
this.captions.active = active;
|
|
||||||
this.storage.set({ captions: active });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force language if the call isn't passive and there is no matching language to toggle to
|
|
||||||
if (!this.language && active && !passive) {
|
|
||||||
const tracks = captions.getTracks.call(this);
|
|
||||||
const track = captions.findTrack.call(this, [this.captions.language, ...this.captions.languages], true);
|
|
||||||
|
|
||||||
// Override user preferences to avoid switching languages if a matching track is added
|
|
||||||
this.captions.language = track.language;
|
|
||||||
|
|
||||||
// Set caption, but don't store in localStorage as user preference
|
|
||||||
captions.set.call(this, tracks.indexOf(track));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle button if it's enabled
|
|
||||||
if (this.elements.buttons.captions) {
|
|
||||||
this.elements.buttons.captions.pressed = active;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add class hook
|
|
||||||
toggleClass(this.elements.container, activeClass, active);
|
|
||||||
|
|
||||||
this.captions.toggled = active;
|
|
||||||
|
|
||||||
// Update settings menu
|
|
||||||
controls.updateSetting.call(this, 'captions');
|
|
||||||
|
|
||||||
// Trigger event (not used internally)
|
|
||||||
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Set captions by track index
|
|
||||||
// Used internally for the currentTrack setter with the passive option forced to false
|
|
||||||
set(index, passive = true) {
|
|
||||||
const tracks = captions.getTracks.call(this);
|
const tracks = captions.getTracks.call(this);
|
||||||
|
const track = captions.findTrack.call(this, [this.captions.language, ...this.captions.languages], true);
|
||||||
|
|
||||||
// Disable captions if setting to -1
|
// Override user preferences to avoid switching languages if a matching track is added
|
||||||
if (index === -1) {
|
this.captions.language = track.language;
|
||||||
captions.toggle.call(this, false, passive);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is.number(index)) {
|
// Set caption, but don't store in localStorage as user preference
|
||||||
this.debug.warn('Invalid caption argument', index);
|
captions.set.call(this, tracks.indexOf(track));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(index in tracks)) {
|
// Toggle button if it's enabled
|
||||||
this.debug.warn('Track not found', index);
|
if (this.elements.buttons.captions) {
|
||||||
return;
|
this.elements.buttons.captions.pressed = active;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.captions.currentTrack !== index) {
|
// Add class hook
|
||||||
this.captions.currentTrack = index;
|
toggleClass(this.elements.container, activeClass, active);
|
||||||
const track = tracks[index];
|
|
||||||
const { language } = track || {};
|
|
||||||
|
|
||||||
// Store reference to node for invalidation on remove
|
this.captions.toggled = active;
|
||||||
this.captions.currentTrackNode = track;
|
|
||||||
|
|
||||||
// Update settings menu
|
// Update settings menu
|
||||||
controls.updateSetting.call(this, 'captions');
|
controls.updateSetting.call(this, 'captions');
|
||||||
|
|
||||||
// When passive, don't override user preferences
|
// Trigger event (not used internally)
|
||||||
if (!passive) {
|
triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
|
||||||
this.captions.language = language;
|
}
|
||||||
this.storage.set({ language });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Vimeo captions
|
// Wait for the call stack to clear before setting mode='hidden'
|
||||||
if (this.isVimeo) {
|
// on the active track - forcing the browser to download it
|
||||||
this.embed.enableTextTrack(language);
|
setTimeout(() => {
|
||||||
}
|
if (active && this.captions.toggled) {
|
||||||
|
this.captions.currentTrackNode.mode = 'hidden';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Trigger event
|
// Set captions by track index
|
||||||
triggerEvent.call(this, this.media, 'languagechange');
|
// Used internally for the currentTrack setter with the passive option forced to false
|
||||||
}
|
set(index, passive = true) {
|
||||||
|
const tracks = captions.getTracks.call(this);
|
||||||
|
|
||||||
// Show captions
|
// Disable captions if setting to -1
|
||||||
captions.toggle.call(this, true, passive);
|
if (index === -1) {
|
||||||
|
captions.toggle.call(this, false, passive);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isHTML5 && this.isVideo) {
|
if (!is.number(index)) {
|
||||||
// If we change the active track while a cue is already displayed we need to update it
|
this.debug.warn('Invalid caption argument', index);
|
||||||
captions.updateCues.call(this);
|
return;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
// Set captions by language
|
if (!(index in tracks)) {
|
||||||
// Used internally for the language setter with the passive option forced to false
|
this.debug.warn('Track not found', index);
|
||||||
setLanguage(input, passive = true) {
|
return;
|
||||||
if (!is.string(input)) {
|
}
|
||||||
this.debug.warn('Invalid language argument', input);
|
|
||||||
return;
|
if (this.captions.currentTrack !== index) {
|
||||||
}
|
this.captions.currentTrack = index;
|
||||||
// Normalize
|
const track = tracks[index];
|
||||||
const language = input.toLowerCase();
|
const { language } = track || {};
|
||||||
|
|
||||||
|
// Store reference to node for invalidation on remove
|
||||||
|
this.captions.currentTrackNode = track;
|
||||||
|
|
||||||
|
// Update settings menu
|
||||||
|
controls.updateSetting.call(this, 'captions');
|
||||||
|
|
||||||
|
// When passive, don't override user preferences
|
||||||
|
if (!passive) {
|
||||||
this.captions.language = language;
|
this.captions.language = language;
|
||||||
|
this.storage.set({ language });
|
||||||
|
}
|
||||||
|
|
||||||
// Set currentTrack
|
// Handle Vimeo captions
|
||||||
const tracks = captions.getTracks.call(this);
|
if (this.isVimeo) {
|
||||||
const track = captions.findTrack.call(this, [language]);
|
this.embed.enableTextTrack(language);
|
||||||
captions.set.call(this, tracks.indexOf(track), passive);
|
}
|
||||||
},
|
|
||||||
|
|
||||||
// Get current valid caption tracks
|
// Trigger event
|
||||||
// If update is false it will also ignore tracks without metadata
|
triggerEvent.call(this, this.media, 'languagechange');
|
||||||
// This is used to "freeze" the language options when captions.update is false
|
}
|
||||||
getTracks(update = false) {
|
|
||||||
// Handle media or textTracks missing or null
|
|
||||||
const tracks = Array.from((this.media || {}).textTracks || []);
|
|
||||||
// For HTML5, use cache instead of current tracks when it exists (if captions.update is false)
|
|
||||||
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
|
||||||
return tracks
|
|
||||||
.filter(track => !this.isHTML5 || update || this.captions.meta.has(track))
|
|
||||||
.filter(track => ['captions', 'subtitles'].includes(track.kind));
|
|
||||||
},
|
|
||||||
|
|
||||||
// Match tracks based on languages and get the first
|
// Show captions
|
||||||
findTrack(languages, force = false) {
|
captions.toggle.call(this, true, passive);
|
||||||
const tracks = captions.getTracks.call(this);
|
|
||||||
const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default);
|
|
||||||
const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a));
|
|
||||||
let track;
|
|
||||||
|
|
||||||
languages.every(language => {
|
if (this.isHTML5 && this.isVideo) {
|
||||||
track = sorted.find(t => t.language === language);
|
// If we change the active track while a cue is already displayed we need to update it
|
||||||
return !track; // Break iteration if there is a match
|
captions.updateCues.call(this);
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// If no match is found but is required, get first
|
// Set captions by language
|
||||||
return track || (force ? sorted[0] : undefined);
|
// Used internally for the language setter with the passive option forced to false
|
||||||
},
|
setLanguage(input, passive = true) {
|
||||||
|
if (!is.string(input)) {
|
||||||
|
this.debug.warn('Invalid language argument', input);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Normalize
|
||||||
|
const language = input.toLowerCase();
|
||||||
|
this.captions.language = language;
|
||||||
|
|
||||||
// Get the current track
|
// Set currentTrack
|
||||||
getCurrentTrack() {
|
const tracks = captions.getTracks.call(this);
|
||||||
return captions.getTracks.call(this)[this.currentTrack];
|
const track = captions.findTrack.call(this, [language]);
|
||||||
},
|
captions.set.call(this, tracks.indexOf(track), passive);
|
||||||
|
},
|
||||||
|
|
||||||
// Get UI label for track
|
// Get current valid caption tracks
|
||||||
getLabel(track) {
|
// If update is false it will also ignore tracks without metadata
|
||||||
let currentTrack = track;
|
// This is used to "freeze" the language options when captions.update is false
|
||||||
|
getTracks(update = false) {
|
||||||
|
// Handle media or textTracks missing or null
|
||||||
|
const tracks = Array.from((this.media || {}).textTracks || []);
|
||||||
|
// For HTML5, use cache instead of current tracks when it exists (if captions.update is false)
|
||||||
|
// Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
|
||||||
|
return tracks
|
||||||
|
.filter(track => !this.isHTML5 || update || this.captions.meta.has(track))
|
||||||
|
.filter(track => ['captions', 'subtitles'].includes(track.kind));
|
||||||
|
},
|
||||||
|
|
||||||
if (!is.track(currentTrack) && support.textTracks && this.captions.toggled) {
|
// Match tracks based on languages and get the first
|
||||||
currentTrack = captions.getCurrentTrack.call(this);
|
findTrack(languages, force = false) {
|
||||||
}
|
const tracks = captions.getTracks.call(this);
|
||||||
|
const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default);
|
||||||
|
const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a));
|
||||||
|
let track;
|
||||||
|
|
||||||
if (is.track(currentTrack)) {
|
languages.every(language => {
|
||||||
if (!is.empty(currentTrack.label)) {
|
track = sorted.find(t => t.language === language);
|
||||||
return currentTrack.label;
|
return !track; // Break iteration if there is a match
|
||||||
}
|
});
|
||||||
|
|
||||||
if (!is.empty(currentTrack.language)) {
|
// If no match is found but is required, get first
|
||||||
return track.language.toUpperCase();
|
return track || (force ? sorted[0] : undefined);
|
||||||
}
|
},
|
||||||
|
|
||||||
return i18n.get('enabled', this.config);
|
// Get the current track
|
||||||
}
|
getCurrentTrack() {
|
||||||
|
return captions.getTracks.call(this)[this.currentTrack];
|
||||||
|
},
|
||||||
|
|
||||||
return i18n.get('disabled', this.config);
|
// Get UI label for track
|
||||||
},
|
getLabel(track) {
|
||||||
|
let currentTrack = track;
|
||||||
|
|
||||||
// Update captions using current track's active cues
|
if (!is.track(currentTrack) && support.textTracks && this.captions.toggled) {
|
||||||
// Also optional array argument in case there isn't any track (ex: vimeo)
|
currentTrack = captions.getCurrentTrack.call(this);
|
||||||
updateCues(input) {
|
}
|
||||||
// Requires UI
|
|
||||||
if (!this.supported.ui) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is.element(this.elements.captions)) {
|
if (is.track(currentTrack)) {
|
||||||
this.debug.warn('No captions element to render to');
|
if (!is.empty(currentTrack.label)) {
|
||||||
return;
|
return currentTrack.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only accept array or empty input
|
if (!is.empty(currentTrack.language)) {
|
||||||
if (!is.nullOrUndefined(input) && !Array.isArray(input)) {
|
return track.language.toUpperCase();
|
||||||
this.debug.warn('updateCues: Invalid input', input);
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cues = input;
|
return i18n.get('enabled', this.config);
|
||||||
|
}
|
||||||
|
|
||||||
// Get cues from track
|
return i18n.get('disabled', this.config);
|
||||||
if (!cues) {
|
},
|
||||||
const track = captions.getCurrentTrack.call(this);
|
|
||||||
|
|
||||||
cues = Array.from((track || {}).activeCues || [])
|
// Update captions using current track's active cues
|
||||||
.map(cue => cue.getCueAsHTML())
|
// Also optional array argument in case there isn't any track (ex: vimeo)
|
||||||
.map(getHTML);
|
updateCues(input) {
|
||||||
}
|
// Requires UI
|
||||||
|
if (!this.supported.ui) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Set new caption text
|
if (!is.element(this.elements.captions)) {
|
||||||
const content = cues.map(cueText => cueText.trim()).join('\n');
|
this.debug.warn('No captions element to render to');
|
||||||
const changed = content !== this.elements.captions.innerHTML;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (changed) {
|
// Only accept array or empty input
|
||||||
// Empty the container and create a new child element
|
if (!is.nullOrUndefined(input) && !Array.isArray(input)) {
|
||||||
emptyElement(this.elements.captions);
|
this.debug.warn('updateCues: Invalid input', input);
|
||||||
const caption = createElement('span', getAttributesFromSelector(this.config.selectors.caption));
|
return;
|
||||||
caption.innerHTML = content;
|
}
|
||||||
this.elements.captions.appendChild(caption);
|
|
||||||
|
|
||||||
// Trigger event
|
let cues = input;
|
||||||
triggerEvent.call(this, this.media, 'cuechange');
|
|
||||||
}
|
// Get cues from track
|
||||||
},
|
if (!cues) {
|
||||||
|
const track = captions.getCurrentTrack.call(this);
|
||||||
|
|
||||||
|
cues = Array.from((track || {}).activeCues || [])
|
||||||
|
.map(cue => cue.getCueAsHTML())
|
||||||
|
.map(getHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set new caption text
|
||||||
|
const content = cues.map(cueText => cueText.trim()).join('\n');
|
||||||
|
const changed = content !== this.elements.captions.innerHTML;
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
// Empty the container and create a new child element
|
||||||
|
emptyElement(this.elements.captions);
|
||||||
|
const caption = createElement('span', getAttributesFromSelector(this.config.selectors.caption));
|
||||||
|
caption.innerHTML = content;
|
||||||
|
this.elements.captions.appendChild(caption);
|
||||||
|
|
||||||
|
// Trigger event
|
||||||
|
triggerEvent.call(this, this.media, 'cuechange');
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default captions;
|
export default captions;
|
||||||
|
|||||||
+425
-422
@@ -3,437 +3,440 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
// Disable
|
// Disable
|
||||||
|
enabled: true,
|
||||||
|
|
||||||
|
// Custom media title
|
||||||
|
title: '',
|
||||||
|
|
||||||
|
// Logging to console
|
||||||
|
debug: false,
|
||||||
|
|
||||||
|
// Auto play (if supported)
|
||||||
|
autoplay: false,
|
||||||
|
|
||||||
|
// Only allow one media playing at once (vimeo only)
|
||||||
|
autopause: true,
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
|
||||||
|
// Default time to skip when rewind/fast forward
|
||||||
|
seekTime: 10,
|
||||||
|
|
||||||
|
// Default volume
|
||||||
|
volume: 1,
|
||||||
|
muted: false,
|
||||||
|
|
||||||
|
// Pass a custom duration
|
||||||
|
duration: null,
|
||||||
|
|
||||||
|
// Display the media duration on load in the current time position
|
||||||
|
// If you have opted to display both duration and currentTime, this is ignored
|
||||||
|
displayDuration: true,
|
||||||
|
|
||||||
|
// Invert the current time to be a countdown
|
||||||
|
invertTime: true,
|
||||||
|
|
||||||
|
// Clicking the currentTime inverts it's value to show time left rather than elapsed
|
||||||
|
toggleInvert: true,
|
||||||
|
|
||||||
|
// Force an aspect ratio
|
||||||
|
// The format must be `'w:h'` (e.g. `'16:9'`)
|
||||||
|
ratio: null,
|
||||||
|
|
||||||
|
// Click video container to play/pause
|
||||||
|
clickToPlay: true,
|
||||||
|
|
||||||
|
// Auto hide the controls
|
||||||
|
hideControls: true,
|
||||||
|
|
||||||
|
// Reset to start when playback ended
|
||||||
|
resetOnEnd: false,
|
||||||
|
|
||||||
|
// Disable the standard context menu
|
||||||
|
disableContextMenu: true,
|
||||||
|
|
||||||
|
// Sprite (for icons)
|
||||||
|
loadSprite: true,
|
||||||
|
iconPrefix: 'plyr',
|
||||||
|
iconUrl: 'https://cdn.plyr.io/3.6.1/plyr.svg',
|
||||||
|
|
||||||
|
// Blank video (used to prevent errors on source change)
|
||||||
|
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||||
|
|
||||||
|
// Quality default
|
||||||
|
quality: {
|
||||||
|
default: 576,
|
||||||
|
// The options to display in the UI, if available for the source media
|
||||||
|
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],
|
||||||
|
forced: false,
|
||||||
|
onChange: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set loops
|
||||||
|
loop: {
|
||||||
|
active: false,
|
||||||
|
// start: null,
|
||||||
|
// end: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Speed default and options to display
|
||||||
|
speed: {
|
||||||
|
selected: 1,
|
||||||
|
// The options to display in the UI, if available for the source media (e.g. Vimeo and YouTube only support 0.5x-4x)
|
||||||
|
options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Keyboard shortcut settings
|
||||||
|
keyboard: {
|
||||||
|
focused: true,
|
||||||
|
global: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Display tooltips
|
||||||
|
tooltips: {
|
||||||
|
controls: false,
|
||||||
|
seek: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Captions settings
|
||||||
|
captions: {
|
||||||
|
active: false,
|
||||||
|
language: 'auto',
|
||||||
|
// Listen to new tracks added after Plyr is initialized.
|
||||||
|
// This is needed for streaming captions, but may result in unselectable options
|
||||||
|
update: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fullscreen settings
|
||||||
|
fullscreen: {
|
||||||
|
enabled: true, // Allow fullscreen?
|
||||||
|
fallback: true, // Fallback using full viewport/window
|
||||||
|
iosNative: false, // Use the native fullscreen in iOS (disables custom controls)
|
||||||
|
// Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode
|
||||||
|
// Non-ancestors of the player element will be ignored
|
||||||
|
// container: null, // defaults to the player element
|
||||||
|
},
|
||||||
|
|
||||||
|
// Local storage
|
||||||
|
storage: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
key: 'plyr',
|
||||||
|
},
|
||||||
|
|
||||||
// Custom media title
|
// Default controls
|
||||||
title: '',
|
controls: [
|
||||||
|
'play-large',
|
||||||
|
// 'restart',
|
||||||
|
// 'rewind',
|
||||||
|
'play',
|
||||||
|
// 'fast-forward',
|
||||||
|
'progress',
|
||||||
|
'current-time',
|
||||||
|
// 'duration',
|
||||||
|
'mute',
|
||||||
|
'volume',
|
||||||
|
'captions',
|
||||||
|
'settings',
|
||||||
|
'pip',
|
||||||
|
'airplay',
|
||||||
|
// 'download',
|
||||||
|
'fullscreen',
|
||||||
|
],
|
||||||
|
settings: ['captions', 'quality', 'speed'],
|
||||||
|
|
||||||
// Logging to console
|
// Localisation
|
||||||
debug: false,
|
i18n: {
|
||||||
|
restart: 'Restart',
|
||||||
// Auto play (if supported)
|
rewind: 'Rewind {seektime}s',
|
||||||
autoplay: false,
|
play: 'Play',
|
||||||
|
pause: 'Pause',
|
||||||
// Only allow one media playing at once (vimeo only)
|
fastForward: 'Forward {seektime}s',
|
||||||
autopause: true,
|
seek: 'Seek',
|
||||||
|
seekLabel: '{currentTime} of {duration}',
|
||||||
// Allow inline playback on iOS (this effects YouTube/Vimeo - HTML5 requires the attribute present)
|
played: 'Played',
|
||||||
// TODO: Remove iosNative fullscreen option in favour of this (logic needs work)
|
buffered: 'Buffered',
|
||||||
playsinline: true,
|
currentTime: 'Current time',
|
||||||
|
duration: 'Duration',
|
||||||
// Default time to skip when rewind/fast forward
|
volume: 'Volume',
|
||||||
seekTime: 10,
|
mute: 'Mute',
|
||||||
|
unmute: 'Unmute',
|
||||||
// Default volume
|
enableCaptions: 'Enable captions',
|
||||||
volume: 1,
|
disableCaptions: 'Disable captions',
|
||||||
muted: false,
|
download: 'Download',
|
||||||
|
enterFullscreen: 'Enter fullscreen',
|
||||||
// Pass a custom duration
|
exitFullscreen: 'Exit fullscreen',
|
||||||
duration: null,
|
frameTitle: 'Player for {title}',
|
||||||
|
captions: 'Captions',
|
||||||
// Display the media duration on load in the current time position
|
settings: 'Settings',
|
||||||
// If you have opted to display both duration and currentTime, this is ignored
|
pip: 'PIP',
|
||||||
displayDuration: true,
|
menuBack: 'Go back to previous menu',
|
||||||
|
speed: 'Speed',
|
||||||
// Invert the current time to be a countdown
|
normal: 'Normal',
|
||||||
invertTime: true,
|
quality: 'Quality',
|
||||||
|
loop: 'Loop',
|
||||||
// Clicking the currentTime inverts it's value to show time left rather than elapsed
|
start: 'Start',
|
||||||
toggleInvert: true,
|
end: 'End',
|
||||||
|
all: 'All',
|
||||||
// Force an aspect ratio
|
reset: 'Reset',
|
||||||
// The format must be `'w:h'` (e.g. `'16:9'`)
|
disabled: 'Disabled',
|
||||||
ratio: null,
|
enabled: 'Enabled',
|
||||||
|
advertisement: 'Ad',
|
||||||
// Click video container to play/pause
|
qualityBadge: {
|
||||||
clickToPlay: true,
|
2160: '4K',
|
||||||
|
1440: 'HD',
|
||||||
// Auto hide the controls
|
1080: 'HD',
|
||||||
hideControls: true,
|
720: 'HD',
|
||||||
|
576: 'SD',
|
||||||
// Reset to start when playback ended
|
480: 'SD',
|
||||||
resetOnEnd: false,
|
|
||||||
|
|
||||||
// Disable the standard context menu
|
|
||||||
disableContextMenu: true,
|
|
||||||
|
|
||||||
// Sprite (for icons)
|
|
||||||
loadSprite: true,
|
|
||||||
iconPrefix: 'plyr',
|
|
||||||
iconUrl: 'https://cdn.plyr.io/3.5.10/plyr.svg',
|
|
||||||
|
|
||||||
// Blank video (used to prevent errors on source change)
|
|
||||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
|
||||||
|
|
||||||
// Quality default
|
|
||||||
quality: {
|
|
||||||
default: 576,
|
|
||||||
// The options to display in the UI, if available for the source media
|
|
||||||
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],
|
|
||||||
forced: false,
|
|
||||||
onChange: null,
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
// Set loops
|
// URLs
|
||||||
loop: {
|
urls: {
|
||||||
active: false,
|
download: null,
|
||||||
// start: null,
|
|
||||||
// end: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Speed default and options to display
|
|
||||||
speed: {
|
|
||||||
selected: 1,
|
|
||||||
// The options to display in the UI, if available for the source media (e.g. Vimeo and YouTube only support 0.5x-4x)
|
|
||||||
options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4],
|
|
||||||
},
|
|
||||||
|
|
||||||
// Keyboard shortcut settings
|
|
||||||
keyboard: {
|
|
||||||
focused: true,
|
|
||||||
global: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Display tooltips
|
|
||||||
tooltips: {
|
|
||||||
controls: false,
|
|
||||||
seek: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Captions settings
|
|
||||||
captions: {
|
|
||||||
active: false,
|
|
||||||
language: 'auto',
|
|
||||||
// Listen to new tracks added after Plyr is initialized.
|
|
||||||
// This is needed for streaming captions, but may result in unselectable options
|
|
||||||
update: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Fullscreen settings
|
|
||||||
fullscreen: {
|
|
||||||
enabled: true, // Allow fullscreen?
|
|
||||||
fallback: true, // Fallback using full viewport/window
|
|
||||||
iosNative: false, // Use the native fullscreen in iOS (disables custom controls)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Local storage
|
|
||||||
storage: {
|
|
||||||
enabled: true,
|
|
||||||
key: 'plyr',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Default controls
|
|
||||||
controls: [
|
|
||||||
'play-large',
|
|
||||||
// 'restart',
|
|
||||||
// 'rewind',
|
|
||||||
'play',
|
|
||||||
// 'fast-forward',
|
|
||||||
'progress',
|
|
||||||
'current-time',
|
|
||||||
// 'duration',
|
|
||||||
'mute',
|
|
||||||
'volume',
|
|
||||||
'captions',
|
|
||||||
'settings',
|
|
||||||
'pip',
|
|
||||||
'airplay',
|
|
||||||
// 'download',
|
|
||||||
'fullscreen',
|
|
||||||
],
|
|
||||||
settings: ['captions', 'quality', 'speed'],
|
|
||||||
|
|
||||||
// Localisation
|
|
||||||
i18n: {
|
|
||||||
restart: 'Restart',
|
|
||||||
rewind: 'Rewind {seektime}s',
|
|
||||||
play: 'Play',
|
|
||||||
pause: 'Pause',
|
|
||||||
fastForward: 'Forward {seektime}s',
|
|
||||||
seek: 'Seek',
|
|
||||||
seekLabel: '{currentTime} of {duration}',
|
|
||||||
played: 'Played',
|
|
||||||
buffered: 'Buffered',
|
|
||||||
currentTime: 'Current time',
|
|
||||||
duration: 'Duration',
|
|
||||||
volume: 'Volume',
|
|
||||||
mute: 'Mute',
|
|
||||||
unmute: 'Unmute',
|
|
||||||
enableCaptions: 'Enable captions',
|
|
||||||
disableCaptions: 'Disable captions',
|
|
||||||
download: 'Download',
|
|
||||||
enterFullscreen: 'Enter fullscreen',
|
|
||||||
exitFullscreen: 'Exit fullscreen',
|
|
||||||
frameTitle: 'Player for {title}',
|
|
||||||
captions: 'Captions',
|
|
||||||
settings: 'Settings',
|
|
||||||
pip: 'PIP',
|
|
||||||
menuBack: 'Go back to previous menu',
|
|
||||||
speed: 'Speed',
|
|
||||||
normal: 'Normal',
|
|
||||||
quality: 'Quality',
|
|
||||||
loop: 'Loop',
|
|
||||||
start: 'Start',
|
|
||||||
end: 'End',
|
|
||||||
all: 'All',
|
|
||||||
reset: 'Reset',
|
|
||||||
disabled: 'Disabled',
|
|
||||||
enabled: 'Enabled',
|
|
||||||
advertisement: 'Ad',
|
|
||||||
qualityBadge: {
|
|
||||||
2160: '4K',
|
|
||||||
1440: 'HD',
|
|
||||||
1080: 'HD',
|
|
||||||
720: 'HD',
|
|
||||||
576: 'SD',
|
|
||||||
480: 'SD',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// URLs
|
|
||||||
urls: {
|
|
||||||
download: null,
|
|
||||||
vimeo: {
|
|
||||||
sdk: 'https://player.vimeo.com/api/player.js',
|
|
||||||
iframe: 'https://player.vimeo.com/video/{0}?{1}',
|
|
||||||
api: 'https://vimeo.com/api/v2/video/{0}.json',
|
|
||||||
},
|
|
||||||
youtube: {
|
|
||||||
sdk: 'https://www.youtube.com/iframe_api',
|
|
||||||
api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}',
|
|
||||||
},
|
|
||||||
googleIMA: {
|
|
||||||
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Custom control listeners
|
|
||||||
listeners: {
|
|
||||||
seek: null,
|
|
||||||
play: null,
|
|
||||||
pause: null,
|
|
||||||
restart: null,
|
|
||||||
rewind: null,
|
|
||||||
fastForward: null,
|
|
||||||
mute: null,
|
|
||||||
volume: null,
|
|
||||||
captions: null,
|
|
||||||
download: null,
|
|
||||||
fullscreen: null,
|
|
||||||
pip: null,
|
|
||||||
airplay: null,
|
|
||||||
speed: null,
|
|
||||||
quality: null,
|
|
||||||
loop: null,
|
|
||||||
language: null,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Events to watch and bubble
|
|
||||||
events: [
|
|
||||||
// Events to watch on HTML5 media elements and bubble
|
|
||||||
// https://developer.mozilla.org/en/docs/Web/Guide/Events/Media_events
|
|
||||||
'ended',
|
|
||||||
'progress',
|
|
||||||
'stalled',
|
|
||||||
'playing',
|
|
||||||
'waiting',
|
|
||||||
'canplay',
|
|
||||||
'canplaythrough',
|
|
||||||
'loadstart',
|
|
||||||
'loadeddata',
|
|
||||||
'loadedmetadata',
|
|
||||||
'timeupdate',
|
|
||||||
'volumechange',
|
|
||||||
'play',
|
|
||||||
'pause',
|
|
||||||
'error',
|
|
||||||
'seeking',
|
|
||||||
'seeked',
|
|
||||||
'emptied',
|
|
||||||
'ratechange',
|
|
||||||
'cuechange',
|
|
||||||
|
|
||||||
// Custom events
|
|
||||||
'download',
|
|
||||||
'enterfullscreen',
|
|
||||||
'exitfullscreen',
|
|
||||||
'captionsenabled',
|
|
||||||
'captionsdisabled',
|
|
||||||
'languagechange',
|
|
||||||
'controlshidden',
|
|
||||||
'controlsshown',
|
|
||||||
'ready',
|
|
||||||
|
|
||||||
// YouTube
|
|
||||||
'statechange',
|
|
||||||
|
|
||||||
// Quality
|
|
||||||
'qualitychange',
|
|
||||||
|
|
||||||
// Ads
|
|
||||||
'adsloaded',
|
|
||||||
'adscontentpause',
|
|
||||||
'adscontentresume',
|
|
||||||
'adstarted',
|
|
||||||
'adsmidpoint',
|
|
||||||
'adscomplete',
|
|
||||||
'adsallcomplete',
|
|
||||||
'adsimpression',
|
|
||||||
'adsclick',
|
|
||||||
],
|
|
||||||
|
|
||||||
// Selectors
|
|
||||||
// Change these to match your template if using custom HTML
|
|
||||||
selectors: {
|
|
||||||
editable: 'input, textarea, select, [contenteditable]',
|
|
||||||
container: '.plyr',
|
|
||||||
controls: {
|
|
||||||
container: null,
|
|
||||||
wrapper: '.plyr__controls',
|
|
||||||
},
|
|
||||||
labels: '[data-plyr]',
|
|
||||||
buttons: {
|
|
||||||
play: '[data-plyr="play"]',
|
|
||||||
pause: '[data-plyr="pause"]',
|
|
||||||
restart: '[data-plyr="restart"]',
|
|
||||||
rewind: '[data-plyr="rewind"]',
|
|
||||||
fastForward: '[data-plyr="fast-forward"]',
|
|
||||||
mute: '[data-plyr="mute"]',
|
|
||||||
captions: '[data-plyr="captions"]',
|
|
||||||
download: '[data-plyr="download"]',
|
|
||||||
fullscreen: '[data-plyr="fullscreen"]',
|
|
||||||
pip: '[data-plyr="pip"]',
|
|
||||||
airplay: '[data-plyr="airplay"]',
|
|
||||||
settings: '[data-plyr="settings"]',
|
|
||||||
loop: '[data-plyr="loop"]',
|
|
||||||
},
|
|
||||||
inputs: {
|
|
||||||
seek: '[data-plyr="seek"]',
|
|
||||||
volume: '[data-plyr="volume"]',
|
|
||||||
speed: '[data-plyr="speed"]',
|
|
||||||
language: '[data-plyr="language"]',
|
|
||||||
quality: '[data-plyr="quality"]',
|
|
||||||
},
|
|
||||||
display: {
|
|
||||||
currentTime: '.plyr__time--current',
|
|
||||||
duration: '.plyr__time--duration',
|
|
||||||
buffer: '.plyr__progress__buffer',
|
|
||||||
loop: '.plyr__progress__loop', // Used later
|
|
||||||
volume: '.plyr__volume--display',
|
|
||||||
},
|
|
||||||
progress: '.plyr__progress',
|
|
||||||
captions: '.plyr__captions',
|
|
||||||
caption: '.plyr__caption',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Class hooks added to the player in different states
|
|
||||||
classNames: {
|
|
||||||
type: 'plyr--{0}',
|
|
||||||
provider: 'plyr--{0}',
|
|
||||||
video: 'plyr__video-wrapper',
|
|
||||||
embed: 'plyr__video-embed',
|
|
||||||
videoFixedRatio: 'plyr__video-wrapper--fixed-ratio',
|
|
||||||
embedContainer: 'plyr__video-embed__container',
|
|
||||||
poster: 'plyr__poster',
|
|
||||||
posterEnabled: 'plyr__poster-enabled',
|
|
||||||
ads: 'plyr__ads',
|
|
||||||
control: 'plyr__control',
|
|
||||||
controlPressed: 'plyr__control--pressed',
|
|
||||||
playing: 'plyr--playing',
|
|
||||||
paused: 'plyr--paused',
|
|
||||||
stopped: 'plyr--stopped',
|
|
||||||
loading: 'plyr--loading',
|
|
||||||
hover: 'plyr--hover',
|
|
||||||
tooltip: 'plyr__tooltip',
|
|
||||||
cues: 'plyr__cues',
|
|
||||||
hidden: 'plyr__sr-only',
|
|
||||||
hideControls: 'plyr--hide-controls',
|
|
||||||
isIos: 'plyr--is-ios',
|
|
||||||
isTouch: 'plyr--is-touch',
|
|
||||||
uiSupported: 'plyr--full-ui',
|
|
||||||
noTransition: 'plyr--no-transition',
|
|
||||||
display: {
|
|
||||||
time: 'plyr__time',
|
|
||||||
},
|
|
||||||
menu: {
|
|
||||||
value: 'plyr__menu__value',
|
|
||||||
badge: 'plyr__badge',
|
|
||||||
open: 'plyr--menu-open',
|
|
||||||
},
|
|
||||||
captions: {
|
|
||||||
enabled: 'plyr--captions-enabled',
|
|
||||||
active: 'plyr--captions-active',
|
|
||||||
},
|
|
||||||
fullscreen: {
|
|
||||||
enabled: 'plyr--fullscreen-enabled',
|
|
||||||
fallback: 'plyr--fullscreen-fallback',
|
|
||||||
},
|
|
||||||
pip: {
|
|
||||||
supported: 'plyr--pip-supported',
|
|
||||||
active: 'plyr--pip-active',
|
|
||||||
},
|
|
||||||
airplay: {
|
|
||||||
supported: 'plyr--airplay-supported',
|
|
||||||
active: 'plyr--airplay-active',
|
|
||||||
},
|
|
||||||
tabFocus: 'plyr__tab-focus',
|
|
||||||
previewThumbnails: {
|
|
||||||
// Tooltip thumbs
|
|
||||||
thumbContainer: 'plyr__preview-thumb',
|
|
||||||
thumbContainerShown: 'plyr__preview-thumb--is-shown',
|
|
||||||
imageContainer: 'plyr__preview-thumb__image-container',
|
|
||||||
timeContainer: 'plyr__preview-thumb__time-container',
|
|
||||||
// Scrubbing
|
|
||||||
scrubbingContainer: 'plyr__preview-scrubbing',
|
|
||||||
scrubbingContainerShown: 'plyr__preview-scrubbing--is-shown',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Embed attributes
|
|
||||||
attributes: {
|
|
||||||
embed: {
|
|
||||||
provider: 'data-plyr-provider',
|
|
||||||
id: 'data-plyr-embed-id',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Advertisements plugin
|
|
||||||
// Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
|
|
||||||
ads: {
|
|
||||||
enabled: false,
|
|
||||||
publisherId: '',
|
|
||||||
tagUrl: '',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Preview Thumbnails plugin
|
|
||||||
previewThumbnails: {
|
|
||||||
enabled: false,
|
|
||||||
src: '',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Vimeo plugin
|
|
||||||
vimeo: {
|
vimeo: {
|
||||||
byline: false,
|
sdk: 'https://player.vimeo.com/api/player.js',
|
||||||
portrait: false,
|
iframe: 'https://player.vimeo.com/video/{0}?{1}',
|
||||||
title: false,
|
api: 'https://vimeo.com/api/v2/video/{0}.json',
|
||||||
speed: true,
|
|
||||||
transparent: false,
|
|
||||||
// These settings require a pro or premium account to work
|
|
||||||
sidedock: false,
|
|
||||||
controls: false,
|
|
||||||
// Custom settings from Plyr
|
|
||||||
referrerPolicy: null, // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// YouTube plugin
|
|
||||||
youtube: {
|
youtube: {
|
||||||
noCookie: false, // Whether to use an alternative version of YouTube without cookies
|
sdk: 'https://www.youtube.com/iframe_api',
|
||||||
rel: 0, // No related vids
|
api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}',
|
||||||
showinfo: 0, // Hide info
|
|
||||||
iv_load_policy: 3, // Hide annotations
|
|
||||||
modestbranding: 1, // Hide logos as much as possible (they still show one in the corner when paused)
|
|
||||||
},
|
},
|
||||||
|
googleIMA: {
|
||||||
|
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Custom control listeners
|
||||||
|
listeners: {
|
||||||
|
seek: null,
|
||||||
|
play: null,
|
||||||
|
pause: null,
|
||||||
|
restart: null,
|
||||||
|
rewind: null,
|
||||||
|
fastForward: null,
|
||||||
|
mute: null,
|
||||||
|
volume: null,
|
||||||
|
captions: null,
|
||||||
|
download: null,
|
||||||
|
fullscreen: null,
|
||||||
|
pip: null,
|
||||||
|
airplay: null,
|
||||||
|
speed: null,
|
||||||
|
quality: null,
|
||||||
|
loop: null,
|
||||||
|
language: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Events to watch and bubble
|
||||||
|
events: [
|
||||||
|
// Events to watch on HTML5 media elements and bubble
|
||||||
|
// https://developer.mozilla.org/en/docs/Web/Guide/Events/Media_events
|
||||||
|
'ended',
|
||||||
|
'progress',
|
||||||
|
'stalled',
|
||||||
|
'playing',
|
||||||
|
'waiting',
|
||||||
|
'canplay',
|
||||||
|
'canplaythrough',
|
||||||
|
'loadstart',
|
||||||
|
'loadeddata',
|
||||||
|
'loadedmetadata',
|
||||||
|
'timeupdate',
|
||||||
|
'volumechange',
|
||||||
|
'play',
|
||||||
|
'pause',
|
||||||
|
'error',
|
||||||
|
'seeking',
|
||||||
|
'seeked',
|
||||||
|
'emptied',
|
||||||
|
'ratechange',
|
||||||
|
'cuechange',
|
||||||
|
|
||||||
|
// Custom events
|
||||||
|
'download',
|
||||||
|
'enterfullscreen',
|
||||||
|
'exitfullscreen',
|
||||||
|
'captionsenabled',
|
||||||
|
'captionsdisabled',
|
||||||
|
'languagechange',
|
||||||
|
'controlshidden',
|
||||||
|
'controlsshown',
|
||||||
|
'ready',
|
||||||
|
|
||||||
|
// YouTube
|
||||||
|
'statechange',
|
||||||
|
|
||||||
|
// Quality
|
||||||
|
'qualitychange',
|
||||||
|
|
||||||
|
// Ads
|
||||||
|
'adsloaded',
|
||||||
|
'adscontentpause',
|
||||||
|
'adscontentresume',
|
||||||
|
'adstarted',
|
||||||
|
'adsmidpoint',
|
||||||
|
'adscomplete',
|
||||||
|
'adsallcomplete',
|
||||||
|
'adsimpression',
|
||||||
|
'adsclick',
|
||||||
|
],
|
||||||
|
|
||||||
|
// Selectors
|
||||||
|
// Change these to match your template if using custom HTML
|
||||||
|
selectors: {
|
||||||
|
editable: 'input, textarea, select, [contenteditable]',
|
||||||
|
container: '.plyr',
|
||||||
|
controls: {
|
||||||
|
container: null,
|
||||||
|
wrapper: '.plyr__controls',
|
||||||
|
},
|
||||||
|
labels: '[data-plyr]',
|
||||||
|
buttons: {
|
||||||
|
play: '[data-plyr="play"]',
|
||||||
|
pause: '[data-plyr="pause"]',
|
||||||
|
restart: '[data-plyr="restart"]',
|
||||||
|
rewind: '[data-plyr="rewind"]',
|
||||||
|
fastForward: '[data-plyr="fast-forward"]',
|
||||||
|
mute: '[data-plyr="mute"]',
|
||||||
|
captions: '[data-plyr="captions"]',
|
||||||
|
download: '[data-plyr="download"]',
|
||||||
|
fullscreen: '[data-plyr="fullscreen"]',
|
||||||
|
pip: '[data-plyr="pip"]',
|
||||||
|
airplay: '[data-plyr="airplay"]',
|
||||||
|
settings: '[data-plyr="settings"]',
|
||||||
|
loop: '[data-plyr="loop"]',
|
||||||
|
},
|
||||||
|
inputs: {
|
||||||
|
seek: '[data-plyr="seek"]',
|
||||||
|
volume: '[data-plyr="volume"]',
|
||||||
|
speed: '[data-plyr="speed"]',
|
||||||
|
language: '[data-plyr="language"]',
|
||||||
|
quality: '[data-plyr="quality"]',
|
||||||
|
},
|
||||||
|
display: {
|
||||||
|
currentTime: '.plyr__time--current',
|
||||||
|
duration: '.plyr__time--duration',
|
||||||
|
buffer: '.plyr__progress__buffer',
|
||||||
|
loop: '.plyr__progress__loop', // Used later
|
||||||
|
volume: '.plyr__volume--display',
|
||||||
|
},
|
||||||
|
progress: '.plyr__progress',
|
||||||
|
captions: '.plyr__captions',
|
||||||
|
caption: '.plyr__caption',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Class hooks added to the player in different states
|
||||||
|
classNames: {
|
||||||
|
type: 'plyr--{0}',
|
||||||
|
provider: 'plyr--{0}',
|
||||||
|
video: 'plyr__video-wrapper',
|
||||||
|
embed: 'plyr__video-embed',
|
||||||
|
videoFixedRatio: 'plyr__video-wrapper--fixed-ratio',
|
||||||
|
embedContainer: 'plyr__video-embed__container',
|
||||||
|
poster: 'plyr__poster',
|
||||||
|
posterEnabled: 'plyr__poster-enabled',
|
||||||
|
ads: 'plyr__ads',
|
||||||
|
control: 'plyr__control',
|
||||||
|
controlPressed: 'plyr__control--pressed',
|
||||||
|
playing: 'plyr--playing',
|
||||||
|
paused: 'plyr--paused',
|
||||||
|
stopped: 'plyr--stopped',
|
||||||
|
loading: 'plyr--loading',
|
||||||
|
hover: 'plyr--hover',
|
||||||
|
tooltip: 'plyr__tooltip',
|
||||||
|
cues: 'plyr__cues',
|
||||||
|
hidden: 'plyr__sr-only',
|
||||||
|
hideControls: 'plyr--hide-controls',
|
||||||
|
isIos: 'plyr--is-ios',
|
||||||
|
isTouch: 'plyr--is-touch',
|
||||||
|
uiSupported: 'plyr--full-ui',
|
||||||
|
noTransition: 'plyr--no-transition',
|
||||||
|
display: {
|
||||||
|
time: 'plyr__time',
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
value: 'plyr__menu__value',
|
||||||
|
badge: 'plyr__badge',
|
||||||
|
open: 'plyr--menu-open',
|
||||||
|
},
|
||||||
|
captions: {
|
||||||
|
enabled: 'plyr--captions-enabled',
|
||||||
|
active: 'plyr--captions-active',
|
||||||
|
},
|
||||||
|
fullscreen: {
|
||||||
|
enabled: 'plyr--fullscreen-enabled',
|
||||||
|
fallback: 'plyr--fullscreen-fallback',
|
||||||
|
},
|
||||||
|
pip: {
|
||||||
|
supported: 'plyr--pip-supported',
|
||||||
|
active: 'plyr--pip-active',
|
||||||
|
},
|
||||||
|
airplay: {
|
||||||
|
supported: 'plyr--airplay-supported',
|
||||||
|
active: 'plyr--airplay-active',
|
||||||
|
},
|
||||||
|
tabFocus: 'plyr__tab-focus',
|
||||||
|
previewThumbnails: {
|
||||||
|
// Tooltip thumbs
|
||||||
|
thumbContainer: 'plyr__preview-thumb',
|
||||||
|
thumbContainerShown: 'plyr__preview-thumb--is-shown',
|
||||||
|
imageContainer: 'plyr__preview-thumb__image-container',
|
||||||
|
timeContainer: 'plyr__preview-thumb__time-container',
|
||||||
|
// Scrubbing
|
||||||
|
scrubbingContainer: 'plyr__preview-scrubbing',
|
||||||
|
scrubbingContainerShown: 'plyr__preview-scrubbing--is-shown',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Embed attributes
|
||||||
|
attributes: {
|
||||||
|
embed: {
|
||||||
|
provider: 'data-plyr-provider',
|
||||||
|
id: 'data-plyr-embed-id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Advertisements plugin
|
||||||
|
// Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
|
||||||
|
ads: {
|
||||||
|
enabled: false,
|
||||||
|
publisherId: '',
|
||||||
|
tagUrl: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Preview Thumbnails plugin
|
||||||
|
previewThumbnails: {
|
||||||
|
enabled: false,
|
||||||
|
src: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Vimeo plugin
|
||||||
|
vimeo: {
|
||||||
|
byline: false,
|
||||||
|
portrait: false,
|
||||||
|
title: false,
|
||||||
|
speed: true,
|
||||||
|
transparent: false,
|
||||||
|
// Whether the owner of the video has a Pro or Business account
|
||||||
|
// (which allows us to properly hide controls without CSS hacks, etc)
|
||||||
|
premium: false,
|
||||||
|
// Custom settings from Plyr
|
||||||
|
referrerPolicy: null, // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy
|
||||||
|
},
|
||||||
|
|
||||||
|
// YouTube plugin
|
||||||
|
youtube: {
|
||||||
|
noCookie: true, // Whether to use an alternative version of YouTube without cookies
|
||||||
|
rel: 0, // No related vids
|
||||||
|
showinfo: 0, // Hide info
|
||||||
|
iv_load_policy: 3, // Hide annotations
|
||||||
|
modestbranding: 1, // Hide logos as much as possible (they still show one in the corner when paused)
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defaults;
|
export default defaults;
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
export const pip = {
|
export const pip = {
|
||||||
active: 'picture-in-picture',
|
active: 'picture-in-picture',
|
||||||
inactive: 'inline',
|
inactive: 'inline',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default { pip };
|
export default { pip };
|
||||||
|
|||||||
+14
-14
@@ -3,14 +3,14 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
export const providers = {
|
export const providers = {
|
||||||
html5: 'html5',
|
html5: 'html5',
|
||||||
youtube: 'youtube',
|
youtube: 'youtube',
|
||||||
vimeo: 'vimeo',
|
vimeo: 'vimeo',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const types = {
|
export const types = {
|
||||||
audio: 'audio',
|
audio: 'audio',
|
||||||
video: 'video',
|
video: 'video',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,17 +18,17 @@ export const types = {
|
|||||||
* @param {String} url
|
* @param {String} url
|
||||||
*/
|
*/
|
||||||
export function getProviderByUrl(url) {
|
export function getProviderByUrl(url) {
|
||||||
// YouTube
|
// YouTube
|
||||||
if (/^(https?:\/\/)?(www\.)?(youtube\.com|youtube-nocookie\.com|youtu\.?be)\/.+$/.test(url)) {
|
if (/^(https?:\/\/)?(www\.)?(youtube\.com|youtube-nocookie\.com|youtu\.?be)\/.+$/.test(url)) {
|
||||||
return providers.youtube;
|
return providers.youtube;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vimeo
|
// Vimeo
|
||||||
if (/^https?:\/\/player.vimeo.com\/video\/\d{0,9}(?=\b|\/)/.test(url)) {
|
if (/^https?:\/\/player.vimeo.com\/video\/\d{0,9}(?=\b|\/)/.test(url)) {
|
||||||
return providers.vimeo;
|
return providers.vimeo;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { providers, types };
|
export default { providers, types };
|
||||||
|
|||||||
+17
-17
@@ -5,26 +5,26 @@
|
|||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
|
|
||||||
export default class Console {
|
export default class Console {
|
||||||
constructor(enabled = false) {
|
constructor(enabled = false) {
|
||||||
this.enabled = window.console && enabled;
|
this.enabled = window.console && enabled;
|
||||||
|
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
this.log('Debugging enabled');
|
this.log('Debugging enabled');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get log() {
|
get log() {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
return this.enabled ? Function.prototype.bind.call(console.log, console) : noop;
|
return this.enabled ? Function.prototype.bind.call(console.log, console) : noop;
|
||||||
}
|
}
|
||||||
|
|
||||||
get warn() {
|
get warn() {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
return this.enabled ? Function.prototype.bind.call(console.warn, console) : noop;
|
return this.enabled ? Function.prototype.bind.call(console.warn, console) : noop;
|
||||||
}
|
}
|
||||||
|
|
||||||
get error() {
|
get error() {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
return this.enabled ? Function.prototype.bind.call(console.error, console) : noop;
|
return this.enabled ? Function.prototype.bind.call(console.error, console) : noop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1580
-1584
File diff suppressed because it is too large
Load Diff
+263
-258
@@ -5,288 +5,293 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
import browser from './utils/browser';
|
import browser from './utils/browser';
|
||||||
import { getElements, hasClass, toggleClass } from './utils/elements';
|
import { closest,getElements, hasClass, toggleClass } from './utils/elements';
|
||||||
import { on, triggerEvent } from './utils/events';
|
import { on, triggerEvent } from './utils/events';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
|
import { silencePromise } from './utils/promise';
|
||||||
|
|
||||||
class Fullscreen {
|
class Fullscreen {
|
||||||
constructor(player) {
|
constructor(player) {
|
||||||
// Keep reference to parent
|
// Keep reference to parent
|
||||||
this.player = player;
|
this.player = player;
|
||||||
|
|
||||||
// Get prefix
|
// Get prefix
|
||||||
this.prefix = Fullscreen.prefix;
|
this.prefix = Fullscreen.prefix;
|
||||||
this.property = Fullscreen.property;
|
this.property = Fullscreen.property;
|
||||||
|
|
||||||
// Scroll position
|
// Scroll position
|
||||||
this.scrollPosition = { x: 0, y: 0 };
|
this.scrollPosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
// Force the use of 'full window/browser' rather than fullscreen
|
// Force the use of 'full window/browser' rather than fullscreen
|
||||||
this.forceFallback = player.config.fullscreen.fallback === 'force';
|
this.forceFallback = player.config.fullscreen.fallback === 'force';
|
||||||
|
|
||||||
// Register event listeners
|
// Get the fullscreen element
|
||||||
// Handle event (incase user presses escape etc)
|
// Checks container is an ancestor, defaults to null
|
||||||
on.call(
|
this.player.elements.fullscreen =
|
||||||
this.player,
|
player.config.fullscreen.container && closest(this.player.elements.container, player.config.fullscreen.container);
|
||||||
document,
|
|
||||||
this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`,
|
|
||||||
() => {
|
|
||||||
// TODO: Filter for target??
|
|
||||||
this.onChange();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fullscreen toggle on double click
|
// Register event listeners
|
||||||
on.call(this.player, this.player.elements.container, 'dblclick', event => {
|
// Handle event (incase user presses escape etc)
|
||||||
// Ignore double click in controls
|
on.call(
|
||||||
if (is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) {
|
this.player,
|
||||||
return;
|
document,
|
||||||
}
|
this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`,
|
||||||
|
() => {
|
||||||
this.toggle();
|
// TODO: Filter for target??
|
||||||
});
|
|
||||||
|
|
||||||
// Tap focus when in fullscreen
|
|
||||||
on.call(this, this.player.elements.container, 'keydown', event => this.trapFocus(event));
|
|
||||||
|
|
||||||
// Update the UI
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if native supported
|
|
||||||
static get native() {
|
|
||||||
return !!(
|
|
||||||
document.fullscreenEnabled ||
|
|
||||||
document.webkitFullscreenEnabled ||
|
|
||||||
document.mozFullScreenEnabled ||
|
|
||||||
document.msFullscreenEnabled
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're actually using native
|
|
||||||
get usingNative() {
|
|
||||||
return Fullscreen.native && !this.forceFallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the prefix for handlers
|
|
||||||
static get prefix() {
|
|
||||||
// No prefix
|
|
||||||
if (is.function(document.exitFullscreen)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for fullscreen support by vendor prefix
|
|
||||||
let value = '';
|
|
||||||
const prefixes = ['webkit', 'moz', 'ms'];
|
|
||||||
|
|
||||||
prefixes.some(pre => {
|
|
||||||
if (is.function(document[`${pre}ExitFullscreen`]) || is.function(document[`${pre}CancelFullScreen`])) {
|
|
||||||
value = pre;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get property() {
|
|
||||||
return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if fullscreen is enabled
|
|
||||||
get enabled() {
|
|
||||||
return (
|
|
||||||
(Fullscreen.native || this.player.config.fullscreen.fallback) &&
|
|
||||||
this.player.config.fullscreen.enabled &&
|
|
||||||
this.player.supported.ui &&
|
|
||||||
this.player.isVideo
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get active state
|
|
||||||
get active() {
|
|
||||||
if (!this.enabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback using classname
|
|
||||||
if (!Fullscreen.native || this.forceFallback) {
|
|
||||||
return hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.property}Element`];
|
|
||||||
|
|
||||||
return element === this.target;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get target element
|
|
||||||
get target() {
|
|
||||||
return browser.isIos && this.player.config.fullscreen.iosNative
|
|
||||||
? this.player.media
|
|
||||||
: this.player.elements.container;
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange() {
|
|
||||||
if (!this.enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update toggle button
|
|
||||||
const button = this.player.elements.buttons.fullscreen;
|
|
||||||
if (is.element(button)) {
|
|
||||||
button.pressed = this.active;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger an event
|
|
||||||
triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleFallback(toggle = false) {
|
|
||||||
// Store or restore scroll position
|
|
||||||
if (toggle) {
|
|
||||||
this.scrollPosition = {
|
|
||||||
x: window.scrollX || 0,
|
|
||||||
y: window.scrollY || 0,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle scroll
|
|
||||||
document.body.style.overflow = toggle ? 'hidden' : '';
|
|
||||||
|
|
||||||
// Toggle class hook
|
|
||||||
toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
|
||||||
|
|
||||||
// Force full viewport on iPhone X+
|
|
||||||
if (browser.isIos) {
|
|
||||||
let viewport = document.head.querySelector('meta[name="viewport"]');
|
|
||||||
const property = 'viewport-fit=cover';
|
|
||||||
|
|
||||||
// Inject the viewport meta if required
|
|
||||||
if (!viewport) {
|
|
||||||
viewport = document.createElement('meta');
|
|
||||||
viewport.setAttribute('name', 'viewport');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the property already exists
|
|
||||||
const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
|
|
||||||
|
|
||||||
if (toggle) {
|
|
||||||
this.cleanupViewport = !hasProperty;
|
|
||||||
|
|
||||||
if (!hasProperty) {
|
|
||||||
viewport.content += `,${property}`;
|
|
||||||
}
|
|
||||||
} else if (this.cleanupViewport) {
|
|
||||||
viewport.content = viewport.content
|
|
||||||
.split(',')
|
|
||||||
.filter(part => part.trim() !== property)
|
|
||||||
.join(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle button and fire events
|
|
||||||
this.onChange();
|
this.onChange();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fullscreen toggle on double click
|
||||||
|
on.call(this.player, this.player.elements.container, 'dblclick', event => {
|
||||||
|
// Ignore double click in controls
|
||||||
|
if (is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggle();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tap focus when in fullscreen
|
||||||
|
on.call(this, this.player.elements.container, 'keydown', event => this.trapFocus(event));
|
||||||
|
|
||||||
|
// Update the UI
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if native supported
|
||||||
|
static get native() {
|
||||||
|
return !!(
|
||||||
|
document.fullscreenEnabled ||
|
||||||
|
document.webkitFullscreenEnabled ||
|
||||||
|
document.mozFullScreenEnabled ||
|
||||||
|
document.msFullscreenEnabled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're actually using native
|
||||||
|
get usingNative() {
|
||||||
|
return Fullscreen.native && !this.forceFallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the prefix for handlers
|
||||||
|
static get prefix() {
|
||||||
|
// No prefix
|
||||||
|
if (is.function(document.exitFullscreen)) {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trap focus inside container
|
// Check for fullscreen support by vendor prefix
|
||||||
trapFocus(event) {
|
let value = '';
|
||||||
// Bail if iOS, not active, not the tab key
|
const prefixes = ['webkit', 'moz', 'ms'];
|
||||||
if (browser.isIos || !this.active || event.key !== 'Tab' || event.keyCode !== 9) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current focused element
|
prefixes.some(pre => {
|
||||||
const focused = document.activeElement;
|
if (is.function(document[`${pre}ExitFullscreen`]) || is.function(document[`${pre}CancelFullScreen`])) {
|
||||||
const focusable = getElements.call(
|
value = pre;
|
||||||
this.player,
|
return true;
|
||||||
'a[href], button:not(:disabled), input:not(:disabled), [tabindex]',
|
}
|
||||||
);
|
|
||||||
const [first] = focusable;
|
|
||||||
const last = focusable[focusable.length - 1];
|
|
||||||
|
|
||||||
if (focused === last && !event.shiftKey) {
|
return false;
|
||||||
// Move focus to first element that can be tabbed if Shift isn't used
|
});
|
||||||
first.focus();
|
|
||||||
event.preventDefault();
|
return value;
|
||||||
} else if (focused === first && event.shiftKey) {
|
}
|
||||||
// Move focus to last element that can be tabbed if Shift is used
|
|
||||||
last.focus();
|
static get property() {
|
||||||
event.preventDefault();
|
return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if fullscreen is enabled
|
||||||
|
get enabled() {
|
||||||
|
return (
|
||||||
|
(Fullscreen.native || this.player.config.fullscreen.fallback) &&
|
||||||
|
this.player.config.fullscreen.enabled &&
|
||||||
|
this.player.supported.ui &&
|
||||||
|
this.player.isVideo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get active state
|
||||||
|
get active() {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update UI
|
// Fallback using classname
|
||||||
update() {
|
if (!Fullscreen.native || this.forceFallback) {
|
||||||
if (this.enabled) {
|
return hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
|
||||||
let mode;
|
|
||||||
|
|
||||||
if (this.forceFallback) {
|
|
||||||
mode = 'Fallback (forced)';
|
|
||||||
} else if (Fullscreen.native) {
|
|
||||||
mode = 'Native';
|
|
||||||
} else {
|
|
||||||
mode = 'Fallback';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.player.debug.log(`${mode} fullscreen enabled`);
|
|
||||||
} else {
|
|
||||||
this.player.debug.log('Fullscreen not supported and fallback disabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add styling hook to show button
|
|
||||||
toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make an element fullscreen
|
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}${this.property}Element`];
|
||||||
enter() {
|
|
||||||
if (!this.enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// iOS native fullscreen doesn't need the request step
|
return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target;
|
||||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
}
|
||||||
this.target.webkitEnterFullscreen();
|
|
||||||
} else if (!Fullscreen.native || this.forceFallback) {
|
// Get target element
|
||||||
this.toggleFallback(true);
|
get target() {
|
||||||
} else if (!this.prefix) {
|
return browser.isIos && this.player.config.fullscreen.iosNative
|
||||||
this.target.requestFullscreen({ navigationUI: 'hide' });
|
? this.player.media
|
||||||
} else if (!is.empty(this.prefix)) {
|
: this.player.elements.fullscreen || this.player.elements.container;
|
||||||
this.target[`${this.prefix}Request${this.property}`]();
|
}
|
||||||
}
|
|
||||||
|
onChange() {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bail from fullscreen
|
// Update toggle button
|
||||||
exit() {
|
const button = this.player.elements.buttons.fullscreen;
|
||||||
if (!this.enabled) {
|
if (is.element(button)) {
|
||||||
return;
|
button.pressed = this.active;
|
||||||
}
|
|
||||||
|
|
||||||
// iOS native fullscreen
|
|
||||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
|
||||||
this.target.webkitExitFullscreen();
|
|
||||||
this.player.play();
|
|
||||||
} else if (!Fullscreen.native || this.forceFallback) {
|
|
||||||
this.toggleFallback(false);
|
|
||||||
} else if (!this.prefix) {
|
|
||||||
(document.cancelFullScreen || document.exitFullscreen).call(document);
|
|
||||||
} else if (!is.empty(this.prefix)) {
|
|
||||||
const action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
|
|
||||||
document[`${this.prefix}${action}${this.property}`]();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle state
|
// Always trigger events on the plyr / media element (not a fullscreen container) and let them bubble up
|
||||||
toggle() {
|
const target = this.target === this.player.media ? this.target : this.player.elements.container;
|
||||||
if (!this.active) {
|
// Trigger an event
|
||||||
this.enter();
|
triggerEvent.call(this.player, target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
|
||||||
} else {
|
}
|
||||||
this.exit();
|
|
||||||
}
|
toggleFallback(toggle = false) {
|
||||||
|
// Store or restore scroll position
|
||||||
|
if (toggle) {
|
||||||
|
this.scrollPosition = {
|
||||||
|
x: window.scrollX || 0,
|
||||||
|
y: window.scrollY || 0,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle scroll
|
||||||
|
document.body.style.overflow = toggle ? 'hidden' : '';
|
||||||
|
|
||||||
|
// Toggle class hook
|
||||||
|
toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
||||||
|
|
||||||
|
// Force full viewport on iPhone X+
|
||||||
|
if (browser.isIos) {
|
||||||
|
let viewport = document.head.querySelector('meta[name="viewport"]');
|
||||||
|
const property = 'viewport-fit=cover';
|
||||||
|
|
||||||
|
// Inject the viewport meta if required
|
||||||
|
if (!viewport) {
|
||||||
|
viewport = document.createElement('meta');
|
||||||
|
viewport.setAttribute('name', 'viewport');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the property already exists
|
||||||
|
const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
|
||||||
|
|
||||||
|
if (toggle) {
|
||||||
|
this.cleanupViewport = !hasProperty;
|
||||||
|
|
||||||
|
if (!hasProperty) {
|
||||||
|
viewport.content += `,${property}`;
|
||||||
|
}
|
||||||
|
} else if (this.cleanupViewport) {
|
||||||
|
viewport.content = viewport.content
|
||||||
|
.split(',')
|
||||||
|
.filter(part => part.trim() !== property)
|
||||||
|
.join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle button and fire events
|
||||||
|
this.onChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trap focus inside container
|
||||||
|
trapFocus(event) {
|
||||||
|
// Bail if iOS, not active, not the tab key
|
||||||
|
if (browser.isIos || !this.active || event.key !== 'Tab' || event.keyCode !== 9) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current focused element
|
||||||
|
const focused = document.activeElement;
|
||||||
|
const focusable = getElements.call(this.player, 'a[href], button:not(:disabled), input:not(:disabled), [tabindex]');
|
||||||
|
const [first] = focusable;
|
||||||
|
const last = focusable[focusable.length - 1];
|
||||||
|
|
||||||
|
if (focused === last && !event.shiftKey) {
|
||||||
|
// Move focus to first element that can be tabbed if Shift isn't used
|
||||||
|
first.focus();
|
||||||
|
event.preventDefault();
|
||||||
|
} else if (focused === first && event.shiftKey) {
|
||||||
|
// Move focus to last element that can be tabbed if Shift is used
|
||||||
|
last.focus();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
update() {
|
||||||
|
if (this.enabled) {
|
||||||
|
let mode;
|
||||||
|
|
||||||
|
if (this.forceFallback) {
|
||||||
|
mode = 'Fallback (forced)';
|
||||||
|
} else if (Fullscreen.native) {
|
||||||
|
mode = 'Native';
|
||||||
|
} else {
|
||||||
|
mode = 'Fallback';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.debug.log(`${mode} fullscreen enabled`);
|
||||||
|
} else {
|
||||||
|
this.player.debug.log('Fullscreen not supported and fallback disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add styling hook to show button
|
||||||
|
toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make an element fullscreen
|
||||||
|
enter() {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS native fullscreen doesn't need the request step
|
||||||
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
|
this.target.webkitEnterFullscreen();
|
||||||
|
} else if (!Fullscreen.native || this.forceFallback) {
|
||||||
|
this.toggleFallback(true);
|
||||||
|
} else if (!this.prefix) {
|
||||||
|
this.target.requestFullscreen({ navigationUI: 'hide' });
|
||||||
|
} else if (!is.empty(this.prefix)) {
|
||||||
|
this.target[`${this.prefix}Request${this.property}`]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bail from fullscreen
|
||||||
|
exit() {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS native fullscreen
|
||||||
|
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||||
|
this.target.webkitExitFullscreen();
|
||||||
|
silencePromise(this.player.play());
|
||||||
|
} else if (!Fullscreen.native || this.forceFallback) {
|
||||||
|
this.toggleFallback(false);
|
||||||
|
} else if (!this.prefix) {
|
||||||
|
(document.cancelFullScreen || document.exitFullscreen).call(document);
|
||||||
|
} else if (!is.empty(this.prefix)) {
|
||||||
|
const action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
|
||||||
|
document[`${this.prefix}${action}${this.property}`]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle state
|
||||||
|
toggle() {
|
||||||
|
if (!this.active) {
|
||||||
|
this.enter();
|
||||||
|
} else {
|
||||||
|
this.exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Fullscreen;
|
export default Fullscreen;
|
||||||
|
|||||||
+123
-122
@@ -6,141 +6,142 @@ import support from './support';
|
|||||||
import { removeElement } from './utils/elements';
|
import { removeElement } from './utils/elements';
|
||||||
import { triggerEvent } from './utils/events';
|
import { triggerEvent } from './utils/events';
|
||||||
import is from './utils/is';
|
import is from './utils/is';
|
||||||
|
import { silencePromise } from './utils/promise';
|
||||||
import { setAspectRatio } from './utils/style';
|
import { setAspectRatio } from './utils/style';
|
||||||
|
|
||||||
const html5 = {
|
const html5 = {
|
||||||
getSources() {
|
getSources() {
|
||||||
if (!this.isHTML5) {
|
if (!this.isHTML5) {
|
||||||
return [];
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sources = Array.from(this.media.querySelectorAll('source'));
|
||||||
|
|
||||||
|
// Filter out unsupported sources (if type is specified)
|
||||||
|
return sources.filter(source => {
|
||||||
|
const type = source.getAttribute('type');
|
||||||
|
|
||||||
|
if (is.empty(type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return support.mime.call(this, type);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get quality levels
|
||||||
|
getQualityOptions() {
|
||||||
|
// Whether we're forcing all options (e.g. for streaming)
|
||||||
|
if (this.config.quality.forced) {
|
||||||
|
return this.config.quality.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get sizes from <source> elements
|
||||||
|
return html5.getSources
|
||||||
|
.call(this)
|
||||||
|
.map(source => Number(source.getAttribute('size')))
|
||||||
|
.filter(Boolean);
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
if (!this.isHTML5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = this;
|
||||||
|
|
||||||
|
// Set speed options from config
|
||||||
|
player.options.speed = player.config.speed.options;
|
||||||
|
|
||||||
|
// Set aspect ratio if fixed
|
||||||
|
if (!is.empty(this.config.ratio)) {
|
||||||
|
setAspectRatio.call(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quality
|
||||||
|
Object.defineProperty(player.media, 'quality', {
|
||||||
|
get() {
|
||||||
|
// Get sources
|
||||||
|
const sources = html5.getSources.call(player);
|
||||||
|
const source = sources.find(s => s.getAttribute('src') === player.source);
|
||||||
|
|
||||||
|
// Return size, if match is found
|
||||||
|
return source && Number(source.getAttribute('size'));
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
if (player.quality === input) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sources = Array.from(this.media.querySelectorAll('source'));
|
// If we're using an an external handler...
|
||||||
|
if (player.config.quality.forced && is.function(player.config.quality.onChange)) {
|
||||||
|
player.config.quality.onChange(input);
|
||||||
|
} else {
|
||||||
|
// Get sources
|
||||||
|
const sources = html5.getSources.call(player);
|
||||||
|
// Get first match for requested size
|
||||||
|
const source = sources.find(s => Number(s.getAttribute('size')) === input);
|
||||||
|
|
||||||
// Filter out unsupported sources (if type is specified)
|
// No matching source found
|
||||||
return sources.filter(source => {
|
if (!source) {
|
||||||
const type = source.getAttribute('type');
|
|
||||||
|
|
||||||
if (is.empty(type)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return support.mime.call(this, type);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get quality levels
|
|
||||||
getQualityOptions() {
|
|
||||||
// Whether we're forcing all options (e.g. for streaming)
|
|
||||||
if (this.config.quality.forced) {
|
|
||||||
return this.config.quality.options;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get sizes from <source> elements
|
|
||||||
return html5.getSources
|
|
||||||
.call(this)
|
|
||||||
.map(source => Number(source.getAttribute('size')))
|
|
||||||
.filter(Boolean);
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
if (!this.isHTML5) {
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current state
|
||||||
|
const { currentTime, paused, preload, readyState, playbackRate } = player.media;
|
||||||
|
|
||||||
|
// Set new source
|
||||||
|
player.media.src = source.getAttribute('src');
|
||||||
|
|
||||||
|
// Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
||||||
|
if (preload !== 'none' || readyState) {
|
||||||
|
// Restore time
|
||||||
|
player.once('loadedmetadata', () => {
|
||||||
|
player.speed = playbackRate;
|
||||||
|
player.currentTime = currentTime;
|
||||||
|
|
||||||
|
// Resume playing
|
||||||
|
if (!paused) {
|
||||||
|
silencePromise(player.play());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load new source
|
||||||
|
player.media.load();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const player = this;
|
// Trigger change event
|
||||||
|
triggerEvent.call(player, player.media, 'qualitychange', false, {
|
||||||
// Set speed options from config
|
quality: input,
|
||||||
player.options.speed = player.config.speed.options;
|
|
||||||
|
|
||||||
// Set aspect ratio if fixed
|
|
||||||
if (!is.empty(this.config.ratio)) {
|
|
||||||
setAspectRatio.call(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quality
|
|
||||||
Object.defineProperty(player.media, 'quality', {
|
|
||||||
get() {
|
|
||||||
// Get sources
|
|
||||||
const sources = html5.getSources.call(player);
|
|
||||||
const source = sources.find(s => s.getAttribute('src') === player.source);
|
|
||||||
|
|
||||||
// Return size, if match is found
|
|
||||||
return source && Number(source.getAttribute('size'));
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
if (player.quality === input) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're using an an external handler...
|
|
||||||
if (player.config.quality.forced && is.function(player.config.quality.onChange)) {
|
|
||||||
player.config.quality.onChange(input);
|
|
||||||
} else {
|
|
||||||
// Get sources
|
|
||||||
const sources = html5.getSources.call(player);
|
|
||||||
// Get first match for requested size
|
|
||||||
const source = sources.find(s => Number(s.getAttribute('size')) === input);
|
|
||||||
|
|
||||||
// No matching source found
|
|
||||||
if (!source) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current state
|
|
||||||
const { currentTime, paused, preload, readyState, playbackRate } = player.media;
|
|
||||||
|
|
||||||
// Set new source
|
|
||||||
player.media.src = source.getAttribute('src');
|
|
||||||
|
|
||||||
// Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
|
||||||
if (preload !== 'none' || readyState) {
|
|
||||||
// Restore time
|
|
||||||
player.once('loadedmetadata', () => {
|
|
||||||
player.speed = playbackRate;
|
|
||||||
player.currentTime = currentTime;
|
|
||||||
|
|
||||||
// Resume playing
|
|
||||||
if (!paused) {
|
|
||||||
player.play();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load new source
|
|
||||||
player.media.load();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger change event
|
|
||||||
triggerEvent.call(player, player.media, 'qualitychange', false, {
|
|
||||||
quality: input,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Cancel current network requests
|
// Cancel current network requests
|
||||||
|
// See https://github.com/sampotts/plyr/issues/174
|
||||||
|
cancelRequests() {
|
||||||
|
if (!this.isHTML5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove child sources
|
||||||
|
removeElement(html5.getSources.call(this));
|
||||||
|
|
||||||
|
// Set blank video src attribute
|
||||||
|
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
|
||||||
|
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
|
||||||
|
this.media.setAttribute('src', this.config.blankVideo);
|
||||||
|
|
||||||
|
// Load the new empty source
|
||||||
|
// This will cancel existing requests
|
||||||
// See https://github.com/sampotts/plyr/issues/174
|
// See https://github.com/sampotts/plyr/issues/174
|
||||||
cancelRequests() {
|
this.media.load();
|
||||||
if (!this.isHTML5) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove child sources
|
// Debugging
|
||||||
removeElement(html5.getSources.call(this));
|
this.debug.log('Cancelled network requests');
|
||||||
|
},
|
||||||
// Set blank video src attribute
|
|
||||||
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
|
|
||||||
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
|
|
||||||
this.media.setAttribute('src', this.config.blankVideo);
|
|
||||||
|
|
||||||
// Load the new empty source
|
|
||||||
// This will cancel existing requests
|
|
||||||
// See https://github.com/sampotts/plyr/issues/174
|
|
||||||
this.media.load();
|
|
||||||
|
|
||||||
// Debugging
|
|
||||||
this.debug.log('Cancelled network requests');
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default html5;
|
export default html5;
|
||||||
|
|||||||
+793
-760
File diff suppressed because it is too large
Load Diff
+38
-40
@@ -8,54 +8,52 @@ import youtube from './plugins/youtube';
|
|||||||
import { createElement, toggleClass, wrap } from './utils/elements';
|
import { createElement, toggleClass, wrap } from './utils/elements';
|
||||||
|
|
||||||
const media = {
|
const media = {
|
||||||
// Setup media
|
// Setup media
|
||||||
setup() {
|
setup() {
|
||||||
// If there's no media, bail
|
// If there's no media, bail
|
||||||
if (!this.media) {
|
if (!this.media) {
|
||||||
this.debug.warn('No media element found!');
|
this.debug.warn('No media element found!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add type class
|
// Add type class
|
||||||
toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', this.type), true);
|
toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', this.type), true);
|
||||||
|
|
||||||
// Add provider class
|
// Add provider class
|
||||||
toggleClass(this.elements.container, this.config.classNames.provider.replace('{0}', this.provider), true);
|
toggleClass(this.elements.container, this.config.classNames.provider.replace('{0}', this.provider), true);
|
||||||
|
|
||||||
// Add video class for embeds
|
// Add video class for embeds
|
||||||
// This will require changes if audio embeds are added
|
// This will require changes if audio embeds are added
|
||||||
if (this.isEmbed) {
|
if (this.isEmbed) {
|
||||||
toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', 'video'), true);
|
toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', 'video'), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the player wrapper
|
// Inject the player wrapper
|
||||||
if (this.isVideo) {
|
if (this.isVideo) {
|
||||||
// Create the wrapper div
|
// Create the wrapper div
|
||||||
this.elements.wrapper = createElement('div', {
|
this.elements.wrapper = createElement('div', {
|
||||||
class: this.config.classNames.video,
|
class: this.config.classNames.video,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wrap the video in a container
|
// Wrap the video in a container
|
||||||
wrap(this.media, this.elements.wrapper);
|
wrap(this.media, this.elements.wrapper);
|
||||||
|
|
||||||
// Faux poster container
|
// Poster image container
|
||||||
if (this.isEmbed) {
|
this.elements.poster = createElement('div', {
|
||||||
this.elements.poster = createElement('div', {
|
class: this.config.classNames.poster,
|
||||||
class: this.config.classNames.poster,
|
});
|
||||||
});
|
|
||||||
|
|
||||||
this.elements.wrapper.appendChild(this.elements.poster);
|
this.elements.wrapper.appendChild(this.elements.poster);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
html5.setup.call(this);
|
html5.setup.call(this);
|
||||||
} else if (this.isYouTube) {
|
} else if (this.isYouTube) {
|
||||||
youtube.setup.call(this);
|
youtube.setup.call(this);
|
||||||
} else if (this.isVimeo) {
|
} else if (this.isVimeo) {
|
||||||
vimeo.setup.call(this);
|
vimeo.setup.call(this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default media;
|
export default media;
|
||||||
|
|||||||
+571
-563
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+334
-322
@@ -10,393 +10,405 @@ import { triggerEvent } from '../utils/events';
|
|||||||
import fetch from '../utils/fetch';
|
import fetch from '../utils/fetch';
|
||||||
import is from '../utils/is';
|
import is from '../utils/is';
|
||||||
import loadScript from '../utils/load-script';
|
import loadScript from '../utils/load-script';
|
||||||
import { extend } from '../utils/objects';
|
|
||||||
import { format, stripHTML } from '../utils/strings';
|
import { format, stripHTML } from '../utils/strings';
|
||||||
import { setAspectRatio } from '../utils/style';
|
import { setAspectRatio } from '../utils/style';
|
||||||
import { buildUrlParams } from '../utils/urls';
|
import { buildUrlParams } from '../utils/urls';
|
||||||
|
|
||||||
// Parse Vimeo ID from URL
|
// Parse Vimeo ID from URL
|
||||||
function parseId(url) {
|
function parseId(url) {
|
||||||
if (is.empty(url)) {
|
if (is.empty(url)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is.number(Number(url))) {
|
if (is.number(Number(url))) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
const regex = /^.*(vimeo.com\/|video\/)(\d+).*/;
|
const regex = /^.*(vimeo.com\/|video\/)(\d+).*/;
|
||||||
return url.match(regex) ? RegExp.$2 : url;
|
return url.match(regex) ? RegExp.$2 : url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set playback state and trigger change (only on actual change)
|
// Set playback state and trigger change (only on actual change)
|
||||||
function assurePlaybackState(play) {
|
function assurePlaybackState(play) {
|
||||||
if (play && !this.embed.hasPlayed) {
|
if (play && !this.embed.hasPlayed) {
|
||||||
this.embed.hasPlayed = true;
|
this.embed.hasPlayed = true;
|
||||||
}
|
}
|
||||||
if (this.media.paused === play) {
|
if (this.media.paused === play) {
|
||||||
this.media.paused = !play;
|
this.media.paused = !play;
|
||||||
triggerEvent.call(this, this.media, play ? 'play' : 'pause');
|
triggerEvent.call(this, this.media, play ? 'play' : 'pause');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const vimeo = {
|
const vimeo = {
|
||||||
setup() {
|
setup() {
|
||||||
const player = this;
|
const player = this;
|
||||||
|
|
||||||
// Add embed class for responsive
|
// Add embed class for responsive
|
||||||
toggleClass(player.elements.wrapper, player.config.classNames.embed, true);
|
toggleClass(player.elements.wrapper, player.config.classNames.embed, true);
|
||||||
|
|
||||||
// Set speed options from config
|
// Set speed options from config
|
||||||
player.options.speed = player.config.speed.options;
|
player.options.speed = player.config.speed.options;
|
||||||
|
|
||||||
// Set intial ratio
|
// Set intial ratio
|
||||||
setAspectRatio.call(player);
|
setAspectRatio.call(player);
|
||||||
|
|
||||||
// Load the SDK if not already
|
// Load the SDK if not already
|
||||||
if (!is.object(window.Vimeo)) {
|
if (!is.object(window.Vimeo)) {
|
||||||
loadScript(player.config.urls.vimeo.sdk)
|
loadScript(player.config.urls.vimeo.sdk)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
vimeo.ready.call(player);
|
vimeo.ready.call(player);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
player.debug.warn('Vimeo SDK (player.js) failed to load', error);
|
player.debug.warn('Vimeo SDK (player.js) failed to load', error);
|
||||||
});
|
|
||||||
} else {
|
|
||||||
vimeo.ready.call(player);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// API Ready
|
|
||||||
ready() {
|
|
||||||
const player = this;
|
|
||||||
const config = player.config.vimeo;
|
|
||||||
|
|
||||||
// Get Vimeo params for the iframe
|
|
||||||
const params = buildUrlParams(
|
|
||||||
extend(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
loop: player.config.loop.active,
|
|
||||||
autoplay: player.autoplay,
|
|
||||||
muted: player.muted,
|
|
||||||
gesture: 'media',
|
|
||||||
playsinline: !this.config.fullscreen.iosNative,
|
|
||||||
},
|
|
||||||
config,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get the source URL or ID
|
|
||||||
let source = player.media.getAttribute('src');
|
|
||||||
|
|
||||||
// Get from <div> if needed
|
|
||||||
if (is.empty(source)) {
|
|
||||||
source = player.media.getAttribute(player.config.attributes.embed.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = parseId(source);
|
|
||||||
// Build an iframe
|
|
||||||
const iframe = createElement('iframe');
|
|
||||||
const src = format(player.config.urls.vimeo.iframe, id, params);
|
|
||||||
iframe.setAttribute('src', src);
|
|
||||||
iframe.setAttribute('allowfullscreen', '');
|
|
||||||
iframe.setAttribute('allowtransparency', '');
|
|
||||||
iframe.setAttribute('allow', 'autoplay');
|
|
||||||
|
|
||||||
// Set the referrer policy if required
|
|
||||||
if (!is.empty(config.referrerPolicy)) {
|
|
||||||
iframe.setAttribute('referrerPolicy', config.referrerPolicy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get poster, if already set
|
|
||||||
const { poster } = player;
|
|
||||||
// Inject the package
|
|
||||||
const wrapper = createElement('div', { poster, class: player.config.classNames.embedContainer });
|
|
||||||
wrapper.appendChild(iframe);
|
|
||||||
player.media = replaceElement(wrapper, player.media);
|
|
||||||
|
|
||||||
// Get poster image
|
|
||||||
fetch(format(player.config.urls.vimeo.api, id), 'json').then(response => {
|
|
||||||
if (is.empty(response)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the URL for thumbnail
|
|
||||||
const url = new URL(response[0].thumbnail_large);
|
|
||||||
|
|
||||||
// Get original image
|
|
||||||
url.pathname = `${url.pathname.split('_')[0]}.jpg`;
|
|
||||||
|
|
||||||
// Set and show poster
|
|
||||||
ui.setPoster.call(player, url.href).catch(() => {});
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
vimeo.ready.call(player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Setup instance
|
// API Ready
|
||||||
// https://github.com/vimeo/player.js
|
ready() {
|
||||||
player.embed = new window.Vimeo.Player(iframe, {
|
const player = this;
|
||||||
autopause: player.config.autopause,
|
const config = player.config.vimeo;
|
||||||
muted: player.muted,
|
const { premium, referrerPolicy, ...frameParams } = config;
|
||||||
});
|
|
||||||
|
|
||||||
player.media.paused = true;
|
// If the owner has a pro or premium account then we can hide controls etc
|
||||||
player.media.currentTime = 0;
|
if (premium) {
|
||||||
|
Object.assign(frameParams, {
|
||||||
|
controls: false,
|
||||||
|
sidedock: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Disable native text track rendering
|
// Get Vimeo params for the iframe
|
||||||
if (player.supported.ui) {
|
const params = buildUrlParams({
|
||||||
player.embed.disableTextTrack();
|
loop: player.config.loop.active,
|
||||||
}
|
autoplay: player.autoplay,
|
||||||
|
muted: player.muted,
|
||||||
|
gesture: 'media',
|
||||||
|
playsinline: !this.config.fullscreen.iosNative,
|
||||||
|
...frameParams,
|
||||||
|
});
|
||||||
|
|
||||||
// Create a faux HTML5 API using the Vimeo API
|
// Get the source URL or ID
|
||||||
player.media.play = () => {
|
let source = player.media.getAttribute('src');
|
||||||
assurePlaybackState.call(player, true);
|
|
||||||
return player.embed.play();
|
|
||||||
};
|
|
||||||
|
|
||||||
player.media.pause = () => {
|
// Get from <div> if needed
|
||||||
assurePlaybackState.call(player, false);
|
if (is.empty(source)) {
|
||||||
return player.embed.pause();
|
source = player.media.getAttribute(player.config.attributes.embed.id);
|
||||||
};
|
}
|
||||||
|
|
||||||
player.media.stop = () => {
|
const id = parseId(source);
|
||||||
player.pause();
|
// Build an iframe
|
||||||
player.currentTime = 0;
|
const iframe = createElement('iframe');
|
||||||
};
|
const src = format(player.config.urls.vimeo.iframe, id, params);
|
||||||
|
iframe.setAttribute('src', src);
|
||||||
|
iframe.setAttribute('allowfullscreen', '');
|
||||||
|
iframe.setAttribute('allow', 'autoplay,fullscreen,picture-in-picture');
|
||||||
|
|
||||||
// Seeking
|
// Set the referrer policy if required
|
||||||
let { currentTime } = player.media;
|
if (!is.empty(referrerPolicy)) {
|
||||||
Object.defineProperty(player.media, 'currentTime', {
|
iframe.setAttribute('referrerPolicy', referrerPolicy);
|
||||||
get() {
|
}
|
||||||
return currentTime;
|
|
||||||
},
|
|
||||||
set(time) {
|
|
||||||
// Vimeo will automatically play on seek if the video hasn't been played before
|
|
||||||
|
|
||||||
// Get current paused state and volume etc
|
// Inject the package
|
||||||
const { embed, media, paused, volume } = player;
|
const { poster } = player;
|
||||||
const restorePause = paused && !embed.hasPlayed;
|
if (premium) {
|
||||||
|
iframe.setAttribute('data-poster', poster);
|
||||||
|
player.media = replaceElement(iframe, player.media);
|
||||||
|
} else {
|
||||||
|
const wrapper = createElement('div', { class: player.config.classNames.embedContainer, 'data-poster': poster });
|
||||||
|
wrapper.appendChild(iframe);
|
||||||
|
player.media = replaceElement(wrapper, player.media);
|
||||||
|
}
|
||||||
|
|
||||||
// Set seeking state and trigger event
|
// Get poster image
|
||||||
media.seeking = true;
|
fetch(format(player.config.urls.vimeo.api, id), 'json').then(response => {
|
||||||
triggerEvent.call(player, media, 'seeking');
|
if (is.empty(response)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If paused, mute until seek is complete
|
// Get the URL for thumbnail
|
||||||
Promise.resolve(restorePause && embed.setVolume(0))
|
const url = new URL(response[0].thumbnail_large);
|
||||||
// Seek
|
|
||||||
.then(() => embed.setCurrentTime(time))
|
|
||||||
// Restore paused
|
|
||||||
.then(() => restorePause && embed.pause())
|
|
||||||
// Restore volume
|
|
||||||
.then(() => restorePause && embed.setVolume(volume))
|
|
||||||
.catch(() => {
|
|
||||||
// Do nothing
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Playback speed
|
// Get original image
|
||||||
let speed = player.config.speed.selected;
|
url.pathname = `${url.pathname.split('_')[0]}.jpg`;
|
||||||
Object.defineProperty(player.media, 'playbackRate', {
|
|
||||||
get() {
|
|
||||||
return speed;
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
player.embed.setPlaybackRate(input).then(() => {
|
|
||||||
speed = input;
|
|
||||||
triggerEvent.call(player, player.media, 'ratechange');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Volume
|
// Set and show poster
|
||||||
let { volume } = player.config;
|
ui.setPoster.call(player, url.href).catch(() => {});
|
||||||
Object.defineProperty(player.media, 'volume', {
|
});
|
||||||
get() {
|
|
||||||
return volume;
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
player.embed.setVolume(input).then(() => {
|
|
||||||
volume = input;
|
|
||||||
triggerEvent.call(player, player.media, 'volumechange');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Muted
|
// Setup instance
|
||||||
let { muted } = player.config;
|
// https://github.com/vimeo/player.js
|
||||||
Object.defineProperty(player.media, 'muted', {
|
player.embed = new window.Vimeo.Player(iframe, {
|
||||||
get() {
|
autopause: player.config.autopause,
|
||||||
return muted;
|
muted: player.muted,
|
||||||
},
|
});
|
||||||
set(input) {
|
|
||||||
const toggle = is.boolean(input) ? input : false;
|
|
||||||
|
|
||||||
player.embed.setVolume(toggle ? 0 : player.config.volume).then(() => {
|
player.media.paused = true;
|
||||||
muted = toggle;
|
player.media.currentTime = 0;
|
||||||
triggerEvent.call(player, player.media, 'volumechange');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Loop
|
// Disable native text track rendering
|
||||||
let { loop } = player.config;
|
if (player.supported.ui) {
|
||||||
Object.defineProperty(player.media, 'loop', {
|
player.embed.disableTextTrack();
|
||||||
get() {
|
}
|
||||||
return loop;
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
const toggle = is.boolean(input) ? input : player.config.loop.active;
|
|
||||||
|
|
||||||
player.embed.setLoop(toggle).then(() => {
|
// Create a faux HTML5 API using the Vimeo API
|
||||||
loop = toggle;
|
player.media.play = () => {
|
||||||
});
|
assurePlaybackState.call(player, true);
|
||||||
},
|
return player.embed.play();
|
||||||
});
|
};
|
||||||
|
|
||||||
// Source
|
player.media.pause = () => {
|
||||||
let currentSrc;
|
assurePlaybackState.call(player, false);
|
||||||
|
return player.embed.pause();
|
||||||
|
};
|
||||||
|
|
||||||
|
player.media.stop = () => {
|
||||||
|
player.pause();
|
||||||
|
player.currentTime = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Seeking
|
||||||
|
let { currentTime } = player.media;
|
||||||
|
Object.defineProperty(player.media, 'currentTime', {
|
||||||
|
get() {
|
||||||
|
return currentTime;
|
||||||
|
},
|
||||||
|
set(time) {
|
||||||
|
// Vimeo will automatically play on seek if the video hasn't been played before
|
||||||
|
|
||||||
|
// Get current paused state and volume etc
|
||||||
|
const { embed, media, paused, volume } = player;
|
||||||
|
const restorePause = paused && !embed.hasPlayed;
|
||||||
|
|
||||||
|
// Set seeking state and trigger event
|
||||||
|
media.seeking = true;
|
||||||
|
triggerEvent.call(player, media, 'seeking');
|
||||||
|
|
||||||
|
// If paused, mute until seek is complete
|
||||||
|
Promise.resolve(restorePause && embed.setVolume(0))
|
||||||
|
// Seek
|
||||||
|
.then(() => embed.setCurrentTime(time))
|
||||||
|
// Restore paused
|
||||||
|
.then(() => restorePause && embed.pause())
|
||||||
|
// Restore volume
|
||||||
|
.then(() => restorePause && embed.setVolume(volume))
|
||||||
|
.catch(() => {
|
||||||
|
// Do nothing
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Playback speed
|
||||||
|
let speed = player.config.speed.selected;
|
||||||
|
Object.defineProperty(player.media, 'playbackRate', {
|
||||||
|
get() {
|
||||||
|
return speed;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
player.embed
|
player.embed
|
||||||
.getVideoUrl()
|
.setPlaybackRate(input)
|
||||||
.then(value => {
|
.then(() => {
|
||||||
currentSrc = value;
|
speed = input;
|
||||||
controls.setDownloadUrl.call(player);
|
triggerEvent.call(player, player.media, 'ratechange');
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(() => {
|
||||||
this.debug.warn(error);
|
// Cannot set Playback Rate, Video is probably not on Pro account
|
||||||
});
|
player.options.speed = [1];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Object.defineProperty(player.media, 'currentSrc', {
|
// Volume
|
||||||
get() {
|
let { volume } = player.config;
|
||||||
return currentSrc;
|
Object.defineProperty(player.media, 'volume', {
|
||||||
},
|
get() {
|
||||||
|
return volume;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
player.embed.setVolume(input).then(() => {
|
||||||
|
volume = input;
|
||||||
|
triggerEvent.call(player, player.media, 'volumechange');
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Ended
|
// Muted
|
||||||
Object.defineProperty(player.media, 'ended', {
|
let { muted } = player.config;
|
||||||
get() {
|
Object.defineProperty(player.media, 'muted', {
|
||||||
return player.currentTime === player.duration;
|
get() {
|
||||||
},
|
return muted;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
const toggle = is.boolean(input) ? input : false;
|
||||||
|
|
||||||
|
player.embed.setVolume(toggle ? 0 : player.config.volume).then(() => {
|
||||||
|
muted = toggle;
|
||||||
|
triggerEvent.call(player, player.media, 'volumechange');
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Set aspect ratio based on video size
|
// Loop
|
||||||
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
|
let { loop } = player.config;
|
||||||
const [width, height] = dimensions;
|
Object.defineProperty(player.media, 'loop', {
|
||||||
player.embed.ratio = [width, height];
|
get() {
|
||||||
setAspectRatio.call(this);
|
return loop;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
const toggle = is.boolean(input) ? input : player.config.loop.active;
|
||||||
|
|
||||||
|
player.embed.setLoop(toggle).then(() => {
|
||||||
|
loop = toggle;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Set autopause
|
// Source
|
||||||
player.embed.setAutopause(player.config.autopause).then(state => {
|
let currentSrc;
|
||||||
player.config.autopause = state;
|
player.embed
|
||||||
});
|
.getVideoUrl()
|
||||||
|
.then(value => {
|
||||||
|
currentSrc = value;
|
||||||
|
controls.setDownloadUrl.call(player);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.debug.warn(error);
|
||||||
|
});
|
||||||
|
|
||||||
// Get title
|
Object.defineProperty(player.media, 'currentSrc', {
|
||||||
player.embed.getVideoTitle().then(title => {
|
get() {
|
||||||
player.config.title = title;
|
return currentSrc;
|
||||||
ui.setTitle.call(this);
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get current time
|
// Ended
|
||||||
player.embed.getCurrentTime().then(value => {
|
Object.defineProperty(player.media, 'ended', {
|
||||||
currentTime = value;
|
get() {
|
||||||
triggerEvent.call(player, player.media, 'timeupdate');
|
return player.currentTime === player.duration;
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Get duration
|
// Set aspect ratio based on video size
|
||||||
player.embed.getDuration().then(value => {
|
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
|
||||||
player.media.duration = value;
|
const [width, height] = dimensions;
|
||||||
triggerEvent.call(player, player.media, 'durationchange');
|
player.embed.ratio = [width, height];
|
||||||
});
|
setAspectRatio.call(this);
|
||||||
|
});
|
||||||
|
|
||||||
// Get captions
|
// Set autopause
|
||||||
player.embed.getTextTracks().then(tracks => {
|
player.embed.setAutopause(player.config.autopause).then(state => {
|
||||||
player.media.textTracks = tracks;
|
player.config.autopause = state;
|
||||||
captions.setup.call(player);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
player.embed.on('cuechange', ({ cues = [] }) => {
|
// Get title
|
||||||
const strippedCues = cues.map(cue => stripHTML(cue.text));
|
player.embed.getVideoTitle().then(title => {
|
||||||
captions.updateCues.call(player, strippedCues);
|
player.config.title = title;
|
||||||
});
|
ui.setTitle.call(this);
|
||||||
|
});
|
||||||
|
|
||||||
player.embed.on('loaded', () => {
|
// Get current time
|
||||||
// Assure state and events are updated on autoplay
|
player.embed.getCurrentTime().then(value => {
|
||||||
player.embed.getPaused().then(paused => {
|
currentTime = value;
|
||||||
assurePlaybackState.call(player, !paused);
|
triggerEvent.call(player, player.media, 'timeupdate');
|
||||||
if (!paused) {
|
});
|
||||||
triggerEvent.call(player, player.media, 'playing');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (is.element(player.embed.element) && player.supported.ui) {
|
// Get duration
|
||||||
const frame = player.embed.element;
|
player.embed.getDuration().then(value => {
|
||||||
|
player.media.duration = value;
|
||||||
|
triggerEvent.call(player, player.media, 'durationchange');
|
||||||
|
});
|
||||||
|
|
||||||
// Fix keyboard focus issues
|
// Get captions
|
||||||
// https://github.com/sampotts/plyr/issues/317
|
player.embed.getTextTracks().then(tracks => {
|
||||||
frame.setAttribute('tabindex', -1);
|
player.media.textTracks = tracks;
|
||||||
}
|
captions.setup.call(player);
|
||||||
});
|
});
|
||||||
|
|
||||||
player.embed.on('bufferstart', () => {
|
player.embed.on('cuechange', ({ cues = [] }) => {
|
||||||
triggerEvent.call(player, player.media, 'waiting');
|
const strippedCues = cues.map(cue => stripHTML(cue.text));
|
||||||
});
|
captions.updateCues.call(player, strippedCues);
|
||||||
|
});
|
||||||
|
|
||||||
player.embed.on('bufferend', () => {
|
player.embed.on('loaded', () => {
|
||||||
triggerEvent.call(player, player.media, 'playing');
|
// Assure state and events are updated on autoplay
|
||||||
});
|
player.embed.getPaused().then(paused => {
|
||||||
|
assurePlaybackState.call(player, !paused);
|
||||||
|
if (!paused) {
|
||||||
|
triggerEvent.call(player, player.media, 'playing');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
player.embed.on('play', () => {
|
if (is.element(player.embed.element) && player.supported.ui) {
|
||||||
assurePlaybackState.call(player, true);
|
const frame = player.embed.element;
|
||||||
triggerEvent.call(player, player.media, 'playing');
|
|
||||||
});
|
|
||||||
|
|
||||||
player.embed.on('pause', () => {
|
// Fix keyboard focus issues
|
||||||
assurePlaybackState.call(player, false);
|
// https://github.com/sampotts/plyr/issues/317
|
||||||
});
|
frame.setAttribute('tabindex', -1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
player.embed.on('timeupdate', data => {
|
player.embed.on('bufferstart', () => {
|
||||||
player.media.seeking = false;
|
triggerEvent.call(player, player.media, 'waiting');
|
||||||
currentTime = data.seconds;
|
});
|
||||||
triggerEvent.call(player, player.media, 'timeupdate');
|
|
||||||
});
|
|
||||||
|
|
||||||
player.embed.on('progress', data => {
|
player.embed.on('bufferend', () => {
|
||||||
player.media.buffered = data.percent;
|
triggerEvent.call(player, player.media, 'playing');
|
||||||
triggerEvent.call(player, player.media, 'progress');
|
});
|
||||||
|
|
||||||
// Check all loaded
|
player.embed.on('play', () => {
|
||||||
if (parseInt(data.percent, 10) === 1) {
|
assurePlaybackState.call(player, true);
|
||||||
triggerEvent.call(player, player.media, 'canplaythrough');
|
triggerEvent.call(player, player.media, 'playing');
|
||||||
}
|
});
|
||||||
|
|
||||||
// Get duration as if we do it before load, it gives an incorrect value
|
player.embed.on('pause', () => {
|
||||||
// https://github.com/sampotts/plyr/issues/891
|
assurePlaybackState.call(player, false);
|
||||||
player.embed.getDuration().then(value => {
|
});
|
||||||
if (value !== player.media.duration) {
|
|
||||||
player.media.duration = value;
|
|
||||||
triggerEvent.call(player, player.media, 'durationchange');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
player.embed.on('seeked', () => {
|
player.embed.on('timeupdate', data => {
|
||||||
player.media.seeking = false;
|
player.media.seeking = false;
|
||||||
triggerEvent.call(player, player.media, 'seeked');
|
currentTime = data.seconds;
|
||||||
});
|
triggerEvent.call(player, player.media, 'timeupdate');
|
||||||
|
});
|
||||||
|
|
||||||
player.embed.on('ended', () => {
|
player.embed.on('progress', data => {
|
||||||
player.media.paused = true;
|
player.media.buffered = data.percent;
|
||||||
triggerEvent.call(player, player.media, 'ended');
|
triggerEvent.call(player, player.media, 'progress');
|
||||||
});
|
|
||||||
|
|
||||||
player.embed.on('error', detail => {
|
// Check all loaded
|
||||||
player.media.error = detail;
|
if (parseInt(data.percent, 10) === 1) {
|
||||||
triggerEvent.call(player, player.media, 'error');
|
triggerEvent.call(player, player.media, 'canplaythrough');
|
||||||
});
|
}
|
||||||
|
|
||||||
// Rebuild UI
|
// Get duration as if we do it before load, it gives an incorrect value
|
||||||
setTimeout(() => ui.build.call(player), 0);
|
// https://github.com/sampotts/plyr/issues/891
|
||||||
},
|
player.embed.getDuration().then(value => {
|
||||||
|
if (value !== player.media.duration) {
|
||||||
|
player.media.duration = value;
|
||||||
|
triggerEvent.call(player, player.media, 'durationchange');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
player.embed.on('seeked', () => {
|
||||||
|
player.media.seeking = false;
|
||||||
|
triggerEvent.call(player, player.media, 'seeked');
|
||||||
|
});
|
||||||
|
|
||||||
|
player.embed.on('ended', () => {
|
||||||
|
player.media.paused = true;
|
||||||
|
triggerEvent.call(player, player.media, 'ended');
|
||||||
|
});
|
||||||
|
|
||||||
|
player.embed.on('error', detail => {
|
||||||
|
player.media.error = detail;
|
||||||
|
triggerEvent.call(player, player.media, 'error');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Rebuild UI
|
||||||
|
setTimeout(() => ui.build.call(player), 0);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default vimeo;
|
export default vimeo;
|
||||||
|
|||||||
+390
-390
@@ -15,426 +15,426 @@ import { setAspectRatio } from '../utils/style';
|
|||||||
|
|
||||||
// Parse YouTube ID from URL
|
// Parse YouTube ID from URL
|
||||||
function parseId(url) {
|
function parseId(url) {
|
||||||
if (is.empty(url)) {
|
if (is.empty(url)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
|
const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
|
||||||
return url.match(regex) ? RegExp.$2 : url;
|
return url.match(regex) ? RegExp.$2 : url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set playback state and trigger change (only on actual change)
|
// Set playback state and trigger change (only on actual change)
|
||||||
function assurePlaybackState(play) {
|
function assurePlaybackState(play) {
|
||||||
if (play && !this.embed.hasPlayed) {
|
if (play && !this.embed.hasPlayed) {
|
||||||
this.embed.hasPlayed = true;
|
this.embed.hasPlayed = true;
|
||||||
}
|
}
|
||||||
if (this.media.paused === play) {
|
if (this.media.paused === play) {
|
||||||
this.media.paused = !play;
|
this.media.paused = !play;
|
||||||
triggerEvent.call(this, this.media, play ? 'play' : 'pause');
|
triggerEvent.call(this, this.media, play ? 'play' : 'pause');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHost(config) {
|
function getHost(config) {
|
||||||
if (config.noCookie) {
|
if (config.noCookie) {
|
||||||
return 'https://www.youtube-nocookie.com';
|
return 'https://www.youtube-nocookie.com';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.location.protocol === 'http:') {
|
if (window.location.protocol === 'http:') {
|
||||||
return 'http://www.youtube.com';
|
return 'http://www.youtube.com';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use YouTube's default
|
// Use YouTube's default
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const youtube = {
|
const youtube = {
|
||||||
setup() {
|
setup() {
|
||||||
// Add embed class for responsive
|
// Add embed class for responsive
|
||||||
toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
|
toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
|
||||||
|
|
||||||
// Setup API
|
// Setup API
|
||||||
if (is.object(window.YT) && is.function(window.YT.Player)) {
|
if (is.object(window.YT) && is.function(window.YT.Player)) {
|
||||||
youtube.ready.call(this);
|
youtube.ready.call(this);
|
||||||
} else {
|
} else {
|
||||||
// Reference current global callback
|
// Reference current global callback
|
||||||
const callback = window.onYouTubeIframeAPIReady;
|
const callback = window.onYouTubeIframeAPIReady;
|
||||||
|
|
||||||
// Set callback to process queue
|
// Set callback to process queue
|
||||||
window.onYouTubeIframeAPIReady = () => {
|
window.onYouTubeIframeAPIReady = () => {
|
||||||
// Call global callback if set
|
// Call global callback if set
|
||||||
if (is.function(callback)) {
|
if (is.function(callback)) {
|
||||||
callback();
|
callback();
|
||||||
}
|
|
||||||
|
|
||||||
youtube.ready.call(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load the SDK
|
|
||||||
loadScript(this.config.urls.youtube.sdk).catch(error => {
|
|
||||||
this.debug.warn('YouTube API failed to load', error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
// Get the media title
|
youtube.ready.call(this);
|
||||||
getTitle(videoId) {
|
};
|
||||||
const url = format(this.config.urls.youtube.api, videoId);
|
|
||||||
|
|
||||||
fetch(url)
|
// Load the SDK
|
||||||
.then(data => {
|
loadScript(this.config.urls.youtube.sdk).catch(error => {
|
||||||
if (is.object(data)) {
|
this.debug.warn('YouTube API failed to load', error);
|
||||||
const { title, height, width } = data;
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Set title
|
// Get the media title
|
||||||
this.config.title = title;
|
getTitle(videoId) {
|
||||||
ui.setTitle.call(this);
|
const url = format(this.config.urls.youtube.api, videoId);
|
||||||
|
|
||||||
// Set aspect ratio
|
fetch(url)
|
||||||
this.embed.ratio = [width, height];
|
.then(data => {
|
||||||
}
|
if (is.object(data)) {
|
||||||
|
const { title, height, width } = data;
|
||||||
|
|
||||||
setAspectRatio.call(this);
|
// Set title
|
||||||
})
|
this.config.title = title;
|
||||||
.catch(() => {
|
ui.setTitle.call(this);
|
||||||
// Set aspect ratio
|
|
||||||
setAspectRatio.call(this);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// API ready
|
// Set aspect ratio
|
||||||
ready() {
|
this.embed.ratio = [width, height];
|
||||||
const player = this;
|
}
|
||||||
// Ignore already setup (race condition)
|
|
||||||
const currentId = player.media && player.media.getAttribute('id');
|
setAspectRatio.call(this);
|
||||||
if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Set aspect ratio
|
||||||
|
setAspectRatio.call(this);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// API ready
|
||||||
|
ready() {
|
||||||
|
const player = this;
|
||||||
|
// Ignore already setup (race condition)
|
||||||
|
const currentId = player.media && player.media.getAttribute('id');
|
||||||
|
if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the source URL or ID
|
||||||
|
let source = player.media.getAttribute('src');
|
||||||
|
|
||||||
|
// Get from <div> if needed
|
||||||
|
if (is.empty(source)) {
|
||||||
|
source = player.media.getAttribute(this.config.attributes.embed.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the <iframe> with a <div> due to YouTube API issues
|
||||||
|
const videoId = parseId(source);
|
||||||
|
const id = generateId(player.provider);
|
||||||
|
// Get poster, if already set
|
||||||
|
const { poster } = player;
|
||||||
|
// Replace media element
|
||||||
|
const container = createElement('div', { id, 'data-poster': poster });
|
||||||
|
player.media = replaceElement(container, player.media);
|
||||||
|
|
||||||
|
// Id to poster wrapper
|
||||||
|
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)
|
||||||
|
loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
|
||||||
|
.catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
|
||||||
|
.catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
|
||||||
|
.then(image => ui.setPoster.call(player, image.src))
|
||||||
|
.then(src => {
|
||||||
|
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
|
||||||
|
if (!src.includes('maxres')) {
|
||||||
|
player.elements.poster.style.backgroundSize = 'cover';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
const config = player.config.youtube;
|
||||||
|
|
||||||
|
// Setup instance
|
||||||
|
// https://developers.google.com/youtube/iframe_api_reference
|
||||||
|
player.embed = new window.YT.Player(id, {
|
||||||
|
videoId,
|
||||||
|
host: getHost(config),
|
||||||
|
playerVars: extend(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
autoplay: player.config.autoplay ? 1 : 0, // Autoplay
|
||||||
|
hl: player.config.hl, // iframe interface language
|
||||||
|
controls: player.supported.ui ? 0 : 1, // Only show controls if not fully supported
|
||||||
|
disablekb: 1, // Disable keyboard as we handle it
|
||||||
|
playsinline: !player.config.fullscreen.iosNative ? 1 : 0, // Allow iOS inline playback
|
||||||
|
// Captions are flaky on YouTube
|
||||||
|
cc_load_policy: player.captions.active ? 1 : 0,
|
||||||
|
cc_lang_pref: player.config.captions.language,
|
||||||
|
// Tracking for stats
|
||||||
|
widget_referrer: window ? window.location.href : null,
|
||||||
|
},
|
||||||
|
config,
|
||||||
|
),
|
||||||
|
events: {
|
||||||
|
onError(event) {
|
||||||
|
// YouTube may fire onError twice, so only handle it once
|
||||||
|
if (!player.media.error) {
|
||||||
|
const code = event.data;
|
||||||
|
// Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
|
||||||
|
const message =
|
||||||
|
{
|
||||||
|
2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
|
||||||
|
5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
|
||||||
|
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.',
|
||||||
|
150: 'The owner of the requested video does not allow it to be played in embedded players.',
|
||||||
|
}[code] || 'An unknown error occured';
|
||||||
|
|
||||||
|
player.media.error = { code, message };
|
||||||
|
|
||||||
|
triggerEvent.call(player, player.media, 'error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPlaybackRateChange(event) {
|
||||||
|
// Get the instance
|
||||||
|
const instance = event.target;
|
||||||
|
|
||||||
|
// Get current speed
|
||||||
|
player.media.playbackRate = instance.getPlaybackRate();
|
||||||
|
|
||||||
|
triggerEvent.call(player, player.media, 'ratechange');
|
||||||
|
},
|
||||||
|
onReady(event) {
|
||||||
|
// Bail if onReady has already been called. See issue #1108
|
||||||
|
if (is.function(player.media.play)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Get the instance
|
||||||
|
const instance = event.target;
|
||||||
|
|
||||||
// Get the source URL or ID
|
// Get the title
|
||||||
let source = player.media.getAttribute('src');
|
youtube.getTitle.call(player, videoId);
|
||||||
|
|
||||||
// Get from <div> if needed
|
// Create a faux HTML5 API using the YouTube API
|
||||||
if (is.empty(source)) {
|
player.media.play = () => {
|
||||||
source = player.media.getAttribute(this.config.attributes.embed.id);
|
assurePlaybackState.call(player, true);
|
||||||
}
|
instance.playVideo();
|
||||||
|
};
|
||||||
|
|
||||||
// Replace the <iframe> with a <div> due to YouTube API issues
|
player.media.pause = () => {
|
||||||
const videoId = parseId(source);
|
assurePlaybackState.call(player, false);
|
||||||
const id = generateId(player.provider);
|
instance.pauseVideo();
|
||||||
// Get poster, if already set
|
};
|
||||||
const { poster } = player;
|
|
||||||
// Replace media element
|
|
||||||
const container = createElement('div', { id, poster });
|
|
||||||
player.media = replaceElement(container, player.media);
|
|
||||||
|
|
||||||
// Id to poster wrapper
|
player.media.stop = () => {
|
||||||
const posterSrc = s => `https://i.ytimg.com/vi/${videoId}/${s}default.jpg`;
|
instance.stopVideo();
|
||||||
|
};
|
||||||
|
|
||||||
// Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
|
player.media.duration = instance.getDuration();
|
||||||
loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
|
player.media.paused = true;
|
||||||
.catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
|
|
||||||
.catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
|
|
||||||
.then(image => ui.setPoster.call(player, image.src))
|
|
||||||
.then(src => {
|
|
||||||
// If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
|
|
||||||
if (!src.includes('maxres')) {
|
|
||||||
player.elements.poster.style.backgroundSize = 'cover';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
|
|
||||||
const config = player.config.youtube;
|
// Seeking
|
||||||
|
player.media.currentTime = 0;
|
||||||
// Setup instance
|
Object.defineProperty(player.media, 'currentTime', {
|
||||||
// https://developers.google.com/youtube/iframe_api_reference
|
get() {
|
||||||
player.embed = new window.YT.Player(id, {
|
return Number(instance.getCurrentTime());
|
||||||
videoId,
|
|
||||||
host: getHost(config),
|
|
||||||
playerVars: extend(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
autoplay: player.config.autoplay ? 1 : 0, // Autoplay
|
|
||||||
hl: player.config.hl, // iframe interface language
|
|
||||||
controls: player.supported.ui ? 0 : 1, // Only show controls if not fully supported
|
|
||||||
disablekb: 1, // Disable keyboard as we handle it
|
|
||||||
playsinline: !player.config.fullscreen.iosNative ? 1 : 0, // Allow iOS inline playback
|
|
||||||
// Captions are flaky on YouTube
|
|
||||||
cc_load_policy: player.captions.active ? 1 : 0,
|
|
||||||
cc_lang_pref: player.config.captions.language,
|
|
||||||
// Tracking for stats
|
|
||||||
widget_referrer: window ? window.location.href : null,
|
|
||||||
},
|
|
||||||
config,
|
|
||||||
),
|
|
||||||
events: {
|
|
||||||
onError(event) {
|
|
||||||
// YouTube may fire onError twice, so only handle it once
|
|
||||||
if (!player.media.error) {
|
|
||||||
const code = event.data;
|
|
||||||
// Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
|
|
||||||
const message =
|
|
||||||
{
|
|
||||||
2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
|
|
||||||
5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
|
|
||||||
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.',
|
|
||||||
150: 'The owner of the requested video does not allow it to be played in embedded players.',
|
|
||||||
}[code] || 'An unknown error occured';
|
|
||||||
|
|
||||||
player.media.error = { code, message };
|
|
||||||
|
|
||||||
triggerEvent.call(player, player.media, 'error');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPlaybackRateChange(event) {
|
|
||||||
// Get the instance
|
|
||||||
const instance = event.target;
|
|
||||||
|
|
||||||
// Get current speed
|
|
||||||
player.media.playbackRate = instance.getPlaybackRate();
|
|
||||||
|
|
||||||
triggerEvent.call(player, player.media, 'ratechange');
|
|
||||||
},
|
|
||||||
onReady(event) {
|
|
||||||
// Bail if onReady has already been called. See issue #1108
|
|
||||||
if (is.function(player.media.play)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Get the instance
|
|
||||||
const instance = event.target;
|
|
||||||
|
|
||||||
// Get the title
|
|
||||||
youtube.getTitle.call(player, videoId);
|
|
||||||
|
|
||||||
// Create a faux HTML5 API using the YouTube API
|
|
||||||
player.media.play = () => {
|
|
||||||
assurePlaybackState.call(player, true);
|
|
||||||
instance.playVideo();
|
|
||||||
};
|
|
||||||
|
|
||||||
player.media.pause = () => {
|
|
||||||
assurePlaybackState.call(player, false);
|
|
||||||
instance.pauseVideo();
|
|
||||||
};
|
|
||||||
|
|
||||||
player.media.stop = () => {
|
|
||||||
instance.stopVideo();
|
|
||||||
};
|
|
||||||
|
|
||||||
player.media.duration = instance.getDuration();
|
|
||||||
player.media.paused = true;
|
|
||||||
|
|
||||||
// Seeking
|
|
||||||
player.media.currentTime = 0;
|
|
||||||
Object.defineProperty(player.media, 'currentTime', {
|
|
||||||
get() {
|
|
||||||
return Number(instance.getCurrentTime());
|
|
||||||
},
|
|
||||||
set(time) {
|
|
||||||
// If paused and never played, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
|
|
||||||
if (player.paused && !player.embed.hasPlayed) {
|
|
||||||
player.embed.mute();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set seeking state and trigger event
|
|
||||||
player.media.seeking = true;
|
|
||||||
triggerEvent.call(player, player.media, 'seeking');
|
|
||||||
|
|
||||||
// Seek after events sent
|
|
||||||
instance.seekTo(time);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Playback speed
|
|
||||||
Object.defineProperty(player.media, 'playbackRate', {
|
|
||||||
get() {
|
|
||||||
return instance.getPlaybackRate();
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
instance.setPlaybackRate(input);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Volume
|
|
||||||
let { volume } = player.config;
|
|
||||||
Object.defineProperty(player.media, 'volume', {
|
|
||||||
get() {
|
|
||||||
return volume;
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
volume = input;
|
|
||||||
instance.setVolume(volume * 100);
|
|
||||||
triggerEvent.call(player, player.media, 'volumechange');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Muted
|
|
||||||
let { muted } = player.config;
|
|
||||||
Object.defineProperty(player.media, 'muted', {
|
|
||||||
get() {
|
|
||||||
return muted;
|
|
||||||
},
|
|
||||||
set(input) {
|
|
||||||
const toggle = is.boolean(input) ? input : muted;
|
|
||||||
muted = toggle;
|
|
||||||
instance[toggle ? 'mute' : 'unMute']();
|
|
||||||
triggerEvent.call(player, player.media, 'volumechange');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Source
|
|
||||||
Object.defineProperty(player.media, 'currentSrc', {
|
|
||||||
get() {
|
|
||||||
return instance.getVideoUrl();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ended
|
|
||||||
Object.defineProperty(player.media, 'ended', {
|
|
||||||
get() {
|
|
||||||
return player.currentTime === player.duration;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get available speeds
|
|
||||||
const speeds = instance.getAvailablePlaybackRates();
|
|
||||||
// Filter based on config
|
|
||||||
player.options.speed = speeds.filter(s => player.config.speed.options.includes(s));
|
|
||||||
|
|
||||||
// Set the tabindex to avoid focus entering iframe
|
|
||||||
if (player.supported.ui) {
|
|
||||||
player.media.setAttribute('tabindex', -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerEvent.call(player, player.media, 'timeupdate');
|
|
||||||
triggerEvent.call(player, player.media, 'durationchange');
|
|
||||||
|
|
||||||
// Reset timer
|
|
||||||
clearInterval(player.timers.buffering);
|
|
||||||
|
|
||||||
// Setup buffering
|
|
||||||
player.timers.buffering = setInterval(() => {
|
|
||||||
// Get loaded % from YouTube
|
|
||||||
player.media.buffered = instance.getVideoLoadedFraction();
|
|
||||||
|
|
||||||
// Trigger progress only when we actually buffer something
|
|
||||||
if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
|
|
||||||
triggerEvent.call(player, player.media, 'progress');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set last buffer point
|
|
||||||
player.media.lastBuffered = player.media.buffered;
|
|
||||||
|
|
||||||
// Bail if we're at 100%
|
|
||||||
if (player.media.buffered === 1) {
|
|
||||||
clearInterval(player.timers.buffering);
|
|
||||||
|
|
||||||
// Trigger event
|
|
||||||
triggerEvent.call(player, player.media, 'canplaythrough');
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
// Rebuild UI
|
|
||||||
setTimeout(() => ui.build.call(player), 50);
|
|
||||||
},
|
|
||||||
onStateChange(event) {
|
|
||||||
// Get the instance
|
|
||||||
const instance = event.target;
|
|
||||||
|
|
||||||
// Reset timer
|
|
||||||
clearInterval(player.timers.playing);
|
|
||||||
|
|
||||||
const seeked = player.media.seeking && [1, 2].includes(event.data);
|
|
||||||
|
|
||||||
if (seeked) {
|
|
||||||
// Unset seeking and fire seeked event
|
|
||||||
player.media.seeking = false;
|
|
||||||
triggerEvent.call(player, player.media, 'seeked');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle events
|
|
||||||
// -1 Unstarted
|
|
||||||
// 0 Ended
|
|
||||||
// 1 Playing
|
|
||||||
// 2 Paused
|
|
||||||
// 3 Buffering
|
|
||||||
// 5 Video cued
|
|
||||||
switch (event.data) {
|
|
||||||
case -1:
|
|
||||||
// Update scrubber
|
|
||||||
triggerEvent.call(player, player.media, 'timeupdate');
|
|
||||||
|
|
||||||
// Get loaded % from YouTube
|
|
||||||
player.media.buffered = instance.getVideoLoadedFraction();
|
|
||||||
triggerEvent.call(player, player.media, 'progress');
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0:
|
|
||||||
assurePlaybackState.call(player, false);
|
|
||||||
|
|
||||||
// YouTube doesn't support loop for a single video, so mimick it.
|
|
||||||
if (player.media.loop) {
|
|
||||||
// YouTube needs a call to `stopVideo` before playing again
|
|
||||||
instance.stopVideo();
|
|
||||||
instance.playVideo();
|
|
||||||
} else {
|
|
||||||
triggerEvent.call(player, player.media, 'ended');
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
|
|
||||||
if (!player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
|
|
||||||
player.media.pause();
|
|
||||||
} else {
|
|
||||||
assurePlaybackState.call(player, true);
|
|
||||||
|
|
||||||
triggerEvent.call(player, player.media, 'playing');
|
|
||||||
|
|
||||||
// Poll to get playback progress
|
|
||||||
player.timers.playing = setInterval(() => {
|
|
||||||
triggerEvent.call(player, player.media, 'timeupdate');
|
|
||||||
}, 50);
|
|
||||||
|
|
||||||
// Check duration again due to YouTube bug
|
|
||||||
// https://github.com/sampotts/plyr/issues/374
|
|
||||||
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
|
|
||||||
if (player.media.duration !== instance.getDuration()) {
|
|
||||||
player.media.duration = instance.getDuration();
|
|
||||||
triggerEvent.call(player, player.media, 'durationchange');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
// Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
|
|
||||||
if (!player.muted) {
|
|
||||||
player.embed.unMute();
|
|
||||||
}
|
|
||||||
assurePlaybackState.call(player, false);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
// Trigger waiting event to add loading classes to container as the video buffers.
|
|
||||||
triggerEvent.call(player, player.media, 'waiting');
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerEvent.call(player, player.elements.container, 'statechange', false, {
|
|
||||||
code: event.data,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
set(time) {
|
||||||
},
|
// If paused and never played, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
|
||||||
|
if (player.paused && !player.embed.hasPlayed) {
|
||||||
|
player.embed.mute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set seeking state and trigger event
|
||||||
|
player.media.seeking = true;
|
||||||
|
triggerEvent.call(player, player.media, 'seeking');
|
||||||
|
|
||||||
|
// Seek after events sent
|
||||||
|
instance.seekTo(time);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Playback speed
|
||||||
|
Object.defineProperty(player.media, 'playbackRate', {
|
||||||
|
get() {
|
||||||
|
return instance.getPlaybackRate();
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
instance.setPlaybackRate(input);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Volume
|
||||||
|
let { volume } = player.config;
|
||||||
|
Object.defineProperty(player.media, 'volume', {
|
||||||
|
get() {
|
||||||
|
return volume;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
volume = input;
|
||||||
|
instance.setVolume(volume * 100);
|
||||||
|
triggerEvent.call(player, player.media, 'volumechange');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Muted
|
||||||
|
let { muted } = player.config;
|
||||||
|
Object.defineProperty(player.media, 'muted', {
|
||||||
|
get() {
|
||||||
|
return muted;
|
||||||
|
},
|
||||||
|
set(input) {
|
||||||
|
const toggle = is.boolean(input) ? input : muted;
|
||||||
|
muted = toggle;
|
||||||
|
instance[toggle ? 'mute' : 'unMute']();
|
||||||
|
triggerEvent.call(player, player.media, 'volumechange');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Source
|
||||||
|
Object.defineProperty(player.media, 'currentSrc', {
|
||||||
|
get() {
|
||||||
|
return instance.getVideoUrl();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ended
|
||||||
|
Object.defineProperty(player.media, 'ended', {
|
||||||
|
get() {
|
||||||
|
return player.currentTime === player.duration;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get available speeds
|
||||||
|
const speeds = instance.getAvailablePlaybackRates();
|
||||||
|
// Filter based on config
|
||||||
|
player.options.speed = speeds.filter(s => player.config.speed.options.includes(s));
|
||||||
|
|
||||||
|
// Set the tabindex to avoid focus entering iframe
|
||||||
|
if (player.supported.ui) {
|
||||||
|
player.media.setAttribute('tabindex', -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerEvent.call(player, player.media, 'timeupdate');
|
||||||
|
triggerEvent.call(player, player.media, 'durationchange');
|
||||||
|
|
||||||
|
// Reset timer
|
||||||
|
clearInterval(player.timers.buffering);
|
||||||
|
|
||||||
|
// Setup buffering
|
||||||
|
player.timers.buffering = setInterval(() => {
|
||||||
|
// Get loaded % from YouTube
|
||||||
|
player.media.buffered = instance.getVideoLoadedFraction();
|
||||||
|
|
||||||
|
// Trigger progress only when we actually buffer something
|
||||||
|
if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
|
||||||
|
triggerEvent.call(player, player.media, 'progress');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set last buffer point
|
||||||
|
player.media.lastBuffered = player.media.buffered;
|
||||||
|
|
||||||
|
// Bail if we're at 100%
|
||||||
|
if (player.media.buffered === 1) {
|
||||||
|
clearInterval(player.timers.buffering);
|
||||||
|
|
||||||
|
// Trigger event
|
||||||
|
triggerEvent.call(player, player.media, 'canplaythrough');
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
// Rebuild UI
|
||||||
|
setTimeout(() => ui.build.call(player), 50);
|
||||||
|
},
|
||||||
|
onStateChange(event) {
|
||||||
|
// Get the instance
|
||||||
|
const instance = event.target;
|
||||||
|
|
||||||
|
// Reset timer
|
||||||
|
clearInterval(player.timers.playing);
|
||||||
|
|
||||||
|
const seeked = player.media.seeking && [1, 2].includes(event.data);
|
||||||
|
|
||||||
|
if (seeked) {
|
||||||
|
// Unset seeking and fire seeked event
|
||||||
|
player.media.seeking = false;
|
||||||
|
triggerEvent.call(player, player.media, 'seeked');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle events
|
||||||
|
// -1 Unstarted
|
||||||
|
// 0 Ended
|
||||||
|
// 1 Playing
|
||||||
|
// 2 Paused
|
||||||
|
// 3 Buffering
|
||||||
|
// 5 Video cued
|
||||||
|
switch (event.data) {
|
||||||
|
case -1:
|
||||||
|
// Update scrubber
|
||||||
|
triggerEvent.call(player, player.media, 'timeupdate');
|
||||||
|
|
||||||
|
// Get loaded % from YouTube
|
||||||
|
player.media.buffered = instance.getVideoLoadedFraction();
|
||||||
|
triggerEvent.call(player, player.media, 'progress');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
assurePlaybackState.call(player, false);
|
||||||
|
|
||||||
|
// YouTube doesn't support loop for a single video, so mimick it.
|
||||||
|
if (player.media.loop) {
|
||||||
|
// YouTube needs a call to `stopVideo` before playing again
|
||||||
|
instance.stopVideo();
|
||||||
|
instance.playVideo();
|
||||||
|
} else {
|
||||||
|
triggerEvent.call(player, player.media, 'ended');
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
|
||||||
|
if (!player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
|
||||||
|
player.media.pause();
|
||||||
|
} else {
|
||||||
|
assurePlaybackState.call(player, true);
|
||||||
|
|
||||||
|
triggerEvent.call(player, player.media, 'playing');
|
||||||
|
|
||||||
|
// Poll to get playback progress
|
||||||
|
player.timers.playing = setInterval(() => {
|
||||||
|
triggerEvent.call(player, player.media, 'timeupdate');
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
// Check duration again due to YouTube bug
|
||||||
|
// https://github.com/sampotts/plyr/issues/374
|
||||||
|
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
|
||||||
|
if (player.media.duration !== instance.getDuration()) {
|
||||||
|
player.media.duration = instance.getDuration();
|
||||||
|
triggerEvent.call(player, player.media, 'durationchange');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
// Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
|
||||||
|
if (!player.muted) {
|
||||||
|
player.embed.unMute();
|
||||||
|
}
|
||||||
|
assurePlaybackState.call(player, false);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
// Trigger waiting event to add loading classes to container as the video buffers.
|
||||||
|
triggerEvent.call(player, player.media, 'waiting');
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerEvent.call(player, player.elements.container, 'statechange', false, {
|
||||||
|
code: event.data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default youtube;
|
export default youtube;
|
||||||
|
|||||||
Vendored
+500
-489
File diff suppressed because it is too large
Load Diff
+1068
-1058
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Plyr Polyfilled Build
|
// Plyr Polyfilled Build
|
||||||
// plyr.js v3.5.10
|
// plyr.js v3.6.1
|
||||||
// https://github.com/sampotts/plyr
|
// https://github.com/sampotts/plyr
|
||||||
// License: The MIT License (MIT)
|
// License: The MIT License (MIT)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|||||||
+122
-122
@@ -13,146 +13,146 @@ import is from './utils/is';
|
|||||||
import { getDeep } from './utils/objects';
|
import { getDeep } from './utils/objects';
|
||||||
|
|
||||||
const source = {
|
const source = {
|
||||||
// Add elements to HTML5 media (source, tracks, etc)
|
// Add elements to HTML5 media (source, tracks, etc)
|
||||||
insertElements(type, attributes) {
|
insertElements(type, attributes) {
|
||||||
if (is.string(attributes)) {
|
if (is.string(attributes)) {
|
||||||
insertElement(type, this.media, {
|
insertElement(type, this.media, {
|
||||||
src: attributes,
|
src: attributes,
|
||||||
});
|
});
|
||||||
} else if (is.array(attributes)) {
|
} else if (is.array(attributes)) {
|
||||||
attributes.forEach(attribute => {
|
attributes.forEach(attribute => {
|
||||||
insertElement(type, this.media, attribute);
|
insertElement(type, this.media, attribute);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Update source
|
// Update source
|
||||||
// Sources are not checked for support so be careful
|
// Sources are not checked for support so be careful
|
||||||
change(input) {
|
change(input) {
|
||||||
if (!getDeep(input, 'sources.length')) {
|
if (!getDeep(input, 'sources.length')) {
|
||||||
this.debug.warn('Invalid source format');
|
this.debug.warn('Invalid source format');
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel current network requests
|
||||||
|
html5.cancelRequests.call(this);
|
||||||
|
|
||||||
|
// Destroy instance and re-setup
|
||||||
|
this.destroy.call(
|
||||||
|
this,
|
||||||
|
() => {
|
||||||
|
// Reset quality options
|
||||||
|
this.options.quality = [];
|
||||||
|
|
||||||
|
// Remove elements
|
||||||
|
removeElement(this.media);
|
||||||
|
this.media = null;
|
||||||
|
|
||||||
|
// Reset class name
|
||||||
|
if (is.element(this.elements.container)) {
|
||||||
|
this.elements.container.removeAttribute('class');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel current network requests
|
// Set the type and provider
|
||||||
html5.cancelRequests.call(this);
|
const { sources, type } = input;
|
||||||
|
const [{ provider = providers.html5, src }] = sources;
|
||||||
|
const tagName = provider === 'html5' ? type : 'div';
|
||||||
|
const attributes = provider === 'html5' ? {} : { src };
|
||||||
|
|
||||||
// Destroy instance and re-setup
|
Object.assign(this, {
|
||||||
this.destroy.call(
|
provider,
|
||||||
this,
|
type,
|
||||||
() => {
|
// Check for support
|
||||||
// Reset quality options
|
supported: support.check(type, provider, this.config.playsinline),
|
||||||
this.options.quality = [];
|
// Create new element
|
||||||
|
media: createElement(tagName, attributes),
|
||||||
|
});
|
||||||
|
|
||||||
// Remove elements
|
// Inject the new element
|
||||||
removeElement(this.media);
|
this.elements.container.appendChild(this.media);
|
||||||
this.media = null;
|
|
||||||
|
|
||||||
// Reset class name
|
// Autoplay the new source?
|
||||||
if (is.element(this.elements.container)) {
|
if (is.boolean(input.autoplay)) {
|
||||||
this.elements.container.removeAttribute('class');
|
this.config.autoplay = input.autoplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the type and provider
|
// Set attributes for audio and video
|
||||||
const { sources, type } = input;
|
if (this.isHTML5) {
|
||||||
const [{ provider = providers.html5, src }] = sources;
|
if (this.config.crossorigin) {
|
||||||
const tagName = provider === 'html5' ? type : 'div';
|
this.media.setAttribute('crossorigin', '');
|
||||||
const attributes = provider === 'html5' ? {} : { src };
|
}
|
||||||
|
if (this.config.autoplay) {
|
||||||
|
this.media.setAttribute('autoplay', '');
|
||||||
|
}
|
||||||
|
if (!is.empty(input.poster)) {
|
||||||
|
this.poster = input.poster;
|
||||||
|
}
|
||||||
|
if (this.config.loop.active) {
|
||||||
|
this.media.setAttribute('loop', '');
|
||||||
|
}
|
||||||
|
if (this.config.muted) {
|
||||||
|
this.media.setAttribute('muted', '');
|
||||||
|
}
|
||||||
|
if (this.config.playsinline) {
|
||||||
|
this.media.setAttribute('playsinline', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(this, {
|
// Restore class hook
|
||||||
provider,
|
ui.addStyleHook.call(this);
|
||||||
type,
|
|
||||||
// Check for support
|
|
||||||
supported: support.check(type, provider, this.config.playsinline),
|
|
||||||
// Create new element
|
|
||||||
media: createElement(tagName, attributes),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Inject the new element
|
// Set new sources for html5
|
||||||
this.elements.container.appendChild(this.media);
|
if (this.isHTML5) {
|
||||||
|
source.insertElements.call(this, 'source', sources);
|
||||||
|
}
|
||||||
|
|
||||||
// Autoplay the new source?
|
// Set video title
|
||||||
if (is.boolean(input.autoplay)) {
|
this.config.title = input.title;
|
||||||
this.config.autoplay = input.autoplay;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set attributes for audio and video
|
// Set up from scratch
|
||||||
if (this.isHTML5) {
|
media.setup.call(this);
|
||||||
if (this.config.crossorigin) {
|
|
||||||
this.media.setAttribute('crossorigin', '');
|
|
||||||
}
|
|
||||||
if (this.config.autoplay) {
|
|
||||||
this.media.setAttribute('autoplay', '');
|
|
||||||
}
|
|
||||||
if (!is.empty(input.poster)) {
|
|
||||||
this.poster = input.poster;
|
|
||||||
}
|
|
||||||
if (this.config.loop.active) {
|
|
||||||
this.media.setAttribute('loop', '');
|
|
||||||
}
|
|
||||||
if (this.config.muted) {
|
|
||||||
this.media.setAttribute('muted', '');
|
|
||||||
}
|
|
||||||
if (this.config.playsinline) {
|
|
||||||
this.media.setAttribute('playsinline', '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore class hook
|
// HTML5 stuff
|
||||||
ui.addStyleHook.call(this);
|
if (this.isHTML5) {
|
||||||
|
// Setup captions
|
||||||
|
if (Object.keys(input).includes('tracks')) {
|
||||||
|
source.insertElements.call(this, 'track', input.tracks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set new sources for html5
|
// If HTML5 or embed but not fully supported, setupInterface and call ready now
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5 || (this.isEmbed && !this.supported.ui)) {
|
||||||
source.insertElements.call(this, 'source', sources);
|
// Setup interface
|
||||||
}
|
ui.build.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
// Set video title
|
// Load HTML5 sources
|
||||||
this.config.title = input.title;
|
if (this.isHTML5) {
|
||||||
|
this.media.load();
|
||||||
|
}
|
||||||
|
|
||||||
// Set up from scratch
|
// Update previewThumbnails config & reload plugin
|
||||||
media.setup.call(this);
|
if (!is.empty(input.previewThumbnails)) {
|
||||||
|
Object.assign(this.config.previewThumbnails, input.previewThumbnails);
|
||||||
|
|
||||||
// HTML5 stuff
|
// Cleanup previewThumbnails plugin if it was loaded
|
||||||
if (this.isHTML5) {
|
if (this.previewThumbnails && this.previewThumbnails.loaded) {
|
||||||
// Setup captions
|
this.previewThumbnails.destroy();
|
||||||
if (Object.keys(input).includes('tracks')) {
|
this.previewThumbnails = null;
|
||||||
source.insertElements.call(this, 'track', input.tracks);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If HTML5 or embed but not fully supported, setupInterface and call ready now
|
// Create new instance if it is still enabled
|
||||||
if (this.isHTML5 || (this.isEmbed && !this.supported.ui)) {
|
if (this.config.previewThumbnails.enabled) {
|
||||||
// Setup interface
|
this.previewThumbnails = new PreviewThumbnails(this);
|
||||||
ui.build.call(this);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load HTML5 sources
|
// Update the fullscreen support
|
||||||
if (this.isHTML5) {
|
this.fullscreen.update();
|
||||||
this.media.load();
|
},
|
||||||
}
|
true,
|
||||||
|
);
|
||||||
// Update previewThumbnails config & reload plugin
|
},
|
||||||
if (!is.empty(input.previewThumbnails)) {
|
|
||||||
Object.assign(this.config.previewThumbnails, input.previewThumbnails);
|
|
||||||
|
|
||||||
// Cleanup previewThumbnails plugin if it was loaded
|
|
||||||
if (this.previewThumbnails && this.previewThumbnails.loaded) {
|
|
||||||
this.previewThumbnails.destroy();
|
|
||||||
this.previewThumbnails = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new instance if it is still enabled
|
|
||||||
if (this.config.previewThumbnails.enabled) {
|
|
||||||
this.previewThumbnails = new PreviewThumbnails(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the fullscreen support
|
|
||||||
this.fullscreen.update();
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default source;
|
export default source;
|
||||||
|
|||||||
+56
-56
@@ -6,72 +6,72 @@ import is from './utils/is';
|
|||||||
import { extend } from './utils/objects';
|
import { extend } from './utils/objects';
|
||||||
|
|
||||||
class Storage {
|
class Storage {
|
||||||
constructor(player) {
|
constructor(player) {
|
||||||
this.enabled = player.config.storage.enabled;
|
this.enabled = player.config.storage.enabled;
|
||||||
this.key = player.config.storage.key;
|
this.key = player.config.storage.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for actual support (see if we can use it)
|
||||||
|
static get supported() {
|
||||||
|
try {
|
||||||
|
if (!('localStorage' in window)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const test = '___test';
|
||||||
|
|
||||||
|
// Try to use it (it might be disabled, e.g. user is in private mode)
|
||||||
|
// see: https://github.com/sampotts/plyr/issues/131
|
||||||
|
window.localStorage.setItem(test, test);
|
||||||
|
window.localStorage.removeItem(test);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
if (!Storage.supported || !this.enabled) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for actual support (see if we can use it)
|
const store = window.localStorage.getItem(this.key);
|
||||||
static get supported() {
|
|
||||||
try {
|
|
||||||
if (!('localStorage' in window)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = '___test';
|
if (is.empty(store)) {
|
||||||
|
return null;
|
||||||
// Try to use it (it might be disabled, e.g. user is in private mode)
|
|
||||||
// see: https://github.com/sampotts/plyr/issues/131
|
|
||||||
window.localStorage.setItem(test, test);
|
|
||||||
window.localStorage.removeItem(test);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
const json = JSON.parse(store);
|
||||||
if (!Storage.supported || !this.enabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const store = window.localStorage.getItem(this.key);
|
return is.string(key) && key.length ? json[key] : json;
|
||||||
|
}
|
||||||
|
|
||||||
if (is.empty(store)) {
|
set(object) {
|
||||||
return null;
|
// Bail if we don't have localStorage support or it's disabled
|
||||||
}
|
if (!Storage.supported || !this.enabled) {
|
||||||
|
return;
|
||||||
const json = JSON.parse(store);
|
|
||||||
|
|
||||||
return is.string(key) && key.length ? json[key] : json;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set(object) {
|
// Can only store objectst
|
||||||
// Bail if we don't have localStorage support or it's disabled
|
if (!is.object(object)) {
|
||||||
if (!Storage.supported || !this.enabled) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can only store objectst
|
|
||||||
if (!is.object(object)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current storage
|
|
||||||
let storage = this.get();
|
|
||||||
|
|
||||||
// Default to empty object
|
|
||||||
if (is.empty(storage)) {
|
|
||||||
storage = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the working copy of the values
|
|
||||||
extend(storage, object);
|
|
||||||
|
|
||||||
// Update storage
|
|
||||||
window.localStorage.setItem(this.key, JSON.stringify(storage));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get current storage
|
||||||
|
let storage = this.get();
|
||||||
|
|
||||||
|
// Default to empty object
|
||||||
|
if (is.empty(storage)) {
|
||||||
|
storage = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the working copy of the values
|
||||||
|
extend(storage, object);
|
||||||
|
|
||||||
|
// Update storage
|
||||||
|
window.localStorage.setItem(this.key, JSON.stringify(storage));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Storage;
|
export default Storage;
|
||||||
|
|||||||
+82
-82
@@ -9,110 +9,110 @@ import is from './utils/is';
|
|||||||
|
|
||||||
// Default codecs for checking mimetype support
|
// Default codecs for checking mimetype support
|
||||||
const defaultCodecs = {
|
const defaultCodecs = {
|
||||||
'audio/ogg': 'vorbis',
|
'audio/ogg': 'vorbis',
|
||||||
'audio/wav': '1',
|
'audio/wav': '1',
|
||||||
'video/webm': 'vp8, vorbis',
|
'video/webm': 'vp8, vorbis',
|
||||||
'video/mp4': 'avc1.42E01E, mp4a.40.2',
|
'video/mp4': 'avc1.42E01E, mp4a.40.2',
|
||||||
'video/ogg': 'theora',
|
'video/ogg': 'theora',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check for feature support
|
// Check for feature support
|
||||||
const support = {
|
const support = {
|
||||||
// Basic support
|
// Basic support
|
||||||
audio: 'canPlayType' in document.createElement('audio'),
|
audio: 'canPlayType' in document.createElement('audio'),
|
||||||
video: 'canPlayType' in document.createElement('video'),
|
video: 'canPlayType' in document.createElement('video'),
|
||||||
|
|
||||||
// Check for support
|
// Check for support
|
||||||
// Basic functionality vs full UI
|
// Basic functionality vs full UI
|
||||||
check(type, provider, playsinline) {
|
check(type, provider, playsinline) {
|
||||||
const canPlayInline = browser.isIPhone && playsinline && support.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 && (type !== 'video' || !browser.isIPhone || canPlayInline);
|
const ui = api && support.rangeInput && (type !== 'video' || !browser.isIPhone || canPlayInline);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
api,
|
api,
|
||||||
ui,
|
ui,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
// Picture-in-picture support
|
// Picture-in-picture support
|
||||||
// Safari & Chrome only currently
|
// Safari & Chrome only currently
|
||||||
pip: (() => {
|
pip: (() => {
|
||||||
if (browser.isIPhone) {
|
if (browser.isIPhone) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safari
|
// Safari
|
||||||
// https://developer.apple.com/documentation/webkitjs/adding_picture_in_picture_to_your_safari_media_controls
|
// https://developer.apple.com/documentation/webkitjs/adding_picture_in_picture_to_your_safari_media_controls
|
||||||
if (is.function(createElement('video').webkitSetPresentationMode)) {
|
if (is.function(createElement('video').webkitSetPresentationMode)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chrome
|
// Chrome
|
||||||
// https://developers.google.com/web/updates/2018/10/watch-video-using-picture-in-picture
|
// https://developers.google.com/web/updates/2018/10/watch-video-using-picture-in-picture
|
||||||
if (document.pictureInPictureEnabled && !createElement('video').disablePictureInPicture) {
|
if (document.pictureInPictureEnabled && !createElement('video').disablePictureInPicture) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
})(),
|
})(),
|
||||||
|
|
||||||
// Airplay support
|
// Airplay support
|
||||||
// Safari only currently
|
// Safari only currently
|
||||||
airplay: is.function(window.WebKitPlaybackTargetAvailabilityEvent),
|
airplay: is.function(window.WebKitPlaybackTargetAvailabilityEvent),
|
||||||
|
|
||||||
// Inline playback support
|
// Inline playback support
|
||||||
// https://webkit.org/blog/6784/new-video-policies-for-ios/
|
// https://webkit.org/blog/6784/new-video-policies-for-ios/
|
||||||
playsinline: 'playsInline' in document.createElement('video'),
|
playsinline: 'playsInline' in document.createElement('video'),
|
||||||
|
|
||||||
// Check for mime type support against a player instance
|
// Check for mime type support against a player instance
|
||||||
// Credits: http://diveintohtml5.info/everything.html
|
// Credits: http://diveintohtml5.info/everything.html
|
||||||
// Related: http://www.leanbackplayer.com/test/h5mt.html
|
// Related: http://www.leanbackplayer.com/test/h5mt.html
|
||||||
mime(input) {
|
mime(input) {
|
||||||
if (is.empty(input)) {
|
if (is.empty(input)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [mediaType] = input.split('/');
|
const [mediaType] = input.split('/');
|
||||||
let type = input;
|
let type = input;
|
||||||
|
|
||||||
// Verify we're using HTML5 and there's no media type mismatch
|
// Verify we're using HTML5 and there's no media type mismatch
|
||||||
if (!this.isHTML5 || mediaType !== this.type) {
|
if (!this.isHTML5 || mediaType !== this.type) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add codec if required
|
// Add codec if required
|
||||||
if (Object.keys(defaultCodecs).includes(type)) {
|
if (Object.keys(defaultCodecs).includes(type)) {
|
||||||
type += `; codecs="${defaultCodecs[input]}"`;
|
type += `; codecs="${defaultCodecs[input]}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return Boolean(type && this.media.canPlayType(type).replace(/no/, ''));
|
return Boolean(type && this.media.canPlayType(type).replace(/no/, ''));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Check for textTracks support
|
// Check for textTracks support
|
||||||
textTracks: 'textTracks' in document.createElement('video'),
|
textTracks: 'textTracks' in document.createElement('video'),
|
||||||
|
|
||||||
// <input type="range"> Sliders
|
// <input type="range"> Sliders
|
||||||
rangeInput: (() => {
|
rangeInput: (() => {
|
||||||
const range = document.createElement('input');
|
const range = document.createElement('input');
|
||||||
range.type = 'range';
|
range.type = 'range';
|
||||||
return range.type === 'range';
|
return range.type === 'range';
|
||||||
})(),
|
})(),
|
||||||
|
|
||||||
// Touch
|
// Touch
|
||||||
// NOTE: Remember a device can be mouse + touch enabled so we check on first touch event
|
// NOTE: Remember a device can be mouse + touch enabled so we check on first touch event
|
||||||
touch: 'ontouchstart' in document.documentElement,
|
touch: 'ontouchstart' in document.documentElement,
|
||||||
|
|
||||||
// Detect transitions support
|
// Detect transitions support
|
||||||
transitions: transitionEndEvent !== false,
|
transitions: transitionEndEvent !== false,
|
||||||
|
|
||||||
// Reduced motion iOS & MacOS setting
|
// Reduced motion iOS & MacOS setting
|
||||||
// https://webkit.org/blog/7551/responsive-design-for-motion/
|
// https://webkit.org/blog/7551/responsive-design-for-motion/
|
||||||
reducedMotion: 'matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches,
|
reducedMotion: 'matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default support;
|
export default support;
|
||||||
|
|||||||
+220
-210
@@ -13,267 +13,277 @@ import is from './utils/is';
|
|||||||
import loadImage from './utils/load-image';
|
import loadImage from './utils/load-image';
|
||||||
|
|
||||||
const ui = {
|
const ui = {
|
||||||
addStyleHook() {
|
addStyleHook() {
|
||||||
toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
|
toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
|
||||||
toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui);
|
toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Toggle native HTML5 media controls
|
// Toggle native HTML5 media controls
|
||||||
toggleNativeControls(toggle = false) {
|
toggleNativeControls(toggle = false) {
|
||||||
if (toggle && this.isHTML5) {
|
if (toggle && this.isHTML5) {
|
||||||
this.media.setAttribute('controls', '');
|
this.media.setAttribute('controls', '');
|
||||||
} else {
|
} else {
|
||||||
this.media.removeAttribute('controls');
|
this.media.removeAttribute('controls');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Setup the UI
|
// Setup the UI
|
||||||
build() {
|
build() {
|
||||||
// Re-attach media element listeners
|
// Re-attach media element listeners
|
||||||
// TODO: Use event bubbling?
|
// TODO: Use event bubbling?
|
||||||
this.listeners.media();
|
this.listeners.media();
|
||||||
|
|
||||||
// Don't setup interface if no support
|
// Don't setup interface if no support
|
||||||
if (!this.supported.ui) {
|
if (!this.supported.ui) {
|
||||||
this.debug.warn(`Basic support only for ${this.provider} ${this.type}`);
|
this.debug.warn(`Basic support only for ${this.provider} ${this.type}`);
|
||||||
|
|
||||||
// Restore native controls
|
// Restore native controls
|
||||||
ui.toggleNativeControls.call(this, true);
|
ui.toggleNativeControls.call(this, true);
|
||||||
|
|
||||||
// Bail
|
// Bail
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject custom controls if not present
|
// Inject custom controls if not present
|
||||||
if (!is.element(this.elements.controls)) {
|
if (!is.element(this.elements.controls)) {
|
||||||
// Inject custom controls
|
// Inject custom controls
|
||||||
controls.inject.call(this);
|
controls.inject.call(this);
|
||||||
|
|
||||||
// Re-attach control listeners
|
// Re-attach control listeners
|
||||||
this.listeners.controls();
|
this.listeners.controls();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove native controls
|
// Remove native controls
|
||||||
ui.toggleNativeControls.call(this);
|
ui.toggleNativeControls.call(this);
|
||||||
|
|
||||||
// Setup captions for HTML5
|
// Setup captions for HTML5
|
||||||
if (this.isHTML5) {
|
if (this.isHTML5) {
|
||||||
captions.setup.call(this);
|
captions.setup.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset volume
|
// Reset volume
|
||||||
this.volume = null;
|
this.volume = null;
|
||||||
|
|
||||||
// Reset mute state
|
// Reset mute state
|
||||||
this.muted = null;
|
this.muted = null;
|
||||||
|
|
||||||
// Reset loop state
|
// Reset loop state
|
||||||
this.loop = null;
|
this.loop = null;
|
||||||
|
|
||||||
// Reset quality setting
|
// Reset quality setting
|
||||||
this.quality = null;
|
this.quality = null;
|
||||||
|
|
||||||
// Reset speed
|
// Reset speed
|
||||||
this.speed = null;
|
this.speed = null;
|
||||||
|
|
||||||
// Reset volume display
|
// Reset volume display
|
||||||
controls.updateVolume.call(this);
|
controls.updateVolume.call(this);
|
||||||
|
|
||||||
// Reset time display
|
// Reset time display
|
||||||
controls.timeUpdate.call(this);
|
controls.timeUpdate.call(this);
|
||||||
|
|
||||||
// Update the UI
|
// Update the UI
|
||||||
ui.checkPlaying.call(this);
|
ui.checkPlaying.call(this);
|
||||||
|
|
||||||
// Check for picture-in-picture support
|
// Check for picture-in-picture support
|
||||||
toggleClass(
|
toggleClass(
|
||||||
this.elements.container,
|
this.elements.container,
|
||||||
this.config.classNames.pip.supported,
|
this.config.classNames.pip.supported,
|
||||||
support.pip && this.isHTML5 && this.isVideo,
|
support.pip && this.isHTML5 && this.isVideo,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 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
|
// Add iOS class
|
||||||
toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
|
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);
|
||||||
|
|
||||||
// Ready for API calls
|
// Ready for API calls
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
|
|
||||||
// Ready event at end of execution stack
|
// Ready event at end of execution stack
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
triggerEvent.call(this, this.media, 'ready');
|
triggerEvent.call(this, this.media, 'ready');
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
// Set the title
|
// Set the title
|
||||||
ui.setTitle.call(this);
|
ui.setTitle.call(this);
|
||||||
|
|
||||||
// Assure the poster image is set, if the property was added before the element was created
|
// Assure the poster image is set, if the property was added before the element was created
|
||||||
if (this.poster) {
|
if (this.poster) {
|
||||||
ui.setPoster.call(this, this.poster, false).catch(() => {});
|
ui.setPoster.call(this, this.poster, false).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manually set the duration if user has overridden it.
|
// Manually set the duration if user has overridden it.
|
||||||
// The event listeners for it doesn't get called if preload is disabled (#701)
|
// The event listeners for it doesn't get called if preload is disabled (#701)
|
||||||
if (this.config.duration) {
|
if (this.config.duration) {
|
||||||
controls.durationUpdate.call(this);
|
controls.durationUpdate.call(this);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Setup aria attribute for play and iframe title
|
// Setup aria attribute for play and iframe title
|
||||||
setTitle() {
|
setTitle() {
|
||||||
// Find the current text
|
// Find the current text
|
||||||
let label = i18n.get('play', this.config);
|
let label = i18n.get('play', this.config);
|
||||||
|
|
||||||
// If there's a media title set, use that for the label
|
// If there's a media title set, use that for the label
|
||||||
if (is.string(this.config.title) && !is.empty(this.config.title)) {
|
if (is.string(this.config.title) && !is.empty(this.config.title)) {
|
||||||
label += `, ${this.config.title}`;
|
label += `, ${this.config.title}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's a play button, set label
|
// If there's a play button, set label
|
||||||
Array.from(this.elements.buttons.play || []).forEach(button => {
|
Array.from(this.elements.buttons.play || []).forEach(button => {
|
||||||
button.setAttribute('aria-label', label);
|
button.setAttribute('aria-label', label);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set iframe title
|
// Set iframe title
|
||||||
// https://github.com/sampotts/plyr/issues/124
|
// https://github.com/sampotts/plyr/issues/124
|
||||||
if (this.isEmbed) {
|
if (this.isEmbed) {
|
||||||
const iframe = getElement.call(this, 'iframe');
|
const iframe = getElement.call(this, 'iframe');
|
||||||
|
|
||||||
if (!is.element(iframe)) {
|
if (!is.element(iframe)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to media type
|
// Default to media type
|
||||||
const title = !is.empty(this.config.title) ? this.config.title : 'video';
|
const title = !is.empty(this.config.title) ? this.config.title : 'video';
|
||||||
const format = i18n.get('frameTitle', this.config);
|
const format = i18n.get('frameTitle', this.config);
|
||||||
|
|
||||||
iframe.setAttribute('title', format.replace('{title}', title));
|
iframe.setAttribute('title', format.replace('{title}', title));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Toggle poster
|
// Toggle poster
|
||||||
togglePoster(enable) {
|
togglePoster(enable) {
|
||||||
toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable);
|
toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set the poster image (async)
|
// Set the poster image (async)
|
||||||
// Used internally for the poster setter, with the passive option forced to false
|
// Used internally for the poster setter, with the passive option forced to false
|
||||||
setPoster(poster, passive = true) {
|
setPoster(poster, passive = true) {
|
||||||
// Don't override if call is passive
|
// Don't override if call is passive
|
||||||
if (passive && this.poster) {
|
if (passive && this.poster) {
|
||||||
return Promise.reject(new Error('Poster already set'));
|
return Promise.reject(new Error('Poster already set'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set property synchronously to respect the call order
|
// Set property synchronously to respect the call order
|
||||||
this.media.setAttribute('poster', poster);
|
this.media.setAttribute('data-poster', poster);
|
||||||
|
|
||||||
// HTML5 uses native poster attribute
|
// Wait until ui is ready
|
||||||
if (this.isHTML5) {
|
return (
|
||||||
return Promise.resolve(poster);
|
ready
|
||||||
}
|
.call(this)
|
||||||
|
// Load image
|
||||||
|
.then(() => loadImage(poster))
|
||||||
|
.catch(err => {
|
||||||
|
// Hide poster on error unless it's been set by another call
|
||||||
|
if (poster === this.poster) {
|
||||||
|
ui.togglePoster.call(this, false);
|
||||||
|
}
|
||||||
|
// Rethrow
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Prevent race conditions
|
||||||
|
if (poster !== this.poster) {
|
||||||
|
throw new Error('setPoster cancelled by later call to setPoster');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
Object.assign(this.elements.poster.style, {
|
||||||
|
backgroundImage: `url('${poster}')`,
|
||||||
|
// Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
|
||||||
|
backgroundSize: '',
|
||||||
|
});
|
||||||
|
|
||||||
// Wait until ui is ready
|
ui.togglePoster.call(this, true);
|
||||||
return (
|
|
||||||
ready
|
|
||||||
.call(this)
|
|
||||||
// Load image
|
|
||||||
.then(() => loadImage(poster))
|
|
||||||
.catch(err => {
|
|
||||||
// Hide poster on error unless it's been set by another call
|
|
||||||
if (poster === this.poster) {
|
|
||||||
ui.togglePoster.call(this, false);
|
|
||||||
}
|
|
||||||
// Rethrow
|
|
||||||
throw err;
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
// Prevent race conditions
|
|
||||||
if (poster !== this.poster) {
|
|
||||||
throw new Error('setPoster cancelled by later call to setPoster');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
Object.assign(this.elements.poster.style, {
|
|
||||||
backgroundImage: `url('${poster}')`,
|
|
||||||
// Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
|
|
||||||
backgroundSize: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.togglePoster.call(this, true);
|
return poster;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
return poster;
|
// Check playing state
|
||||||
})
|
checkPlaying(event) {
|
||||||
);
|
// Class hooks
|
||||||
},
|
toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
|
||||||
|
toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
|
||||||
|
toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
|
||||||
|
|
||||||
// Check playing state
|
// Set state
|
||||||
checkPlaying(event) {
|
Array.from(this.elements.buttons.play || []).forEach(target => {
|
||||||
// Class hooks
|
Object.assign(target, { pressed: this.playing });
|
||||||
toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
|
target.setAttribute('aria-label', i18n.get(this.playing ? 'pause' : 'play', this.config));
|
||||||
toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
|
});
|
||||||
toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
|
|
||||||
|
|
||||||
// Set state
|
// Only update controls on non timeupdate events
|
||||||
Array.from(this.elements.buttons.play || []).forEach(target => {
|
if (is.event(event) && event.type === 'timeupdate') {
|
||||||
Object.assign(target, { pressed: this.playing });
|
return;
|
||||||
target.setAttribute('aria-label', i18n.get(this.playing ? 'pause' : 'play', this.config));
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Only update controls on non timeupdate events
|
// Toggle controls
|
||||||
if (is.event(event) && event.type === 'timeupdate') {
|
ui.toggleControls.call(this);
|
||||||
return;
|
},
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle controls
|
// Check if media is loading
|
||||||
|
checkLoading(event) {
|
||||||
|
this.loading = ['stalled', 'waiting'].includes(event.type);
|
||||||
|
|
||||||
|
// Clear timer
|
||||||
|
clearTimeout(this.timers.loading);
|
||||||
|
|
||||||
|
// Timer to prevent flicker when seeking
|
||||||
|
this.timers.loading = setTimeout(
|
||||||
|
() => {
|
||||||
|
// Update progress bar loading class state
|
||||||
|
toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
|
||||||
|
|
||||||
|
// Update controls visibility
|
||||||
ui.toggleControls.call(this);
|
ui.toggleControls.call(this);
|
||||||
},
|
},
|
||||||
|
this.loading ? 250 : 0,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
// Check if media is loading
|
// Toggle controls based on state and `force` argument
|
||||||
checkLoading(event) {
|
toggleControls(force) {
|
||||||
this.loading = ['stalled', 'waiting'].includes(event.type);
|
const { controls: controlsElement } = this.elements;
|
||||||
|
|
||||||
// Clear timer
|
if (controlsElement && this.config.hideControls) {
|
||||||
clearTimeout(this.timers.loading);
|
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
|
||||||
|
const recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now();
|
||||||
|
|
||||||
// Timer to prevent flicker when seeking
|
// Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
|
||||||
this.timers.loading = setTimeout(
|
this.toggleControls(
|
||||||
() => {
|
Boolean(
|
||||||
// Update progress bar loading class state
|
force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek,
|
||||||
toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Update controls visibility
|
// Migrate any custom properties from the media to the parent
|
||||||
ui.toggleControls.call(this);
|
migrateStyles() {
|
||||||
},
|
// Loop through values (as they are the keys when the object is spread 🤔)
|
||||||
this.loading ? 250 : 0,
|
Object.values({ ...this.media.style })
|
||||||
);
|
// We're only fussed about Plyr specific properties
|
||||||
},
|
.filter(key => !is.empty(key) && key.startsWith('--plyr'))
|
||||||
|
.forEach(key => {
|
||||||
|
// Set on the container
|
||||||
|
this.elements.container.style.setProperty(key, this.media.style.getPropertyValue(key));
|
||||||
|
|
||||||
// Toggle controls based on state and `force` argument
|
// Clean up from media element
|
||||||
toggleControls(force) {
|
this.media.style.removeProperty(key);
|
||||||
const { controls: controlsElement } = this.elements;
|
});
|
||||||
|
|
||||||
if (controlsElement && this.config.hideControls) {
|
// Remove attribute if empty
|
||||||
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
|
if (is.empty(this.media.style)) {
|
||||||
const recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now();
|
this.media.removeAttribute('style');
|
||||||
|
}
|
||||||
// Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
|
},
|
||||||
this.toggleControls(
|
|
||||||
Boolean(
|
|
||||||
force ||
|
|
||||||
this.loading ||
|
|
||||||
this.paused ||
|
|
||||||
controlsElement.pressed ||
|
|
||||||
controlsElement.hover ||
|
|
||||||
recentTouchSeek,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ui;
|
export default ui;
|
||||||
|
|||||||
+21
-21
@@ -5,34 +5,34 @@
|
|||||||
import is from './is';
|
import is from './is';
|
||||||
|
|
||||||
export const transitionEndEvent = (() => {
|
export const transitionEndEvent = (() => {
|
||||||
const element = document.createElement('span');
|
const element = document.createElement('span');
|
||||||
|
|
||||||
const events = {
|
const events = {
|
||||||
WebkitTransition: 'webkitTransitionEnd',
|
WebkitTransition: 'webkitTransitionEnd',
|
||||||
MozTransition: 'transitionend',
|
MozTransition: 'transitionend',
|
||||||
OTransition: 'oTransitionEnd otransitionend',
|
OTransition: 'oTransitionEnd otransitionend',
|
||||||
transition: 'transitionend',
|
transition: 'transitionend',
|
||||||
};
|
};
|
||||||
|
|
||||||
const type = Object.keys(events).find(event => element.style[event] !== undefined);
|
const type = Object.keys(events).find(event => element.style[event] !== undefined);
|
||||||
|
|
||||||
return is.string(type) ? events[type] : false;
|
return is.string(type) ? events[type] : false;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Force repaint of element
|
// Force repaint of element
|
||||||
export function repaint(element, delay) {
|
export function repaint(element, delay) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
element.hidden = true;
|
element.hidden = true;
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-expressions
|
// eslint-disable-next-line no-unused-expressions
|
||||||
element.offsetHeight;
|
element.offsetHeight;
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
element.hidden = false;
|
element.hidden = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,18 @@ import is from './is';
|
|||||||
|
|
||||||
// Remove duplicates in an array
|
// Remove duplicates in an array
|
||||||
export function dedupe(array) {
|
export function dedupe(array) {
|
||||||
if (!is.array(array)) {
|
if (!is.array(array)) {
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
return array.filter((item, index) => array.indexOf(item) === index);
|
return array.filter((item, index) => array.indexOf(item) === index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the closest value in an array
|
// Get the closest value in an array
|
||||||
export function closest(array, value) {
|
export function closest(array, value) {
|
||||||
if (!is.array(array) || !array.length) {
|
if (!is.array(array) || !array.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return array.reduce((prev, curr) => (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev));
|
return array.reduce((prev, curr) => (Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
const browser = {
|
const browser = {
|
||||||
isIE: /* @cc_on!@ */ false || !!document.documentMode,
|
isIE: /* @cc_on!@ */ false || !!document.documentMode,
|
||||||
isEdge: window.navigator.userAgent.includes('Edge'),
|
isEdge: window.navigator.userAgent.includes('Edge'),
|
||||||
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
|
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
|
||||||
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
|
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
|
||||||
isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform),
|
isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default browser;
|
export default browser;
|
||||||
|
|||||||
+181
-161
@@ -7,257 +7,277 @@ import { extend } from './objects';
|
|||||||
|
|
||||||
// Wrap an element
|
// Wrap an element
|
||||||
export function wrap(elements, wrapper) {
|
export function wrap(elements, wrapper) {
|
||||||
// Convert `elements` to an array, if necessary.
|
// Convert `elements` to an array, if necessary.
|
||||||
const targets = elements.length ? elements : [elements];
|
const targets = elements.length ? elements : [elements];
|
||||||
|
|
||||||
// Loops backwards to prevent having to clone the wrapper on the
|
// Loops backwards to prevent having to clone the wrapper on the
|
||||||
// first element (see `child` below).
|
// first element (see `child` below).
|
||||||
Array.from(targets)
|
Array.from(targets)
|
||||||
.reverse()
|
.reverse()
|
||||||
.forEach((element, index) => {
|
.forEach((element, index) => {
|
||||||
const child = index > 0 ? wrapper.cloneNode(true) : wrapper;
|
const child = index > 0 ? wrapper.cloneNode(true) : wrapper;
|
||||||
// Cache the current parent and sibling.
|
// Cache the current parent and sibling.
|
||||||
const parent = element.parentNode;
|
const parent = element.parentNode;
|
||||||
const sibling = element.nextSibling;
|
const sibling = element.nextSibling;
|
||||||
|
|
||||||
// Wrap the element (is automatically removed from its current
|
// Wrap the element (is automatically removed from its current
|
||||||
// parent).
|
// parent).
|
||||||
child.appendChild(element);
|
child.appendChild(element);
|
||||||
|
|
||||||
// If the element had a sibling, insert the wrapper before
|
// If the element had a sibling, insert the wrapper before
|
||||||
// the sibling to maintain the HTML structure; otherwise, just
|
// the sibling to maintain the HTML structure; otherwise, just
|
||||||
// append it to the parent.
|
// append it to the parent.
|
||||||
if (sibling) {
|
if (sibling) {
|
||||||
parent.insertBefore(child, sibling);
|
parent.insertBefore(child, sibling);
|
||||||
} else {
|
} else {
|
||||||
parent.appendChild(child);
|
parent.appendChild(child);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set attributes
|
// Set attributes
|
||||||
export function setAttributes(element, attributes) {
|
export function setAttributes(element, attributes) {
|
||||||
if (!is.element(element) || is.empty(attributes)) {
|
if (!is.element(element) || is.empty(attributes)) {
|
||||||
return;
|
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"
|
||||||
Object.entries(attributes)
|
Object.entries(attributes)
|
||||||
.filter(([, value]) => !is.nullOrUndefined(value))
|
.filter(([, value]) => !is.nullOrUndefined(value))
|
||||||
.forEach(([key, value]) => element.setAttribute(key, value));
|
.forEach(([key, value]) => element.setAttribute(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a DocumentFragment
|
// Create a DocumentFragment
|
||||||
export function createElement(type, attributes, text) {
|
export function createElement(type, attributes, text) {
|
||||||
// Create a new <element>
|
// Create a new <element>
|
||||||
const element = document.createElement(type);
|
const element = document.createElement(type);
|
||||||
|
|
||||||
// Set all passed attributes
|
// Set all passed attributes
|
||||||
if (is.object(attributes)) {
|
if (is.object(attributes)) {
|
||||||
setAttributes(element, attributes);
|
setAttributes(element, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add text node
|
// Add text node
|
||||||
if (is.string(text)) {
|
if (is.string(text)) {
|
||||||
element.innerText = text;
|
element.innerText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return built element
|
// Return built element
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inaert 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)) {
|
if (!is.element(element) || !is.element(target)) {
|
||||||
return;
|
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)) {
|
if (!is.element(parent)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.appendChild(createElement(type, attributes, text));
|
parent.appendChild(createElement(type, attributes, text));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove element(s)
|
// Remove element(s)
|
||||||
export function removeElement(element) {
|
export function removeElement(element) {
|
||||||
if (is.nodeList(element) || is.array(element)) {
|
if (is.nodeList(element) || is.array(element)) {
|
||||||
Array.from(element).forEach(removeElement);
|
Array.from(element).forEach(removeElement);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is.element(element) || !is.element(element.parentNode)) {
|
if (!is.element(element) || !is.element(element.parentNode)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
element.parentNode.removeChild(element);
|
element.parentNode.removeChild(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all child elements
|
// Remove all child elements
|
||||||
export function emptyElement(element) {
|
export function emptyElement(element) {
|
||||||
if (!is.element(element)) {
|
if (!is.element(element)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { length } = element.childNodes;
|
let { length } = element.childNodes;
|
||||||
|
|
||||||
while (length > 0) {
|
while (length > 0) {
|
||||||
element.removeChild(element.lastChild);
|
element.removeChild(element.lastChild);
|
||||||
length -= 1;
|
length -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)) {
|
if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
oldChild.parentNode.replaceChild(newChild, oldChild);
|
oldChild.parentNode.replaceChild(newChild, oldChild);
|
||||||
|
|
||||||
return newChild;
|
return newChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get an attribute object from a string selector
|
// Get an attribute object from a string selector
|
||||||
export function getAttributesFromSelector(sel, existingAttributes) {
|
export function getAttributesFromSelector(sel, existingAttributes) {
|
||||||
// For example:
|
// For example:
|
||||||
// '.test' to { class: 'test' }
|
// '.test' to { class: 'test' }
|
||||||
// '#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)) {
|
if (!is.string(sel) || is.empty(sel)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const attributes = {};
|
const attributes = {};
|
||||||
const existing = extend({}, existingAttributes);
|
const existing = extend({}, existingAttributes);
|
||||||
|
|
||||||
sel.split(',').forEach(s => {
|
sel.split(',').forEach(s => {
|
||||||
// Remove whitespace
|
// Remove whitespace
|
||||||
const selector = s.trim();
|
const selector = s.trim();
|
||||||
const className = selector.replace('.', '');
|
const className = selector.replace('.', '');
|
||||||
const stripped = selector.replace(/[[\]]/g, '');
|
const stripped = selector.replace(/[[\]]/g, '');
|
||||||
// Get the parts and value
|
// Get the parts and value
|
||||||
const parts = stripped.split('=');
|
const parts = stripped.split('=');
|
||||||
const [key] = parts;
|
const [key] = parts;
|
||||||
const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : '';
|
const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : '';
|
||||||
// Get the first character
|
// Get the first character
|
||||||
const start = selector.charAt(0);
|
const start = selector.charAt(0);
|
||||||
|
|
||||||
switch (start) {
|
switch (start) {
|
||||||
case '.':
|
case '.':
|
||||||
// Add to existing classname
|
// Add to existing classname
|
||||||
if (is.string(existing.class)) {
|
if (is.string(existing.class)) {
|
||||||
attributes.class = `${existing.class} ${className}`;
|
attributes.class = `${existing.class} ${className}`;
|
||||||
} else {
|
} else {
|
||||||
attributes.class = className;
|
attributes.class = className;
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '#':
|
|
||||||
// ID selector
|
|
||||||
attributes.id = selector.replace('#', '');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '[':
|
|
||||||
// Attribute selector
|
|
||||||
attributes[key] = value;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
});
|
break;
|
||||||
|
|
||||||
return extend(existing, attributes);
|
case '#':
|
||||||
|
// ID selector
|
||||||
|
attributes.id = selector.replace('#', '');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '[':
|
||||||
|
// Attribute selector
|
||||||
|
attributes[key] = value;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return extend(existing, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle hidden
|
// Toggle hidden
|
||||||
export function toggleHidden(element, hidden) {
|
export function toggleHidden(element, hidden) {
|
||||||
if (!is.element(element)) {
|
if (!is.element(element)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let hide = hidden;
|
let hide = hidden;
|
||||||
|
|
||||||
if (!is.boolean(hide)) {
|
if (!is.boolean(hide)) {
|
||||||
hide = !element.hidden;
|
hide = !element.hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
element.hidden = hide;
|
element.hidden = hide;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
|
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
|
||||||
export function toggleClass(element, className, force) {
|
export function toggleClass(element, className, force) {
|
||||||
if (is.nodeList(element)) {
|
if (is.nodeList(element)) {
|
||||||
return Array.from(element).map(e => toggleClass(e, className, force));
|
return Array.from(element).map(e => toggleClass(e, className, force));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.element(element)) {
|
||||||
|
let method = 'toggle';
|
||||||
|
if (typeof force !== 'undefined') {
|
||||||
|
method = force ? 'add' : 'remove';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is.element(element)) {
|
element.classList[method](className);
|
||||||
let method = 'toggle';
|
return element.classList.contains(className);
|
||||||
if (typeof force !== 'undefined') {
|
}
|
||||||
method = force ? 'add' : 'remove';
|
|
||||||
}
|
|
||||||
|
|
||||||
element.classList[method](className);
|
return false;
|
||||||
return element.classList.contains(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has class name
|
// Has class name
|
||||||
export function hasClass(element, className) {
|
export function hasClass(element, className) {
|
||||||
return is.element(element) && element.classList.contains(className);
|
return is.element(element) && element.classList.contains(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Element matches selector
|
// Element matches selector
|
||||||
export function matches(element, selector) {
|
export function matches(element, selector) {
|
||||||
const prototype = { Element };
|
const { prototype } = Element;
|
||||||
|
|
||||||
function match() {
|
function match() {
|
||||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const method =
|
const method =
|
||||||
prototype.matches ||
|
prototype.matches ||
|
||||||
prototype.webkitMatchesSelector ||
|
prototype.webkitMatchesSelector ||
|
||||||
prototype.mozMatchesSelector ||
|
prototype.mozMatchesSelector ||
|
||||||
prototype.msMatchesSelector ||
|
prototype.msMatchesSelector ||
|
||||||
match;
|
match;
|
||||||
|
|
||||||
return method.call(element, selector);
|
return method.call(element, selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closest ancestor element matching selector (also tests element itself)
|
||||||
|
export function closest(element, selector) {
|
||||||
|
const { prototype } = Element;
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
|
||||||
|
function closestElement() {
|
||||||
|
let el = this;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (matches.matches(el, selector)) return el;
|
||||||
|
el = el.parentElement || el.parentNode;
|
||||||
|
} while (el !== null && el.nodeType === 1);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const method = prototype.closest || closestElement;
|
||||||
|
|
||||||
|
return method.call(element, selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all elements
|
// Find all elements
|
||||||
export function getElements(selector) {
|
export function getElements(selector) {
|
||||||
return this.elements.container.querySelectorAll(selector);
|
return this.elements.container.querySelectorAll(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a single element
|
// Find a single element
|
||||||
export function getElement(selector) {
|
export function getElement(selector) {
|
||||||
return this.elements.container.querySelector(selector);
|
return this.elements.container.querySelector(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set focus and tab focus class
|
// Set focus and tab focus class
|
||||||
export function setFocus(element = null, tabFocus = false) {
|
export function setFocus(element = null, tabFocus = false) {
|
||||||
if (!is.element(element)) {
|
if (!is.element(element)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set regular focus
|
// Set regular focus
|
||||||
element.focus({ preventScroll: true });
|
element.focus({ preventScroll: true });
|
||||||
|
|
||||||
// If we want to mimic keyboard focus via tab
|
// If we want to mimic keyboard focus via tab
|
||||||
if (tabFocus) {
|
if (tabFocus) {
|
||||||
toggleClass(element, this.config.classNames.tabFocus);
|
toggleClass(element, this.config.classNames.tabFocus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+71
-71
@@ -8,110 +8,110 @@ import is from './is';
|
|||||||
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
|
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
|
||||||
// https://www.youtube.com/watch?v=NPM6172J22g
|
// https://www.youtube.com/watch?v=NPM6172J22g
|
||||||
const supportsPassiveListeners = (() => {
|
const supportsPassiveListeners = (() => {
|
||||||
// Test via a getter in the options object to see if the passive property is accessed
|
// Test via a getter in the options object to see if the passive property is accessed
|
||||||
let supported = false;
|
let supported = false;
|
||||||
try {
|
try {
|
||||||
const options = Object.defineProperty({}, 'passive', {
|
const options = Object.defineProperty({}, 'passive', {
|
||||||
get() {
|
get() {
|
||||||
supported = true;
|
supported = true;
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
window.addEventListener('test', null, options);
|
window.addEventListener('test', null, options);
|
||||||
window.removeEventListener('test', null, options);
|
window.removeEventListener('test', null, options);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
return supported;
|
return supported;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Toggle event listener
|
// Toggle event listener
|
||||||
export function toggleListener(element, event, callback, toggle = false, passive = true, capture = false) {
|
export function toggleListener(element, event, callback, toggle = false, passive = true, capture = false) {
|
||||||
// Bail if no element, event, or callback
|
// Bail if no element, event, or callback
|
||||||
if (!element || !('addEventListener' in element) || is.empty(event) || !is.function(callback)) {
|
if (!element || !('addEventListener' in element) || is.empty(event) || !is.function(callback)) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow multiple events
|
||||||
|
const events = event.split(' ');
|
||||||
|
// Build options
|
||||||
|
// Default to just the capture boolean for browsers with no passive listener support
|
||||||
|
let options = capture;
|
||||||
|
|
||||||
|
// If passive events listeners are supported
|
||||||
|
if (supportsPassiveListeners) {
|
||||||
|
options = {
|
||||||
|
// Whether the listener can be passive (i.e. default never prevented)
|
||||||
|
passive,
|
||||||
|
// Whether the listener is a capturing listener or not
|
||||||
|
capture,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a single node is passed, bind the event listener
|
||||||
|
events.forEach(type => {
|
||||||
|
if (this && this.eventListeners && toggle) {
|
||||||
|
// Cache event listener
|
||||||
|
this.eventListeners.push({ element, type, callback, options });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow multiple events
|
element[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options);
|
||||||
const events = event.split(' ');
|
});
|
||||||
// Build options
|
|
||||||
// Default to just the capture boolean for browsers with no passive listener support
|
|
||||||
let options = capture;
|
|
||||||
|
|
||||||
// If passive events listeners are supported
|
|
||||||
if (supportsPassiveListeners) {
|
|
||||||
options = {
|
|
||||||
// Whether the listener can be passive (i.e. default never prevented)
|
|
||||||
passive,
|
|
||||||
// Whether the listener is a capturing listener or not
|
|
||||||
capture,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a single node is passed, bind the event listener
|
|
||||||
events.forEach(type => {
|
|
||||||
if (this && this.eventListeners && toggle) {
|
|
||||||
// Cache event listener
|
|
||||||
this.eventListeners.push({ element, type, callback, options });
|
|
||||||
}
|
|
||||||
|
|
||||||
element[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind event handler
|
// Bind event handler
|
||||||
export function on(element, events = '', callback, passive = true, capture = false) {
|
export function on(element, events = '', callback, passive = true, capture = false) {
|
||||||
toggleListener.call(this, element, events, callback, true, passive, capture);
|
toggleListener.call(this, element, events, callback, true, passive, capture);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unbind event handler
|
// Unbind event handler
|
||||||
export function off(element, events = '', callback, passive = true, capture = false) {
|
export function off(element, events = '', callback, passive = true, capture = false) {
|
||||||
toggleListener.call(this, element, events, callback, false, passive, capture);
|
toggleListener.call(this, element, events, callback, false, passive, capture);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind once-only event handler
|
// Bind once-only event handler
|
||||||
export function once(element, events = '', callback, passive = true, capture = false) {
|
export function once(element, events = '', callback, passive = true, capture = false) {
|
||||||
const onceCallback = (...args) => {
|
const onceCallback = (...args) => {
|
||||||
off(element, events, onceCallback, passive, capture);
|
off(element, events, onceCallback, passive, capture);
|
||||||
callback.apply(this, args);
|
callback.apply(this, args);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleListener.call(this, element, events, onceCallback, true, passive, capture);
|
toggleListener.call(this, element, events, onceCallback, true, passive, capture);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger event
|
// Trigger event
|
||||||
export function triggerEvent(element, type = '', bubbles = false, detail = {}) {
|
export function triggerEvent(element, type = '', bubbles = false, detail = {}) {
|
||||||
// Bail if no element
|
// Bail if no element
|
||||||
if (!is.element(element) || is.empty(type)) {
|
if (!is.element(element) || is.empty(type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and dispatch the event
|
// Create and dispatch the event
|
||||||
const event = new CustomEvent(type, {
|
const event = new CustomEvent(type, {
|
||||||
bubbles,
|
bubbles,
|
||||||
detail: { ...detail, plyr: this,},
|
detail: { ...detail, plyr: this },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dispatch the event
|
// Dispatch the event
|
||||||
element.dispatchEvent(event);
|
element.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unbind all cached event listeners
|
// Unbind all cached event listeners
|
||||||
export function unbindListeners() {
|
export function unbindListeners() {
|
||||||
if (this && this.eventListeners) {
|
if (this && this.eventListeners) {
|
||||||
this.eventListeners.forEach(item => {
|
this.eventListeners.forEach(item => {
|
||||||
const { element, type, callback, options } = item;
|
const { element, type, callback, options } = item;
|
||||||
element.removeEventListener(type, callback, options);
|
element.removeEventListener(type, callback, options);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventListeners = [];
|
this.eventListeners = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run method when / if player is ready
|
// Run method when / if player is ready
|
||||||
export function ready() {
|
export function ready() {
|
||||||
return new Promise(resolve =>
|
return new Promise(resolve =>
|
||||||
this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve),
|
this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve),
|
||||||
).then(() => {});
|
).then(() => {});
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-32
@@ -4,39 +4,39 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
export default function fetch(url, responseType = 'text') {
|
export default function fetch(url, responseType = 'text') {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const request = new XMLHttpRequest();
|
const request = new XMLHttpRequest();
|
||||||
|
|
||||||
// Check for CORS support
|
// Check for CORS support
|
||||||
if (!('withCredentials' in request)) {
|
if (!('withCredentials' in request)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.addEventListener('load', () => {
|
request.addEventListener('load', () => {
|
||||||
if (responseType === 'text') {
|
if (responseType === 'text') {
|
||||||
try {
|
try {
|
||||||
resolve(JSON.parse(request.responseText));
|
resolve(JSON.parse(request.responseText));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
resolve(request.responseText);
|
resolve(request.responseText);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolve(request.response);
|
resolve(request.response);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
request.addEventListener('error', () => {
|
|
||||||
throw new Error(request.status);
|
|
||||||
});
|
|
||||||
|
|
||||||
request.open('GET', url, true);
|
|
||||||
|
|
||||||
// Set the required response type
|
|
||||||
request.responseType = responseType;
|
|
||||||
|
|
||||||
request.send();
|
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
request.addEventListener('error', () => {
|
||||||
|
throw new Error(request.status);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.open('GET', url, true);
|
||||||
|
|
||||||
|
// Set the required response type
|
||||||
|
request.responseType = responseType;
|
||||||
|
|
||||||
|
request.send();
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-25
@@ -8,40 +8,40 @@ import { replaceAll } from './strings';
|
|||||||
|
|
||||||
// Skip i18n for abbreviations and brand names
|
// Skip i18n for abbreviations and brand names
|
||||||
const resources = {
|
const resources = {
|
||||||
pip: 'PIP',
|
pip: 'PIP',
|
||||||
airplay: 'AirPlay',
|
airplay: 'AirPlay',
|
||||||
html5: 'HTML5',
|
html5: 'HTML5',
|
||||||
vimeo: 'Vimeo',
|
vimeo: 'Vimeo',
|
||||||
youtube: 'YouTube',
|
youtube: 'YouTube',
|
||||||
};
|
};
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
get(key = '', config = {}) {
|
get(key = '', config = {}) {
|
||||||
if (is.empty(key) || is.empty(config)) {
|
if (is.empty(key) || is.empty(config)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let string = getDeep(config.i18n, key);
|
let string = getDeep(config.i18n, key);
|
||||||
|
|
||||||
if (is.empty(string)) {
|
if (is.empty(string)) {
|
||||||
if (Object.keys(resources).includes(key)) {
|
if (Object.keys(resources).includes(key)) {
|
||||||
return resources[key];
|
return resources[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const replace = {
|
const replace = {
|
||||||
'{seektime}': config.seekTime,
|
'{seektime}': config.seekTime,
|
||||||
'{title}': config.title,
|
'{title}': config.title,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.entries(replace).forEach(([k, v]) => {
|
Object.entries(replace).forEach(([k, v]) => {
|
||||||
string = replaceAll(string, k, v);
|
string = replaceAll(string, k, v);
|
||||||
});
|
});
|
||||||
|
|
||||||
return string;
|
return string;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|||||||
+40
-40
@@ -19,54 +19,54 @@ const isEvent = input => instanceOf(input, Event);
|
|||||||
const isKeyboardEvent = input => instanceOf(input, KeyboardEvent);
|
const isKeyboardEvent = input => instanceOf(input, KeyboardEvent);
|
||||||
const isCue = input => instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
|
const isCue = input => instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
|
||||||
const isTrack = input => instanceOf(input, TextTrack) || (!isNullOrUndefined(input) && isString(input.kind));
|
const isTrack = input => instanceOf(input, TextTrack) || (!isNullOrUndefined(input) && isString(input.kind));
|
||||||
const isPromise = input => instanceOf(input, Promise);
|
const isPromise = input => instanceOf(input, Promise) && isFunction(input.then);
|
||||||
|
|
||||||
const isEmpty = input =>
|
const isEmpty = input =>
|
||||||
isNullOrUndefined(input) ||
|
isNullOrUndefined(input) ||
|
||||||
((isString(input) || isArray(input) || isNodeList(input)) && !input.length) ||
|
((isString(input) || isArray(input) || isNodeList(input)) && !input.length) ||
|
||||||
(isObject(input) && !Object.keys(input).length);
|
(isObject(input) && !Object.keys(input).length);
|
||||||
|
|
||||||
const isUrl = input => {
|
const isUrl = input => {
|
||||||
// Accept a URL object
|
// Accept a URL object
|
||||||
if (instanceOf(input, window.URL)) {
|
if (instanceOf(input, window.URL)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be string from here
|
// Must be string from here
|
||||||
if (!isString(input)) {
|
if (!isString(input)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the protocol if required
|
// Add the protocol if required
|
||||||
let string = input;
|
let string = input;
|
||||||
if (!input.startsWith('http://') || !input.startsWith('https://')) {
|
if (!input.startsWith('http://') || !input.startsWith('https://')) {
|
||||||
string = `http://${input}`;
|
string = `http://${input}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return !isEmpty(new URL(string).hostname);
|
return !isEmpty(new URL(string).hostname);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
nullOrUndefined: isNullOrUndefined,
|
nullOrUndefined: isNullOrUndefined,
|
||||||
object: isObject,
|
object: isObject,
|
||||||
number: isNumber,
|
number: isNumber,
|
||||||
string: isString,
|
string: isString,
|
||||||
boolean: isBoolean,
|
boolean: isBoolean,
|
||||||
function: isFunction,
|
function: isFunction,
|
||||||
array: isArray,
|
array: isArray,
|
||||||
weakMap: isWeakMap,
|
weakMap: isWeakMap,
|
||||||
nodeList: isNodeList,
|
nodeList: isNodeList,
|
||||||
element: isElement,
|
element: isElement,
|
||||||
textNode: isTextNode,
|
textNode: isTextNode,
|
||||||
event: isEvent,
|
event: isEvent,
|
||||||
keyboardEvent: isKeyboardEvent,
|
keyboardEvent: isKeyboardEvent,
|
||||||
cue: isCue,
|
cue: isCue,
|
||||||
track: isTrack,
|
track: isTrack,
|
||||||
promise: isPromise,
|
promise: isPromise,
|
||||||
url: isUrl,
|
url: isUrl,
|
||||||
empty: isEmpty,
|
empty: isEmpty,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,15 +5,15 @@
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
export default function loadImage(src, minWidth = 1) {
|
export default function loadImage(src, minWidth = 1) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
|
||||||
const handler = () => {
|
const handler = () => {
|
||||||
delete image.onload;
|
delete image.onload;
|
||||||
delete image.onerror;
|
delete image.onerror;
|
||||||
(image.naturalWidth >= minWidth ? resolve : reject)(image);
|
(image.naturalWidth >= minWidth ? resolve : reject)(image);
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(image, { onload: handler, onerror: handler, src });
|
Object.assign(image, { onload: handler, onerror: handler, src });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
import loadjs from 'loadjs';
|
import loadjs from 'loadjs';
|
||||||
|
|
||||||
export default function loadScript(url) {
|
export default function loadScript(url) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
loadjs(url, {
|
loadjs(url, {
|
||||||
success: resolve,
|
success: resolve,
|
||||||
error: reject,
|
error: reject,
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-55
@@ -8,68 +8,68 @@ import is from './is';
|
|||||||
|
|
||||||
// Load an external SVG sprite
|
// Load an external SVG sprite
|
||||||
export default function loadSprite(url, id) {
|
export default function loadSprite(url, id) {
|
||||||
if (!is.string(url)) {
|
if (!is.string(url)) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefix = 'cache';
|
||||||
|
const hasId = is.string(id);
|
||||||
|
let isCached = false;
|
||||||
|
const exists = () => document.getElementById(id) !== null;
|
||||||
|
|
||||||
|
const update = (container, data) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
container.innerHTML = data;
|
||||||
|
|
||||||
|
// Check again incase of race condition
|
||||||
|
if (hasId && exists()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefix = 'cache';
|
// Inject the SVG to the body
|
||||||
const hasId = is.string(id);
|
document.body.insertAdjacentElement('afterbegin', container);
|
||||||
let isCached = false;
|
};
|
||||||
const exists = () => document.getElementById(id) !== null;
|
|
||||||
|
|
||||||
const update = (container, data) => {
|
// Only load once if ID set
|
||||||
// eslint-disable-next-line no-param-reassign
|
if (!hasId || !exists()) {
|
||||||
container.innerHTML = data;
|
const useStorage = Storage.supported;
|
||||||
|
// Create container
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.setAttribute('hidden', '');
|
||||||
|
|
||||||
// Check again incase of race condition
|
if (hasId) {
|
||||||
if (hasId && exists()) {
|
container.setAttribute('id', id);
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
// Check in cache
|
||||||
|
if (useStorage) {
|
||||||
|
const cached = window.localStorage.getItem(`${prefix}-${id}`);
|
||||||
|
isCached = cached !== null;
|
||||||
|
|
||||||
|
if (isCached) {
|
||||||
|
const data = JSON.parse(cached);
|
||||||
|
update(container, data.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the sprite
|
||||||
|
fetch(url)
|
||||||
|
.then(result => {
|
||||||
|
if (is.empty(result)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the SVG to the body
|
|
||||||
document.body.insertAdjacentElement('afterbegin', container);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only load once if ID set
|
|
||||||
if (!hasId || !exists()) {
|
|
||||||
const useStorage = Storage.supported;
|
|
||||||
// Create container
|
|
||||||
const container = document.createElement('div');
|
|
||||||
container.setAttribute('hidden', '');
|
|
||||||
|
|
||||||
if (hasId) {
|
|
||||||
container.setAttribute('id', id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check in cache
|
|
||||||
if (useStorage) {
|
if (useStorage) {
|
||||||
const cached = window.localStorage.getItem(`${prefix}-${id}`);
|
window.localStorage.setItem(
|
||||||
isCached = cached !== null;
|
`${prefix}-${id}`,
|
||||||
|
JSON.stringify({
|
||||||
if (isCached) {
|
content: result,
|
||||||
const data = JSON.parse(cached);
|
}),
|
||||||
update(container, data.content);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the sprite
|
update(container, result);
|
||||||
fetch(url)
|
})
|
||||||
.then(result => {
|
.catch(() => {});
|
||||||
if (is.empty(result)) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useStorage) {
|
|
||||||
window.localStorage.setItem(
|
|
||||||
`${prefix}-${id}`,
|
|
||||||
JSON.stringify({
|
|
||||||
content: result,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
update(container, result);
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
* @type Number
|
* @type Number
|
||||||
*/
|
*/
|
||||||
export function clamp(input = 0, min = 0, max = 255) {
|
export function clamp(input = 0, min = 0, max = 255) {
|
||||||
return Math.min(Math.max(input, min), max);
|
return Math.min(Math.max(input, min), max);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { clamp };
|
export default { clamp };
|
||||||
|
|||||||
+23
-23
@@ -6,37 +6,37 @@ import is from './is';
|
|||||||
|
|
||||||
// Clone nested objects
|
// Clone nested objects
|
||||||
export function cloneDeep(object) {
|
export function cloneDeep(object) {
|
||||||
return JSON.parse(JSON.stringify(object));
|
return JSON.parse(JSON.stringify(object));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a nested value in an object
|
// Get a nested value in an object
|
||||||
export function getDeep(object, path) {
|
export function getDeep(object, path) {
|
||||||
return path.split('.').reduce((obj, key) => obj && obj[key], object);
|
return path.split('.').reduce((obj, key) => obj && obj[key], object);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deep extend destination object with N more objects
|
// Deep extend destination object with N more objects
|
||||||
export function extend(target = {}, ...sources) {
|
export function extend(target = {}, ...sources) {
|
||||||
if (!sources.length) {
|
if (!sources.length) {
|
||||||
return target;
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = sources.shift();
|
||||||
|
|
||||||
|
if (!is.object(source)) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(source).forEach(key => {
|
||||||
|
if (is.object(source[key])) {
|
||||||
|
if (!Object.keys(target).includes(key)) {
|
||||||
|
Object.assign(target, { [key]: {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
extend(target[key], source[key]);
|
||||||
|
} else {
|
||||||
|
Object.assign(target, { [key]: source[key] });
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const source = sources.shift();
|
return extend(target, ...sources);
|
||||||
|
|
||||||
if (!is.object(source)) {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(source).forEach(key => {
|
|
||||||
if (is.object(source[key])) {
|
|
||||||
if (!Object.keys(target).includes(key)) {
|
|
||||||
Object.assign(target, { [key]: {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
extend(target[key], source[key]);
|
|
||||||
} else {
|
|
||||||
Object.assign(target, { [key]: source[key] });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return extend(target, ...sources);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import is from './is';
|
||||||
|
/**
|
||||||
|
* Silence a Promise-like object.
|
||||||
|
* This is useful for avoiding non-harmful, but potentially confusing "uncaught
|
||||||
|
* play promise" rejection error messages.
|
||||||
|
* @param {Object} value An object that may or may not be `Promise`-like.
|
||||||
|
*/
|
||||||
|
export function silencePromise(value) {
|
||||||
|
if (is.promise(value)) {
|
||||||
|
value.then(null, () => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { silencePromise };
|
||||||
+35
-40
@@ -6,80 +6,75 @@ import is from './is';
|
|||||||
|
|
||||||
// Generate a random ID
|
// Generate a random ID
|
||||||
export function generateId(prefix) {
|
export function generateId(prefix) {
|
||||||
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
|
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format string
|
// Format string
|
||||||
export function format(input, ...args) {
|
export function format(input, ...args) {
|
||||||
if (is.empty(input)) {
|
if (is.empty(input)) {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
return input.toString().replace(/{(\d+)}/g, (match, i) => args[i].toString());
|
return input.toString().replace(/{(\d+)}/g, (match, i) => args[i].toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get percentage
|
// Get percentage
|
||||||
export function getPercentage(current, max) {
|
export function getPercentage(current, max) {
|
||||||
if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
|
if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ((current / max) * 100).toFixed(2);
|
return ((current / max) * 100).toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace all occurances of a string in a string
|
// Replace all occurances of a string in a string
|
||||||
export function replaceAll(input = '', find = '', replace = '') {
|
export const replaceAll = (input = '', find = '', replace = '') =>
|
||||||
return input.replace(
|
input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
|
||||||
new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'),
|
|
||||||
replace.toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to title case
|
// Convert to title case
|
||||||
export function toTitleCase(input = '') {
|
export const toTitleCase = (input = '') =>
|
||||||
return input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase());
|
input.toString().replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase());
|
||||||
}
|
|
||||||
|
|
||||||
// Convert string to pascalCase
|
// Convert string to pascalCase
|
||||||
export function toPascalCase(input = '') {
|
export function toPascalCase(input = '') {
|
||||||
let string = input.toString();
|
let string = input.toString();
|
||||||
|
|
||||||
// Convert kebab case
|
// Convert kebab case
|
||||||
string = replaceAll(string, '-', ' ');
|
string = replaceAll(string, '-', ' ');
|
||||||
|
|
||||||
// Convert snake case
|
// Convert snake case
|
||||||
string = replaceAll(string, '_', ' ');
|
string = replaceAll(string, '_', ' ');
|
||||||
|
|
||||||
// Convert to title case
|
// Convert to title case
|
||||||
string = toTitleCase(string);
|
string = toTitleCase(string);
|
||||||
|
|
||||||
// Convert to pascal case
|
// Convert to pascal case
|
||||||
return replaceAll(string, ' ', '');
|
return replaceAll(string, ' ', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert string to pascalCase
|
// Convert string to pascalCase
|
||||||
export function toCamelCase(input = '') {
|
export function toCamelCase(input = '') {
|
||||||
let string = input.toString();
|
let string = input.toString();
|
||||||
|
|
||||||
// Convert to pascal case
|
// Convert to pascal case
|
||||||
string = toPascalCase(string);
|
string = toPascalCase(string);
|
||||||
|
|
||||||
// Convert first character to lowercase
|
// Convert first character to lowercase
|
||||||
return string.charAt(0).toLowerCase() + string.slice(1);
|
return string.charAt(0).toLowerCase() + string.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove HTML from a string
|
// Remove HTML from a string
|
||||||
export function stripHTML(source) {
|
export function stripHTML(source) {
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
const element = document.createElement('div');
|
const element = document.createElement('div');
|
||||||
fragment.appendChild(element);
|
fragment.appendChild(element);
|
||||||
element.innerHTML = source;
|
element.innerHTML = source;
|
||||||
return fragment.firstChild.innerText;
|
return fragment.firstChild.innerText;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like outerHTML, but also works for DocumentFragment
|
// Like outerHTML, but also works for DocumentFragment
|
||||||
export function getHTML(element) {
|
export function getHTML(element) {
|
||||||
const wrapper = document.createElement('div');
|
const wrapper = document.createElement('div');
|
||||||
wrapper.appendChild(element);
|
wrapper.appendChild(element);
|
||||||
return wrapper.innerHTML;
|
return wrapper.innerHTML;
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-46
@@ -5,74 +5,75 @@
|
|||||||
import is from './is';
|
import is from './is';
|
||||||
|
|
||||||
export function validateRatio(input) {
|
export function validateRatio(input) {
|
||||||
if (!is.array(input) && (!is.string(input) || !input.includes(':'))) {
|
if (!is.array(input) && (!is.string(input) || !input.includes(':'))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ratio = is.array(input) ? input : input.split(':');
|
const ratio = is.array(input) ? input : input.split(':');
|
||||||
|
|
||||||
return ratio.map(Number).every(is.number);
|
return ratio.map(Number).every(is.number);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reduceAspectRatio(ratio) {
|
export function reduceAspectRatio(ratio) {
|
||||||
if (!is.array(ratio) || !ratio.every(is.number)) {
|
if (!is.array(ratio) || !ratio.every(is.number)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [width, height] = ratio;
|
const [width, height] = ratio;
|
||||||
const getDivider = (w, h) => (h === 0 ? w : getDivider(h, w % h));
|
const getDivider = (w, h) => (h === 0 ? w : getDivider(h, w % h));
|
||||||
const divider = getDivider(width, height);
|
const divider = getDivider(width, height);
|
||||||
|
|
||||||
return [width / divider, height / divider];
|
return [width / divider, height / divider];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAspectRatio(input) {
|
export function getAspectRatio(input) {
|
||||||
const parse = ratio => (validateRatio(ratio) ? ratio.split(':').map(Number) : null);
|
const parse = ratio => (validateRatio(ratio) ? ratio.split(':').map(Number) : null);
|
||||||
// Try provided ratio
|
// Try provided ratio
|
||||||
let ratio = parse(input);
|
let ratio = parse(input);
|
||||||
|
|
||||||
// Get from config
|
// Get from config
|
||||||
if (ratio === null) {
|
if (ratio === null) {
|
||||||
ratio = parse(this.config.ratio);
|
ratio = parse(this.config.ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get from embed
|
// Get from embed
|
||||||
if (ratio === null && !is.empty(this.embed) && is.array(this.embed.ratio)) {
|
if (ratio === null && !is.empty(this.embed) && is.array(this.embed.ratio)) {
|
||||||
({ ratio } = this.embed);
|
({ ratio } = this.embed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get from HTML5 video
|
// Get from HTML5 video
|
||||||
if (ratio === null && this.isHTML5) {
|
if (ratio === null && this.isHTML5) {
|
||||||
const { videoWidth, videoHeight } = this.media;
|
const { videoWidth, videoHeight } = this.media;
|
||||||
ratio = reduceAspectRatio([videoWidth, videoHeight]);
|
ratio = reduceAspectRatio([videoWidth, videoHeight]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ratio;
|
return ratio;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set aspect ratio for responsive container
|
// Set aspect ratio for responsive container
|
||||||
export function setAspectRatio(input) {
|
export function setAspectRatio(input) {
|
||||||
if (!this.isVideo) {
|
if (!this.isVideo) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { wrapper } = this.elements;
|
const { wrapper } = this.elements;
|
||||||
const ratio = getAspectRatio.call(this, input);
|
const ratio = getAspectRatio.call(this, input);
|
||||||
const [w, h] = is.array(ratio) ? ratio : [0, 0];
|
const [w, h] = is.array(ratio) ? ratio : [0, 0];
|
||||||
const padding = (100 / w) * h;
|
const padding = (100 / w) * h;
|
||||||
|
|
||||||
wrapper.style.paddingBottom = `${padding}%`;
|
wrapper.style.paddingBottom = `${padding}%`;
|
||||||
|
|
||||||
// For Vimeo we have an extra <div> to hide the standard controls and UI
|
// For Vimeo we have an extra <div> to hide the standard controls and UI
|
||||||
if (this.isVimeo && this.supported.ui) {
|
if (this.isVimeo && !this.config.vimeo.premium && this.supported.ui) {
|
||||||
const height = 240;
|
const height = (100 / this.media.offsetWidth) * parseInt(window.getComputedStyle(this.media).paddingBottom, 10);
|
||||||
const offset = (height - padding) / (height / 50);
|
const offset = (height - padding) / (height / 50);
|
||||||
this.media.style.transform = `translateY(-${offset}%)`;
|
|
||||||
} else if (this.isHTML5) {
|
|
||||||
wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { padding, ratio };
|
this.media.style.transform = `translateY(-${offset}%)`;
|
||||||
|
} else if (this.isHTML5) {
|
||||||
|
wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { padding, ratio };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { setAspectRatio };
|
export default { setAspectRatio };
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user