Compare commits

...

13 Commits

Author SHA1 Message Date
e568bc9c8d Controls improvements
- Added an option to toggle which controls display
- Better handle missing controls
2015-04-04 12:32:37 +11:00
c2c4172634 Clarified the option 2015-03-30 21:04:38 +11:00
73de5b5773 Added displayDuration option, small bug fix
- Using the native VTT cues, sometimes cues would not disappear
2015-03-30 21:03:48 +11:00
aa1fed0b16 Fixed bug with media longer than 60 minutes (Fixes #69) 2015-03-30 19:17:27 +11:00
22331ae9f1 Added mention 2015-03-22 23:24:15 +11:00
8b436276bf Fixed bug with caption toggle, hide controls in fullscreen 2015-03-22 21:26:29 +11:00
c61db87fd6 API improvements 2015-03-22 11:05:28 +11:00
388cb4df39 Keyboard accessibility improvements (Fixes #66)
- Enter now works on checkboxes within the controls
2015-03-21 17:00:05 +11:00
9feffb2972 Version bump 2015-03-15 10:18:56 +11:00
acea5cdb24 Built js 2015-03-15 10:14:00 +11:00
55ed577b6a Indentation
Converted to 4 space width tabbing
2015-03-15 10:12:36 +11:00
20b206a161 Merge pull request #62 from brunowego/patch-1
Updated SASS support
2015-03-15 10:00:00 +11:00
9c028a0ecc Updated SASS support 2015-03-14 15:50:32 -03:00
22 changed files with 2061 additions and 1667 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ node_modules
.DS_Store
aws.json
docs/index.dev.html
*.mp4

View File

@ -1,5 +1,28 @@
# Changelog
## 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
- Display duration on metadataloaded
## v1.0.30
- Fixed bug with media longer than 60 minutes (Fixes #69)
## v1.0.29
- Added option to hide controls on fullscreen (default `true`) while palying, after 1s. Pause, mouse hover on progress, or focus on a child control re-shows the controls. On touch a tap of the video (which plays/pauses the video by default) is required. (Fixes #47)
- Fixed a bug with caption toggle in 1.0.28
## v1.0.28
- Added API support for browsers that don't have full plyr support (pretty much <=IE9 and `<video>` on iPhone/iPod)
## v1.0.27
- Keyboard accessibility improvements (Fixes #66)
## v1.0.26
- Fixes for SASS (cheers @brunowego)
- Indentation reset to 4 spaces
## v1.0.25
- Fixes for iOS volume controls (hidden)
- Classnames for left/right controls changed

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.
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
@ -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)
- `{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
["<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>",
"</button>",
"<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>",
"</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 = {};
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">
<!-- Docs styles -->
<link rel="stylesheet" href="//cdn.plyr.io/1.0.25/docs.css">
<link rel="stylesheet" href="//cdn.plyr.io/1.1.0/docs.css">
</head>
<body>
<main>

View File

@ -8,10 +8,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Styles -->
<link rel="stylesheet" href="//cdn.plyr.io/1.0.25/plyr.css">
<link rel="stylesheet" href="//cdn.plyr.io/1.1.0/plyr.css">
<!-- Docs styles -->
<link rel="stylesheet" href="//cdn.plyr.io/1.0.25/docs.css">
<link rel="stylesheet" href="//cdn.plyr.io/1.1.0/docs.css">
</head>
<body>
<header>
@ -67,13 +67,29 @@
<!-- Load SVG defs -->
<!-- You should bundle all SVG/Icons into one file using a build tool such as gulp and svg store -->
<script>
(function(d,p){var a=new XMLHttpRequest(),b=d.body;a.open("GET",p,!0);a.send();a.onload=function(){var c=d.createElement("div");c.style.display="none";c.innerHTML=a.responseText;b.insertBefore(c,b.childNodes[0])}})(document,"//cdn.plyr.io/1.0.25/sprite.svg");
(function(d, u){
var a = new XMLHttpRequest(),
b = d.body;
// Check for CORS support
// If you're loading from same domain, you can remove the if statement
if("withCredentials" in a) {
a.open("GET", u, true);
a.send();
a.onload = function(){
var c = d.createElement("div");
c.style.display="none";
c.innerHTML = a.responseText;
b.insertBefore(c, b.childNodes[0]);
}
}
})(document, "https://cdn.plyr.io/1.1.0/sprite.svg");
</script>
<!-- Plyr core script -->
<script src="//cdn.plyr.io/1.0.25/plyr.js"></script>
<script src="//cdn.plyr.io/1.1.0/plyr.js"></script>
<!-- Docs script -->
<script src="//cdn.plyr.io/1.0.25/docs.js"></script>
<script src="//cdn.plyr.io/1.1.0/docs.js"></script>
</body>
</html>

View File

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

View File

@ -31,7 +31,11 @@
<span class="sr-only">Forward {seektime} secs</span>
</button>
<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>
</span>

View File

@ -22,7 +22,8 @@ var fs = require("fs"),
s3 = require("gulp-s3"),
gzip = require("gulp-gzip"),
replace = require("gulp-replace"),
open = require("gulp-open");
open = require("gulp-open"),
size = require("gulp-size");
var root = __dirname,
paths = {
@ -223,6 +224,10 @@ gulp.task("cdn", function () {
// Upload to CDN
gulp.src(paths.upload)
.pipe(size({
showFiles: true,
gzip: true
}))
.pipe(rename(function (path) {
path.dirname = path.dirname.replace(".", version);
}))
@ -234,6 +239,11 @@ gulp.task("cdn", function () {
gulp.task("docs", function () {
console.log("Uploading " + version + " docs to " + aws.docs.bucket);
// Replace versioned files in readme.md
gulp.src([root + "/readme.md"])
.pipe(replace(cdnpath, aws.cdn.bucket + "/" + version))
.pipe(gulp.dest(root));
// Replace versioned files in *.html
gulp.src([paths.docs.root + "*.html"])
.pipe(replace(cdnpath, aws.cdn.bucket + "/" + version))

View File

@ -1,6 +1,6 @@
{
"name": "plyr",
"version": "1.0.25",
"version": "1.1.0",
"description": "A simple HTML5 media player using custom controls",
"homepage": "http://plyr.io",
"main": "gulpfile.js",
@ -18,6 +18,7 @@
"gulp-replace": "^0.5.3",
"gulp-s3": "^0.3.0",
"gulp-sass": "^1.3.3",
"gulp-size": "^1.2.1",
"gulp-svgmin": "^1.0.0",
"gulp-svgstore": "^5.0.0",
"gulp-uglify": "~0.3.1",

View File

@ -10,7 +10,7 @@ We wanted a lightweight, accessible and customisable media player that just supp
## Features
- **Accessible** - full support for captions and screen readers.
- **Lightweight** - just 5.7KB minified and gzipped.
- **Lightweight** - just 6KB minified and gzipped.
- **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.
- **Responsive** - as you'd expect these days.
@ -38,7 +38,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.
**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.25/plyr.js` to `https://cdn.plyr.io/1.0.25/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.0/plyr.js` to `https://cdn.plyr.io/1.1.0/plyr.js`
### Bower
If bower is your thang, you can grab Plyr using:
@ -51,11 +51,11 @@ More info on setting up dependencies can be found in the [Bower Docs](http://bow
If you want to use our CDN, you can use the following. HTTPS (SSL) is supported.
```html
<link rel="stylesheet" href="//cdn.plyr.io/1.0.25/plyr.css">
<script src="//cdn.plyr.io/1.0.25/plyr.js"></script>
<link rel="stylesheet" href="//cdn.plyr.io/1.1.0/plyr.css">
<script src="//cdn.plyr.io/1.1.0/plyr.js"></script>
```
You can also access the `sprite.svg` file at `//cdn.plyr.io/1.0.25/sprite.svg`.
You can also access the `sprite.svg` file at `//cdn.plyr.io/1.1.0/sprite.svg`.
### 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.
@ -168,6 +168,12 @@ You can pass the following options to the setup method.
<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>
</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>
<td><code>debug</code></td>
<td>Boolean</td>
@ -197,6 +203,12 @@ You can pass the following options to the setup method.
<td>Boolean</td>
<td><code>false</code></td>
<td>Display control labels as tooltips on :hover &amp; :focus (by default, the labels are screen reader only).</td>
</tr>
<tr>
<td><code>displayDuration</code></td>
<td>Boolean</td>
<td><code>true</code></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>
<td><code>selectors</code></td>
@ -220,7 +232,7 @@ You can pass the following options to the setup method.
<td><code>fullscreen</code></td>
<td>Object</td>
<td>&mdash;</td>
<td>Two properties; <code>enabled</code> which toggles if fullscreen should be enabled (if the browser supports it). The default value is <code>true</code>. Also an extra property called <code>fallback</code> which will enable a full window view for older browsers. The default value is <code>true</code>.</td>
<td>Three properties; <code>enabled</code> which toggles if fullscreen should be enabled (if the browser supports it). The default value is <code>true</code>. A <code>fallback</code> property which will enable a full window view for older browsers. The default value is <code>true</code>. A <code>hideControls</code> property which will hide the controls when fullscreen is active and the video is playing, after 1s. The controls reappear on hover of the progress bar (mouse), focusing a child control or pausing the video (by tap/click of video if `click` is `true`). The default value is <code>true</code>.</td>
</tr>
<tr>
<td><code>storage</code></td>
@ -359,15 +371,15 @@ Fullscreen in Plyr is supported for all browsers that [currently support it](htt
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td>&sup2;</td>
<td>API&sup2;</td>
<td>✔&sup3;</td>
</tr>
</tbody>
</table>
&sup1; iPhone forces the native player for `<video>` so no customisation possible. `<audio>` elements have volume controls disabled.
&sup1; Mobile Safari on the iPhone forces the native player for `<video>` so no useful customisation is possible. `<audio>` elements have volume controls disabled.
&sup2; Native player used (no support for `<progress>` or `<input type="range">`)
&sup2; Native player used (no support for `<progress>` or `<input type="range">`) but the API is supported (v1.0.28+)
&sup3; IE10 has no native fullscreen support, fallback can be used (see options)
@ -389,10 +401,12 @@ Plyr is developed by Sam Potts ([@sam_potts](https://twitter.com/sam_potts)) ([s
## Mentions
- [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
- [HTML5 Weekly #177](http://html5weekly.com/issues/177)
- [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
- [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
- [Hacker News](https://news.ycombinator.com/item?id=9136774)
- [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
- [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
- [The Treehouse Show #131](https://teamtreehouse.com/library/episode-131-origami-react-responsive-hero-images)
## Used by
- [Selz.com](https://selz.com)

View File

@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
// plyr.js v1.0.25
// plyr.js v1.1.0
// https://github.com/selz/plyr
// License: The MIT License (MIT)
// ==========================================================================
@ -21,6 +21,7 @@
volume: 5,
click: true,
tooltips: false,
displayDuration: true,
selectors: {
container: ".player",
controls: ".player-controls",
@ -43,6 +44,7 @@
played: ".player-progress-played"
},
captions: ".player-captions",
currentTime: ".player-current-time",
duration: ".player-duration"
},
classes: {
@ -55,13 +57,15 @@
loading: "loading",
tooltip: "player-tooltip",
hidden: "sr-only",
hover: "hover",
captions: {
enabled: "captions-enabled",
active: "captions-active"
},
fullscreen: {
enabled: "fullscreen-enabled",
active: "fullscreen-active"
active: "fullscreen-active",
hideControls: "fullscreen-hide-controls"
}
},
captions: {
@ -69,14 +73,20 @@
},
fullscreen: {
enabled: true,
fallback: true
fallback: true,
hideControls: true
},
storage: {
enabled: true,
key: "plyr_volume"
},
html: (function() {
return [
controls: ["restart", "rewind", "play", "fast-forward", "current-time", "duration", "mute", "volume", "captions", "fullscreen"]
};
// Build the default HTML
defaults.html = (function() {
// 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>",
@ -88,15 +98,31 @@
"<span>0</span>% buffered",
"</progress>",
"</div>",
"<span class='player-controls-left'>",
"<span class='player-controls-left'>"];
// Restart button
if(_inArray(defaults.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>",
"</button>"
);
}
// Rewind button
if(_inArray(defaults.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>",
"</button>"
);
}
// Play/pause button
if(_inArray(defaults.controls, "play")) {
html.push(
"<button type='button' data-player='play'>",
"<svg><use xlink:href='#icon-play'></use></svg>",
"<span class='sr-only'>Play</span>",
@ -104,41 +130,97 @@
"<button type='button' data-player='pause'>",
"<svg><use xlink:href='#icon-pause'></use></svg>",
"<span class='sr-only'>Pause</span>",
"</button>",
"</button>"
);
}
// Fast forward button
if(_inArray(defaults.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>",
"</button>"
);
}
// Media current time display
if(_inArray(defaults.controls, "current-time")) {
html.push(
"<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>"
);
}
// Media duration display
if(_inArray(defaults.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>",
"<span class='player-controls-right'>",
"<span class='player-controls-right'>"
);
// Toggle mute button
if(_inArray(defaults.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>",
"</label>"
);
}
// Volume range control
if(_inArray(defaults.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'>",
"<input id='volume{id}' class='player-volume' type='range' min='0' max='10' value='5' data-player='volume'>"
);
}
// Toggle captions button
if(_inArray(defaults.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>",
"</label>"
);
}
// Toggle fullscreen button
if(_inArray(defaults.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>",
"</button>"
);
}
// Close everything
html.push(
"</span>",
"</div>"
].join("\n");
})()
};
);
return html.join("");
})();
// Debugging
function _log(text, error) {
@ -219,7 +301,7 @@
// Check for mime type support against a player instance
// Credits: http://diveintohtml5.info/everything.html
// Related: http://www.leanbackplayer.com/test/h5mt.html
function _support(player, mimeType) {
function _supportMime(player, mimeType) {
var media = player.media;
// Only check video types for video players
@ -246,6 +328,11 @@
return false;
}
// Element exists in an array
function _inArray(haystack, needle) {
return (haystack.indexOf(needle) != -1);
}
// Replace all
function _replaceAll(string, find, replace) {
return string.replace(new RegExp(find.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"), "g"), replace);
@ -317,6 +404,17 @@
function _toggleHandler(element, events, callback, toggle) {
events = events.split(" ");
// If a nodelist is passed, call itself on each node
if(element instanceof NodeList) {
for (var x = 0; x < element.length; x++) {
if (element[x] instanceof Node) {
_toggleHandler(element[x], arguments[1], arguments[2], arguments[3]);
}
}
return;
}
// If a single node is passed, bind the event listener
for (var i = 0; i < events.length; i++) {
element[toggle ? "addEventListener" : "removeEventListener"](events[i], callback, false);
}
@ -324,13 +422,43 @@
// Bind event
function _on(element, events, callback) {
if(element) {
_toggleHandler(element, events, callback, true);
}
}
// Unbind event
function _off(element, events, callback) {
if(element) {
_toggleHandler(element, events, callback, false);
}
}
// Trigger event
function _triggerEvent(element, event) {
// Create faux event
var fauxEvent = document.createEvent("MouseEvents");
// Set the event type
fauxEvent.initEvent(event, true, true);
// Dispatch the event
element.dispatchEvent(fauxEvent);
}
// Toggle checkbox
function _toggleCheckbox(event) {
// Only listen for return key
if(event.keyCode && event.keyCode != 13) {
return true;
}
// Toggle the checkbox
event.target.checked = !event.target.checked;
// Trigger change event
_triggerEvent(event.target, "change");
}
// Get percentage
function _getPercentage(current, max) {
@ -456,7 +584,7 @@
// Seek the manual caption time and update UI
function _seekManualCaptions(time) {
// If it's not video, or we're using textTracks, bail.
if (player.usingTextTracks || player.type !== "video") {
if (player.usingTextTracks || player.type !== "video" || !player.supported.full) {
return;
}
@ -492,11 +620,16 @@
// Display captions container and button (for initialization)
function _showCaptions() {
// If there's no caption toggle, bail
if(!player.buttons.captions) {
return;
}
_toggleClass(player.container, config.classes.captions.enabled, true);
if (config.captions.defaultActive) {
_toggleClass(player.container, config.classes.captions.active, true);
player.buttons.captions.setAttribute("checked", "checked");
player.buttons.captions.checked = true;
}
}
@ -551,18 +684,19 @@
// Insert custom video controls
_log("Injecting custom controls.");
// Use specified html
// Need to do a default?
var html = config.html;
// If no controls are specified, bail
if(!config.html) {
return;
}
// Replace seek time instances
html = _replaceAll(html, "{seektime}", config.seekTime);
config.html = _replaceAll(config.html, "{seektime}", config.seekTime);
// Replace all id references
html = _replaceAll(html, "{id}", player.random);
config.html = _replaceAll(config.html, "{id}", player.random);
// Inject into the container
player.container.insertAdjacentHTML("beforeend", html);
player.container.insertAdjacentHTML("beforeend", config.html);
// Setup tooltips
if(config.tooltips) {
@ -590,9 +724,12 @@
player.buttons.restart = _getElement(config.selectors.buttons.restart);
player.buttons.rewind = _getElement(config.selectors.buttons.rewind);
player.buttons.forward = _getElement(config.selectors.buttons.forward);
player.buttons.fullscreen = _getElement(config.selectors.buttons.fullscreen);
// Inputs
player.buttons.mute = _getElement(config.selectors.buttons.mute);
player.buttons.captions = _getElement(config.selectors.buttons.captions);
player.buttons.fullscreen = _getElement(config.selectors.buttons.fullscreen);
player.checkboxes = _getElements("[type='checkbox']");
// Progress
player.progress = {};
@ -601,18 +738,19 @@
// Progress - Buffering
player.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
player.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
player.volume = _getElement(config.selectors.buttons.volume);
// Timing
player.duration = _getElement(config.selectors.duration);
player.currentTime = _getElement(config.selectors.currentTime);
player.seekTime = _getElements(config.selectors.seekTime);
return true;
@ -620,12 +758,21 @@
catch(e) {
_log("It looks like there's a problem with your controls html. Bailing.", true);
// Restore native video controls
player.media.setAttribute("controls", "");
return false;
}
}
// Setup aria attributes
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";
// If there's a media title set, use that for the label
@ -638,20 +785,16 @@
// Setup media
function _setupMedia() {
player.media = player.container.querySelectorAll("audio, video")[0];
// If there's no media, bail
if(!player.media) {
_log("No audio or video element found!", true);
return false;
}
if(player.supported.full) {
// Remove native video controls
player.media.removeAttribute("controls");
// Set media type
player.type = player.media.tagName.toLowerCase();
// Add type class
_toggleClass(player.container, config.classes[player.type], true);
@ -675,6 +818,7 @@
// Cache the container
player.videoContainer = wrapper;
}
}
// Autoplay
if(player.media.getAttribute("autoplay") !== null) {
@ -759,10 +903,12 @@
if (track.kind === "captions") {
_on(track, "cuechange", function() {
if (this.activeCues[0]) {
if (this.activeCues[0].hasOwnProperty("text")) {
player.captionsContainer.innerHTML = this.activeCues[0].text;
}
// Clear container
player.captionsContainer.innerHTML = "";
// Display a cue, if there is one
if (this.activeCues[0] && this.activeCues[0].hasOwnProperty("text")) {
player.captionsContainer.appendChild(this.activeCues[0].getCueAsHTML());
}
});
}
@ -843,6 +989,11 @@
else {
_log("Fullscreen not supported and fallback disabled.");
}
// Set control hide class hook
if(config.fullscreen.hideControls) {
_toggleClass(player.container, config.classes.fullscreen.hideControls, true);
}
}
}
@ -899,7 +1050,11 @@
}
// Set the current time
// Try/catch incase the media isn't set and we're calling seek() from source() and IE moans
try {
player.media.currentTime = targetTime.toFixed(1);
}
catch(e) {}
// Logging
_log("Seeking to " + player.media.currentTime + " seconds");
@ -966,6 +1121,11 @@
// Set volume
function _setVolume(volume) {
// Bail if there's no volume element
if(!player.volume) {
return;
}
// Use default if needed
if(typeof volume === "undefined") {
if(config.storage.enabled && _storage().supported) {
@ -980,8 +1140,15 @@
volume = 10;
}
// If the controls are there
if(player.supported.full) {
player.volume.value = volume;
}
// Set the player volume
player.media.volume = parseFloat(volume / 10);
// Update the UI
_checkMute();
// Store the volume in storage
@ -993,29 +1160,36 @@
// Mute
function _toggleMute(muted) {
// If the method is called without parameter, toggle based on current value
if(typeof active === "undefined") {
if(typeof muted === "undefined") {
muted = !player.media.muted;
}
// If the controls are there
if(player.supported.full) {
player.buttons.mute.checked = muted;
}
// Set mute on the player
player.media.muted = muted;
// Update UI
_checkMute();
}
// Toggle captions
function _toggleCaptions(active) {
// If the method is called without parameter, toggle based on current value
if(typeof active === "undefined") {
active = (player.container.className.indexOf(config.classes.captions.active) === -1);
player.buttons.captions.checked = active;
function _toggleCaptions(show) {
// If there's no full support, or there's no caption toggle
if(!player.supported.full || !player.buttons.captions) {
return;
}
if (active) {
_toggleClass(player.container, config.classes.captions.active, true);
}
else {
_toggleClass(player.container, config.classes.captions.active);
// If the method is called without parameter, toggle based on current value
if(typeof show === "undefined") {
show = (player.container.className.indexOf(config.classes.captions.active) === -1);
player.buttons.captions.checked = show;
}
_toggleClass(player.container, config.classes.captions.active, show);
}
// Check mute state
@ -1050,7 +1224,7 @@
value = _getPercentage(player.media.currentTime, player.media.duration);
// 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;
}
@ -1082,27 +1256,55 @@
}
// Set values
if(progress) {
progress.value = value;
}
if(text) {
text.innerHTML = value;
}
}
// Update the displayed play time
function _updateTimeDisplay() {
player.secs = parseInt(player.media.currentTime % 60);
player.mins = parseInt((player.media.currentTime / 60) % 60);
// Update the displayed time
function _updateTimeDisplay(time, element) {
// Bail if there's no duration display
if(!element) {
return;
}
player.secs = parseInt(time % 60);
player.mins = parseInt((time / 60) % 60);
player.hours = parseInt(((time / 60) / 60) % 60);
// Do we need to display hours?
var displayHours = (parseInt(((player.media.duration / 60) / 60) % 60) > 0)
// Ensure it"s two digits. For example, 03 rather than 3.
player.secs = ("0" + player.secs).slice(-2);
player.mins = ("0" + player.mins).slice(-2);
// Render
player.duration.innerHTML = player.mins + ":" + player.secs;
element.innerHTML = (displayHours ? player.hours + ":" : "") + player.mins + ":" + player.secs;
}
// Show the duration on metadataloaded
function _displayDuration() {
var duration = 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
function _timeUpdate(event) {
// Duration
_updateTimeDisplay();
_updateTimeDisplay(player.media.currentTime, player.currentTime);
// Playing progress
_updateProgress(event);
@ -1145,9 +1347,6 @@
// Restart
_seek();
// Update the UI
_checkPlaying();
// Remove current sources
_removeSources();
@ -1166,9 +1365,14 @@
}
}
if(player.supported.full) {
// Reset time display
_timeUpdate();
// Update the UI
_checkPlaying();
}
// Re-load sources
player.media.load();
@ -1223,22 +1427,8 @@
_on(player.buttons.fullscreen, "click", _toggleFullscreen);
// Handle user exiting fullscreen by escaping etc
if(fullscreen.supportsFullScreen) {
_on(document, fullscreen.fullScreenEventName, _toggleFullscreen);
// Click video
if(player.type === "video" && config.click) {
_on(player.videoContainer, "click", function() {
if(player.media.paused) {
_play();
}
else if(player.media.ended) {
_seek();
_play();
}
else {
_pause();
}
});
}
// Time change on media
@ -1247,19 +1437,25 @@
// Update manual captions
_on(player.media, "timeupdate", _seekManualCaptions);
// Display duration
_on(player.media, "loadedmetadata", _displayDuration);
// Seek
_on(player.buttons.seek, "change input", _seek);
// Captions
_on(player.buttons.captions, "click", function() {
_on(player.buttons.captions, "change", function() {
_toggleCaptions(this.checked);
});
// Clear captions at end of video
// Handle the media finishing
_on(player.media, "ended", function() {
// Clear
if(player.type === "video") {
player.captionsContainer.innerHTML = "";
}
// Reset UI
_checkPlaying();
});
@ -1277,31 +1473,66 @@
// Loading
_on(player.media, "waiting canplay seeked", _checkLoading);
// Toggle checkboxes on return key (as they look like buttons)
_on(player.checkboxes, "keyup", _toggleCheckbox);
// Click video
if(player.type === "video" && config.click) {
_on(player.videoContainer, "click", function() {
if(player.media.paused) {
_play();
}
else if(player.media.ended) {
_seek();
_play();
}
else {
_pause();
}
});
}
// Bind to mouse hover
if(config.fullscreen.hideControls) {
_on(player.controls, "mouseenter mouseleave", function(event) {
_toggleClass(player.controls, config.classes.hover, (event.type === "mouseenter"));
})
}
}
function _init() {
// Setup the fullscreen api
fullscreen = _fullscreen();
// Sniff
// Sniff out the browser
player.browser = _browserSniff();
// Get the media element
player.media = player.container.querySelectorAll("audio, video")[0];
// Set media type
player.type = player.media.tagName.toLowerCase();
// Check for full support
player.supported = api.supported(player.type);
// If no native support, bail
if(!player.supported.basic) {
return false;
}
// Debug info
_log(player.browser.name + " " + player.browser.version);
// If IE8, stop customization (use fallback)
// If IE9, stop customization (use native controls)
if (player.browser.name === "IE" && (player.browser.version === 8 || player.browser.version === 9) ) {
_log("Browser not suppported.", true);
return false;
}
// Setup media
_setupMedia();
// Generate random number for id/for attribute values for controls
player.random = Math.floor(Math.random() * (10000));
// If there's full support
if(player.supported.full) {
// Inject custom controls
_injectControls();
@ -1310,6 +1541,11 @@
return false;
}
// Display duration if available
if(config.displayDuration) {
_displayDuration();
}
// Set up aria-label for Play button with the title option
_setupAria();
@ -1326,7 +1562,13 @@
_listeners();
}
_init();
// Successful setup
return true;
}
if(!_init()) {
return {};
}
return {
media: player.media,
@ -1341,10 +1583,42 @@
toggleCaptions: _toggleCaptions,
source: _parseSource,
poster: _updatePoster,
support: function(mimeType) { return _support(player, mimeType); }
support: function(mimeType) { return _supportMime(player, mimeType); }
}
}
// Check for support
api.supported = function(type) {
var browser = _browserSniff(),
oldIE = (browser.name === "IE" && browser.version <= 9),
iPhone = /iPhone|iPod/i.test(navigator.userAgent),
audio = !!document.createElement("audio").canPlayType,
video = !!document.createElement("video").canPlayType,
basic, full;
switch (type) {
case "video":
basic = video;
full = (basic && (!oldIE && !iPhone));
break;
case "audio":
basic = audio;
full = (basic && !oldIE);
break;
default:
basic = (audio && video);
full = (basic && !oldIE);
break;
}
return {
basic: basic,
full: full
};
}
// Expose setup function
api.setup = function(options){
// Extend the default options with user specified
@ -1352,28 +1626,26 @@
// If enabled carry on
// You may want to disable certain UAs etc
if(!config.enabled) {
if(!config.enabled || !api.supported().basic) {
return false;
}
// Get the players
var elements = document.querySelectorAll(config.selectors.container), players = [];
var elements = document.querySelectorAll(config.selectors.container),
players = [];
// Create a player instance for each element
for (var i = elements.length - 1; i >= 0; i--) {
// Get the current element
var element = elements[i];
// Disabled for <video> for iPhone
// Since it doesn't allow customisation
if (element.querySelectorAll("audio, video")[0].tagName.toLowerCase() === "video"
&& /iPhone/i.test(navigator.userAgent)) {
continue;
}
// Setup a player instance and add to the element
if(typeof element.plyr === "undefined") {
element.plyr = new Plyr(element);
// Create new instance
var instance = new Plyr(element);
// Set plyr to false if setup failed
element.plyr = (Object.keys(instance).length ? instance : false);
}
// Add to return array
@ -1382,4 +1654,5 @@
return players;
}
}(this.plyr = this.plyr || {}));

View File

@ -31,9 +31,9 @@
@tooltip-radius: 3px;
// Progress
@progress-bg: lighten(@gray, 10%);
@progress-bg: rgba(red(@gray), green(@gray), blue(@gray), .2);
@progress-playing-bg: @blue;
@progress-buffered-bg: @gray;
@progress-buffered-bg: rgba(red(@gray), green(@gray), blue(@gray), .25);
@progress-loading-size: 40px;
@progress-loading-bg: rgba(0,0,0, .15);
@ -179,7 +179,7 @@
.clearfix();
.font-smoothing();
position: relative;
padding: (@control-spacing * 2) @control-spacing @control-spacing;
padding: @control-spacing;
background: @controls-bg;
line-height: 1;
text-align: center;
@ -259,6 +259,22 @@
font-size: @font-size-small;
.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
@ -313,7 +329,7 @@
// <progress> element
&-progress {
position: absolute;
top: 0;
bottom: 100%;
left: 0;
right: 0;
width: 100%;
@ -524,13 +540,13 @@
z-index: 10000000;
background: #000;
video {
height: 100%;
}
.player-video-wrapper {
height: 100%;
width: 100%;
video {
height: 100%;
}
.player-captions {
top: auto;
bottom: 90px;
@ -549,6 +565,17 @@
left: 0;
right: 0;
}
// Hide controls when playing in full screen
&.fullscreen-hide-controls.playing .player-controls {
transform: translateY(100%) translateY(@control-spacing / 2);
transition: transform .3s 1s ease;
&.hover {
transform: translateY(0);
transition-delay: 0;
}
}
}
// Change icons on state change
@ -575,10 +602,4 @@
&.fullscreen-enabled [data-player='fullscreen'] + label {
display: inline-block;
}
// Full browser view hides toggle
&-fullscreen [data-player='fullscreen'],
&-fullscreen [data-player='fullscreen'] + label {
display: none !important;
}
}

View File

@ -31,13 +31,13 @@ $tooltip-arrow-size: 5px;
$tooltip-radius: 3px;
// Progress
$progress-bg: lighten($gray, 10%);
$progress-bg: rgba(red($gray), green($gray), blue($gray), .2);
$progress-playing-bg: $blue;
$progress-buffered-bg: $gray;
$progress-buffered-bg: rgba(red($gray), green($gray), blue($gray), .25);
$progress-loading-size: 40px;
$progress-loading-bg: rgba(0,0,0, .15);
// Range
// Volume
$volume-track-height: 6px;
$volume-track-bg: $gray;
$volume-thumb-height: ($volume-track-height * 2);
@ -79,7 +79,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
// Animation
// ---------------------------------------
@keyframes progress {
to { background-position: @progress-loading-size 0; }
to { background-position: $progress-loading-size 0; }
}
// <input type="range"> styling
@ -101,28 +101,30 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
border: 0;
border-radius: ($volume-track-height / 2);
}
@mixin seek-thumb() {
@mixin seek-thumb()
{
background: transparent;
border: 0;
width: ($control-spacing * 2);
height: $control-spacing;
}
@mixin seek-track() {
@mixin seek-track()
{
background: none;
border: 0;
}
// Font smoothing
// ---------------------------------------
@mixin font-smoothing($mode: on) when ($mode = on)
@mixin font-smoothing($mode: on)
{
@if $mode == 'on' {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
@mixin font-smoothing($mode: on) when ($mode = off)
{
} @else if $mode == 'off' {
-moz-osx-font-smoothing: auto;
-webkit-font-smoothing: subpixel-antialiased;
}
}
// Styles
@ -185,13 +187,13 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
@include clearfix();
@include font-smoothing();
position: relative;
padding: ($control-spacing * 2) $control-spacing $control-spacing;
padding: $control-spacing;
background: $controls-bg;
line-height: 1;
text-align: center;
// Layout
&-sound {
&-right {
display: block;
margin: $control-spacing auto 0;
}
@ -238,16 +240,19 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
background: transparent;
overflow: hidden;
}
input:focus + label,
button:focus {
@include tab-focus();
color: $control-color-focus;
}
// Specificity for overriding .inverted
button:focus,
button:hover,
input + label:hover {
[type="checkbox"]:focus + label,
[type="checkbox"] + label:hover {
background: $control-bg-hover;
color: $control-color-hover;
}
button:focus,
input:focus + label {
outline: 0;
}
.icon-exit-fullscreen,
.icon-muted,
.icon-captions-on {
@ -262,6 +267,22 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
font-size: $font-size-small;
@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
@ -316,7 +337,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
// <progress> element
&-progress {
position: absolute;
top: 0;
bottom: 100%;
left: 0;
right: 0;
width: 100%;
@ -330,8 +351,9 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
left: 0;
top: 0;
width: 100%;
height: 100%;
height: $control-spacing;
margin: 0;
padding: 0;
vertical-align: top;
-webkit-appearance: none;
@ -339,7 +361,6 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
border: none;
background: transparent;
}
&-buffer[value],
&-played[value] {
&::-webkit-progress-bar {
@ -366,7 +387,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
// <input[type='range']> element
// Specificity is for bootstrap compatibility
&-seek[type=range] {
z-index: 3;
z-index: 4;
cursor: pointer;
outline: 0;
@ -441,6 +462,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
// <input[type='range']> element
// Specificity is for bootstrap compatibility
&-volume[type=range] {
display: inline-block;
vertical-align: middle;
-webkit-appearance: none;
-moz-appearance: none;
@ -452,20 +474,20 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
// Webkit
&::-webkit-slider-runnable-track {
@include range-track();
@include volume-track();
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
margin-top: -(($volume-thumb-height - $volume-track-height) / 2);
@include range-thumb();
@include volume-thumb();
}
// Mozilla
&::-moz-range-track {
@include range-track();
@include volume-track();
}
&::-moz-range-thumb {
@include range-thumb();
@include volume-thumb();
}
// Microsoft
@ -478,10 +500,10 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
}
&::-ms-fill-lower,
&::-ms-fill-upper {
@include range-track();
@include volume-track();
}
&::-ms-thumb {
@include range-thumb();
@include volume-thumb();
}
&:focus {
@ -526,13 +548,13 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
z-index: 10000000;
background: #000;
video {
height: 100%;
}
.player-video-wrapper {
height: 100%;
width: 100%;
video {
height: 100%;
}
.player-captions {
top: auto;
bottom: 90px;
@ -551,6 +573,17 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
left: 0;
right: 0;
}
// Hide controls when playing in full screen
&.fullscreen-hide-controls.playing .player-controls {
transform: translateY(100%) translateY($control-spacing / 2);
transition: transform .3s 1s ease;
&.hover {
transform: translateY(0);
transition-delay: 0;
}
}
}
// Change icons on state change
@ -577,10 +610,4 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
&.fullscreen-enabled [data-player='fullscreen'] + label {
display: inline-block;
}
// Full browser view hides toggle
&-fullscreen [data-player='fullscreen'],
&-fullscreen [data-player='fullscreen'] + label {
display: none !important;
}
}