Compare commits

...

24 Commits

Author SHA1 Message Date
7ccbfad6ad New API methods (fixes #77), Fix for non strict mode (fixes #78) 2015-05-17 17:23:09 +10:00
91f8a158d2 Update index.html 2015-04-19 23:47:46 +10:00
8925dcde01 Update readme.md 2015-04-19 23:45:25 +10:00
530abac3a7 Update readme.md 2015-04-15 01:15:05 +10:00
e702d9a881 Update readme.md 2015-04-15 01:14:36 +10:00
b0aeccb793 Update readme.md 2015-04-11 11:19:19 +10:00
43d8d748ce Merge pull request #76 from robinpoort/feature/sass-variables
Make SCSS variables optional
2015-04-11 09:29:04 +10:00
b43ea5c7d3 adding !default to variables so they are easily overwritable 2015-04-10 15:13:02 +02:00
f56759222d Update readme.md 2015-04-10 13:38:49 +10:00
c8b7bb570c Update readme.md 2015-04-10 13:37:55 +10:00
d536b29550 Update readme.md 2015-04-10 13:36:51 +10:00
ecbda018c5 Merge branch 'master' of github.com:selz/plyr
# Conflicts:
#	src/js/plyr.js
2015-04-10 07:17:23 +10:00
5187311ff0 Bug fix for isFullscreen() in Mozilla (Fixes #38) 2015-04-10 07:16:53 +10:00
d9a94ac7b0 Version bump 2015-04-08 13:42:34 +10:00
3526e322ef Minor bug fixes 2015-04-06 19:42:26 +10:00
99cabd545d Fix for generated IDs for controls, UI tweak for audio 2015-04-06 13:25:36 +10:00
1f7f7b10de Docs tweak 2015-04-06 12:06:34 +10:00
b2421b592a Merge branch 'master' of github.com:selz/plyr 2015-04-06 11:47:43 +10:00
5322f4c62f Fullscreen API methods (Fixes #74), onSetup callback 2015-04-06 11:47:23 +10:00
7ab8647fc8 Merge pull request #75 from franks921/fs-fix-localstorage-volume-settings
fix: use custom localStorage key for volume setting
2015-04-05 21:39:08 +10:00
50c76f3d7e fix: use custom localStorage key for volume setting 2015-04-05 13:26:17 +02:00
2f4c56176d Updated screenshot 2015-04-04 12:58:09 +11:00
7c5f38311b Bug fixes for controls changes 2015-04-04 12:51:24 +11:00
e568bc9c8d Controls improvements
- Added an option to toggle which controls display
- Better handle missing controls
2015-04-04 12:32:37 +11:00
17 changed files with 582 additions and 262 deletions

View File

@ -1,7 +1,28 @@
# Changelog # Changelog
## v1.1.5
- Fix for incorrect `isFullscreen()` return value in Mozilla (Fixes #38)
## v1.1.4
- Minor bug fixes
## v1.1.3
- Fixes for random id used in controls with multiple instances and one call to setup
- Audio player UI improvements
## v1.1.2
- Added an onSetup callback option
- Added fullscreen API methods `toggleFullscreen()` (must be user iniated), and `isFullscreen()`
## v1.1.1
- Fix for unsupported browser handling
- Fix for config.controls having no effect
## v1.1.0
- Added config option to set which controls are shown (if using the default controls html) and better handling of missing controls
## v1.0.31 ## v1.0.31
- Display duration on metadataloaded - Display duration on `metadataloaded`
## v1.0.30 ## v1.0.30
- Fixed bug with media longer than 60 minutes (Fixes #69) - Fixed bug with media longer than 60 minutes (Fixes #69)

View File

@ -2,7 +2,7 @@
This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs. This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs.
The demo Plyr setup uses a Hogan template. This purely to allow for localization at a later date. Check out `controls.html` in `/src/templates` to get an idea of how the default html is structured. Alternatively check out the `plyr.js` source. The demo Plyr setup uses a Hogan template. This purely to allow for localization at a later date. Check out `controls.html` in `/src/templates` to get an idea of how the default html is structured.
## Requirements ## Requirements
@ -13,11 +13,11 @@ You need to add several placeholders to your html template that are replaced whe
- `{id}` - the dynamically generated ID for the player (for form controls) - `{id}` - the dynamically generated ID for the player (for form controls)
- `{seektime}` - the seek time specified in options for fast forward and rewind - `{seektime}` - the seek time specified in options for fast forward and rewind
Currently all buttons and inputs need to be present for Plyr to work but later we'll make it more dynamic so if you omit a button or input, it'll still work. You can include only the controls you need when specifying custom html.
## Default ## Example
This is the default `html` option from `plyr.js`. This is an example `html` option with all controls.
```javascript ```javascript
["<div class='player-controls'>", ["<div class='player-controls'>",
@ -53,7 +53,11 @@ This is the default `html` option from `plyr.js`.
"<span class='sr-only'>Forward {seektime} secs</span>", "<span class='sr-only'>Forward {seektime} secs</span>",
"</button>", "</button>",
"<span class='player-time'>", "<span class='player-time'>",
"<span class='sr-only'>Time</span>", "<span class='sr-only'>Current time</span>",
"<span class='player-current-time'>00:00</span>",
"</span>",
"<span class='player-time'>",
"<span class='sr-only'>Duration</span>",
"<span class='player-duration'>00:00</span>", "<span class='player-duration'>00:00</span>",
"</span>", "</span>",
"</span>", "</span>",

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.js vendored

File diff suppressed because one or more lines are too long

2
docs/dist/docs.css vendored
View File

@ -1 +1 @@
/*! normalize.css v2.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@font-face{font-family:Avenir;src:url(//cdn.plyr.io/fonts/avenir-medium.woff2) format("woff2"),url(//cdn.plyr.io/fonts/avenir-medium.woff) format("woff"),url(//cdn.plyr.io/fonts/avenir-medium.ttf) format("truetype");font-style:normal;font-weight:400}@font-face{font-family:Avenir;src:url(//cdn.plyr.io/fonts/avenir-bold.woff2) format("woff2"),url(//cdn.plyr.io/fonts/avenir-bold.woff) format("woff"),url(//cdn.plyr.io/fonts/avenir-bold.ttf) format("truetype");font-style:normal;font-weight:600}*,::after,::before{box-sizing:border-box}body{font-family:Avenir,"Helvetica Neue",Helvetica,Arial,sans-serif;background:#fff;line-height:1.5;text-align:center;color:#6D797F}.error body,html.error{height:100%}.error body{width:100%;display:table;table-layout:fixed}.error main{display:table-cell;width:100%;vertical-align:middle}h1,h2{letter-spacing:-.025em;color:#2E3C44;margin:0 0 10px;line-height:1.2;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}h1{font-size:64px;font-size:4rem;color:#3498DB}p,small{margin:0 0 20px}small{display:block;padding:0 10px;font-size:14px;font-size:.9rem}header{padding:20px;margin-bottom:20px}header p{font-size:18px;font-size:1.1rem}@media (min-width:560px){header{padding-top:60px;padding-bottom:60px}}section{padding-bottom:20px}@media (min-width:560px){section{padding-bottom:40px}}a{text-decoration:none;color:#3498db;border-bottom:1px solid currentColor;transition:all .3s ease}a:focus,a:hover{color:#000}a:focus{outline:#343f4a dotted thin;outline-offset:1px}a.logo{border:0}.btn{display:inline-block;padding:10px 30px;background:#3498db;border:0;color:#fff;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-weight:600;border-radius:3px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:hover{color:#fff;background:#258cd1}.example-audio .player{max-width:480px}.example-video .player{max-width:1200px}.example-audio .player,.example-video .player{margin:0 auto 20px}.example-audio .player-fullscreen,.example-audio .player.fullscreen-active,.example-video .player-fullscreen,.example-video .player.fullscreen-active{max-width:none}footer{margin-bottom:20px}footer p{margin-bottom:10px} /*! normalize.css v2.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@font-face{font-family:Avenir;src:url(//cdn.plyr.io/fonts/avenir-medium.woff2) format("woff2"),url(//cdn.plyr.io/fonts/avenir-medium.woff) format("woff"),url(//cdn.plyr.io/fonts/avenir-medium.ttf) format("truetype");font-style:normal;font-weight:400}@font-face{font-family:Avenir;src:url(//cdn.plyr.io/fonts/avenir-bold.woff2) format("woff2"),url(//cdn.plyr.io/fonts/avenir-bold.woff) format("woff"),url(//cdn.plyr.io/fonts/avenir-bold.ttf) format("truetype");font-style:normal;font-weight:600}*,::after,::before{box-sizing:border-box}body{font-family:Avenir,"Helvetica Neue",Helvetica,Arial,sans-serif;background:#fff;line-height:1.5;text-align:center;color:#6D797F}.error body,html.error{height:100%}.error body{width:100%;display:table;table-layout:fixed}.error main{display:table-cell;width:100%;vertical-align:middle}h1,h2{letter-spacing:-.025em;color:#2E3C44;margin:0 0 10px;line-height:1.2;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}h1{font-size:64px;font-size:4rem;color:#3498DB}p,small{margin:0 0 20px}small{display:block;padding:0 10px;font-size:14px;font-size:.9rem}header{padding:20px;margin-bottom:20px}header p{font-size:18px;font-size:1.1rem}@media (min-width:560px){header{padding-top:60px;padding-bottom:60px}}section{padding-bottom:20px}@media (min-width:560px){section{padding-bottom:40px}}a{text-decoration:none;color:#3498db;border-bottom:1px solid currentColor;transition:all .3s ease}a:focus,a:hover{color:#000}a:focus{outline:#343f4a dotted thin;outline-offset:1px}a.logo{border:0}.btn{display:inline-block;padding:10px 30px;background:#3498db;border:0;color:#fff;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-weight:600;border-radius:3px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:hover{color:#fff;background:#258cd1}.example-audio .player{max-width:520px}.example-video .player{max-width:1200px}.example-audio .player,.example-video .player{margin:0 auto 20px}.example-audio .player-fullscreen,.example-audio .player.fullscreen-active,.example-video .player-fullscreen,.example-video .player.fullscreen-active{max-width:none}footer{margin-bottom:20px}footer p{margin-bottom:10px}

2
docs/dist/docs.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
var templates = {}; var templates = {};
templates['controls'] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<div class=\"player-controls\">");t.b("\n" + i);t.b(" <div class=\"player-progress\">");t.b("\n" + i);t.b(" <label for=\"seek{id}\" class=\"sr-only\">Seek</label>");t.b("\n" + i);t.b(" <input id=\"seek{id}\" class=\"player-progress-seek\" type=\"range\" min=\"0\" max=\"100\" step=\"0.5\" value=\"0\" data-player=\"seek\">");t.b("\n" + i);t.b(" <progress class=\"player-progress-played\" max=\"100\" value=\"0\">");t.b("\n" + i);t.b(" <span>0</span>% played");t.b("\n" + i);t.b(" </progress>");t.b("\n" + i);t.b(" <progress class=\"player-progress-buffer\" max=\"100\" value=\"0\">");t.b("\n" + i);t.b(" <span>0</span>% buffered");t.b("\n" + i);t.b(" </progress>");t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b(" <span class=\"player-controls-left\">");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"restart\">");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-restart\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Restart</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"rewind\">");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-rewind\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Rewind {seektime} secs</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"play\">");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-play\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Play</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"pause\">");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-pause\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Pause</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"fast-forward\">");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-fast-forward\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Forward {seektime} secs</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" <span class=\"player-time\">");t.b("\n" + i);t.b(" <span class=\"sr-only\">Time</span>");t.b("\n" + i);t.b(" <span class=\"player-duration\">00:00</span>");t.b("\n" + i);t.b(" </span>");t.b("\n" + i);t.b(" </span>");t.b("\n" + i);t.b(" <span class=\"player-controls-right\">");t.b("\n" + i);t.b(" <input class=\"inverted sr-only\" id=\"mute{id}\" type=\"checkbox\" data-player=\"mute\">");t.b("\n" + i);t.b(" <label id=\"mute{id}\" for=\"mute{id}\">");t.b("\n" + i);t.b(" <svg class=\"icon-muted\"><use xlink:href=\"#icon-muted\"></use></svg>");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-volume\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Toggle Mute</span>");t.b("\n" + i);t.b(" </label>");t.b("\n");t.b("\n" + i);t.b(" <label for=\"volume{id}\" class=\"sr-only\">Volume</label>");t.b("\n" + i);t.b(" <input id=\"volume{id}\" class=\"player-volume\" type=\"range\" min=\"0\" max=\"10\" step=\"0.5\" value=\"0\" data-player=\"volume\">");t.b("\n");t.b("\n" + i);t.b(" <input class=\"sr-only\" id=\"captions{id}\" type=\"checkbox\" data-player=\"captions\">");t.b("\n" + i);t.b(" <label for=\"captions{id}\">");t.b("\n" + i);t.b(" <svg class=\"icon-captions-on\"><use xlink:href=\"#icon-captions-on\"></use></svg>");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-captions-off\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Toggle Captions</span>");t.b("\n" + i);t.b(" </label>");t.b("\n");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"fullscreen\">");t.b("\n" + i);t.b(" <svg class=\"icon-exit-fullscreen\"><use xlink:href=\"#icon-exit-fullscreen\"></use></svg>");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-enter-fullscreen\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Toggle Fullscreen</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" </span>");t.b("\n" + i);t.b("</div>");return t.fl(); },partials: {}, subs: { }}); templates['controls'] = new Hogan.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("<div class=\"player-controls\">");t.b("\n" + i);t.b(" <div class=\"player-progress\">");t.b("\n" + i);t.b(" <label for=\"seek{id}\" class=\"sr-only\">Seek</label>");t.b("\n" + i);t.b(" <input id=\"seek{id}\" class=\"player-progress-seek\" type=\"range\" min=\"0\" max=\"100\" step=\"0.5\" value=\"0\" data-player=\"seek\">");t.b("\n" + i);t.b(" <progress class=\"player-progress-played\" max=\"100\" value=\"0\">");t.b("\n" + i);t.b(" <span>0</span>% played");t.b("\n" + i);t.b(" </progress>");t.b("\n" + i);t.b(" <progress class=\"player-progress-buffer\" max=\"100\" value=\"0\">");t.b("\n" + i);t.b(" <span>0</span>% buffered");t.b("\n" + i);t.b(" </progress>");t.b("\n" + i);t.b(" </div>");t.b("\n" + i);t.b(" <span class=\"player-controls-left\">");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"restart\">");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-restart\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Restart</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"rewind\">");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-rewind\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Rewind {seektime} secs</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"play\">");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-play\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Play</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"pause\">");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-pause\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Pause</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"fast-forward\">");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-fast-forward\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Forward {seektime} secs</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" <span class=\"player-time\">");t.b("\n" + i);t.b(" <span class=\"sr-only\">Current time</span>");t.b("\n" + i);t.b(" <span class=\"player-current-time\">00:00</span>");t.b("\n" + i);t.b(" </span>");t.b("\n" + i);t.b(" <span class=\"player-time\">");t.b("\n" + i);t.b(" <span class=\"sr-only\">Duration</span>");t.b("\n" + i);t.b(" <span class=\"player-duration\">00:00</span>");t.b("\n" + i);t.b(" </span>");t.b("\n" + i);t.b(" </span>");t.b("\n" + i);t.b(" <span class=\"player-controls-right\">");t.b("\n" + i);t.b(" <input class=\"inverted sr-only\" id=\"mute{id}\" type=\"checkbox\" data-player=\"mute\">");t.b("\n" + i);t.b(" <label id=\"mute{id}\" for=\"mute{id}\">");t.b("\n" + i);t.b(" <svg class=\"icon-muted\"><use xlink:href=\"#icon-muted\"></use></svg>");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-volume\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Toggle Mute</span>");t.b("\n" + i);t.b(" </label>");t.b("\n");t.b("\n" + i);t.b(" <label for=\"volume{id}\" class=\"sr-only\">Volume</label>");t.b("\n" + i);t.b(" <input id=\"volume{id}\" class=\"player-volume\" type=\"range\" min=\"0\" max=\"10\" step=\"0.5\" value=\"0\" data-player=\"volume\">");t.b("\n");t.b("\n" + i);t.b(" <input class=\"sr-only\" id=\"captions{id}\" type=\"checkbox\" data-player=\"captions\">");t.b("\n" + i);t.b(" <label for=\"captions{id}\">");t.b("\n" + i);t.b(" <svg class=\"icon-captions-on\"><use xlink:href=\"#icon-captions-on\"></use></svg>");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-captions-off\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Toggle Captions</span>");t.b("\n" + i);t.b(" </label>");t.b("\n");t.b("\n" + i);t.b(" <button type=\"button\" data-player=\"fullscreen\">");t.b("\n" + i);t.b(" <svg class=\"icon-exit-fullscreen\"><use xlink:href=\"#icon-exit-fullscreen\"></use></svg>");t.b("\n" + i);t.b(" <svg><use xlink:href=\"#icon-enter-fullscreen\"></use></svg>");t.b("\n" + i);t.b(" <span class=\"sr-only\">Toggle Fullscreen</span>");t.b("\n" + i);t.b(" </button>");t.b("\n" + i);t.b(" </span>");t.b("\n" + i);t.b("</div>");return t.fl(); },partials: {}, subs: { }});

View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Docs styles --> <!-- Docs styles -->
<link rel="stylesheet" href="//cdn.plyr.io/1.0.31/docs.css"> <link rel="stylesheet" href="//cdn.plyr.io/1.1.6/docs.css">
</head> </head>
<body> <body>
<main> <main>

View File

@ -8,10 +8,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Styles --> <!-- Styles -->
<link rel="stylesheet" href="//cdn.plyr.io/1.0.31/plyr.css"> <link rel="stylesheet" href="https://cdn.plyr.io/1.1.6/plyr.css">
<!-- Docs styles --> <!-- Docs styles -->
<link rel="stylesheet" href="//cdn.plyr.io/1.0.31/docs.css"> <link rel="stylesheet" href="https://cdn.plyr.io/1.1.6/docs.css">
</head> </head>
<body> <body>
<header> <header>
@ -23,18 +23,16 @@
<main> <main>
<section class="example-video"> <section class="example-video">
<div class="player"> <div class="player">
<video poster="//cdn.selz.com/plyr/1.0/poster.jpg" controls crossorigin> <video poster="https://cdn.selz.com/plyr/1.0/poster.jpg" controls crossorigin>
<!-- Video files --> <!-- Video files -->
<source src="//cdn.selz.com/plyr/1.0/movie.mp4" type="video/mp4"> <source src="https://cdn.selz.com/plyr/1.0/movie.mp4" type="video/mp4">
<source src="//cdn.selz.com/plyr/1.0/movie.webm" type="video/webm"> <source src="https://cdn.selz.com/plyr/1.0/movie.webm" type="video/webm">
<!-- Text track file --> <!-- Text track file -->
<track kind="captions" label="English" srclang="en" src="//cdn.selz.com/plyr/1.0/en.vtt" default> <track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.0/en.vtt" default>
<!-- Fallback for browsers that don't support the <video> element --> <!-- Fallback for browsers that don't support the <video> element -->
<div> <a href="https://cdn.selz.com/plyr/1.0/movie.mp4">Download</a>
<a href="//cdn.selz.com/plyr/1.0/movie.mp4">Download</a>
</div>
</video> </video>
</div> </div>
<small>Big Buck Bunny. More info can be found at <a href="https://peach.blender.org" target="_blank">peach.blender.org</a>.</small> <small>Big Buck Bunny. More info can be found at <a href="https://peach.blender.org" target="_blank">peach.blender.org</a>.</small>
@ -44,13 +42,11 @@
<div class="player"> <div class="player">
<audio controls> <audio controls>
<!-- Audio files --> <!-- Audio files -->
<source src="//cdn.selz.com/plyr/1.0/logistics-96-sample.mp3" type="audio/mp3"> <source src="https://cdn.selz.com/plyr/1.0/logistics-96-sample.mp3" type="audio/mp3">
<source src="//cdn.selz.com/plyr/1.0/logistics-96-sample.ogg" type="audio/ogg"> <source src="https://cdn.selz.com/plyr/1.0/logistics-96-sample.ogg" type="audio/ogg">
<!-- Fallback for browsers that don't support the <audio> element --> <!-- Fallback for browsers that don't support the <audio> element -->
<div> <a href="https://cdn.selz.com/plyr/1.0/logistics-96-sample.mp3">Download</a>
<a href="//cdn.selz.com/plyr/1.0/logistics-96-sample.mp3">Download</a>
</div>
</audio> </audio>
</div> </div>
<small>"96" by Logistics, which can be purchased from <a href="https://www.hospitalrecords.com/shop/artist/logistics" target="_blank">Hospital Records</a>.</small> <small>"96" by Logistics, which can be purchased from <a href="https://www.hospitalrecords.com/shop/artist/logistics" target="_blank">Hospital Records</a>.</small>
@ -83,13 +79,13 @@
b.insertBefore(c, b.childNodes[0]); b.insertBefore(c, b.childNodes[0]);
} }
} }
})(document, "https://cdn.plyr.io/1.0.31/sprite.svg"); })(document, "https://cdn.plyr.io/1.1.6/sprite.svg");
</script> </script>
<!-- Plyr core script --> <!-- Plyr core script -->
<script src="//cdn.plyr.io/1.0.31/plyr.js"></script> <script src="https://cdn.plyr.io/1.1.6/plyr.js"></script>
<!-- Docs script --> <!-- Docs script -->
<script src="//cdn.plyr.io/1.0.31/docs.js"></script> <script src="https://cdn.plyr.io/1.1.6/docs.js"></script>
</body> </body>
</html> </html>

View File

@ -11,9 +11,21 @@ plyr.setup({
html: templates.controls.render({}), html: templates.controls.render({}),
captions: { captions: {
defaultActive: true defaultActive: true
},
onSetup: function() {
var player = this,
type = player.media.tagName.toLowerCase(),
toggle = document.querySelector("[data-toggle='fullscreen']");
console.log("✓ Setup done for <" + type + ">");
if(type === "video" && toggle) {
toggle.addEventListener("click", player.toggleFullscreen, false);
}
} }
}); });
// Google analytics // Google analytics
// For demo site (http://[www.]plyr.io) only // For demo site (http://[www.]plyr.io) only
if(document.domain.indexOf("plyr.io") > -1) { if(document.domain.indexOf("plyr.io") > -1) {

View File

@ -139,7 +139,7 @@ a {
// Players // Players
.example-audio .player { .example-audio .player {
max-width: 480px; max-width: 520px;
} }
.example-video .player { .example-video .player {
max-width: 1200px; max-width: 1200px;

View File

@ -31,7 +31,11 @@
<span class="sr-only">Forward {seektime} secs</span> <span class="sr-only">Forward {seektime} secs</span>
</button> </button>
<span class="player-time"> <span class="player-time">
<span class="sr-only">Time</span> <span class="sr-only">Current time</span>
<span class="player-current-time">00:00</span>
</span>
<span class="player-time">
<span class="sr-only">Duration</span>
<span class="player-duration">00:00</span> <span class="player-duration">00:00</span>
</span> </span>
</span> </span>

View File

@ -1,6 +1,6 @@
{ {
"name": "plyr", "name": "plyr",
"version": "1.0.31", "version": "1.1.6",
"description": "A simple HTML5 media player using custom controls", "description": "A simple HTML5 media player using custom controls",
"homepage": "http://plyr.io", "homepage": "http://plyr.io",
"main": "gulpfile.js", "main": "gulpfile.js",

106
readme.md
View File

@ -3,14 +3,14 @@ A simple, accessible HTML5 media player.
[Checkout the demo](http://plyr.io) [Checkout the demo](http://plyr.io)
[![Image of Plyr](https://cdn.plyr.io/static/plyr.png?1)](http://plyr.io) [![Image of Plyr](https://cdn.plyr.io/static/plyr.png?2)](http://plyr.io)
## Why? ## Why?
We wanted a lightweight, accessible and customisable media player that just supports *modern* browsers. Sure, there are many other players out there but we wanted to keep things simple, using the right elements for the job. We wanted a lightweight, accessible and customisable media player that just supports *modern* browsers. Sure, there are many other players out there but we wanted to keep things simple, using the right elements for the job.
## Features ## Features
- **Accessible** - full support for captions and screen readers. - **Accessible** - full support for captions and screen readers.
- **Lightweight** - just 6KB minified and gzipped. - **Lightweight** - just 6.4KB minified and gzipped.
- **Customisable** - make the player look how you want with the markup you want. - **Customisable** - 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** - as you'd expect these days. - **Responsive** - as you'd expect these days.
@ -25,11 +25,10 @@ Oh and yes, it works with Bootstrap.
Check out [the changelog](changelog.md) Check out [the changelog](changelog.md)
## Planned development ## Planned development
- Accept a string selector, a node, or a nodelist for the `container` property of `selectors`.
- Multiple language captions (with selection)
- Playlists (audio and video) - Playlists (audio and video)
- Set source by API - YouTube and Vimeo support
- Tooltip option (for seeking and controls) - Playback speed
- Multiple language captions (with selection)
... and whatever else has been raised in [issues](https://github.com/Selz/plyr/issues) ... and whatever else has been raised in [issues](https://github.com/Selz/plyr/issues)
If you have any cool ideas or features, please let me know by [creating an issue](https://github.com/Selz/plyr/issues/new) or of course, forking and sending a pull request. If you have any cool ideas or features, please let me know by [creating an issue](https://github.com/Selz/plyr/issues/new) or of course, forking and sending a pull request.
@ -38,7 +37,7 @@ If you have any cool ideas or features, please let me know by [creating an issue
Check `docs/index.html` and `docs/dist/docs.js` for an example setup. Check `docs/index.html` and `docs/dist/docs.js` for an example setup.
**Heads up**, the example `index.html` file needs to be served from a webserver (such as Apache, Nginx, IIS or similar) unless you change the file sources to include http or https. e.g. change `//cdn.plyr.io/1.0.31/plyr.js` to `https://cdn.plyr.io/1.0.31/plyr.js` **Heads up**, the example `index.html` file needs to be served from a webserver (such as Apache, Nginx, IIS or similar) unless you change the file sources to include http or https. e.g. change `//cdn.plyr.io/1.1.6/plyr.js` to `https://cdn.plyr.io/1.1.6/plyr.js`
### Bower ### Bower
If bower is your thang, you can grab Plyr using: If bower is your thang, you can grab Plyr using:
@ -47,15 +46,22 @@ bower install plyr
``` ```
More info on setting up dependencies can be found in the [Bower Docs](http://bower.io/docs/creating-packages/#maintaining-dependencies) More info on setting up dependencies can be found in the [Bower Docs](http://bower.io/docs/creating-packages/#maintaining-dependencies)
### Ember
The awesome [@louisrudner](https://twitter.com/louisrudner) has created an ember component, available by running:
```
ember addon:install ember-cli-plyr
```
More info is on [npm](https://www.npmjs.com/package/ember-cli-plyr) and [GitHub](https://github.com/louisrudner/ember-cli-plyr)
### CDN ### CDN
If you want to use our CDN, you can use the following. HTTPS (SSL) is supported. If you want to use our CDN, you can use the following:
```html ```html
<link rel="stylesheet" href="//cdn.plyr.io/1.0.31/plyr.css"> <link rel="stylesheet" href="https://cdn.plyr.io/1.1.6/plyr.css">
<script src="//cdn.plyr.io/1.0.31/plyr.js"></script> <script src="https://cdn.plyr.io/1.1.6/plyr.js"></script>
``` ```
You can also access the `sprite.svg` file at `//cdn.plyr.io/1.0.31/sprite.svg`. You can also access the `sprite.svg` file at `https://cdn.plyr.io/1.1.6/sprite.svg`.
### CSS ### CSS
If you want to use the default css, add the `plyr.css` file from /dist into your head, or even better use `plyr.less` or `plyr.sass` file included in `/src` in your build to save a request. If you want to use the default css, add the `plyr.css` file from /dist into your head, or even better use `plyr.less` or `plyr.sass` file included in `/src` in your build to save a request.
@ -92,18 +98,16 @@ and the AJAX technique here:
The only extra markup that's needed to use plyr is a `<div>` wrapper. Replace the source, poster and captions with urls for your media. The only extra markup that's needed to use plyr is a `<div>` wrapper. Replace the source, poster and captions with urls for your media.
```html ```html
<div class="player"> <div class="player">
<video poster="//cdn.selz.com/plyr/1.0/poster.jpg" controls crossorigin> <video poster="https://cdn.selz.com/plyr/1.0/poster.jpg" controls crossorigin>
<!-- Video files --> <!-- Video files -->
<source src="//cdn.selz.com/plyr/1.0/movie.mp4" type="video/mp4"> <source src="https://cdn.selz.com/plyr/1.0/movie.mp4" type="video/mp4">
<source src="//cdn.selz.com/plyr/1.0/movie.webm" type="video/webm"> <source src="https://cdn.selz.com/plyr/1.0/movie.webm" type="video/webm">
<!-- Text track file --> <!-- Text track file -->
<track kind="captions" label="English captions" src="//cdn.selz.com/plyr/1.0/movie_captions_en.vtt" srclang="en" default> <track kind="captions" label="English captions" src="https://cdn.selz.com/plyr/1.0/movie_captions_en.vtt" srclang="en" default>
<!-- Fallback for browsers that don't support the <video> element --> <!-- Fallback for browsers that don't support the <video> element -->
<div> <a href="https://cdn.selz.com/plyr/1.0/movie.mp4">Download</a>
<a href="//cdn.selz.com/plyr/1.0/movie.mp4">Download</a>
</div>
</video> </video>
</div> </div>
``` ```
@ -113,13 +117,11 @@ And the same for `<audio>`
<div class="player"> <div class="player">
<audio controls> <audio controls>
<!-- Audio files --> <!-- Audio files -->
<source src="//cdn.selz.com/plyr/1.0/logistics-96-sample.mp3" type="audio/mp3"> <source src="https://cdn.selz.com/plyr/1.0/logistics-96-sample.mp3" type="audio/mp3">
<source src="//cdn.selz.com/plyr/1.0/logistics-96-sample.ogg" type="audio/ogg"> <source src="https://cdn.selz.com/plyr/1.0/logistics-96-sample.ogg" type="audio/ogg">
<!-- Fallback for browsers that don't support the <audio> element --> <!-- Fallback for browsers that don't support the <audio> element -->
<div> <a href="https://cdn.selz.com/plyr/1.0/logistics-96-sample.mp3">Download</a>
<a href="//cdn.selz.com/plyr/1.0/logistics-96-sample.mp3">Download</a>
</div>
</audio> </audio>
</div> </div>
``` ```
@ -130,21 +132,17 @@ You'll notice the `crossorigin` attribute on the example `<video>` and `<audio>`
More info on CORS here: More info on CORS here:
[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)
###JavaScript ### JavaScript
Much of the behaviour of the player is configurable when initialising the library. Below is an example of a default instance. Much of the behaviour of the player is configurable when initialising the library. Here's an example of a default setup:
```html ```html
<script src="dist/plyr.js"></script> <script src="dist/plyr.js"></script>
<script> <script>plyr.setup();</script>
plyr.setup({
*options*
});
</script>
``` ```
#### Options #### Options
You can pass the following options to the setup method. You can pass the following options to the setup method using `plyr.setup({...})`.
<table class="table" width="100%"> <table class="table" width="100%">
<thead> <thead>
@ -168,6 +166,12 @@ You can pass the following options to the setup method.
<td><code><a href="controls.md">See controls.md</a></code></td> <td><code><a href="controls.md">See controls.md</a></code></td>
<td>See <a href="controls.md">controls.md</a> for more info on how the html needs to be structured.</td> <td>See <a href="controls.md">controls.md</a> for more info on how the html needs to be structured.</td>
</tr> </tr>
<tr>
<td><code>controls</code></td>
<td>Array</td>
<td><code>["restart", "rewind", "play", "fast-forward", "current-time", "duration", "mute", "volume", "captions", "fullscreen"]</code></td>
<td>Toggle which control elements you would like to display when using the default controls html. If you specify a <code>html</code> option, this is redundant. The default value is to display everything.</td>
</tr>
<tr> <tr>
<td><code>debug</code></td> <td><code>debug</code></td>
<td>Boolean</td> <td>Boolean</td>
@ -202,7 +206,7 @@ You can pass the following options to the setup method.
<td><code>displayDuration</code></td> <td><code>displayDuration</code></td>
<td>Boolean</td> <td>Boolean</td>
<td><code>true</code></td> <td><code>true</code></td>
<td>Displays the duration of the media on the "metadataloaded" event (on startup). This will only work if the `preload` attribute is not set to `none`. It is `auto` by default (if the attribute is not present).</td> <td>Displays the duration of the media on the "metadataloaded" event (on startup) in the current time display. This will only work if the `preload` attribute is not set to `none` (or is not set at all) and you choose not to display the duration (see <code>controls</code> option).</td>
</tr> </tr>
<tr> <tr>
<td><code>selectors</code></td> <td><code>selectors</code></td>
@ -234,6 +238,12 @@ You can pass the following options to the setup method.
<td>&mdash;</td> <td>&mdash;</td>
<td>Two properties; <code>enabled</code> which toggles if local storage should be enabled (if the browser supports it). The default value is `true`. This enables storing user settings, currently it only stores volume but more will be added later. The second property <code>key</code> is the key used for the local storage. The default is <code>plyr_volume</code> until more settings are stored.</td> <td>Two properties; <code>enabled</code> which toggles if local storage should be enabled (if the browser supports it). The default value is `true`. This enables storing user settings, currently it only stores volume but more will be added later. The second property <code>key</code> is the key used for the local storage. The default is <code>plyr_volume</code> until more settings are stored.</td>
</tr> </tr>
<tr>
<td><code>onSetup</code></td>
<td>Function</td>
<td>&mdash;</td>
<td>This callback function is called on every new plyr instance created. The context (<code>this</code>) is the plyr instance itself.</td>
</tr>
</tbody> </tbody>
</table> </table>
@ -301,6 +311,16 @@ Here's a list of the methods supported:
<td>&mdash;</td> <td>&mdash;</td>
<td>Toggles whether captions are enabled.</td> <td>Toggles whether captions are enabled.</td>
</tr> </tr>
<tr>
<td><code>toggleFullscreen()</code></td>
<td>Event</td>
<td>Toggles fullscreen. This can only be initiated by a user gesture due to browser security, i.e. a user event such as click.</td>
</tr>
<tr>
<td><code>isFullscreen()</code></td>
<td>&mdash;</td>
<td>Boolean returned if the player is in fullscreen.</td>
</tr>
<tr> <tr>
<td><code>support(...)</code></td> <td><code>support(...)</code></td>
<td>String</td> <td>String</td>
@ -321,11 +341,21 @@ Here's a list of the methods supported:
This will inject a child `source` element for every element in the array with the specified attributes. `src` is the only required attribute although adding `type` is recommended as it helps the browser decide which file to download and play. This will inject a child `source` element for every element in the array with the specified attributes. `src` is the only required attribute although adding `type` is recommended as it helps the browser decide which file to download and play.
</td> </td>
</tr> </tr>
<tr> <tr>
<td><code>poster(...)</code></td> <td><code>poster(...)</code></td>
<td>String</td> <td>String</td>
<td>Set the poster url. This is supported for the <code>video</code> element only.</td> <td>Set the poster url. This is supported for the <code>video</code> element only.</td>
</tr> </tr>
<tr>
<td><code>destroy()</code></td>
<td>&mdash;</td>
<td>Destroys the plyr UI and any media event listeners, effectively restoring to the previous state before <code>setup()</code> was called.</td>
</tr>
<tr>
<td><code>restore()</code></td>
<td>&mdash;</td>
<td>Reverses the effects of the <code>destroy()</code> method, restoring the UI and listeners.</td>
</tr>
</tbody> </tbody>
</table> </table>
@ -344,11 +374,12 @@ A complete list of events can be found here:
[Media Events - W3.org](http://www.w3.org/2010/05/video/mediaevents.html) [Media Events - W3.org](http://www.w3.org/2010/05/video/mediaevents.html)
## Fullscreen ## Fullscreen
Fullscreen in Plyr is supported for all browsers that [currently support it](http://caniuse.com/#feat=fullscreen). If you're using the default CSS, you can also use a "full browser" mode which will use the full browser window by adding the `player-fullscreen` class to your container. Fullscreen in Plyr is supported for all browsers that [currently support it](http://caniuse.com/#feat=fullscreen). If you're using the default CSS, you can also use a "full browser" mode which will use the full browser window by adding the `player-fullscreen` class to your container.
## Browser support ## Browser support
<table width="100%" style="text-align: center;"> <table width="100%" style="text-align: center">
<thead> <thead>
<tr> <tr>
<td>Safari</td> <td>Safari</td>
@ -386,11 +417,14 @@ If a User Agent is disabled but supports `<video>` and `<audio>` natively, it wi
Any unsupported browsers will display links to download the media if the correct html is used. Any unsupported browsers will display links to download the media if the correct html is used.
### Checking for support
There's an API method for checking support. You can call `plyr.supported()` and optionally pass a type to it, e.g. `plyr.supported("video")`. It will return an object with two keys; `basic` meaning there's basic support for that media type (or both if no type is passed) and `full` meaning there's full support for plyr.
## Issues ## Issues
If you find anything weird with Plyr, please let us know using the GitHub issues tracker. If you find anything weird with Plyr, please let us know using the GitHub issues tracker.
## Author ## Author
Plyr is developed by Sam Potts ([@sam_potts](https://twitter.com/sam_potts)) ([sampotts.me](http://sampotts.me)) 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/Selz/plyr/graphs/contributors)
## Mentions ## Mentions
- [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/) - [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
@ -418,4 +452,4 @@ Also these links helped created Plyr:
- [Styling the `<progress>` element - hongkiat.com](http://www.hongkiat.com/blog/html5-progress-bar/) - [Styling the `<progress>` element - hongkiat.com](http://www.hongkiat.com/blog/html5-progress-bar/)
## Copyright and License ## Copyright and License
[The MIT license](license.md). [The MIT license](license.md).

View File

@ -1,6 +1,6 @@
// ========================================================================== // ==========================================================================
// Plyr // Plyr
// plyr.js v1.0.31 // plyr.js v1.1.6
// https://github.com/selz/plyr // https://github.com/selz/plyr
// License: The MIT License (MIT) // License: The MIT License (MIT)
// ========================================================================== // ==========================================================================
@ -44,6 +44,7 @@
played: ".player-progress-played" played: ".player-progress-played"
}, },
captions: ".player-captions", captions: ".player-captions",
currentTime: ".player-current-time",
duration: ".player-duration" duration: ".player-duration"
}, },
classes: { classes: {
@ -79,71 +80,149 @@
enabled: true, enabled: true,
key: "plyr_volume" key: "plyr_volume"
}, },
html: (function() { controls: ["restart", "rewind", "play", "fast-forward", "current-time", "duration", "mute", "volume", "captions", "fullscreen"],
return [ onSetup: function() {},
"<div class='player-controls'>",
"<div class='player-progress'>",
"<label for='seek{id}' class='sr-only'>Seek</label>",
"<input id='seek{id}' class='player-progress-seek' type='range' min='0' max='100' step='0.5' value='0' data-player='seek'>",
"<progress class='player-progress-played' max='100' value='0'>",
"<span>0</span>% played",
"</progress>",
"<progress class='player-progress-buffer' max='100' value='0'>",
"<span>0</span>% buffered",
"</progress>",
"</div>",
"<span class='player-controls-left'>",
"<button type='button' data-player='restart'>",
"<svg><use xlink:href='#icon-restart'></use></svg>",
"<span class='sr-only'>Restart</span>",
"</button>",
"<button type='button' data-player='rewind'>",
"<svg><use xlink:href='#icon-rewind'></use></svg>",
"<span class='sr-only'>Rewind {seektime} secs</span>",
"</button>",
"<button type='button' data-player='play'>",
"<svg><use xlink:href='#icon-play'></use></svg>",
"<span class='sr-only'>Play</span>",
"</button>",
"<button type='button' data-player='pause'>",
"<svg><use xlink:href='#icon-pause'></use></svg>",
"<span class='sr-only'>Pause</span>",
"</button>",
"<button type='button' data-player='fast-forward'>",
"<svg><use xlink:href='#icon-fast-forward'></use></svg>",
"<span class='sr-only'>Forward {seektime} secs</span>",
"</button>",
"<span class='player-time'>",
"<span class='sr-only'>Time</span>",
"<span class='player-duration'>00:00</span>",
"</span>",
"</span>",
"<span class='player-controls-right'>",
"<input class='inverted sr-only' id='mute{id}' type='checkbox' data-player='mute'>",
"<label id='mute{id}' for='mute{id}'>",
"<svg class='icon-muted'><use xlink:href='#icon-muted'></use></svg>",
"<svg><use xlink:href='#icon-volume'></use></svg>",
"<span class='sr-only'>Toggle Mute</span>",
"</label>",
"<label for='volume{id}' class='sr-only'>Volume</label>",
"<input id='volume{id}' class='player-volume' type='range' min='0' max='10' value='5' data-player='volume'>",
"<input class='sr-only' id='captions{id}' type='checkbox' data-player='captions'>",
"<label for='captions{id}'>",
"<svg class='icon-captions-on'><use xlink:href='#icon-captions-on'></use></svg>",
"<svg><use xlink:href='#icon-captions-off'></use></svg>",
"<span class='sr-only'>Toggle Captions</span>",
"</label>",
"<button type='button' data-player='fullscreen'>",
"<svg class='icon-exit-fullscreen'><use xlink:href='#icon-exit-fullscreen'></use></svg>",
"<svg><use xlink:href='#icon-enter-fullscreen'></use></svg>",
"<span class='sr-only'>Toggle Fullscreen</span>",
"</button>",
"</span>",
"</div>"
].join("\n");
})()
}; };
// Build the default HTML
function _buildControls() {
// Open and add the progress and seek elements
var html = [
"<div class='player-controls'>",
"<div class='player-progress'>",
"<label for='seek{id}' class='sr-only'>Seek</label>",
"<input id='seek{id}' class='player-progress-seek' type='range' min='0' max='100' step='0.5' value='0' data-player='seek'>",
"<progress class='player-progress-played' max='100' value='0'>",
"<span>0</span>% played",
"</progress>",
"<progress class='player-progress-buffer' max='100' value='0'>",
"<span>0</span>% buffered",
"</progress>",
"</div>",
"<span class='player-controls-left'>"];
// Restart button
if(_inArray(config.controls, "restart")) {
html.push(
"<button type='button' data-player='restart'>",
"<svg><use xlink:href='#icon-restart'></use></svg>",
"<span class='sr-only'>Restart</span>",
"</button>"
);
}
// Rewind button
if(_inArray(config.controls, "rewind")) {
html.push(
"<button type='button' data-player='rewind'>",
"<svg><use xlink:href='#icon-rewind'></use></svg>",
"<span class='sr-only'>Rewind {seektime} secs</span>",
"</button>"
);
}
// Play/pause button
if(_inArray(config.controls, "play")) {
html.push(
"<button type='button' data-player='play'>",
"<svg><use xlink:href='#icon-play'></use></svg>",
"<span class='sr-only'>Play</span>",
"</button>",
"<button type='button' data-player='pause'>",
"<svg><use xlink:href='#icon-pause'></use></svg>",
"<span class='sr-only'>Pause</span>",
"</button>"
);
}
// Fast forward button
if(_inArray(config.controls, "fast-forward")) {
html.push(
"<button type='button' data-player='fast-forward'>",
"<svg><use xlink:href='#icon-fast-forward'></use></svg>",
"<span class='sr-only'>Forward {seektime} secs</span>",
"</button>"
);
}
// Media current time display
if(_inArray(config.controls, "current-time")) {
html.push(
"<span class='player-time'>",
"<span class='sr-only'>Current time</span>",
"<span class='player-current-time'>00:00</span>",
"</span>"
);
}
// Media duration display
if(_inArray(config.controls, "duration")) {
html.push(
"<span class='player-time'>",
"<span class='sr-only'>Duration</span>",
"<span class='player-duration'>00:00</span>",
"</span>"
);
}
// Close left controls
html.push(
"</span>",
"<span class='player-controls-right'>"
);
// Toggle mute button
if(_inArray(config.controls, "mute")) {
html.push(
"<input class='inverted sr-only' id='mute{id}' type='checkbox' data-player='mute'>",
"<label id='mute{id}' for='mute{id}'>",
"<svg class='icon-muted'><use xlink:href='#icon-muted'></use></svg>",
"<svg><use xlink:href='#icon-volume'></use></svg>",
"<span class='sr-only'>Toggle Mute</span>",
"</label>"
);
}
// Volume range control
if(_inArray(config.controls, "volume")) {
html.push(
"<label for='volume{id}' class='sr-only'>Volume</label>",
"<input id='volume{id}' class='player-volume' type='range' min='0' max='10' value='5' data-player='volume'>"
);
}
// Toggle captions button
if(_inArray(config.controls, "captions")) {
html.push(
"<input class='sr-only' id='captions{id}' type='checkbox' data-player='captions'>",
"<label for='captions{id}'>",
"<svg class='icon-captions-on'><use xlink:href='#icon-captions-on'></use></svg>",
"<svg><use xlink:href='#icon-captions-off'></use></svg>",
"<span class='sr-only'>Toggle Captions</span>",
"</label>"
);
}
// Toggle fullscreen button
if(_inArray(config.controls, "fullscreen")) {
html.push(
"<button type='button' data-player='fullscreen'>",
"<svg class='icon-exit-fullscreen'><use xlink:href='#icon-exit-fullscreen'></use></svg>",
"<svg><use xlink:href='#icon-enter-fullscreen'></use></svg>",
"<span class='sr-only'>Toggle Fullscreen</span>",
"</button>"
);
}
// Close everything
html.push(
"</span>",
"</div>"
);
return html.join("");
}
// Debugging // Debugging
function _log(text, error) { function _log(text, error) {
if(config.debug && window.console) { if(config.debug && window.console) {
@ -156,8 +235,8 @@
function _browserSniff() { function _browserSniff() {
var nAgt = navigator.userAgent, var nAgt = navigator.userAgent,
name = navigator.appName, name = navigator.appName,
fullVersion = ""+parseFloat(navigator.appVersion), fullVersion = "" + parseFloat(navigator.appVersion),
majorVersion = parseInt(navigator.appVersion,10), majorVersion = parseInt(navigator.appVersion, 10),
nameOffset, nameOffset,
verOffset, verOffset,
ix; ix;
@ -170,46 +249,47 @@
// MSIE // MSIE
else if ((verOffset=nAgt.indexOf("MSIE")) !== -1) { else if ((verOffset=nAgt.indexOf("MSIE")) !== -1) {
name = "IE"; name = "IE";
fullVersion = nAgt.substring(verOffset+5); fullVersion = nAgt.substring(verOffset + 5);
} }
// Chrome // Chrome
else if ((verOffset=nAgt.indexOf("Chrome")) !== -1) { else if ((verOffset=nAgt.indexOf("Chrome")) !== -1) {
name = "Chrome"; name = "Chrome";
fullVersion = nAgt.substring(verOffset+7); fullVersion = nAgt.substring(verOffset + 7);
} }
// Safari // Safari
else if ((verOffset=nAgt.indexOf("Safari")) !== -1) { else if ((verOffset=nAgt.indexOf("Safari")) !== -1) {
name = "Safari"; name = "Safari";
fullVersion = nAgt.substring(verOffset+7); fullVersion = nAgt.substring(verOffset + 7);
if ((verOffset=nAgt.indexOf("Version")) !== -1) { if ((verOffset=nAgt.indexOf("Version")) !== -1) {
fullVersion = nAgt.substring(verOffset+8); fullVersion = nAgt.substring(verOffset + 8);
} }
} }
// Firefox // Firefox
else if ((verOffset=nAgt.indexOf("Firefox")) !== -1) { else if ((verOffset=nAgt.indexOf("Firefox")) !== -1) {
name = "Firefox"; name = "Firefox";
fullVersion = nAgt.substring(verOffset+8); fullVersion = nAgt.substring(verOffset + 8);
} }
// In most other browsers, "name/version" is at the end of userAgent // In most other browsers, "name/version" is at the end of userAgent
else if ( (nameOffset=nAgt.lastIndexOf(" ")+1) < (verOffset=nAgt.lastIndexOf("/")) ) { else if ((nameOffset=nAgt.lastIndexOf(" ") + 1) < (verOffset=nAgt.lastIndexOf("/"))) {
name = nAgt.substring(nameOffset,verOffset); name = nAgt.substring(nameOffset,verOffset);
fullVersion = nAgt.substring(verOffset+1); fullVersion = nAgt.substring(verOffset + 1);
if (name.toLowerCase()==name.toUpperCase()) {
if (name.toLowerCase() == name.toUpperCase()) {
name = navigator.appName; name = navigator.appName;
} }
} }
// Trim the fullVersion string at semicolon/space if present // Trim the fullVersion string at semicolon/space if present
if ((ix=fullVersion.indexOf(";")) !== -1) { if ((ix = fullVersion.indexOf(";")) !== -1) {
fullVersion=fullVersion.substring(0,ix); fullVersion = fullVersion.substring(0, ix);
} }
if ((ix=fullVersion.indexOf(" ")) !== -1) { if ((ix = fullVersion.indexOf(" ")) !== -1) {
fullVersion=fullVersion.substring(0,ix); fullVersion = fullVersion.substring(0, ix);
} }
// Get major version // Get major version
majorVersion = parseInt(""+fullVersion,10); majorVersion = parseInt("" + fullVersion, 10);
if (isNaN(majorVersion)) { if (isNaN(majorVersion)) {
fullVersion = ""+parseFloat(navigator.appVersion); fullVersion = "" + parseFloat(navigator.appVersion);
majorVersion = parseInt(navigator.appVersion,10); majorVersion = parseInt(navigator.appVersion, 10);
} }
// Return data // Return data
@ -249,6 +329,11 @@
// If we got this far, we're stuffed // If we got this far, we're stuffed
return false; return false;
} }
// Element exists in an array
function _inArray(haystack, needle) {
return Array.prototype.indexOf && (haystack.indexOf(needle) != -1);
}
// Replace all // Replace all
function _replaceAll(string, find, replace) { function _replaceAll(string, find, replace) {
@ -257,7 +342,7 @@
// Wrap an element // Wrap an element
function _wrap(elements, wrapper) { function _wrap(elements, wrapper) {
// Convert `elms` to an array, if necessary. // Convert `elements` to an array, if necessary.
if (!elements.length) { if (!elements.length) {
elements = [elements]; elements = [elements];
} }
@ -265,28 +350,44 @@
// Loops backwards to prevent having to clone the wrapper on the // Loops backwards to prevent having to clone the wrapper on the
// first element (see `child` below). // first element (see `child` below).
for (var i = elements.length - 1; i >= 0; i--) { for (var i = elements.length - 1; i >= 0; i--) {
var child = (i > 0) ? wrapper.cloneNode(true) : wrapper; var child = (i > 0) ? wrapper.cloneNode(true) : wrapper;
var el = elements[i]; var element = elements[i];
// Cache the current parent and sibling. // Cache the current parent and sibling.
var parent = el.parentNode; var parent = element.parentNode;
var sibling = el.nextSibling; var sibling = element.nextSibling;
// Wrap the element (is automatically removed from its current // Wrap the element (is automatically removed from its current
// parent). // parent).
child.appendChild(el); child.appendChild(element);
// If the element had a sibling, insert the wrapper before // If the element had a sibling, insert the wrapper before
// the sibling to maintain the HTML structure; otherwise, just // the sibling to maintain the HTML structure; otherwise, just
// append it to the parent. // append it to the parent.
if (sibling) { if (sibling) {
parent.insertBefore(child, sibling); parent.insertBefore(child, sibling);
} else { }
else {
parent.appendChild(child); parent.appendChild(child);
} }
} }
} }
// Unwrap an element
// http://plainjs.com/javascript/manipulation/unwrap-a-dom-element-35/
function _unwrap(wrapper) {
// Get the element's parent node
var parent = wrapper.parentNode;
// Move all children out of the element
while (wrapper.firstChild) {
parent.insertBefore(wrapper.firstChild, wrapper);
}
// Remove the empty element
parent.removeChild(wrapper);
}
// Remove an element // Remove an element
function _remove(element) { function _remove(element) {
element.parentNode.removeChild(element); element.parentNode.removeChild(element);
@ -319,7 +420,7 @@
// Toggle event // Toggle event
function _toggleHandler(element, events, callback, toggle) { function _toggleHandler(element, events, callback, toggle) {
events = events.split(" "); var eventList = events.split(" ");
// If a nodelist is passed, call itself on each node // If a nodelist is passed, call itself on each node
if(element instanceof NodeList) { if(element instanceof NodeList) {
@ -332,19 +433,23 @@
} }
// If a single node is passed, bind the event listener // If a single node is passed, bind the event listener
for (var i = 0; i < events.length; i++) { for (var i = 0; i < eventList.length; i++) {
element[toggle ? "addEventListener" : "removeEventListener"](events[i], callback, false); element[toggle ? "addEventListener" : "removeEventListener"](eventList[i], callback, false);
} }
} }
// Bind event // Bind event
function _on(element, events, callback) { function _on(element, events, callback) {
_toggleHandler(element, events, callback, true); if(element) {
_toggleHandler(element, events, callback, true);
}
} }
// Unbind event // Unbind event
function _off(element, events, callback) { function _off(element, events, callback) {
_toggleHandler(element, events, callback, false); if(element) {
_toggleHandler(element, events, callback, false);
}
} }
// Trigger event // Trigger event
@ -432,7 +537,7 @@
} }
} }
// Safari doesn't support the ALLOW_KEYBOARD_INPUT flag so set it to not supported // Safari doesn't support the ALLOW_KEYBOARD_INPUT flag (for security) so set it to not supported
// https://bugs.webkit.org/show_bug.cgi?id=121496 // https://bugs.webkit.org/show_bug.cgi?id=121496
if(fullscreen.prefix === "webkit" && !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/)) { if(fullscreen.prefix === "webkit" && !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/)) {
fullscreen.supportsFullScreen = false; fullscreen.supportsFullScreen = false;
@ -444,19 +549,18 @@
// Sometimes the prefix is "ms", sometimes "MS" to keep you on your toes // Sometimes the prefix is "ms", sometimes "MS" to keep you on your toes
fullscreen.fullScreenEventName = (fullscreen.prefix == "ms" ? "MSFullscreenChange" : fullscreen.prefix + "fullscreenchange"); fullscreen.fullScreenEventName = (fullscreen.prefix == "ms" ? "MSFullscreenChange" : fullscreen.prefix + "fullscreenchange");
fullscreen.isFullScreen = function() { fullscreen.isFullScreen = function(element) {
if(typeof element == "undefined") {
element = document;
}
switch (this.prefix) { switch (this.prefix) {
case "": case "":
return document.fullScreen; return document.fullscreenElement == element;
case "webkit": case "moz":
return document.webkitIsFullScreen; return document.mozFullScreenElement == element;
case "ms":
// Docs say document.msFullScreenElement returns undefined
// if no element is full screem but it returns null, cheers
// https://msdn.microsoft.com/en-us/library/ie/dn265028%28v=vs.85%29.aspx
return (document.msFullscreenElement !== null);
default: default:
return document[this.prefix + "FullScreen"]; return document[this.prefix + "FullscreenElement"] == element;
} }
}; };
fullscreen.requestFullScreen = function(element) { fullscreen.requestFullScreen = function(element) {
@ -533,6 +637,11 @@
// Display captions container and button (for initialization) // Display captions container and button (for initialization)
function _showCaptions() { function _showCaptions() {
// If there's no caption toggle, bail
if(!player.buttons.captions) {
return;
}
_toggleClass(player.container, config.classes.captions.enabled, true); _toggleClass(player.container, config.classes.captions.enabled, true);
if (config.captions.defaultActive) { if (config.captions.defaultActive) {
@ -589,18 +698,22 @@
// Insert controls // Insert controls
function _injectControls() { function _injectControls() {
// Make a copy of the html
var html = config.html;
// Insert custom video controls // Insert custom video controls
_log("Injecting custom controls."); _log("Injecting custom controls.");
// Use specified html // If no controls are specified, create default
// Need to do a default? if(!html) {
var html = config.html; html = _buildControls();
}
// Replace seek time instances // Replace seek time instances
html = _replaceAll(html, "{seektime}", config.seekTime); html = _replaceAll(html, "{seektime}", config.seekTime);
// Replace all id references // Replace all id references with random numbers
html = _replaceAll(html, "{id}", player.random); html = _replaceAll(html, "{id}", Math.floor(Math.random() * (10000)));
// Inject into the container // Inject into the container
player.container.insertAdjacentHTML("beforeend", html); player.container.insertAdjacentHTML("beforeend", html);
@ -645,18 +758,19 @@
// Progress - Buffering // Progress - Buffering
player.progress.buffer = {}; player.progress.buffer = {};
player.progress.buffer.bar = _getElement(config.selectors.progress.buffer); player.progress.buffer.bar = _getElement(config.selectors.progress.buffer);
player.progress.buffer.text = player.progress.buffer.bar.getElementsByTagName("span")[0]; player.progress.buffer.text = player.progress.buffer.bar && player.progress.buffer.bar.getElementsByTagName("span")[0];
// Progress - Played // Progress - Played
player.progress.played = {}; player.progress.played = {};
player.progress.played.bar = _getElement(config.selectors.progress.played); player.progress.played.bar = _getElement(config.selectors.progress.played);
player.progress.played.text = player.progress.played.bar.getElementsByTagName("span")[0]; player.progress.played.text = player.progress.played.bar && player.progress.played.bar.getElementsByTagName("span")[0];
// Volume // Volume
player.volume = _getElement(config.selectors.buttons.volume); player.volume = _getElement(config.selectors.buttons.volume);
// Timing // Timing
player.duration = _getElement(config.selectors.duration); player.duration = _getElement(config.selectors.duration);
player.currentTime = _getElement(config.selectors.currentTime);
player.seekTime = _getElements(config.selectors.seekTime); player.seekTime = _getElements(config.selectors.seekTime);
return true; return true;
@ -664,12 +778,21 @@
catch(e) { catch(e) {
_log("It looks like there's a problem with your controls html. Bailing.", true); _log("It looks like there's a problem with your controls html. Bailing.", true);
// Restore native video controls
player.media.setAttribute("controls", "");
return false; return false;
} }
} }
// Setup aria attributes // Setup aria attributes
function _setupAria() { function _setupAria() {
// If there's no play button, bail
if(!player.buttons.play) {
return;
}
// Find the current text
var label = player.buttons.play.innerText || "Play"; var label = player.buttons.play.innerText || "Play";
// If there's a media title set, use that for the label // If there's a media title set, use that for the label
@ -932,7 +1055,7 @@
targetTime = input; targetTime = input;
} }
// Event // Event
else if (typeof input === "object" && (input.type === "change" || input.type === "input")) { else if (typeof input === "object" && (input.type === "input" || input.type === "change")) {
// It's the seek slider // It's the seek slider
// Seek to the selected time // Seek to the selected time
targetTime = ((input.target.value / input.target.max) * player.media.duration); targetTime = ((input.target.value / input.target.max) * player.media.duration);
@ -973,12 +1096,12 @@
// If it's a fullscreen change event, it's probably a native close // If it's a fullscreen change event, it's probably a native close
if(event && event.type === fullscreen.fullScreenEventName) { if(event && event.type === fullscreen.fullScreenEventName) {
config.fullscreen.active = fullscreen.isFullScreen(); player.isFullscreen = fullscreen.isFullScreen(player.container);
} }
// If there's native support, use it // If there's native support, use it
else if(nativeSupport) { else if(nativeSupport) {
// Request fullscreen // Request fullscreen
if(!fullscreen.isFullScreen()) { if(!fullscreen.isFullScreen(player.container)) {
fullscreen.requestFullScreen(player.container); fullscreen.requestFullScreen(player.container);
} }
// Bail from fullscreen // Bail from fullscreen
@ -987,14 +1110,14 @@
} }
// Check if we're actually full screen (it could fail) // Check if we're actually full screen (it could fail)
config.fullscreen.active = fullscreen.isFullScreen(); player.isFullscreen = fullscreen.isFullScreen(player.container);
} }
else { else {
// Otherwise, it's a simple toggle // Otherwise, it's a simple toggle
config.fullscreen.active = !config.fullscreen.active; player.isFullscreen = !player.isFullscreen;
// Bind/unbind escape key // Bind/unbind escape key
if(config.fullscreen.active) { if(player.isFullscreen) {
_on(document, "keyup", _handleEscapeFullscreen); _on(document, "keyup", _handleEscapeFullscreen);
document.body.style.overflow = "hidden"; document.body.style.overflow = "hidden";
} }
@ -1005,19 +1128,29 @@
} }
// Set class hook // Set class hook
_toggleClass(player.container, config.classes.fullscreen.active, config.fullscreen.active); _toggleClass(player.container, config.classes.fullscreen.active, player.isFullscreen);
// Remove hover class because mouseleave doesn't occur
if (player.isFullscreen) {
_toggleClass(player.controls, config.classes.hover, false);
}
} }
// Bail from faux-fullscreen // Bail from faux-fullscreen
function _handleEscapeFullscreen(event) { function _handleEscapeFullscreen(event) {
// If it's a keypress and not escape, bail // If it's a keypress and not escape, bail
if((event.which || event.charCode || event.keyCode) === 27 && config.fullscreen.active) { if((event.which || event.charCode || event.keyCode) === 27 && player.isFullscreen) {
_toggleFullscreen(); _toggleFullscreen();
} }
} }
// Set volume // Set volume
function _setVolume(volume) { function _setVolume(volume) {
// Bail if there's no volume element
if(!player.volume) {
return;
}
// Use default if needed // Use default if needed
if(typeof volume === "undefined") { if(typeof volume === "undefined") {
if(config.storage.enabled && _storage().supported) { if(config.storage.enabled && _storage().supported) {
@ -1045,7 +1178,7 @@
// Store the volume in storage // Store the volume in storage
if(config.storage.enabled && _storage().supported) { if(config.storage.enabled && _storage().supported) {
window.localStorage.plyr_volume = volume; window.localStorage.setItem(config.storage.key, volume);
} }
} }
@ -1070,7 +1203,8 @@
// Toggle captions // Toggle captions
function _toggleCaptions(show) { function _toggleCaptions(show) {
if(!player.supported.full) { // If there's no full support, or there's no caption toggle
if(!player.supported.full || !player.buttons.captions) {
return; return;
} }
@ -1115,7 +1249,7 @@
value = _getPercentage(player.media.currentTime, player.media.duration); value = _getPercentage(player.media.currentTime, player.media.duration);
// Set seek range value only if it's a "natural" time event // Set seek range value only if it's a "natural" time event
if(event.type == "timeupdate") { if(event.type == "timeupdate" && player.buttons.seek) {
player.buttons.seek.value = value; player.buttons.seek.value = value;
} }
@ -1147,12 +1281,21 @@
} }
// Set values // Set values
progress.value = value; if(progress) {
text.innerHTML = value; progress.value = value;
}
if(text) {
text.innerHTML = value;
}
} }
// Update the displayed time // Update the displayed time
function _updateTimeDisplay(time) { function _updateTimeDisplay(time, element) {
// Bail if there's no duration display
if(!element) {
return;
}
player.secs = parseInt(time % 60); player.secs = parseInt(time % 60);
player.mins = parseInt((time / 60) % 60); player.mins = parseInt((time / 60) % 60);
player.hours = parseInt(((time / 60) / 60) % 60); player.hours = parseInt(((time / 60) / 60) % 60);
@ -1165,20 +1308,28 @@
player.mins = ("0" + player.mins).slice(-2); player.mins = ("0" + player.mins).slice(-2);
// Render // Render
player.duration.innerHTML = (displayHours ? player.hours + ":" : "") + player.mins + ":" + player.secs; element.innerHTML = (displayHours ? player.hours + ":" : "") + player.mins + ":" + player.secs;
} }
// Show the duration on metadataloaded // Show the duration on metadataloaded
function _displayDuration() { function _displayDuration() {
if(player.media.paused) { var duration = player.media.duration || 0;
_updateTimeDisplay(player.media.duration || 0);
// If there's only one time display, display duration there
if(!player.duration && config.displayDuration && player.media.paused) {
_updateTimeDisplay(duration, player.currentTime);
}
// If there's a duration element, update content
if(player.duration) {
_updateTimeDisplay(duration, player.duration);
} }
} }
// Handle time change event // Handle time change event
function _timeUpdate(event) { function _timeUpdate(event) {
// Duration // Duration
_updateTimeDisplay(player.media.currentTime); _updateTimeDisplay(player.media.currentTime, player.currentTime);
// Playing progress // Playing progress
_updateProgress(event); _updateProgress(event);
@ -1265,6 +1416,9 @@
// Listen for events // Listen for events
function _listeners() { function _listeners() {
// IE doesn't support input event, so we fallback to change
var inputEvent = (player.browser.name == "IE" ? "change" : "input");
// Play // Play
_on(player.buttons.play, "click", function() { _on(player.buttons.play, "click", function() {
_play(); _play();
@ -1286,9 +1440,11 @@
// Fast forward // Fast forward
_on(player.buttons.forward, "click", _forward); _on(player.buttons.forward, "click", _forward);
// Get the HTML5 range input element and append audio volume adjustment on change/input // Seek
// IE10 doesn't support the "input" event so they have to wait for change _on(player.buttons.seek, inputEvent, _seek);
_on(player.volume, "change input", function() {
// Set volume
_on(player.volume, inputEvent, function() {
_setVolume(this.value); _setVolume(this.value);
}); });
@ -1312,12 +1468,7 @@
_on(player.media, "timeupdate", _seekManualCaptions); _on(player.media, "timeupdate", _seekManualCaptions);
// Display duration // Display duration
if(config.displayDuration) { _on(player.media, "loadedmetadata", _displayDuration);
_on(player.media, "loadedmetadata", _displayDuration);
}
// Seek
_on(player.buttons.seek, "change input", _seek);
// Captions // Captions
_on(player.buttons.captions, "change", function() { _on(player.buttons.captions, "change", function() {
@ -1357,14 +1508,14 @@
if(player.type === "video" && config.click) { if(player.type === "video" && config.click) {
_on(player.videoContainer, "click", function() { _on(player.videoContainer, "click", function() {
if(player.media.paused) { if(player.media.paused) {
_play(); _triggerEvent(player.buttons.play, "click");
} }
else if(player.media.ended) { else if(player.media.ended) {
_seek(); _seek();
_play(); _triggerEvent(player.buttons.play, "click");
} }
else { else {
_pause(); _triggerEvent(player.buttons.pause, "click");
} }
}); });
} }
@ -1373,11 +1524,51 @@
if(config.fullscreen.hideControls) { if(config.fullscreen.hideControls) {
_on(player.controls, "mouseenter mouseleave", function(event) { _on(player.controls, "mouseenter mouseleave", function(event) {
_toggleClass(player.controls, config.classes.hover, (event.type === "mouseenter")); _toggleClass(player.controls, config.classes.hover, (event.type === "mouseenter"));
}) });
} }
} }
// Destroy an instance
function _destroy() {
// Bail if the element is not initialized
if(!player.init) {
return null;
}
// Event listeners are removed when elements are removed
// http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory
// Remove controls
_remove(_getElement(config.selectors.controls));
// If video, we need to remove some more
if(player.type === "video") {
// Remove captions
_remove(_getElement(config.selectors.captions));
// Remove video wrapper
_unwrap(player.videoContainer);
}
// Restore native video controls
player.media.setAttribute("controls", "");
// Clone the media element to remove listeners
// http://stackoverflow.com/questions/19469881/javascript-remove-all-event-listeners-of-specific-type
var clone = player.media.cloneNode(true);
player.media.parentNode.replaceChild(clone, player.media);
// Remove init flag
player.init = false;
}
// Setup a player
function _init() { function _init() {
// Bail if the element is initialized
if(player.init) {
return null;
}
// Setup the fullscreen api // Setup the fullscreen api
fullscreen = _fullscreen(); fullscreen = _fullscreen();
@ -1404,9 +1595,6 @@
// Setup media // Setup media
_setupMedia(); _setupMedia();
// Generate random number for id/for attribute values for controls
player.random = Math.floor(Math.random() * (10000));
// If there's full support // If there's full support
if(player.supported.full) { if(player.supported.full) {
// Inject custom controls // Inject custom controls
@ -1439,10 +1627,14 @@
} }
// Successful setup // Successful setup
return true; player.init = true;
} }
if(!_init()) { // Initialize instance
_init();
// If init failed, return an empty object
if(!player.init) {
return {}; return {};
} }
@ -1454,12 +1646,16 @@
rewind: _rewind, rewind: _rewind,
forward: _forward, forward: _forward,
seek: _seek, seek: _seek,
source: _parseSource,
poster: _updatePoster,
setVolume: _setVolume, setVolume: _setVolume,
toggleMute: _toggleMute, toggleMute: _toggleMute,
toggleCaptions: _toggleCaptions, toggleCaptions: _toggleCaptions,
source: _parseSource, toggleFullscreen: _toggleFullscreen,
poster: _updatePoster, isFullscreen: function() { return player.isFullscreen || false; },
support: function(mimeType) { return _supportMime(player, mimeType); } support: function(mimeType) { return _supportMime(player, mimeType); },
destroy: _destroy,
restore: _init
} }
} }
@ -1500,7 +1696,7 @@
// Extend the default options with user specified // Extend the default options with user specified
config = _extend(defaults, options); config = _extend(defaults, options);
// If enabled carry on // Bail if disabled or no basic support
// You may want to disable certain UAs etc // You may want to disable certain UAs etc
if(!config.enabled || !api.supported().basic) { if(!config.enabled || !api.supported().basic) {
return false; return false;
@ -1522,13 +1718,16 @@
// Set plyr to false if setup failed // Set plyr to false if setup failed
element.plyr = (Object.keys(instance).length ? instance : false); element.plyr = (Object.keys(instance).length ? instance : false);
// Callback
config.onSetup.apply(element.plyr);
} }
// Add to return array // Add to return array even if it's already setup
players.push(element.plyr); players.push(element.plyr);
} }
return players; return players;
} }
}(this.plyr = this.plyr || {})); }(this.plyr = this.plyr || {}));

View File

@ -9,6 +9,7 @@
@gray-dark: #343f4a; @gray-dark: #343f4a;
@gray: #565d64; @gray: #565d64;
@gray-light: #cbd0d3; @gray-light: #cbd0d3;
@off-white: #d6dadd;
// Font sizes // Font sizes
@font-size-small: 14px; @font-size-small: 14px;
@ -259,6 +260,22 @@
font-size: @font-size-small; font-size: @font-size-small;
.font-smoothing(); .font-smoothing();
} }
// Media duration hidden on small screens
.player-time + .player-time {
display: none;
@media (min-width: @bp-control-split) {
display: inline-block;
}
// Add a slash in before
&::before {
content: "\2044";
margin-right: @control-spacing;
color: darken(@control-color, 30%);
}
}
} }
// Tooltips // Tooltips
@ -511,6 +528,17 @@
float: none; float: none;
} }
// Audio specific styles
// Position the progress within the container
&-audio .player-controls {
padding-top: (@control-spacing * 2);
}
&-audio .player-progress {
bottom: auto;
top: 0;
background: @off-white;
}
// Full screen mode // Full screen mode
&-fullscreen, &-fullscreen,
&.fullscreen-active { &.fullscreen-active {

View File

@ -5,49 +5,50 @@
// Variables // Variables
// ------------------------------- // -------------------------------
// Colors // Colors
$blue: #3498DB; $blue: #3498DB !default;
$gray-dark: #343f4a; $gray-dark: #343f4a !default;
$gray: #565d64; $gray: #565d64 !default;
$gray-light: #cbd0d3; $gray-light: #cbd0d3 !default;
$off-white: #d6dadd !default;
// Font sizes // Font sizes
$font-size-small: 14px; $font-size-small: 14px !default;
$font-size-base: 16px; $font-size-base: 16px !default;
$font-size-large: ceil(($font-size-base * 1.5)); $font-size-large: ceil(($font-size-base * 1.5)) !default;
// Controls // Controls
$control-spacing: 10px; $control-spacing: 10px !default;
$controls-bg: $gray-dark; $controls-bg: $gray-dark !default;
$control-bg-hover: $blue; $control-bg-hover: $blue !default;
$control-color: $gray-light; $control-color: $gray-light !default;
$control-color-inactive: $gray; $control-color-inactive: $gray !default;
$control-color-hover: #fff; $control-color-hover: #fff !default;
// Tooltips // Tooltips
$tooltip-bg: $controls-bg; $tooltip-bg: $controls-bg !default;
$tooltip-color: #fff; $tooltip-color: #fff !default;
$tooltip-padding: $control-spacing; $tooltip-padding: $control-spacing !default;
$tooltip-arrow-size: 5px; $tooltip-arrow-size: 5px !default;
$tooltip-radius: 3px; $tooltip-radius: 3px !default;
// Progress // Progress
$progress-bg: rgba(red($gray), green($gray), blue($gray), .2); $progress-bg: rgba(red($gray), green($gray), blue($gray), .2) !default;
$progress-playing-bg: $blue; $progress-playing-bg: $blue !default;
$progress-buffered-bg: rgba(red($gray), green($gray), blue($gray), .25); $progress-buffered-bg: rgba(red($gray), green($gray), blue($gray), .25) !default;
$progress-loading-size: 40px; $progress-loading-size: 40px !default;
$progress-loading-bg: rgba(0,0,0, .15); $progress-loading-bg: rgba(0,0,0, .15) !default;
// Volume // Volume
$volume-track-height: 6px; $volume-track-height: 6px !default;
$volume-track-bg: $gray; $volume-track-bg: $gray !default;
$volume-thumb-height: ($volume-track-height * 2); $volume-thumb-height: ($volume-track-height * 2) !default;
$volume-thumb-width: ($volume-track-height * 2); $volume-thumb-width: ($volume-track-height * 2) !default;
$volume-thumb-bg: $control-color; $volume-thumb-bg: $control-color !default;
$volume-thumb-bg-focus: $control-bg-hover; $volume-thumb-bg-focus: $control-bg-hover !default;
// Breakpoints // Breakpoints
$bp-control-split: 560px; // When controls split into left/right $bp-control-split: 560px !default; // When controls split into left/right
$bp-captions-large: 768px; // When captions jump to the larger font size $bp-captions-large: 768px !default; // When captions jump to the larger font size
// Utility classes & mixins // Utility classes & mixins
// ------------------------------- // -------------------------------
@ -267,6 +268,22 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
font-size: $font-size-small; font-size: $font-size-small;
@include font-smoothing(); @include font-smoothing();
} }
// Media duration hidden on small screens
.player-time + .player-time {
display: none;
@media (min-width: $bp-control-split) {
display: inline-block;
}
// Add a slash in before
&::before {
content: "\2044";
margin-right: $control-spacing;
color: darken($control-color, 30%);
}
}
} }
// Tooltips // Tooltips
@ -278,7 +295,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
margin-bottom: $tooltip-padding; margin-bottom: $tooltip-padding;
padding: $tooltip-padding ($tooltip-padding * 1.5); padding: $tooltip-padding ($tooltip-padding * 1.5);
opacity: 0; opacity: 0;
background: $tooltip-bg; background: $tooltip-bg;
border-radius: $tooltip-radius; border-radius: $tooltip-radius;
color: $tooltip-color; color: $tooltip-color;
@ -287,7 +304,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
font-weight: 600; font-weight: 600;
transform: translate(-50%, ($tooltip-padding * 3)); transform: translate(-50%, ($tooltip-padding * 3));
transition: transform .2s .2s ease, opacity .2s .2s ease; transition: transform .2s .2s ease, opacity .2s .2s ease;
&::after { &::after {
content: ""; content: "";
@ -519,6 +536,17 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
float: none; float: none;
} }
// Audio specific styles
// Position the progress within the container
&-audio .player-controls {
padding-top: ($control-spacing * 2);
}
&-audio .player-progress {
bottom: auto;
top: 0;
background: $off-white;
}
// Full screen mode // Full screen mode
&-fullscreen, &-fullscreen,
&.fullscreen-active { &.fullscreen-active {
@ -594,10 +622,4 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
&.fullscreen-enabled [data-player='fullscreen'] + label { &.fullscreen-enabled [data-player='fullscreen'] + label {
display: inline-block; display: inline-block;
} }
}
// Full browser view hides toggle
&-fullscreen [data-player='fullscreen'],
&-fullscreen [data-player='fullscreen'] + label {
display: none !important;
}
}