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

File diff suppressed because one or more lines are too long

View File

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

View File

@ -13,6 +13,10 @@ video {
margin: @spacing-base auto;
border-radius: @border-radius-base;
box-shadow: 0 2px 5px fade(#000, 20%);
&.plyr--audio {
max-width: 480px;
}
}
.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
A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports
[_modern_](#browser-support) browsers.
A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo media player that supports [_modern_](#browser-support) browsers.
[Checkout the demo](https://plyr.io) - [Donate to support Plyr](#donate)
@ -16,8 +15,8 @@ A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo medi
* **Accessible** - full support for VTT captions and screen readers
* **Lightweight** - just 18KB minified and gzipped
* **[Customisable](#html)** - make the player look how you want with the markup you want
* **Semantic** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well,
`<button>`s for buttons. There's no `<span>` or `<a href="#">` button hacks
* **Semantic** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
`<span>` or `<a href="#">` button hacks
* **Responsive** - works with any screen size
* **HTML Video & Audio** - support for both formats
* **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
@ -64,13 +63,12 @@ npm install plyr
## Quick setup
Here's a quick run through on getting up and running. There's also a
[demo on Codepen](http://codepen.io/sampotts/pen/jARJYp).
Here's a quick run through on getting up and running. There's also a [demo on Codepen](http://codepen.io/sampotts/pen/jARJYp).
### HTML
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 [initialising](#initialising).
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
[initialising](#initialising).
#### HTML5 Video
@ -98,29 +96,28 @@ For YouTube and Vimeo, Plyr uses the standard YouTube API markup (an empty `<div
#### YouTube embed
```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
```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
Include the `plyr.js` script before the closing `</body>` tag and then call `plyr.setup()`. More info on `setup()` can
be found under [initialising](#initialising).
Include the `plyr.js` script before the closing `</body>` tag and then call `plyr.setup()`. More info on `setup()` can be found under
[initialising](#initialising).
```html
<script src="path/to/plyr.js"></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
following:
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
```html
<script src="https://cdn.plyr.io/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">
```
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
<link rel="stylesheet" href="https://cdn.plyr.io/2.0.13/plyr.css">
@ -143,50 +139,45 @@ following:
### SVG Sprite
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see
the [options](#options) below. For reference, the CDN hosted SVG sprite can be found at
`https://cdn.plyr.io/2.0.13/plyr.svg`.
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/2.0.13/plyr.svg`.
## Advanced
### 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
design. The LESS and SASS require you to use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin
(you be should already!) as all declarations use the W3C definitions.
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
use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (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
hooks in the options to match any custom CSS you write. Check out the JavaScript source for more on this.
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.
### SVG
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 system in place, you can include the source plyr icons (see `/src/sprite` for
source icons).
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
system in place, you can include the source plyr icons (see `/src/sprite` for source icons).
#### 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
AJAX/CORS due to current browser limitations or if it's a relative path, just use the path directly.
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
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:
[svgfixer.js](https://gist.github.com/leonderijke/c5cf7c5b2e424c0061d2)
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)
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 technique here: [http://css-tricks.com/ajaxing-svg-sprite/](http://css-tricks.com/ajaxing-svg-sprite/)
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
technique here: [http://css-tricks.com/ajaxing-svg-sprite/](http://css-tricks.com/ajaxing-svg-sprite/)
### Cross Origin (CORS)
You'll notice the `crossorigin` attribute on the example `<video>` elements. This is because the TextTrack captions are
loaded from another domain. If your 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 on CORS checkout the MDN docs:
You'll notice the `crossorigin` attribute on the example `<video>` elements. This is because the TextTrack captions are loaded from another domain. If your
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
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)
### Captions
WebVTT captions are supported. To add a caption track, check the HTML example above and look for the `<track>` element.
Be sure to [validate your caption files](https://quuz.org/webvtt/).
WebVTT captions are supported. To add a caption track, check the HTML example above and look for the `<track>` element. Be sure to
[validate your caption files](https://quuz.org/webvtt/).
### 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:
* A CSS string selector that's compatible with
[`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
* A CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
* 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
[HTMLElement](https://developer.mozilla.org/en/docs/Web/API/HTMLElement) - the first element will be used
* 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) -
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
@ -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'));
```
The NodeList, HTMLElement or string selector can be the target `<video>`, `<audio>` or `[data-plyr-provider]` (for
embeds) element itself or a container element.
The NodeList, HTMLElement or string selector can be the target `<video>`, `<audio>` or `[data-plyr-provider]` (for embeds) element itself or a container
element.
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
more info.
The constructor will return a Plyr object that can be used with the [API](#api) methods. See the [API](#api) section for more info.
#### Options
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:
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:
```html
<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
quotes.
Note the single quotes encapsulating the JSON and double quotes on the object keys. Only string values need double quotes.
| Option | Type | Default | Description |
| -------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@ -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. |
| `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. |
| `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. |
| `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. |
@ -290,8 +277,7 @@ There are methods, setters and getters on a Plyr 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.
For example:
The easiest way to access the Plyr object is to set the return value from your call to the constructor to a variable. For example:
```javascript
const player = new Plyr('#player', {
@ -440,7 +426,7 @@ player.source = {
sources: [
{
src: 'bTqVqk7FSmY',
type: 'youtube',
provider: 'youtube',
},
],
};
@ -456,7 +442,7 @@ player.source = {
sources: [
{
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
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 reference to the instance, you can use the `on()` API method or
`addEventListener()`. Access to the API can be obtained this way through the `event.detail.plyr` property. Here's an
example:
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
reference to the instance, you can use the `on()` API method or `addEventListener()`. Access to the API can be obtained this way through the `event.detail.plyr`
property. Here's an example:
```javascript
player.on('ready', event => {
@ -539,10 +524,9 @@ Some event details borrowed from [MDN](https://developer.mozilla.org/en-US/docs/
## Embeds
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 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:
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
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:
* [YouTube iframe API Reference](https://developers.google.com/youtube/iframe_api_reference)
* [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
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 document then the shortcuts will work when any element has focus, apart from
an element that requires input.
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
document then the shortcuts will work when any element has focus, apart from an element that requires input.
| Key | Action |
| ---------- | -------------------------------------- |
@ -571,9 +554,8 @@ an element that requires input.
## Streaming
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 Russitto ([@russitto](https://github.com/russitto)) for working on this. Here's a
few examples:
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
Russitto ([@russitto](https://github.com/russitto)) for working on this. Here's a few examples:
* 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)
@ -598,13 +580,11 @@ Plyr supports the last 2 versions of most _modern_ browsers. IE11 is also suppor
| IE10+ | ✔&sup2; |
| IE9 | API only&sup3; |
1. Mobile Safari on the iPhone forces the native player for `<video>` unless the `playsinline` attribute is present.
Volume controls are also disabled.
1. Mobile Safari on the iPhone forces the native player for `<video>` unless the `playsinline` attribute is present. 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+)
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
smartphones, you could use:
The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for smartphones, you could use:
```javascript
{
@ -616,10 +596,9 @@ If a User Agent is disabled but supports `<video>` and `<audio>` natively, it wi
## RangeTouch
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 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 solution. It's a tiny script with a nice
benefit for users on touch devices.
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
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
solution. It's a tiny script with a nice benefit for users on touch devices.
## Issues
@ -627,13 +606,13 @@ If you find anything weird with Plyr, please let us know using the GitHub issues
## Author
Plyr is developed by [@sam_potts](https://twitter.com/sam_potts) / [sampotts.me](http://sampotts.me) with help from the
awesome [contributors](https://github.com/sampotts/plyr/graphs/contributors)
Plyr is developed by [@sam_potts](https://twitter.com/sam_potts) / [sampotts.me](http://sampotts.me) with help from the awesome
[contributors](https://github.com/sampotts/plyr/graphs/contributors)
## Donate
Plyr costs money to run, not only my time - I donate that for free but domains, hosting and more. Any help is
appreciated... [Donate to support Plyr](https://www.paypal.me/pottsy/20usd)
Plyr costs money to run, not only my time - I donate that for free but domains, hosting and more. Any help is appreciated...
[Donate to support Plyr](https://www.paypal.me/pottsy/20usd)
## Mentions
@ -660,16 +639,14 @@ appreciated... [Donate to support Plyr](https://www.paypal.me/pottsy/20usd)
* [Oscar Radio](http://oscar-radio.xyz/)
* [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
you're using Plyr :-)
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 :-)
## Useful links and credits
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)
* [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

View File

@ -32,7 +32,7 @@ const captions = {
}
// 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
if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this);
@ -71,7 +71,7 @@ const captions = {
// Set the captions language
setLanguage() {
// Setup HTML5 track rendering
if (this.type === 'video') {
if (this.isVideo) {
captions.getTracks.call(this).forEach(track => {
// Remove previous bindings
utils.on(track, 'cuechange', event => captions.setCue.call(this, event));
@ -91,7 +91,7 @@ const captions = {
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);
}
},

2
src/js/controls.js vendored
View File

@ -445,7 +445,7 @@ const controls = {
}
// 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);
// If we're hiding, nothing more to do

View File

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

View File

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

View File

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

View File

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

View File

@ -4,13 +4,12 @@
import utils from './../utils';
import captions from './../captions';
import controls from './../controls';
import ui from './../ui';
const vimeo = {
setup() {
// 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);
// Add embed class for responsive
@ -20,7 +19,7 @@ const vimeo = {
vimeo.setAspectRatio.call(this);
// Set ID
this.media.setAttribute('id', utils.generateId(this.type));
this.media.setAttribute('id', utils.generateId(this.provider));
// Load the API if not already
if (!utils.is.object(window.Vimeo)) {

View File

@ -11,7 +11,7 @@ const youtube = {
const videoId = utils.parseYouTubeId(this.embedId);
// 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);
// Add embed class for responsive
@ -21,7 +21,7 @@ const youtube = {
youtube.setAspectRatio.call(this);
// Set ID
this.media.setAttribute('id', utils.generateId(this.type));
this.media.setAttribute('id', utils.generateId(this.provider));
// Setup API
if (utils.is.object(window.YT)) {
@ -31,6 +31,7 @@ const youtube = {
utils.loadScript(this.config.urls.youtube.api);
// Setup callback for the API
// YouTube has it's own system of course...
window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || [];
// Add to queue

View File

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

View File

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

View File

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

View File

@ -1,10 +1,16 @@
// ==========================================================================
// Plyr supported types
// Plyr supported types and providers
// ==========================================================================
const types = {
embed: ['youtube', 'vimeo'],
html5: ['video', 'audio'],
export const providers = {
html5: 'html5',
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
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
utils.removeElement.call(this, 'controls');

View File

@ -73,24 +73,39 @@ const utils = {
// Load an external script
loadScript(url, callback) {
// Check script is not already referenced
if (document.querySelectorAll(`script[src="${url}"]`).length) {
const current = document.querySelector(`script[src="${url}"]`);
// Check script is not already referenced, if so wait for load
if (current !== null) {
current.callbacks = current.callbacks || [];
current.callbacks.push(callback);
return;
}
// Build the element
const element = document.createElement('script');
element.src = url;
// Find first script
const first = document.getElementsByTagName('script')[0];
// Callback queue
element.callbacks = element.callbacks || [];
element.callbacks.push(callback);
// Bind 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
const first = document.getElementsByTagName('script')[0];
first.parentNode.insertBefore(element, first);
},