Moved to provider + type to make it cleaner in future, fix for multiple players

This commit is contained in:
Sam Potts 2017-11-23 17:35:35 +11:00
parent de6f0f1b77
commit 921cefd212
22 changed files with 199 additions and 215 deletions

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

2
demo/dist/demo.js vendored
View File

@ -1,3 +1,3 @@
document.addEventListener("DOMContentLoaded",function(){function e(e,t,o){e&&e.classList[o?"add":"remove"](t)}function t(t,r){if(t in a&&(r||t!==n)&&(n.length||t!==a.video)){switch(t){case a.video:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"media/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"media/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"media/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case a.audio:o.source={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case a.youtube:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://youtube.com/watch?v=bTqVqk7FSmY",type:"youtube"}]};break;case a.vimeo:o.source={type:"video",sources:[{src:"https://vimeo.com/76979871",type:"vimeo"}]}}n=t,Array.from(i).forEach(function(t){return e(t.parentElement,"active",!1)}),e(document.querySelector('[data-source="'+t+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+t).removeAttribute("hidden")}}window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var o=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","fullscreen","pip","airplay"],keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"}});window.player=o;var i=document.querySelectorAll("[data-source]"),a={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},n=window.location.hash.replace("#",""),r=window.history&&window.history.pushState;if(Array.from(i).forEach(function(e){e.addEventListener("click",function(){var o=e.getAttribute("data-source");t(o),r&&window.history.pushState({type:o},"","#"+o)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),r){var s=!n.length;s&&(n=a.video),n in a&&window.history.replaceState({type:n},"",s?"":"#"+n),n!==a.video&&t(n,!0)}}),"plyr.io"===window.location.host&&(!function(e,t,o,i,a,n,r){e.GoogleAnalyticsObject=a,e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,n=t.createElement(o),r=t.getElementsByTagName(o)[0],n.async=1,n.src="//www.google-analytics.com/analytics.js",r.parentNode.insertBefore(n,r)}(window,document,"script",0,"ga"),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview")); document.addEventListener("DOMContentLoaded",function(){function e(e,t,o){e&&e.classList[o?"add":"remove"](t)}function t(t,n){if(t in r&&(n||t!==a)&&(a.length||t!==r.video)){switch(t){case r.video:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"media/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"media/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"media/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case r.audio:o.source={type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case r.youtube:o.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://youtube.com/watch?v=bTqVqk7FSmY",provider:"youtube"}]};break;case r.vimeo:o.source={type:"video",sources:[{src:"https://vimeo.com/76979871",provider:"vimeo"}]}}a=t,Array.from(i).forEach(function(t){return e(t.parentElement,"active",!1)}),e(document.querySelector('[data-source="'+t+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+t).removeAttribute("hidden")}}window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var o=new window.Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","fullscreen","pip","airplay"],keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"}});window.player=o;var i=document.querySelectorAll("[data-source]"),r={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},a=window.location.hash.replace("#",""),n=window.history&&window.history.pushState;if(Array.from(i).forEach(function(e){e.addEventListener("click",function(){var o=e.getAttribute("data-source");t(o),n&&window.history.pushState({type:o},"","#"+o)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),n){var s=!a.length;s&&(a=r.video),a in r&&window.history.replaceState({type:a},"",s?"":"#"+a),a!==r.video&&t(a,!0)}}),"plyr.io"===window.location.host&&(!function(e,t,o,i,r,a,n){e.GoogleAnalyticsObject=r,e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,a=t.createElement(o),n=t.getElementsByTagName(o)[0],a.async=1,a.src="//www.google-analytics.com/analytics.js",n.parentNode.insertBefore(a,n)}(window,document,"script",0,"ga"),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"));
//# sourceMappingURL=demo.js.map //# sourceMappingURL=demo.js.map

File diff suppressed because one or more lines are too long

View File

@ -52,19 +52,7 @@ document.addEventListener('DOMContentLoaded', () => {
captions: { captions: {
active: true, active: true,
}, },
controls: [ controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'fullscreen', 'pip', 'airplay'],
'play-large',
'play',
'progress',
'current-time',
'mute',
'volume',
'captions',
'settings',
'fullscreen',
'pip',
'airplay',
],
keys: { keys: {
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c', google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
}, },
@ -154,7 +142,7 @@ document.addEventListener('DOMContentLoaded', () => {
sources: [ sources: [
{ {
src: 'https://youtube.com/watch?v=bTqVqk7FSmY', src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
type: 'youtube', provider: 'youtube',
}, },
], ],
}; };
@ -167,7 +155,7 @@ document.addEventListener('DOMContentLoaded', () => {
sources: [ sources: [
{ {
src: 'https://vimeo.com/76979871', src: 'https://vimeo.com/76979871',
type: 'vimeo', provider: 'vimeo',
}, },
], ],
}; };

View File

@ -13,6 +13,10 @@ video {
margin: @spacing-base auto; margin: @spacing-base auto;
border-radius: @border-radius-base; border-radius: @border-radius-base;
box-shadow: 0 2px 5px fade(#000, 20%); box-shadow: 0 2px 5px fade(#000, 20%);
&.plyr--audio {
max-width: 480px;
}
} }
.plyr__video-wrapper::after { .plyr__video-wrapper::after {

2
dist/plyr.js vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.js.map vendored

File diff suppressed because one or more lines are too long

151
readme.md
View File

@ -4,8 +4,7 @@ Beware: This branch is currently in beta and not production-ready
# Plyr # Plyr
A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers.
[_modern_](#browser-support) browsers.
[Checkout the demo](https://plyr.io) - [Donate to support Plyr](#donate) [Checkout the demo](https://plyr.io) - [Donate to support Plyr](#donate)
@ -16,8 +15,8 @@ A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo medi
* **Accessible** - full support for VTT captions and screen readers * **Accessible** - full support for VTT captions and screen readers
* **Lightweight** - just 18KB minified and gzipped * **Lightweight** - just 18KB minified and gzipped
* **[Customisable](#html)** - make the player look how you want with the markup you want * **[Customisable](#html)** - make the player look how you want with the markup you want
* **Semantic** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, * **Semantic** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
`<button>`s for buttons. There's no `<span>` or `<a href="#">` button hacks `<span>` or `<a href="#">` button hacks
* **Responsive** - works with any screen size * **Responsive** - works with any screen size
* **HTML Video & Audio** - support for both formats * **HTML Video & Audio** - support for both formats
* **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback * **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
@ -64,13 +63,12 @@ npm install plyr
## Quick setup ## Quick setup
Here's a quick run through on getting up and running. There's also a Here's a quick run through on getting up and running. There's also a [demo on Codepen](http://codepen.io/sampotts/pen/jARJYp).
[demo on Codepen](http://codepen.io/sampotts/pen/jARJYp).
### HTML ### HTML
Plyr extends upon the standard HTML5 markup so that's all you need for those types. More info on advanced HTML markup Plyr extends upon the standard HTML5 markup so that's all you need for those types. More info on advanced HTML markup can be found under
can be found under [initialising](#initialising). [initialising](#initialising).
#### HTML5 Video #### HTML5 Video
@ -98,29 +96,28 @@ For YouTube and Vimeo, Plyr uses the standard YouTube API markup (an empty `<div
#### YouTube embed #### YouTube embed
```html ```html
<div id="player" data-plyr-provider="youtube" data-plyr-provider-id="bTqVqk7FSmY"></div> <div id="player" data-plyr-provider="youtube" data-plyr-embed-id="bTqVqk7FSmY"></div>
``` ```
#### Vimeo embed #### Vimeo embed
```html ```html
<div id="player" data-plyr-provider="vimeo" data-plyr-provider-id="143418951"></div> <div id="player" data-plyr-provider="vimeo" data-plyr-embed-id="143418951"></div>
``` ```
Note: In both cases, `data-plyr-provider-id` value can be the ID or URL for the media. Note: In both cases, `data-plyr-embed-id` value can be the ID or URL for the media.
### JavaScript ### JavaScript
Include the `plyr.js` script before the closing `</body>` tag and then call `plyr.setup()`. More info on `setup()` can Include the `plyr.js` script before the closing `</body>` tag and then call `plyr.setup()`. More info on `setup()` can be found under
be found under [initialising](#initialising). [initialising](#initialising).
```html ```html
<script src="path/to/plyr.js"></script> <script src="path/to/plyr.js"></script>
<script>const player = new Plyr('#player');</script> <script>const player = new Plyr('#player');</script>
``` ```
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
following:
```html ```html
<script src="https://cdn.plyr.io/2.0.13/plyr.js"></script> <script src="https://cdn.plyr.io/2.0.13/plyr.js"></script>
@ -134,8 +131,7 @@ Include the `plyr.css` stylsheet into your `<head>`
<link rel="stylesheet" href="path/to/plyr.css"> <link rel="stylesheet" href="path/to/plyr.css">
``` ```
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
following:
```html ```html
<link rel="stylesheet" href="https://cdn.plyr.io/2.0.13/plyr.css"> <link rel="stylesheet" href="https://cdn.plyr.io/2.0.13/plyr.css">
@ -143,50 +139,45 @@ following:
### 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 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 [options](#options) below. For reference, the CDN hosted SVG sprite can be found at reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/2.0.13/plyr.svg`.
`https://cdn.plyr.io/2.0.13/plyr.svg`.
## Advanced ## Advanced
### LESS & SASS/SCSS ### LESS & SASS/SCSS
You can use `plyr.less` or `plyr.scss` file included in `/src` as part of your build and change variables to suit your You can use `plyr.less` or `plyr.scss` file included in `/src` as part of your build and change variables to suit your design. The LESS and SASS require you to
design. The LESS and SASS require you to use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you be should already!) as all declarations use the W3C definitions.
(you be should 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 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
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.
### SVG ### SVG
The icons used in the Plyr controls are loaded in an SVG sprite. The sprite is automatically loaded from our CDN by The icons used in the Plyr controls are loaded in an SVG sprite. The sprite is automatically loaded from our CDN by default. If you already have an icon build
default. If you already have an icon build system in place, you can include the source plyr icons (see `/src/sprite` for system in place, you can include the source plyr icons (see `/src/sprite` for source icons).
source icons).
#### Using the `iconUrl` option #### Using the `iconUrl` option
You can however specify your own `iconUrl` option and Plyr will determine if the url is absolute and requires loading by You can however specify your own `iconUrl` option and Plyr will determine if the url is absolute and requires loading by AJAX/CORS due to current browser
AJAX/CORS due to current browser limitations or if it's a relative path, just use the path directly. limitations or if it's a relative path, just use the path directly.
If you're using the `<base>` tag on your site, you may need to use something like this: If you're using the `<base>` tag on your site, you may need to use something like this: [svgfixer.js](https://gist.github.com/leonderijke/c5cf7c5b2e424c0061d2)
[svgfixer.js](https://gist.github.com/leonderijke/c5cf7c5b2e424c0061d2)
More info on SVG sprites here: More info on SVG sprites here: [http://css-tricks.com/svg-sprites-use-better-icon-fonts/](http://css-tricks.com/svg-sprites-use-better-icon-fonts/) and the AJAX
[http://css-tricks.com/svg-sprites-use-better-icon-fonts/](http://css-tricks.com/svg-sprites-use-better-icon-fonts/) and technique here: [http://css-tricks.com/ajaxing-svg-sprite/](http://css-tricks.com/ajaxing-svg-sprite/)
the AJAX technique here: [http://css-tricks.com/ajaxing-svg-sprite/](http://css-tricks.com/ajaxing-svg-sprite/)
### Cross Origin (CORS) ### Cross Origin (CORS)
You'll notice the `crossorigin` attribute on the example `<video>` elements. This is because the TextTrack captions are You'll notice the `crossorigin` attribute on the example `<video>` elements. This is because the TextTrack captions are loaded from another domain. If your
loaded from another domain. If your TextTrack captions are also hosted on another domain, you will need to add this TextTrack captions are also hosted on another domain, you will need to add this attribute and make sure your host has the correct headers setup. For more info
attribute and make sure your host has the correct headers setup. For more info on CORS checkout the MDN docs: on CORS checkout the MDN docs:
[https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) [https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)
### Captions ### Captions
WebVTT captions are supported. To add a caption track, check the HTML example above and look for the `<track>` element. WebVTT captions are supported. To add a caption track, check the HTML example above and look for the `<track>` element. Be sure to
Be sure to [validate your caption files](https://quuz.org/webvtt/). [validate your caption files](https://quuz.org/webvtt/).
### JavaScript ### JavaScript
@ -194,11 +185,10 @@ Be sure to [validate your caption files](https://quuz.org/webvtt/).
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 that's compatible with * A CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
[`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
* A [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement) * A [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
* A [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList) or Array of * A [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList) or Array of [HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement) -
[HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement) - the first element will be used the first element will be used
* A [jQuery](https://jquery.com) object - if multiple are passed, the first element will be used * A [jQuery](https://jquery.com) object - if multiple are passed, the first element will be used
Here's some examples Here's some examples
@ -221,8 +211,8 @@ Passing a [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList):
const player = new Plyr(document.querySelectorAll('.js-player')); const player = new Plyr(document.querySelectorAll('.js-player'));
``` ```
The NodeList, HTMLElement or string selector can be the target `<video>`, `<audio>` or `[data-plyr-provider]` (for The NodeList, HTMLElement or string selector can be the target `<video>`, `<audio>` or `[data-plyr-provider]` (for embeds) element itself or a container
embeds) element itself or a container element. element.
The second argument for the constructor is the [#options](options) object: The second argument for the constructor is the [#options](options) object:
@ -232,20 +222,17 @@ const player = new Plyr('#player', {
}); });
``` ```
The constructor will return a Plyr object that can be used with the [API](#api) methods. See the [API](#api) section for The constructor will return a Plyr object that can be used with the [API](#api) methods. See the [API](#api) section for more info.
more info.
#### Options #### Options
Options can be passed as an object to the constructor as above or as JSON in `data-plyr-config` attribute on each of Options can be passed as an object to the constructor as above or as JSON in `data-plyr-config` attribute on each of your target elements:
your target elements:
```html ```html
<video src="/path/to/video.mp4" id="player" controls data-plyr-config='{ "title": "This is an example video", "volume": 1, "debug": true }'></video> <video src="/path/to/video.mp4" id="player" controls data-plyr-config='{ "title": "This is an example video", "volume": 1, "debug": true }'></video>
``` ```
Note the single quotes encapsulating the JSON and double quotes on the object keys. Only string values need double Note the single quotes encapsulating the JSON and double quotes on the object keys. Only string values need double quotes.
quotes.
| Option | Type | Default | Description | | Option | Type | Default | Description |
| -------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | -------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@ -259,7 +246,7 @@ quotes.
| `iconPrefix` | String | `plyr` | Specify the id prefix for the icons used in the default controls (e.g. "plyr-play" would be "plyr"). This is to prevent clashes if you're using your own SVG sprite but with the default controls. Most people can ignore this option. | | `iconPrefix` | String | `plyr` | Specify the id prefix for the icons used in the default controls (e.g. "plyr-play" would be "plyr"). This is to prevent clashes if you're using your own SVG sprite but with the default controls. Most people can ignore this option. |
| `blankUrl` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. | | `blankUrl` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. |
| `autoplay` | Boolean | `false` | Autoplay the media on load. This is generally advised against on UX grounds. It is also disabled by default in some browsers. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. | | `autoplay` | Boolean | `false` | Autoplay the media on load. This is generally advised against on UX grounds. It is also disabled by default in some browsers. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
| `autopause`&sup1; | Boolean | `false` | Only allow one player playing at once. | | `autopause`&sup1; | Boolean | `true` | Only allow one player playing at once. |
| `seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind. | | `seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind. |
| `volume` | Number | `1` | A number, between 0 and 1, representing the initial volume of the player. | | `volume` | Number | `1` | A number, between 0 and 1, representing the initial volume of the player. |
| `muted` | Boolean | `false` | Whether to start playback muted. If the `muted` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. | | `muted` | Boolean | `false` | Whether to start playback muted. If the `muted` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
@ -290,8 +277,7 @@ There are methods, setters and getters on a Plyr object.
### Object ### Object
The easiest way to access the Plyr object is to set the return value from your call to the constructor to a variable. The easiest way to access the Plyr object is to set the return value from your call to the constructor to a variable. For example:
For example:
```javascript ```javascript
const player = new Plyr('#player', { const player = new Plyr('#player', {
@ -440,7 +426,7 @@ player.source = {
sources: [ sources: [
{ {
src: 'bTqVqk7FSmY', src: 'bTqVqk7FSmY',
type: 'youtube', provider: 'youtube',
}, },
], ],
}; };
@ -456,7 +442,7 @@ player.source = {
sources: [ sources: [
{ {
src: '143418951', src: '143418951',
type: 'vimeo', provider: 'vimeo',
}, },
], ],
}; };
@ -476,10 +462,9 @@ _Note:_ `src` property for YouTube and Vimeo can either be the video ID or the w
## Events ## Events
You can listen for events on the target element you setup Plyr on (see example under the table). Some events only apply You can listen for events on the target element you setup Plyr on (see example under the table). Some events only apply to HTML5 audio and video. Using your
to HTML5 audio and video. Using your reference to the instance, you can use the `on()` API method or reference to the instance, you can use the `on()` API method or `addEventListener()`. Access to the API can be obtained this way through the `event.detail.plyr`
`addEventListener()`. Access to the API can be obtained this way through the `event.detail.plyr` property. Here's an property. Here's an example:
example:
```javascript ```javascript
player.on('ready', event => { player.on('ready', event => {
@ -539,10 +524,9 @@ Some event details borrowed from [MDN](https://developer.mozilla.org/en-US/docs/
## Embeds ## Embeds
YouTube and Vimeo are currently supported and function much like a HTML5 video. Similar events and API methods are YouTube and Vimeo are currently supported and function much like a HTML5 video. Similar events and API methods are available for all types. However if you wish
available for all types. However if you wish to access the API's directly. You can do so via the `embed` property of 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
your player object - e.g. `player.embed`. You can then use the relevant methods from the third party APIs. More info on third party APIs. More info on the respective API's here:
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)
@ -551,9 +535,8 @@ _Note_: Not all API methods may work 100%. Your mileage may vary. It's better to
## Shortcuts ## Shortcuts
By default, a player will bind the following keyboard shortcuts when it has focus. If you have the `global` option to By default, a player will bind the following keyboard shortcuts when it has focus. If you have the `global` option to `true` and there's only one player in the
`true` and there's only one player in the document then the shortcuts will work when any element has focus, apart from document then the shortcuts will work when any element has focus, apart from an element that requires input.
an element that requires input.
| Key | Action | | Key | Action |
| ---------- | -------------------------------------- | | ---------- | -------------------------------------- |
@ -571,9 +554,8 @@ an element that requires input.
## Streaming ## Streaming
Because Plyr is an extension of the standard HTML5 video and audio elements, third party streaming plugins can be used Because Plyr is an extension of the standard HTML5 video and audio elements, third party streaming plugins can be used with Plyr. Massive thanks to Matias
with Plyr. Massive thanks to Matias Russitto ([@russitto](https://github.com/russitto)) for working on this. Here's a Russitto ([@russitto](https://github.com/russitto)) for working on this. Here's a few examples:
few examples:
* Using [hls.js](https://github.com/dailymotion/hls.js) - [Demo](http://codepen.io/sampotts/pen/JKEMqB) * Using [hls.js](https://github.com/dailymotion/hls.js) - [Demo](http://codepen.io/sampotts/pen/JKEMqB)
* Using [Shaka](https://github.com/google/shaka-player) - [Demo](http://codepen.io/sampotts/pen/zBNpVR) * Using [Shaka](https://github.com/google/shaka-player) - [Demo](http://codepen.io/sampotts/pen/zBNpVR)
@ -598,13 +580,11 @@ Plyr supports the last 2 versions of most _modern_ browsers. IE11 is also suppor
| IE10+ | ✔&sup2; | | IE10+ | ✔&sup2; |
| IE9 | API only&sup3; | | IE9 | API only&sup3; |
1. Mobile Safari on the iPhone forces the native player for `<video>` unless the `playsinline` attribute is present. 1. Mobile Safari on the iPhone forces the native player for `<video>` unless the `playsinline` attribute is present. Volume controls are also disabled.
Volume controls are also disabled.
2. Native player used (no support for `<progress>` or `<input type="range">`) but the API is supported (v1.0.28+) 2. Native player used (no support for `<progress>` or `<input type="range">`) but the API is supported (v1.0.28+)
3. IE10 has no native fullscreen support, fallback can be used (see [options](#options)) 3. IE10 has no native fullscreen support, fallback can be used (see [options](#options))
The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for smartphones, you could use:
smartphones, you could use:
```javascript ```javascript
{ {
@ -616,10 +596,9 @@ If a User Agent is disabled but supports `<video>` and `<audio>` natively, it wi
## RangeTouch ## RangeTouch
Some touch browsers (particularly Mobile Safari on iOS) seem to have issues with `<input type="range">` elements whereby Some touch browsers (particularly Mobile Safari on iOS) seem to have issues with `<input type="range">` elements whereby touching the track to set the value
touching the track to set the value doesn't work and sliding the thumb can be tricky. To combat this, I've created doesn't work and sliding the thumb can be tricky. To combat this, I've created [RangeTouch](https://rangetouch.com) which I'd recommend including in your
[RangeTouch](https://rangetouch.com) which I'd recommend including in your solution. It's a tiny script with a nice solution. It's a tiny script with a nice benefit for users on touch devices.
benefit for users on touch devices.
## Issues ## Issues
@ -627,13 +606,13 @@ If you find anything weird with Plyr, please let us know using the GitHub issues
## Author ## Author
Plyr is developed by [@sam_potts](https://twitter.com/sam_potts) / [sampotts.me](http://sampotts.me) with help from the Plyr is developed by [@sam_potts](https://twitter.com/sam_potts) / [sampotts.me](http://sampotts.me) with help from the awesome
awesome [contributors](https://github.com/sampotts/plyr/graphs/contributors) [contributors](https://github.com/sampotts/plyr/graphs/contributors)
## Donate ## Donate
Plyr costs money to run, not only my time - I donate that for free but domains, hosting and more. Any help is Plyr costs money to run, not only my time - I donate that for free but domains, hosting and more. Any help is appreciated...
appreciated... [Donate to support Plyr](https://www.paypal.me/pottsy/20usd) [Donate to support Plyr](https://www.paypal.me/pottsy/20usd)
## Mentions ## Mentions
@ -660,16 +639,14 @@ appreciated... [Donate to support Plyr](https://www.paypal.me/pottsy/20usd)
* [Oscar Radio](http://oscar-radio.xyz/) * [Oscar Radio](http://oscar-radio.xyz/)
* [Sparkk TV](https://www.sparkktv.com/) * [Sparkk TV](https://www.sparkktv.com/)
Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the above list. It'd be awesome to see how Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the above list. It'd be awesome to see how you're using Plyr :-)
you're using Plyr :-)
## Useful links and credits ## Useful links and credits
Credit to the PayPal HTML5 Video player from which Plyr's caption functionality was originally ported from: Credit to the PayPal HTML5 Video player from which Plyr's caption functionality was originally ported from:
* [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player) * [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player)
* [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by * [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
[@arayutw](https://twitter.com/arayutw)
## Thanks ## Thanks

View File

@ -32,7 +32,7 @@ const captions = {
} }
// Only Vimeo and HTML5 video supported at this point // Only Vimeo and HTML5 video supported at this point
if (!['video', 'vimeo'].includes(this.type) || (this.type === 'video' && !support.textTracks)) { if (!this.isVideo || this.isYouTube || (this.isVideo && !support.textTracks)) {
// Clear menu and hide // Clear menu and hide
if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) { if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this); controls.setCaptionsMenu.call(this);
@ -71,7 +71,7 @@ const captions = {
// Set the captions language // Set the captions language
setLanguage() { setLanguage() {
// Setup HTML5 track rendering // Setup HTML5 track rendering
if (this.type === 'video') { if (this.isVideo) {
captions.getTracks.call(this).forEach(track => { captions.getTracks.call(this).forEach(track => {
// Remove previous bindings // Remove previous bindings
utils.on(track, 'cuechange', event => captions.setCue.call(this, event)); utils.on(track, 'cuechange', event => captions.setCue.call(this, event));
@ -91,7 +91,7 @@ const captions = {
captions.setCue.call(this, currentTrack); captions.setCue.call(this, currentTrack);
} }
} }
} else if (this.type === 'vimeo' && this.captions.active) { } else if (this.isVimeo && this.captions.active) {
this.embed.enableTextTrack(this.language); this.embed.enableTextTrack(this.language);
} }
}, },

2
src/js/controls.js vendored
View File

@ -445,7 +445,7 @@ const controls = {
} }
// Toggle the pane and tab // Toggle the pane and tab
const toggle = !utils.is.empty(this.options.quality) && this.type === 'youtube'; const toggle = !utils.is.empty(this.options.quality) && this.isYouTube;
controls.toggleTab.call(this, type, toggle); controls.toggleTab.call(this, type, toggle);
// If we're hiding, nothing more to do // If we're hiding, nothing more to do

View File

@ -13,7 +13,7 @@ const defaults = {
autoplay: false, autoplay: false,
// Only allow one media playing at once (vimeo only) // Only allow one media playing at once (vimeo only)
autopause: false, autopause: true,
// Default time to skip when rewind/fast forward // Default time to skip when rewind/fast forward
seekTime: 10, seekTime: 10,
@ -267,6 +267,7 @@ const defaults = {
embed: 'plyr__video-embed', embed: 'plyr__video-embed',
control: 'plyr__control', control: 'plyr__control',
type: 'plyr--{0}', type: 'plyr--{0}',
provider: 'plyr--{0}',
stopped: 'plyr--stopped', stopped: 'plyr--stopped',
playing: 'plyr--playing', playing: 'plyr--playing',
loading: 'plyr--loading', loading: 'plyr--loading',

View File

@ -92,7 +92,7 @@ const fullscreen = {
// Setup fullscreen // Setup fullscreen
setup() { setup() {
if (!this.supported.ui || this.type === 'audio' || !this.config.fullscreen.enabled) { if (!this.supported.ui || this.isAudio || !this.config.fullscreen.enabled) {
return; return;
} }

View File

@ -221,7 +221,7 @@ const listeners = {
// Handle the media finishing // Handle the media finishing
utils.on(this.media, 'ended', () => { utils.on(this.media, 'ended', () => {
// Show poster on end // Show poster on end
if (this.type === 'video' && this.config.showPosterOnEnd) { if (this.isHTML5 && this.isVideo && this.config.showPosterOnEnd) {
// Restart // Restart
this.restart(); this.restart();
@ -243,7 +243,7 @@ const listeners = {
utils.on(this.media, 'stalled waiting canplay seeked playing', event => ui.checkLoading.call(this, event)); utils.on(this.media, 'stalled waiting canplay seeked playing', event => ui.checkLoading.call(this, event));
// Click video // Click video
if (this.supported.ui && this.config.clickToPlay && this.type !== 'audio') { if (this.supported.ui && this.config.clickToPlay && !this.isAudio) {
// Re-fetch the wrapper // Re-fetch the wrapper
const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`); const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`);

View File

@ -23,6 +23,9 @@ const media = {
// Add type class // Add type class
utils.toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', this.type), true); utils.toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', this.type), true);
// Add provider class
utils.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) {
@ -31,7 +34,7 @@ const media = {
if (this.supported.ui) { if (this.supported.ui) {
// Check for picture-in-picture support // Check for picture-in-picture support
utils.toggleClass(this.elements.container, this.config.classNames.pip.supported, support.pip && this.type === 'video'); utils.toggleClass(this.elements.container, this.config.classNames.pip.supported, support.pip && this.isHTML5 && this.isVideo);
// Check for airplay support // Check for airplay support
utils.toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5); utils.toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5);
@ -47,7 +50,7 @@ const media = {
} }
// Inject the player wrapper // Inject the player wrapper
if (['video', 'youtube', 'vimeo'].includes(this.type)) { if (this.isVideo || this.isYouTube || this.isVimeo) {
// Create the wrapper div // Create the wrapper div
this.elements.wrapper = utils.createElement('div', { this.elements.wrapper = utils.createElement('div', {
class: this.config.classNames.video, class: this.config.classNames.video,
@ -58,7 +61,7 @@ const media = {
} }
if (this.isEmbed) { if (this.isEmbed) {
switch (this.type) { switch (this.provider) {
case 'youtube': case 'youtube':
youtube.setup.call(this); youtube.setup.call(this);
break; break;

View File

@ -4,13 +4,12 @@
import utils from './../utils'; import utils from './../utils';
import captions from './../captions'; import captions from './../captions';
import controls from './../controls';
import ui from './../ui'; import ui from './../ui';
const vimeo = { const vimeo = {
setup() { setup() {
// Remove old containers // Remove old containers
const containers = utils.getElements.call(this, `[id^="${this.type}-"]`); const containers = utils.getElements.call(this, `[id^="${this.provider}-"]`);
Array.from(containers).forEach(utils.removeElement); Array.from(containers).forEach(utils.removeElement);
// Add embed class for responsive // Add embed class for responsive
@ -20,7 +19,7 @@ const vimeo = {
vimeo.setAspectRatio.call(this); vimeo.setAspectRatio.call(this);
// Set ID // Set ID
this.media.setAttribute('id', utils.generateId(this.type)); this.media.setAttribute('id', utils.generateId(this.provider));
// Load the API if not already // Load the API if not already
if (!utils.is.object(window.Vimeo)) { if (!utils.is.object(window.Vimeo)) {

View File

@ -11,7 +11,7 @@ const youtube = {
const videoId = utils.parseYouTubeId(this.embedId); const videoId = utils.parseYouTubeId(this.embedId);
// Remove old containers // Remove old containers
const containers = utils.getElements.call(this, `[id^="${this.type}-"]`); const containers = utils.getElements.call(this, `[id^="${this.provider}-"]`);
Array.from(containers).forEach(utils.removeElement); Array.from(containers).forEach(utils.removeElement);
// Add embed class for responsive // Add embed class for responsive
@ -21,7 +21,7 @@ const youtube = {
youtube.setAspectRatio.call(this); youtube.setAspectRatio.call(this);
// Set ID // Set ID
this.media.setAttribute('id', utils.generateId(this.type)); this.media.setAttribute('id', utils.generateId(this.provider));
// Setup API // Setup API
if (utils.is.object(window.YT)) { if (utils.is.object(window.YT)) {
@ -31,6 +31,7 @@ const youtube = {
utils.loadScript(this.config.urls.youtube.api); utils.loadScript(this.config.urls.youtube.api);
// Setup callback for the API // Setup callback for the API
// YouTube has it's own system of course...
window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || []; window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || [];
// Add to queue // Add to queue

View File

@ -5,8 +5,8 @@
// License: The MIT License (MIT) // License: The MIT License (MIT)
// ========================================================================== // ==========================================================================
import { providers, types } from './types';
import defaults from './defaults'; import defaults from './defaults';
import types from './types';
import support from './support'; import support from './support';
import utils from './utils'; import utils from './utils';
@ -40,11 +40,7 @@ class Plyr {
} }
// jQuery, NodeList or Array passed, use first element // jQuery, NodeList or Array passed, use first element
if ( if ((window.jQuery && this.media instanceof jQuery) || utils.is.nodeList(this.media) || utils.is.array(this.media)) {
(window.jQuery && this.media instanceof jQuery) ||
utils.is.nodeList(this.media) ||
utils.is.array(this.media)
) {
// eslint-disable-next-line // eslint-disable-next-line
this.media = this.media[0]; this.media = this.media[0];
} }
@ -149,7 +145,7 @@ class Plyr {
// Embed attributes // Embed attributes
const attributes = { const attributes = {
provider: 'data-plyr-provider', provider: 'data-plyr-provider',
id: 'data-plyr-provider-id', id: 'data-plyr-embed-id',
}; };
// Different setup based on type // Different setup based on type
@ -157,16 +153,18 @@ class Plyr {
// TODO: Handle passing an iframe for true progressive enhancement // TODO: Handle passing an iframe for true progressive enhancement
// case 'iframe': // case 'iframe':
case 'div': case 'div':
this.type = this.media.getAttribute(attributes.provider); this.type = types.video; // Audio will come later for external providers
this.provider = this.media.getAttribute(attributes.provider);
this.embedId = this.media.getAttribute(attributes.id); this.embedId = this.media.getAttribute(attributes.id);
if (utils.is.empty(this.type)) { if (utils.is.empty(this.provider) || !Object.keys(providers).includes(this.provider)) {
this.console.error('Setup failed: embed type missing'); this.console.error('Setup failed: Invalid provider');
return; return;
} }
// Try and get the embed id
if (utils.is.empty(this.embedId)) { if (utils.is.empty(this.embedId)) {
this.console.error('Setup failed: video id missing'); this.console.error('Setup failed: Embed ID or URL missing');
return; return;
} }
@ -179,19 +177,24 @@ class Plyr {
case 'video': case 'video':
case 'audio': case 'audio':
this.type = type; this.type = type;
this.provider = providers.html5;
if (this.media.hasAttribute('crossorigin')) { if (this.media.hasAttribute('crossorigin')) {
this.config.crossorigin = true; this.config.crossorigin = true;
} }
if (this.media.hasAttribute('autoplay')) { if (this.media.hasAttribute('autoplay')) {
this.config.autoplay = true; this.config.autoplay = true;
} }
if (this.media.hasAttribute('playsinline')) { if (this.media.hasAttribute('playsinline')) {
this.config.inline = true; this.config.inline = true;
} }
if (this.media.hasAttribute('muted')) { if (this.media.hasAttribute('muted')) {
this.config.muted = true; this.config.muted = true;
} }
if (this.media.hasAttribute('loop')) { if (this.media.hasAttribute('loop')) {
this.config.loop.active = true; this.config.loop.active = true;
} }
@ -207,7 +210,7 @@ class Plyr {
storage.setup.call(this); storage.setup.call(this);
// Check for support again but with type // Check for support again but with type
this.supported = support.check(this.type, this.config.inline); this.supported = support.check(this.type, this.provider, this.config.inline);
// If no support for even API, bail // If no support for even API, bail
if (!this.supported.api) { if (!this.supported.api) {
@ -253,17 +256,25 @@ class Plyr {
// --------------------------------------- // ---------------------------------------
/** /**
* If the player is HTML5 * Types and provider helpers
*/ */
get isHTML5() { get isHTML5() {
return types.html5.includes(this.type); return this.provider === providers.html5;
} }
/**
* If the player is an embed - e.g. YouTube or Vimeo
*/
get isEmbed() { get isEmbed() {
return types.embed.includes(this.type); return this.isYouTube || this.isVimeo;
}
get isYouTube() {
return this.provider === providers.youtube;
}
get isVimeo() {
return this.provider === providers.vimeo;
}
get isVideo() {
return this.type === types.video;
}
get isAudio() {
return this.type === types.audio;
} }
/** /**
@ -518,11 +529,7 @@ class Plyr {
} }
// Get audio tracks // Get audio tracks
return ( return this.media.mozHasAudio || Boolean(this.media.webkitAudioDecodedByteCount) || Boolean(this.media.audioTracks && this.media.audioTracks.length);
this.media.mozHasAudio ||
Boolean(this.media.webkitAudioDecodedByteCount) ||
Boolean(this.media.audioTracks && this.media.audioTracks.length)
);
} }
/** /**
@ -683,7 +690,7 @@ class Plyr {
* @param {input} - the URL for the new poster image * @param {input} - the URL for the new poster image
*/ */
set poster(input) { set poster(input) {
if (!this.isHTML5 || this.type !== 'video') { if (!this.isHTML5 || !this.isVideo) {
this.console.warn('Poster can only be set on HTML5 video'); this.console.warn('Poster can only be set on HTML5 video');
return; return;
} }
@ -697,7 +704,7 @@ class Plyr {
* Get the current poster image * Get the current poster image
*/ */
get poster() { get poster() {
if (!this.isHTML5 || this.type !== 'video') { if (!this.isHTML5 || !this.isVideo) {
return null; return null;
} }
@ -731,9 +738,7 @@ class Plyr {
} }
// If the method is called without parameter, toggle based on current value // If the method is called without parameter, toggle based on current value
const show = utils.is.boolean(input) const show = utils.is.boolean(input) ? input : this.elements.container.className.indexOf(this.config.classNames.captions.active) === -1;
? input
: this.elements.container.className.indexOf(this.config.classNames.captions.active) === -1;
// Nothing to change... // Nothing to change...
if (this.captions.enabled === show) { if (this.captions.enabled === show) {
@ -828,11 +833,7 @@ class Plyr {
this.fullscreen.active = !this.fullscreen.active; this.fullscreen.active = !this.fullscreen.active;
// Add class hook // Add class hook
utils.toggleClass( utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.fallback, this.fullscreen.active);
this.elements.container,
this.config.classNames.fullscreen.fallback,
this.fullscreen.active
);
// Make sure we don't lose scroll position // Make sure we don't lose scroll position
if (this.fullscreen.active) { if (this.fullscreen.active) {
@ -920,7 +921,7 @@ class Plyr {
} }
// Don't hide if no UI support or it's audio // Don't hide if no UI support or it's audio
if (!this.supported.ui || this.type === 'audio') { if (!this.supported.ui || this.isAudio) {
return this; return this;
} }
@ -980,13 +981,13 @@ class Plyr {
// then set the timer to hide the controls // then set the timer to hide the controls
if (!show || this.playing) { if (!show || this.playing) {
this.timers.controls = window.setTimeout(() => { this.timers.controls = window.setTimeout(() => {
console.warn({ /* this.console.warn({
pressed: this.elements.controls.pressed, pressed: this.elements.controls.pressed,
hover: this.elements.controls.pressed, hover: this.elements.controls.pressed,
playing: this.playing, playing: this.playing,
paused: this.paused, paused: this.paused,
loading: this.loading, loading: this.loading,
}); }); */
// If the mouse is over the controls (and not entering fullscreen), bail // If the mouse is over the controls (and not entering fullscreen), bail
if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) { if ((this.elements.controls.pressed || this.elements.controls.hover) && !isEnterFullscreen) {
@ -1105,8 +1106,18 @@ class Plyr {
}; };
// Type specific stuff // Type specific stuff
switch (this.type) { switch (`${this.provider}:${this.type}`) {
case 'youtube': case 'html5:video':
case 'html5:audio':
// Restore native video controls
ui.toggleNativeControls.call(this, true);
// Clean up
done();
break;
case 'youtube:video':
// Clear timers // Clear timers
window.clearInterval(this.timers.buffering); window.clearInterval(this.timers.buffering);
window.clearInterval(this.timers.playing); window.clearInterval(this.timers.playing);
@ -1119,7 +1130,7 @@ class Plyr {
break; break;
case 'vimeo': case 'vimeo:video':
// Destroy Vimeo API // Destroy Vimeo API
// then clean up (wait, to prevent postmessage errors) // then clean up (wait, to prevent postmessage errors)
this.embed.unload().then(done); this.embed.unload().then(done);
@ -1129,16 +1140,6 @@ class Plyr {
break; break;
case 'video':
case 'audio':
// Restore native video controls
ui.toggleNativeControls.call(this, true);
// Clean up
done();
break;
default: default:
break; break;
} }

View File

@ -2,7 +2,7 @@
// Plyr source update // Plyr source update
// ========================================================================== // ==========================================================================
import types from './types'; import { providers } from './types';
import utils from './utils'; import utils from './utils';
import media from './media'; import media from './media';
import ui from './ui'; import ui from './ui';
@ -48,35 +48,25 @@ const source = {
this.elements.container.removeAttribute('class'); this.elements.container.removeAttribute('class');
} }
// Set the type // Set the type and provider
if ('type' in input) { this.type = input.type;
this.type = input.type; this.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5;
// Get child type for video (it might be an embed)
if (this.type === 'video') {
const firstSource = input.sources[0];
if ('type' in firstSource && types.embed.includes(firstSource.type)) {
this.type = firstSource.type;
}
}
}
// Check for support // Check for support
this.supported = support.check(this.type, this.config.inline); this.supported = support.check(this.type, this.provider, this.config.inline);
// Create new markup // Create new markup
switch (this.type) { switch (`${this.provider}:${this.type}`) {
case 'video': case 'html5:video':
this.media = utils.createElement('video'); this.media = utils.createElement('video');
break; break;
case 'audio': case 'html5:audio':
this.media = utils.createElement('audio'); this.media = utils.createElement('audio');
break; break;
case 'youtube': case 'youtube:video':
case 'vimeo': case 'vimeo:video':
this.media = utils.createElement('div'); this.media = utils.createElement('div');
this.embedId = input.sources[0].src; this.embedId = input.sources[0].src;
break; break;
@ -117,7 +107,6 @@ const source = {
// Restore class hooks // Restore class hooks
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.supported.ui && this.captions.enabled); utils.toggleClass(this.elements.container, this.config.classNames.captions.active, this.supported.ui && this.captions.enabled);
ui.addStyleHook.call(this); ui.addStyleHook.call(this);
// Set new sources for html5 // Set new sources for html5

View File

@ -12,29 +12,29 @@ const support = {
// Check for support // Check for support
// Basic functionality vs full UI // Basic functionality vs full UI
check(type, inline) { check(type, provider, inline) {
let api = false; let api = false;
let ui = false; let ui = false;
const browser = utils.getBrowser(); const browser = utils.getBrowser();
const playsInline = browser.isIPhone && inline && support.inline; const playsInline = browser.isIPhone && inline && support.inline;
switch (type) { switch (`${provider}:${type}`) {
case 'video': case 'html5:video':
api = support.video; api = support.video;
ui = api && support.rangeInput && (!browser.isIPhone || playsInline); ui = api && support.rangeInput && (!browser.isIPhone || playsInline);
break; break;
case 'audio': case 'html5:audio':
api = support.audio; api = support.audio;
ui = api && support.rangeInput; ui = api && support.rangeInput;
break; break;
case 'youtube': case 'youtube:video':
api = true; api = true;
ui = support.rangeInput && (!browser.isIPhone || playsInline); ui = support.rangeInput && (!browser.isIPhone || playsInline);
break; break;
case 'vimeo': case 'vimeo:video':
api = true; api = true;
ui = support.rangeInput && !browser.isIPhone; ui = support.rangeInput && !browser.isIPhone;
break; break;
@ -92,12 +92,12 @@ const support = {
try { try {
// Bail if no checking function // Bail if no checking function
if (!utils.is.function(media.canPlayType)) { if (!this.isHTML5 || !utils.is.function(media.canPlayType)) {
return false; return false;
} }
// Type specific checks // Type specific checks
if (this.type === 'video') { if (this.isVideo) {
switch (type) { switch (type) {
case 'video/webm': case 'video/webm':
return media.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/, ''); return media.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/, '');
@ -111,7 +111,7 @@ const support = {
default: default:
return false; return false;
} }
} else if (this.type === 'audio') { } else if (this.isAudio) {
switch (type) { switch (type) {
case 'audio/mpeg': case 'audio/mpeg':
return media.canPlayType('audio/mpeg;').replace(/no/, ''); return media.canPlayType('audio/mpeg;').replace(/no/, '');

View File

@ -1,10 +1,16 @@
// ========================================================================== // ==========================================================================
// Plyr supported types // Plyr supported types and providers
// ========================================================================== // ==========================================================================
const types = { export const providers = {
embed: ['youtube', 'vimeo'], html5: 'html5',
html5: ['video', 'audio'], youtube: 'youtube',
vimeo: 'vimeo',
}; };
export default types; export const types = {
audio: 'audio',
video: 'video',
};
export default { providers, types };

View File

@ -31,7 +31,7 @@ const ui = {
// Don't setup interface if no support // Don't setup interface if no support
if (!this.supported.ui) { if (!this.supported.ui) {
this.console.warn(`Basic support only for ${this.type}`); this.console.warn(`Basic support only for ${this.provider} ${this.type}`);
// Remove controls // Remove controls
utils.removeElement.call(this, 'controls'); utils.removeElement.call(this, 'controls');

View File

@ -73,24 +73,39 @@ const utils = {
// Load an external script // Load an external script
loadScript(url, callback) { loadScript(url, callback) {
// Check script is not already referenced const current = document.querySelector(`script[src="${url}"]`);
if (document.querySelectorAll(`script[src="${url}"]`).length) {
// Check script is not already referenced, if so wait for load
if (current !== null) {
current.callbacks = current.callbacks || [];
current.callbacks.push(callback);
return; return;
} }
// Build the element // Build the element
const element = document.createElement('script'); const element = document.createElement('script');
element.src = url;
// Find first script // Callback queue
const first = document.getElementsByTagName('script')[0]; element.callbacks = element.callbacks || [];
element.callbacks.push(callback);
// Bind callback // Bind callback
if (utils.is.function(callback)) { if (utils.is.function(callback)) {
element.addEventListener('load', event => callback.call(null, event), false); element.addEventListener(
'load',
event => {
element.callbacks.forEach(cb => cb.call(null, event));
element.callbacks = null;
},
false
);
} }
// Set the URL after binding callback
element.src = url;
// Inject // Inject
const first = document.getElementsByTagName('script')[0];
first.parentNode.insertBefore(element, first); first.parentNode.insertBefore(element, first);
}, },