Compare commits

...

29 Commits

Author SHA1 Message Date
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
b677c3d7ad Merge branch 'master' of https://github.com/selz/plyr
Conflicts:
	dist/plyr.js
	src/js/plyr.js
2015-03-10 23:57:36 +11:00
aa6bc2df2f Fixes for volume control on iOS 2015-03-10 23:54:52 +11:00
a16579fd21 Fix for repo url 2015-03-09 11:53:26 +11:00
ae5a816df1 Fix for potential issue with .tagName 2015-03-09 11:51:01 +11:00
1532f2ab23 Added tooltip option (Fixes #50) 2015-03-09 00:47:38 +11:00
5370fc5c83 Loading state handling
Fixes #36
2015-03-08 01:24:23 +11:00
8482a1a320 Updated captions 2015-03-07 19:39:01 +11:00
f3603ac3fa Size updated 2015-03-07 19:27:11 +11:00
44fe647a49 Typo 2015-03-07 19:25:25 +11:00
928a89e599 More formatting 2015-03-07 19:24:41 +11:00
80d6d806c4 Formatting 2015-03-07 19:21:54 +11:00
aeecf40191 Slight tweaks 2015-03-07 19:17:57 +11:00
f368ed572d source(), poster() and supports() API methods 2015-03-07 19:12:56 +11:00
5291bf616e More work on source change 2015-03-07 12:24:07 +11:00
a00407a475 Removed extra log 2015-03-06 17:47:08 +11:00
4f47ec49b1 Work on source update and caption fixes 2015-03-06 17:46:06 +11:00
c48afb7880 Merge remote-tracking branch 'origin/master' into develop 2015-03-06 12:21:51 +11:00
5d3fb359b8 Note about demo code 2015-03-06 12:20:47 +11:00
0227dfa7d8 Working on source() api method 2015-03-06 01:51:12 +11:00
b9915b4b94 Removed notes 2015-03-06 01:50:37 +11:00
f4c1313c19 Updated screenshot 2015-03-05 23:36:45 +11:00
7681d63876 Removed progress transition as it feels laggy 2015-03-05 23:27:53 +11:00
21 changed files with 2087 additions and 1533 deletions

View File

@ -1,8 +1,34 @@
# Changelog # Changelog
## 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
## v1.0.24
- Added tooltip option to display labels as tooltips (Fixes #50)
## v1.0.23
- Handling loading states in the UI (Fixes #36)
## v1.0.22
- Added support() API method for checking mimetype support
- Added source() API method for setting media source(s) (Fixes #44)
- Added poster() API method for setting poster source
- Refactored captions logic for manual captions
## v1.0.21 ## v1.0.21
- Added an <input type="range"> for seeking to improve experience (and support dragging) - Added an <input type="range"> for seeking to improve experience (and support dragging) (Fixes #40, #42)
- Icons for restart and captions improved (and some IDs changed) - Icons for restart and captions improved (and some IDs changed) (Fixes #49)
## v1.0.20 ## v1.0.20
- Default controls included (Fixes #45) - Default controls included (Fixes #45)
@ -11,7 +37,7 @@
- License changed to MIT - License changed to MIT
## v1.0.19 ## v1.0.19
- Fixed firefox fullscreen issue (#38) - Fixed firefox fullscreen issue (Fixes #38)
## v1.0.18 ## v1.0.18
- Added CDN references - Added CDN references

View File

@ -31,14 +31,14 @@ This is the default `html` option from `plyr.js`.
"<span>0</span>% buffered", "<span>0</span>% buffered",
"</progress>", "</progress>",
"</div>", "</div>",
"<span class='player-controls-playback'>", "<span class='player-controls-left'>",
"<button type='button' data-player='restart'>", "<button type='button' data-player='restart'>",
"<svg><use xlink:href='#icon-restart'></use></svg>", "<svg><use xlink:href='#icon-restart'></use></svg>",
"<span class='sr-only'>Restart</span>", "<span class='sr-only'>Restart</span>",
"</button>", "</button>",
"<button type='button' data-player='rewind'>", "<button type='button' data-player='rewind'>",
"<svg><use xlink:href='#icon-rewind'></use></svg>", "<svg><use xlink:href='#icon-rewind'></use></svg>",
"<span class='sr-only'>Rewind {seektime} seconds</span>", "<span class='sr-only'>Rewind {seektime} secs</span>",
"</button>", "</button>",
"<button type='button' data-player='play'>", "<button type='button' data-player='play'>",
"<svg><use xlink:href='#icon-play'></use></svg>", "<svg><use xlink:href='#icon-play'></use></svg>",
@ -50,14 +50,14 @@ This is the default `html` option from `plyr.js`.
"</button>", "</button>",
"<button type='button' data-player='fast-forward'>", "<button type='button' data-player='fast-forward'>",
"<svg><use xlink:href='#icon-fast-forward'></use></svg>", "<svg><use xlink:href='#icon-fast-forward'></use></svg>",
"<span class='sr-only'>Fast forward {seektime} seconds</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'>Time</span>",
"<span class='player-duration'>00:00</span>", "<span class='player-duration'>00:00</span>",
"</span>", "</span>",
"</span>", "</span>",
"<span class='player-controls-sound'>", "<span class='player-controls-right'>",
"<input class='inverted sr-only' id='mute{id}' type='checkbox' data-player='mute'>", "<input class='inverted sr-only' id='mute{id}' type='checkbox' data-player='mute'>",
"<label id='mute{id}' for='mute{id}'>", "<label id='mute{id}' for='mute{id}'>",
"<svg class='icon-muted'><use xlink:href='#icon-muted'></use></svg>", "<svg class='icon-muted'><use xlink:href='#icon-muted'></use></svg>",
@ -75,7 +75,7 @@ This is the default `html` option from `plyr.js`.
"<button type='button' data-player='fullscreen'>", "<button type='button' data-player='fullscreen'>",
"<svg class='icon-exit-fullscreen'><use xlink:href='#icon-exit-fullscreen'></use></svg>", "<svg class='icon-exit-fullscreen'><use xlink:href='#icon-exit-fullscreen'></use></svg>",
"<svg><use xlink:href='#icon-enter-fullscreen'></use></svg>", "<svg><use xlink:href='#icon-enter-fullscreen'></use></svg>",
"<span class='sr-only'>Toggle fullscreen</span>", "<span class='sr-only'>Toggle Fullscreen</span>",
"</button>", "</button>",
"</span>", "</span>",
"</div>"].join("\n"); "</div>"].join("\n");

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.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-playback\">");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} seconds</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\">Fast forward {seektime} seconds</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-sound\">");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\">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: { }});

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.21/docs.css"> <link rel="stylesheet" href="//cdn.plyr.io/1.0.28/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.21/plyr.css"> <link rel="stylesheet" href="//cdn.plyr.io/1.0.28/plyr.css">
<!-- Docs styles --> <!-- Docs styles -->
<link rel="stylesheet" href="//cdn.plyr.io/1.0.21/docs.css"> <link rel="stylesheet" href="//cdn.plyr.io/1.0.28/docs.css">
</head> </head>
<body> <body>
<header> <header>
@ -29,7 +29,7 @@
<source src="//cdn.selz.com/plyr/1.0/movie.webm" type="video/webm"> <source src="//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/movie_en_captions.vtt" default> <track kind="captions" label="English" srclang="en" src="//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> <div>
@ -67,13 +67,29 @@
<!-- Load SVG defs --> <!-- Load SVG defs -->
<!-- You should bundle all SVG/Icons into one file using a build tool such as gulp and svg store --> <!-- You should bundle all SVG/Icons into one file using a build tool such as gulp and svg store -->
<script> <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.21/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.0.28/sprite.svg");
</script> </script>
<!-- Plyr core script --> <!-- Plyr core script -->
<script src="//cdn.plyr.io/1.0.21/plyr.js"></script> <script src="//cdn.plyr.io/1.0.28/plyr.js"></script>
<!-- Docs script --> <!-- Docs script -->
<script src="//cdn.plyr.io/1.0.21/docs.js"></script> <script src="//cdn.plyr.io/1.0.28/docs.js"></script>
</body> </body>
</html> </html>

View File

@ -9,14 +9,14 @@
<span>0</span>% buffered <span>0</span>% buffered
</progress> </progress>
</div> </div>
<span class="player-controls-playback"> <span class="player-controls-left">
<button type="button" data-player="restart"> <button type="button" data-player="restart">
<svg><use xlink:href="#icon-restart"></use></svg> <svg><use xlink:href="#icon-restart"></use></svg>
<span class="sr-only">Restart</span> <span class="sr-only">Restart</span>
</button> </button>
<button type="button" data-player="rewind"> <button type="button" data-player="rewind">
<svg><use xlink:href="#icon-rewind"></use></svg> <svg><use xlink:href="#icon-rewind"></use></svg>
<span class="sr-only">Rewind {seektime} seconds</span> <span class="sr-only">Rewind {seektime} secs</span>
</button> </button>
<button type="button" data-player="play"> <button type="button" data-player="play">
<svg><use xlink:href="#icon-play"></use></svg> <svg><use xlink:href="#icon-play"></use></svg>
@ -28,14 +28,14 @@
</button> </button>
<button type="button" data-player="fast-forward"> <button type="button" data-player="fast-forward">
<svg><use xlink:href="#icon-fast-forward"></use></svg> <svg><use xlink:href="#icon-fast-forward"></use></svg>
<span class="sr-only">Fast forward {seektime} seconds</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">Time</span>
<span class="player-duration">00:00</span> <span class="player-duration">00:00</span>
</span> </span>
</span> </span>
<span class="player-controls-sound"> <span class="player-controls-right">
<input class="inverted sr-only" id="mute{id}" type="checkbox" data-player="mute"> <input class="inverted sr-only" id="mute{id}" type="checkbox" data-player="mute">
<label id="mute{id}" for="mute{id}"> <label id="mute{id}" for="mute{id}">
<svg class="icon-muted"><use xlink:href="#icon-muted"></use></svg> <svg class="icon-muted"><use xlink:href="#icon-muted"></use></svg>
@ -56,7 +56,7 @@
<button type="button" data-player="fullscreen"> <button type="button" data-player="fullscreen">
<svg class="icon-exit-fullscreen"><use xlink:href="#icon-exit-fullscreen"></use></svg> <svg class="icon-exit-fullscreen"><use xlink:href="#icon-exit-fullscreen"></use></svg>
<svg><use xlink:href="#icon-enter-fullscreen"></use></svg> <svg><use xlink:href="#icon-enter-fullscreen"></use></svg>
<span class="sr-only">Toggle fullscreen</span> <span class="sr-only">Toggle Fullscreen</span>
</button> </button>
</span> </span>
</div> </div>

View File

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

View File

@ -1,9 +0,0 @@
Loading
--------------
http://stackoverflow.com/questions/8685038/tell-whether-video-is-loaded-or-not-in-javascript
http://stackoverflow.com/questions/5181865/checking-if-a-html5-video-is-ready
Events
--------------
https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events

View File

@ -1,6 +1,6 @@
{ {
"name": "plyr", "name": "plyr",
"version": "1.0.21", "version": "1.0.28",
"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",
@ -18,6 +18,7 @@
"gulp-replace": "^0.5.3", "gulp-replace": "^0.5.3",
"gulp-s3": "^0.3.0", "gulp-s3": "^0.3.0",
"gulp-sass": "^1.3.3", "gulp-sass": "^1.3.3",
"gulp-size": "^1.2.1",
"gulp-svgmin": "^1.0.0", "gulp-svgmin": "^1.0.0",
"gulp-svgstore": "^5.0.0", "gulp-svgstore": "^5.0.0",
"gulp-uglify": "~0.3.1", "gulp-uglify": "~0.3.1",

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)](http://plyr.io) [![Image of Plyr](https://cdn.plyr.io/static/plyr.png?1)](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 5.3KB minified and gzipped. - **Lightweight** - just 6KB 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.
@ -38,6 +38,8 @@ 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.28/plyr.js` to `https://cdn.plyr.io/1.0.28/plyr.js`
### Bower ### Bower
If bower is your thang, you can grab Plyr using: If bower is your thang, you can grab Plyr using:
``` ```
@ -49,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. If you want to use our CDN, you can use the following. HTTPS (SSL) is supported.
```html ```html
<link rel="stylesheet" href="//cdn.plyr.io/1.0.21/plyr.css"> <link rel="stylesheet" href="//cdn.plyr.io/1.0.28/plyr.css">
<script src="//cdn.plyr.io/1.0.21/plyr.js"></script> <script src="//cdn.plyr.io/1.0.28/plyr.js"></script>
``` ```
You can also access the `sprite.svg` file at `//cdn.plyr.io/1.0.21/sprite.svg`. You can also access the `sprite.svg` file at `//cdn.plyr.io/1.0.28/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.
@ -188,13 +190,19 @@ You can pass the following options to the setup method.
<td><code>click</code></td> <td><code>click</code></td>
<td>Boolean</td> <td>Boolean</td>
<td><code>true</code></td> <td><code>true</code></td>
<td>Click (or tap) will toggle pause/play of a `<video>`.</td> <td>Click (or tap) will toggle pause/play of a <code>&lt;video&gt;</code>.</td>
</tr>
<tr>
<td><code>tooltips</code></td>
<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>
<tr> <tr>
<td><code>selectors</code></td> <td><code>selectors</code></td>
<td>Object</td> <td>Object</td>
<td>&mdash;</td> <td>&mdash;</td>
<td>See `plyr.js` in `/src` for more info. The only option you might want to change is `player` which is the hook used for Plyr, the default is `.player`.</td> <td>See <code>plyr.js</code> in <code>/src</code> for more info. The only option you might want to change is <code>player</code> which is the hook used for Plyr, the default is <code>.player</code>.</td>
</tr> </tr>
<tr> <tr>
<td><code>classes</code></td> <td><code>classes</code></td>
@ -206,19 +214,19 @@ You can pass the following options to the setup method.
<td><code>captions</code></td> <td><code>captions</code></td>
<td>Object</td> <td>Object</td>
<td>&mdash;</td> <td>&mdash;</td>
<td>This currently contains one property `defaultActive` which toggles if captions should be on by default. The default value is `false`.</td> <td>One property <code>defaultActive</code> which toggles if captions should be on by default. The default value is <code>false</code>.</td>
</tr> </tr>
<tr> <tr>
<td><code>fullscreen</code></td> <td><code>fullscreen</code></td>
<td>Object</td> <td>Object</td>
<td>&mdash;</td> <td>&mdash;</td>
<td>This currently contains two properties; `enabled` which toggles if fullscreen should be enabled (if the browser supports it). The default value is `true`. Also an extra property called `fallback` which will enable a 'full window' view for older browsers. The default value is `true`.</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>
</tr> </tr>
<tr> <tr>
<td><code>storage</code></td> <td><code>storage</code></td>
<td>Object</td> <td>Object</td>
<td>&mdash;</td> <td>&mdash;</td>
<td>This currently contains one property `enabled` 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.</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>
</tbody> </tbody>
</table> </table>
@ -243,50 +251,75 @@ Here's a list of the methods supported:
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td><code>play</code></td> <td><code>play()</code></td>
<td>&mdash;</td> <td>&mdash;</td>
<td>Plays the media</td> <td>Plays the media</td>
</tr> </tr>
<tr> <tr>
<td><code>pause</code></td> <td><code>pause()</code></td>
<td>&mdash;</td> <td>&mdash;</td>
<td>Pauses the media</td> <td>Pauses the media</td>
</tr> </tr>
<tr> <tr>
<td><code>restart</code></td> <td><code>restart()</code></td>
<td>&mdash;</td> <td>&mdash;</td>
<td>Restarts playback</td> <td>Restarts playback</td>
</tr> </tr>
<tr> <tr>
<td><code>rewind</code></td> <td><code>rewind(...)</code></td>
<td>Number</td> <td>Number</td>
<td>Rewinds by the provided parameter, in seconds. If no parameter is provided, the default seekInterval is used (10 seconds).</td> <td>Rewinds by the provided parameter, in seconds. If no parameter is provided, the default seekInterval is used (10 seconds).</td>
</tr> </tr>
<tr> <tr>
<td><code>forward</code></td> <td><code>forward(...)</code></td>
<td>Number</td> <td>Number</td>
<td>Fast forwards by the provided parameter, in seconds. If no parameter is provided, the default seekInterval is used (10 seconds).</td> <td>Fast forwards by the provided parameter, in seconds. If no parameter is provided, the default seekInterval is used (10 seconds).</td>
</tr> </tr>
<tr> <tr>
<td><code>seek</code></td> <td><code>seek(...)</code></td>
<td>Number</td> <td>Number</td>
<td>Seeks the media to the provided parameter, time in seconds.</td> <td>Seeks the media to the provided parameter, time in seconds.</td>
</tr> </tr>
<tr> <tr>
<td><code>setVolume</code></td> <td><code>setVolume(...)</code></td>
<td>Number</td> <td>Number</td>
<td>Sets the player volume to the provided parameter. The value should be between 0 (muted) and 10 (loudest). If no parameter is provided, the default volume is used (5). Values over 10 are ignored.</td> <td>Sets the player volume to the provided parameter. The value should be between 0 (muted) and 10 (loudest). If no parameter is provided, the default volume is used (5). Values over 10 are ignored.</td>
</tr> </tr>
<tr> <tr>
<td><code>toggleMute</code></td> <td><code>toggleMute()</code></td>
<td>&mdash;</td> <td>&mdash;</td>
<td>Toggles mute for the player.</td> <td>Toggles mute for the player.</td>
</tr> </tr>
<tr> <tr>
<td><code>toggleCaptions</code></td> <td><code>toggleCaptions()</code></td>
<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>support(...)</code></td>
<td>String</td>
<td>Determine if a player supports a certain MIME type.</td>
</tr>
<tr>
<td><code>source(...)</code></td>
<td>String or Array</td>
<td>
Set the media source.
<br><br>
<strong>string</strong><br>
<code>.source("/path/to/video.mp4")</code><br>
This will set the <code>src</code> attribute on the <code>video</code> or <code>audio</code> element.
<br><br>
<strong>array</strong><br>
<code>.source([{ src: "/path/to/video.webm", type: "video/webm", ...more attributes... }, { src: "/path/to/video.mp4", type: "video/mp4", ...more attributes... }])`</code><br>
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>
</tr>
<tr>
<td><code>poster(...)</code></td>
<td>String</td>
<td>Set the poster url. This is supported for the <code>video</code> element only.</td>
</tr>
</tbody> </tbody>
</table> </table>
@ -322,19 +355,21 @@ Fullscreen in Plyr is supported for all browsers that [currently support it](htt
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>✔&sup1;</td>
<td>✔</td> <td>✔</td>
<td>✔</td> <td>✔</td>
<td>✔</td> <td>✔</td>
<td></td> <td>API&sup2;</td>
<td>&sup1;</td> <td>✔&sup3;</td>
<td>&sup2;</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
&sup1; Native player used (no support for `<progress>` or `<input type="range">`) &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; IE10 has no native fullscreen support, fallback can be used (see options) &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)
The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for smartphones, you could use: The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for smartphones, you could use:

View File

@ -1,7 +1,7 @@
// ========================================================================== // ==========================================================================
// Plyr // Plyr
// plyr.js v1.0.21 // plyr.js v1.0.28
// https://github.com/sampotts/plyr // https://github.com/selz/plyr
// License: The MIT License (MIT) // License: The MIT License (MIT)
// ========================================================================== // ==========================================================================
// Credits: http://paypal.github.io/accessible-html5-video-player/ // Credits: http://paypal.github.io/accessible-html5-video-player/
@ -20,9 +20,11 @@
seekTime: 10, seekTime: 10,
volume: 5, volume: 5,
click: true, click: true,
tooltips: false,
selectors: { selectors: {
container: ".player", container: ".player",
controls: ".player-controls", controls: ".player-controls",
labels: "[data-player] .sr-only, label .sr-only",
buttons: { buttons: {
seek: "[data-player='seek']", seek: "[data-player='seek']",
play: "[data-player='play']", play: "[data-player='play']",
@ -50,6 +52,9 @@
stopped: "stopped", stopped: "stopped",
playing: "playing", playing: "playing",
muted: "muted", muted: "muted",
loading: "loading",
tooltip: "player-tooltip",
hidden: "sr-only",
captions: { captions: {
enabled: "captions-enabled", enabled: "captions-enabled",
active: "captions-active" active: "captions-active"
@ -67,7 +72,8 @@
fallback: true fallback: true
}, },
storage: { storage: {
enabled: true enabled: true,
key: "plyr_volume"
}, },
html: (function() { html: (function() {
return [ return [
@ -82,14 +88,14 @@
"<span>0</span>% buffered", "<span>0</span>% buffered",
"</progress>", "</progress>",
"</div>", "</div>",
"<span class='player-controls-playback'>", "<span class='player-controls-left'>",
"<button type='button' data-player='restart'>", "<button type='button' data-player='restart'>",
"<svg><use xlink:href='#icon-restart'></use></svg>", "<svg><use xlink:href='#icon-restart'></use></svg>",
"<span class='sr-only'>Restart</span>", "<span class='sr-only'>Restart</span>",
"</button>", "</button>",
"<button type='button' data-player='rewind'>", "<button type='button' data-player='rewind'>",
"<svg><use xlink:href='#icon-rewind'></use></svg>", "<svg><use xlink:href='#icon-rewind'></use></svg>",
"<span class='sr-only'>Rewind {seektime} seconds</span>", "<span class='sr-only'>Rewind {seektime} secs</span>",
"</button>", "</button>",
"<button type='button' data-player='play'>", "<button type='button' data-player='play'>",
"<svg><use xlink:href='#icon-play'></use></svg>", "<svg><use xlink:href='#icon-play'></use></svg>",
@ -101,14 +107,14 @@
"</button>", "</button>",
"<button type='button' data-player='fast-forward'>", "<button type='button' data-player='fast-forward'>",
"<svg><use xlink:href='#icon-fast-forward'></use></svg>", "<svg><use xlink:href='#icon-fast-forward'></use></svg>",
"<span class='sr-only'>Fast forward {seektime} seconds</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'>Time</span>",
"<span class='player-duration'>00:00</span>", "<span class='player-duration'>00:00</span>",
"</span>", "</span>",
"</span>", "</span>",
"<span class='player-controls-sound'>", "<span class='player-controls-right'>",
"<input class='inverted sr-only' id='mute{id}' type='checkbox' data-player='mute'>", "<input class='inverted sr-only' id='mute{id}' type='checkbox' data-player='mute'>",
"<label id='mute{id}' for='mute{id}'>", "<label id='mute{id}' for='mute{id}'>",
"<svg class='icon-muted'><use xlink:href='#icon-muted'></use></svg>", "<svg class='icon-muted'><use xlink:href='#icon-muted'></use></svg>",
@ -126,7 +132,7 @@
"<button type='button' data-player='fullscreen'>", "<button type='button' data-player='fullscreen'>",
"<svg class='icon-exit-fullscreen'><use xlink:href='#icon-exit-fullscreen'></use></svg>", "<svg class='icon-exit-fullscreen'><use xlink:href='#icon-exit-fullscreen'></use></svg>",
"<svg><use xlink:href='#icon-enter-fullscreen'></use></svg>", "<svg><use xlink:href='#icon-enter-fullscreen'></use></svg>",
"<span class='sr-only'>Toggle fullscreen</span>", "<span class='sr-only'>Toggle Fullscreen</span>",
"</button>", "</button>",
"</span>", "</span>",
"</div>" "</div>"
@ -142,10 +148,10 @@
} }
// Credits: http://paypal.github.io/accessible-html5-video-player/ // Credits: http://paypal.github.io/accessible-html5-video-player/
// Unfortunately, due to scattered support, browser sniffing is required // Unfortunately, due to mixed support, UA sniffing is required
function _browserSniff() { function _browserSniff() {
var nAgt = navigator.userAgent, var nAgt = navigator.userAgent,
browserName = 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,
@ -154,22 +160,22 @@
// MSIE 11 // MSIE 11
if ((navigator.appVersion.indexOf("Windows NT") !== -1) && (navigator.appVersion.indexOf("rv:11") !== -1)) { if ((navigator.appVersion.indexOf("Windows NT") !== -1) && (navigator.appVersion.indexOf("rv:11") !== -1)) {
browserName = "IE"; name = "IE";
fullVersion = "11;"; fullVersion = "11;";
} }
// MSIE // MSIE
else if ((verOffset=nAgt.indexOf("MSIE")) !== -1) { else if ((verOffset=nAgt.indexOf("MSIE")) !== -1) {
browserName = "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) {
browserName = "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) {
browserName = "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);
@ -177,15 +183,15 @@
} }
// Firefox // Firefox
else if ((verOffset=nAgt.indexOf("Firefox")) !== -1) { else if ((verOffset=nAgt.indexOf("Firefox")) !== -1) {
browserName = "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("/")) ) {
browserName = nAgt.substring(nameOffset,verOffset); name = nAgt.substring(nameOffset,verOffset);
fullVersion = nAgt.substring(verOffset+1); fullVersion = nAgt.substring(verOffset+1);
if (browserName.toLowerCase()==browserName.toUpperCase()) { if (name.toLowerCase()==name.toUpperCase()) {
browserName = navigator.appName; name = navigator.appName;
} }
} }
// Trim the fullVersion string at semicolon/space if present // Trim the fullVersion string at semicolon/space if present
@ -201,8 +207,43 @@
fullVersion = ""+parseFloat(navigator.appVersion); fullVersion = ""+parseFloat(navigator.appVersion);
majorVersion = parseInt(navigator.appVersion,10); majorVersion = parseInt(navigator.appVersion,10);
} }
// Return data // Return data
return [browserName, majorVersion]; return {
name: name,
version: majorVersion,
ios: /(iPad|iPhone|iPod)/g.test(navigator.platform)
};
}
// Check for mime type support against a player instance
// Credits: http://diveintohtml5.info/everything.html
// Related: http://www.leanbackplayer.com/test/h5mt.html
function _supportMime(player, mimeType) {
var media = player.media;
// Only check video types for video players
if(player.type == "video") {
// Check type
switch(mimeType) {
case "video/webm": return !!(media.canPlayType && media.canPlayType("video/webm; codecs=\"vp8, vorbis\"").replace(/no/, ""));
case "video/mp4": return !!(media.canPlayType && media.canPlayType("video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"").replace(/no/, ""));
case "video/ogg": return !!(media.canPlayType && media.canPlayType("video/ogg; codecs=\"theora\"").replace(/no/, ""));
}
}
// Only check audio types for audio players
else if(player.type == "audio") {
// Check type
switch(mimeType) {
case "audio/mpeg": return !!(media.canPlayType && media.canPlayType("audio/mpeg;").replace(/no/, ""));
case "audio/ogg": return !!(media.canPlayType && media.canPlayType("audio/ogg; codecs=\"vorbis\"").replace(/no/, ""));
case "audio/wav": return !!(media.canPlayType && media.canPlayType("audio/wav; codecs=\"1\"").replace(/no/, ""));
}
}
// If we got this far, we're stuffed
return false;
} }
// Replace all // Replace all
@ -242,6 +283,23 @@
} }
} }
// Remove an element
function _remove(element) {
element.parentNode.removeChild(element);
}
// Prepend child
function _prependChild(parent, element) {
parent.insertBefore(element, parent.firstChild);
}
// Set attributes
function _setAttributes(element, attributes) {
for(var key in attributes) {
element.setAttribute(key, attributes[key]);
}
}
// Toggle class on an element // Toggle class on an element
function _toggleClass(element, name, state) { function _toggleClass(element, name, state) {
if(element){ if(element){
@ -259,6 +317,17 @@
function _toggleHandler(element, events, callback, toggle) { function _toggleHandler(element, events, callback, toggle) {
events = events.split(" "); 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++) { for (var i = 0; i < events.length; i++) {
element[toggle ? "addEventListener" : "removeEventListener"](events[i], callback, false); element[toggle ? "addEventListener" : "removeEventListener"](events[i], callback, false);
} }
@ -274,8 +343,40 @@
_toggleHandler(element, events, callback, false); _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;
// Set the attribute for CSS hooks
event.target[event.target.checked ? "setAttribute" : "removeAttribute"]("checked", "");
// Trigger change event
_triggerEvent(event.target, "change");
}
// Get percentage // Get percentage
function _getPercentage(current, max) { function _getPercentage(current, max) {
if(current === 0 || max === 0 || isNaN(current) || isNaN(max)) {
return 0;
}
return ((current / max) * 100).toFixed(2); return ((current / max) * 100).toFixed(2);
} }
@ -392,28 +493,53 @@
player.container = container; player.container = container;
// Captions functions // Captions functions
// Credits: http://paypal.github.io/accessible-html5-video-player/ // 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" || !player.supported.full) {
return;
}
// For "manual" captions, adjust caption position when play time changed (via rewind, clicking progress bar, etc.) // Reset subcount
function _adjustManualCaptions() {
player.subcount = 0; player.subcount = 0;
while (_timecodeMax(player.captions[player.subcount][0]) < player.media.currentTime.toFixed(1)) {
// Check time is a number, if not use currentTime
// IE has a bug where currentTime doesn't go to 0
// https://twitter.com/Sam_Potts/status/573715746506731521
time = typeof time === "number" ? time : player.media.currentTime;
while (_timecodeMax(player.captions[player.subcount][0]) < time.toFixed(1)) {
player.subcount++; player.subcount++;
if (player.subcount > player.captions.length-1) { if (player.subcount > player.captions.length-1) {
player.subcount = player.captions.length-1; player.subcount = player.captions.length-1;
break; break;
} }
} }
// Check if the next caption is in the current time range
if (player.media.currentTime.toFixed(1) >= _timecodeMin(player.captions[player.subcount][0]) &&
player.media.currentTime.toFixed(1) <= _timecodeMax(player.captions[player.subcount][0])) {
player.currentCaption = player.captions[player.subcount][1];
// Render the caption
player.captionsContainer.innerHTML = player.currentCaption;
} }
else {
// Clear the caption
player.captionsContainer.innerHTML = "";
}
}
// Display captions container and button (for initialization) // Display captions container and button (for initialization)
function _showCaptions() { function _showCaptions() {
_toggleClass(player.container, config.classes.captions.enabled, true); _toggleClass(player.container, config.classes.captions.enabled, true);
if (config.captions.defaultActive) { if (config.captions.defaultActive) {
_toggleClass(player.container, config.classes.captions.active, true); _toggleClass(player.container, config.classes.captions.active, true);
player.buttons.captions.setAttribute("checked", "checked"); player.buttons.captions.setAttribute("checked", "");
} }
} }
// Utilities for caption time codes // Utilities for caption time codes
function _timecodeMin(tc) { function _timecodeMin(tc) {
var tcpair = []; var tcpair = [];
@ -477,6 +603,18 @@
// Inject into the container // Inject into the container
player.container.insertAdjacentHTML("beforeend", html); player.container.insertAdjacentHTML("beforeend", html);
// Setup tooltips
if(config.tooltips) {
var labels = _getElements(config.selectors.labels);
for (var i = labels.length - 1; i >= 0; i--) {
var label = labels[i];
_toggleClass(label, config.classes.hidden, false);
_toggleClass(label, config.classes.tooltip, true);
}
}
} }
// Find the UI controls and store references // Find the UI controls and store references
@ -492,9 +630,12 @@
player.buttons.restart = _getElement(config.selectors.buttons.restart); player.buttons.restart = _getElement(config.selectors.buttons.restart);
player.buttons.rewind = _getElement(config.selectors.buttons.rewind); player.buttons.rewind = _getElement(config.selectors.buttons.rewind);
player.buttons.forward = _getElement(config.selectors.buttons.forward); 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.mute = _getElement(config.selectors.buttons.mute);
player.buttons.captions = _getElement(config.selectors.buttons.captions); player.buttons.captions = _getElement(config.selectors.buttons.captions);
player.buttons.fullscreen = _getElement(config.selectors.buttons.fullscreen); player.checkboxes = _getElements("[type='checkbox']");
// Progress // Progress
player.progress = {}; player.progress = {};
@ -521,6 +662,7 @@
} }
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);
return false; return false;
} }
} }
@ -539,8 +681,6 @@
// Setup media // Setup media
function _setupMedia() { function _setupMedia() {
player.media = player.container.querySelectorAll("audio, video")[0];
// If there's no media, bail // If there's no media, bail
if(!player.media) { if(!player.media) {
_log("No audio or video element found!", true); _log("No audio or video element found!", true);
@ -548,10 +688,9 @@
} }
// Remove native video controls // Remove native video controls
if(player.supported.full) {
player.media.removeAttribute("controls"); player.media.removeAttribute("controls");
}
// Set media type
player.type = (player.media.tagName.toLowerCase() == "video" ? "video" : "audio");
// Add type class // Add type class
_toggleClass(player.container, config.classes[player.type], true); _toggleClass(player.container, config.classes[player.type], true);
@ -559,8 +698,13 @@
// If there's no autoplay attribute, assume the video is stopped and add state class // If there's no autoplay attribute, assume the video is stopped and add state class
_toggleClass(player.container, config.classes.stopped, (player.media.getAttribute("autoplay") === null)); _toggleClass(player.container, config.classes.stopped, (player.media.getAttribute("autoplay") === null));
// Add iOS class
if(player.browser.ios) {
_toggleClass(player.container, "ios", true);
}
// Inject the player wrapper // Inject the player wrapper
if(player.type === "video") { if(player.type === "video" && player.supported.full) {
// Create the wrapper div // Create the wrapper div
var wrapper = document.createElement("div"); var wrapper = document.createElement("div");
wrapper.setAttribute("class", config.classes.videoWrapper); wrapper.setAttribute("class", config.classes.videoWrapper);
@ -571,6 +715,11 @@
// Cache the container // Cache the container
player.videoContainer = wrapper; player.videoContainer = wrapper;
} }
// Autoplay
if(player.media.getAttribute("autoplay") !== null) {
_play();
}
} }
// Setup captions // Setup captions
@ -583,9 +732,9 @@
player.captionsContainer = _getElement(config.selectors.captions); player.captionsContainer = _getElement(config.selectors.captions);
// Determine if HTML5 textTracks is supported // Determine if HTML5 textTracks is supported
player.isTextTracks = false; player.usingTextTracks = false;
if (player.media.textTracks) { if (player.media.textTracks) {
player.isTextTracks = true; player.usingTextTracks = true;
} }
// Get URL of caption file if exists // Get URL of caption file if exists
@ -629,20 +778,20 @@
_showCaptions(player); _showCaptions(player);
// If IE 10/11 or Firefox 31+ or Safari 7+, don"t use native captioning (still doesn"t work although they claim it"s now supported) // If IE 10/11 or Firefox 31+ or Safari 7+, don"t use native captioning (still doesn"t work although they claim it"s now supported)
if ((player.browserName === "IE" && player.browserMajorVersion === 10) || if ((player.browser.name === "IE" && player.browser.version === 10) ||
(player.browserName === "IE" && player.browserMajorVersion === 11) || (player.browser.name === "IE" && player.browser.version === 11) ||
(player.browserName === "Firefox" && player.browserMajorVersion >= 31) || (player.browser.name === "Firefox" && player.browser.version >= 31) ||
(player.browserName === "Safari" && player.browserMajorVersion >= 7)) { (player.browser.name === "Safari" && player.browser.version >= 7)) {
// Debugging // Debugging
_log("Detected IE 10/11 or Firefox 31+ or Safari 7+."); _log("Detected IE 10/11 or Firefox 31+ or Safari 7+.");
// Set to false so skips to "manual" captioning // Set to false so skips to "manual" captioning
player.isTextTracks = false; player.usingTextTracks = false;
} }
// Rendering caption tracks // Rendering caption tracks
// Native support required - http://caniuse.com/webvtt // Native support required - http://caniuse.com/webvtt
if (player.isTextTracks) { if (player.usingTextTracks) {
_log("TextTracks supported."); _log("TextTracks supported.");
for (var y=0; y < tracks.length; y++) { for (var y=0; y < tracks.length; y++) {
@ -665,24 +814,8 @@
// Render captions from array at appropriate time // Render captions from array at appropriate time
player.currentCaption = ""; player.currentCaption = "";
player.subcount = 0;
player.captions = []; player.captions = [];
_on(player.media, "timeupdate", function() {
// Check if the next caption is in the current time range
if (player.media.currentTime.toFixed(1) > _timecodeMin(player.captions[player.subcount][0]) &&
player.media.currentTime.toFixed(1) < _timecodeMax(player.captions[player.subcount][0])) {
player.currentCaption = player.captions[player.subcount][1];
}
// Is there a next timecode?
if (player.media.currentTime.toFixed(1) > _timecodeMax(player.captions[player.subcount][0]) &&
player.subcount < (player.captions.length-1)) {
player.subcount++;
}
// Render the caption
player.captionsContainer.innerHTML = player.currentCaption;
});
if (captionSrc !== "") { if (captionSrc !== "") {
// Create XMLHttpRequest Object // Create XMLHttpRequest Object
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
@ -720,7 +853,7 @@
} }
// If Safari 7+, removing track from DOM [see "turn off native caption rendering" above] // If Safari 7+, removing track from DOM [see "turn off native caption rendering" above]
if (player.browserName === "Safari" && player.browserMajorVersion >= 7) { if (player.browser.name === "Safari" && player.browser.version >= 7) {
_log("Safari 7+ detected; removing track from DOM."); _log("Safari 7+ detected; removing track from DOM.");
// Find all <track> elements // Find all <track> elements
@ -763,20 +896,6 @@
player.media.pause(); player.media.pause();
} }
// Restart playback
function _restart() {
// Move to beginning
player.media.currentTime = 0;
// Special handling for "manual" captions
if (!player.isTextTracks) {
player.subcount = 0;
}
// Play and ensure the play button is in correct state
_play();
}
// Rewind // Rewind
function _rewind(seekTime) { function _rewind(seekTime) {
// Use default if needed // Use default if needed
@ -796,35 +915,41 @@
} }
// Seek to time // Seek to time
var _seek = function(input) { // The input parameter can be an event or a number
//var value = config.seekTime; function _seek(input) {
var targetTime = 0; var targetTime = 0;
// If no event or time is passed, bail
if (typeof input === "undefined") {
return;
}
// Explicit position // Explicit position
else if (typeof input === "number") { if (typeof input === "number") {
targetTime = input; targetTime = input;
} }
// Event // Event
else if (input.type === "change" || input.type === "input") { else if (typeof input === "object" && (input.type === "change" || input.type === "input")) {
// It's the seek slider // It's the seek slider
// Seek to the selected time // Seek to the selected time
targetTime = ((this.value / this.max) * player.media.duration).toFixed(1); targetTime = ((input.target.value / input.target.max) * player.media.duration);
}
// Normalise targetTime
if (targetTime < 0) {
targetTime = 0;
}
else if (targetTime > player.media.duration) {
targetTime = player.media.duration;
} }
// Set the current time // Set the current time
player.media.currentTime = targetTime; // 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 // Logging
_log("Seeking to " + player.media.currentTime + " seconds"); _log("Seeking to " + player.media.currentTime + " seconds");
// Special handling for "manual" captions // Special handling for "manual" captions
if (!player.isTextTracks && player.type === "video") { _seekManualCaptions(targetTime);
_adjustManualCaptions(player);
}
} }
// Check playing state // Check playing state
@ -888,7 +1013,7 @@
// 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) {
volume = window.localStorage.plyr_volume || config.volume; volume = window.localStorage[config.storage.key] || config.volume;
} }
else { else {
volume = config.volume; volume = config.volume;
@ -899,8 +1024,15 @@
volume = 10; volume = 10;
} }
// If the controls are there
if(player.supported.full) {
player.volume.value = volume; player.volume.value = volume;
}
// Set the player volume
player.media.volume = parseFloat(volume / 10); player.media.volume = parseFloat(volume / 10);
// Update the UI
_checkMute(); _checkMute();
// Store the volume in storage // Store the volume in storage
@ -912,24 +1044,35 @@
// Mute // Mute
function _toggleMute(muted) { function _toggleMute(muted) {
// If the method is called without parameter, toggle based on current value // If the method is called without parameter, toggle based on current value
if(typeof active === "undefined") { if(typeof muted === "undefined") {
muted = !player.media.muted; muted = !player.media.muted;
}
// If the controls are there
if(player.supported.full) {
player.buttons.mute.checked = muted; player.buttons.mute.checked = muted;
} }
// Set mute on the player
player.media.muted = muted; player.media.muted = muted;
// Update UI
_checkMute(); _checkMute();
} }
// Toggle captions // Toggle captions
function _toggleCaptions(active) { function _toggleCaptions(show) {
// If the method is called without parameter, toggle based on current value if(!player.supported.plyr) {
if(typeof active === "undefined") { return;
active = (player.container.className.indexOf(config.classes.captions.active) === -1);
player.buttons.captions.checked = active;
} }
if (active) { // If the method is called without parameter, toggle based on current value
if(typeof active === "undefined") {
show = (player.container.className.indexOf(config.classes.captions.active) === -1);
player.buttons.captions.checked = show;
}
if (show) {
_toggleClass(player.container, config.classes.captions.active, true); _toggleClass(player.container, config.classes.captions.active, true);
} }
else { else {
@ -942,16 +1085,30 @@
_toggleClass(player.container, config.classes.muted, (player.media.volume === 0 || player.media.muted)); _toggleClass(player.container, config.classes.muted, (player.media.volume === 0 || player.media.muted));
} }
// Check if media is loading
function _checkLoading(event) {
var loading = (event.type === "waiting");
// Clear timer
clearTimeout(player.loadingTimer);
// Timer to prevent flicker when seeking
player.loadingTimer = setTimeout(function() {
_toggleClass(player.container, config.classes.loading, loading);
}, (loading ? 250 : 0));
}
// Update <progress> elements // Update <progress> elements
function _updateProgress(event) { function _updateProgress(event) {
var progress, text, value = 0; var progress = player.progress.played.bar,
text = player.progress.played.text,
value = 0;
if(event) {
switch(event.type) { switch(event.type) {
// Video playing // Video playing
case "timeupdate": case "timeupdate":
case "seeking": case "seeking":
progress = player.progress.played.bar;
text = player.progress.played.text;
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
@ -964,8 +1121,6 @@
// Events from seek range // Events from seek range
case "change": case "change":
case "input": case "input":
progress = player.progress.played.bar;
text = player.progress.played.text;
value = event.target.value; value = event.target.value;
break; break;
@ -986,13 +1141,11 @@
})(); })();
break; break;
} }
if (progress && value > 0) {
progress.value = value;
text.innerHTML = value;
} }
//_log(event); // Set values
progress.value = value;
text.innerHTML = value;
} }
// Update the displayed play time // Update the displayed play time
@ -1008,13 +1161,94 @@
player.duration.innerHTML = player.mins + ":" + player.secs; player.duration.innerHTML = player.mins + ":" + player.secs;
} }
// Handle time change event
function _timeUpdate(event) { function _timeUpdate(event) {
// Duration // Duration
_updateTimeDisplay(); _updateTimeDisplay();
// Playing progress // Playing progress
_updateProgress(event); _updateProgress(event);
} }
// Remove <source> children and src attribute
function _removeSources() {
// Find child <source> elements
var sources = player.media.querySelectorAll("source");
// Remove each
for (var i = sources.length - 1; i >= 0; i--) {
_remove(sources[i]);
}
// Remove src attribute
player.media.removeAttribute("src");
}
// Inject a source
function _addSource(attributes) {
if(attributes.src) {
// Create a new <source>
var element = document.createElement("source");
// Set all passed attributes
_setAttributes(element, attributes);
// Inject the new source
_prependChild(player.media, element);
}
}
// Update source
// Sources are not checked for support so be careful
function _parseSource(sources) {
// Pause playback (webkit freaks out)
_pause();
// Restart
_seek();
// Update the UI
_checkPlaying();
// Remove current sources
_removeSources();
// If a single source is passed
// .source("path/to/video.mp4")
if(typeof sources === "string") {
player.media.setAttribute("src", sources);
}
// An array of source objects
// Check if a source exists, use that or set the "src" attribute?
// .source([{ src: "path/to/video.mp4", type: "video/mp4" },{ src: "path/to/video.webm", type: "video/webm" }])
else if (sources.constructor === Array) {
for (var index in sources) {
_addSource(sources[index]);
}
}
// Reset time display
if(player.supported.full) {
_timeUpdate();
}
// Re-load sources
player.media.load();
// Play if autoplay attribute is present
if(player.media.getAttribute("autoplay") !== null) {
_play();
}
}
// Update poster
function _updatePoster(source) {
if(player.type === "video") {
player.media.setAttribute("poster", source);
}
}
// Listen for events // Listen for events
function _listeners() { function _listeners() {
// Play // Play
@ -1030,7 +1264,7 @@
}); });
// Restart // Restart
_on(player.buttons.restart, "click", _restart); _on(player.buttons.restart, "click", _seek);
// Rewind // Rewind
_on(player.buttons.rewind, "click", _rewind); _on(player.buttons.rewind, "click", _rewind);
@ -1053,39 +1287,32 @@
_on(player.buttons.fullscreen, "click", _toggleFullscreen); _on(player.buttons.fullscreen, "click", _toggleFullscreen);
// Handle user exiting fullscreen by escaping etc // Handle user exiting fullscreen by escaping etc
if(fullscreen.supportsFullScreen) {
_on(document, fullscreen.fullScreenEventName, _toggleFullscreen); _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) {
_restart();
}
else {
_pause();
}
});
} }
// Time change on media // Time change on media
_on(player.media, "timeupdate seeking", _timeUpdate); _on(player.media, "timeupdate seeking", _timeUpdate);
// Update manual captions
_on(player.media, "timeupdate", _seekManualCaptions);
// Seek // Seek
_on(player.buttons.seek, "change input", _seek); _on(player.buttons.seek, "change input", _seek);
// Captions // Captions
_on(player.buttons.captions, "click", function() { _on(player.buttons.captions, "change", function() {
_toggleCaptions(this.checked); _toggleCaptions(this.checked);
}); });
// Clear captions at end of video // Handle the media finishing
_on(player.media, "ended", function() { _on(player.media, "ended", function() {
// Clear
if(player.type === "video") { if(player.type === "video") {
player.captionsContainer.innerHTML = ""; player.captionsContainer.innerHTML = "";
} }
// Reset UI
_checkPlaying(); _checkPlaying();
}); });
@ -1100,33 +1327,67 @@
// Handle native play/pause // Handle native play/pause
_on(player.media, "play pause", _checkPlaying); _on(player.media, "play pause", _checkPlaying);
// 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();
}
});
}
} }
function _init() { function _init() {
// Setup the fullscreen api // Setup the fullscreen api
fullscreen = _fullscreen(); fullscreen = _fullscreen();
// Sniff // Sniff out the browser
player.browserInfo = _browserSniff(); player.browser = _browserSniff();
player.browserName = player.browserInfo[0];
player.browserMajorVersion = player.browserInfo[1];
// Debug info // Check for full support
_log(player.browserName + " " + player.browserMajorVersion); player.supported = api.supported();
// If IE8, stop customization (use fallback) // If no native support, bail
// If IE9, stop customization (use native controls) if(!player.supported.basic) {
if (player.browserName === "IE" && (player.browserMajorVersion === 8 || player.browserMajorVersion === 9) ) {
_log("Browser not suppported.", true);
return false; return false;
} }
// Get the media element
player.media = player.container.querySelectorAll("audio, video")[0];
// Set media type
player.type = player.media.tagName.toLowerCase();
// If iPhone/iPod and video, customisation support is limited
if(/iPhone|iPod/i.test(navigator.userAgent) && player.type === "video") {
player.supported.full = false;
}
// Debug info
_log(player.browser.name + " " + player.browser.version);
// Setup media // Setup media
_setupMedia(); _setupMedia();
// Generate random number for id/for attribute values for controls // Generate random number for id/for attribute values for controls
player.random = Math.floor(Math.random() * (10000)); player.random = Math.floor(Math.random() * (10000));
// If there's full support
if(player.supported.full) {
// Inject custom controls // Inject custom controls
_injectControls(); _injectControls();
@ -1151,22 +1412,42 @@
_listeners(); _listeners();
} }
_init(); // Successful setup
return true;
}
if(!_init()) {
return {};
}
return { return {
media: player.media, media: player.media,
play: _play, play: _play,
pause: _pause, pause: _pause,
restart: _restart, restart: _seek,
rewind: _rewind, rewind: _rewind,
forward: _forward, forward: _forward,
seek: _seek, seek: _seek,
setVolume: _setVolume, setVolume: _setVolume,
toggleMute: _toggleMute, toggleMute: _toggleMute,
toggleCaptions: _toggleCaptions toggleCaptions: _toggleCaptions,
source: _parseSource,
poster: _updatePoster,
support: function(mimeType) { return _supportMime(player, mimeType); }
} }
} }
// Check for support
api.supported = function() {
var browser = _browserSniff(),
basic = (!!document.createElement("audio").canPlayType && !!document.createElement("video").canPlayType);
return {
basic: basic,
full: basic && !(browser.name === "IE" && (browser.version <= 9))
};
}
// Expose setup function // Expose setup function
api.setup = function(options){ api.setup = function(options){
// Extend the default options with user specified // Extend the default options with user specified
@ -1174,12 +1455,13 @@
// If enabled carry on // If enabled carry on
// You may want to disable certain UAs etc // You may want to disable certain UAs etc
if(!config.enabled) { if(!config.enabled || !api.supported().basic) {
return false; return false;
} }
// Get the players // 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 // Create a player instance for each element
for (var i = elements.length - 1; i >= 0; i--) { for (var i = elements.length - 1; i >= 0; i--) {
@ -1188,7 +1470,11 @@
// Setup a player instance and add to the element // Setup a player instance and add to the element
if(typeof element.plyr === "undefined") { 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 // Add to return array
@ -1197,4 +1483,5 @@
return players; return players;
} }
}(this.plyr = this.plyr || {})); }(this.plyr = this.plyr || {}));

View File

@ -23,10 +23,19 @@
@control-color-inactive: @gray; @control-color-inactive: @gray;
@control-color-hover: #fff; @control-color-hover: #fff;
// Tooltips
@tooltip-bg: @controls-bg;
@tooltip-color: #fff;
@tooltip-padding: @control-spacing;
@tooltip-arrow-size: 5px;
@tooltip-radius: 3px;
// Progress // Progress
@progress-bg: lighten(@gray, 10%); @progress-bg: lighten(@gray, 10%);
@progress-playing-bg: @blue; @progress-playing-bg: @blue;
@progress-buffered-bg: @gray; @progress-buffered-bg: @gray;
@progress-loading-size: 40px;
@progress-loading-bg: rgba(0,0,0, .15);
// Volume // Volume
@volume-track-height: 6px; @volume-track-height: 6px;
@ -59,13 +68,18 @@
&:after { content: ""; display: table; } &:after { content: ""; display: table; }
&:after { clear: both; } &:after { clear: both; }
} }
// Tab focus styles // Tab focus styles
.tab-focus() { .tab-focus() {
outline: thin dotted #000; outline: thin dotted #000;
outline-offset: 0; outline-offset: 0;
} }
// Animation
// ---------------------------------------
@keyframes progress {
to { background-position: @progress-loading-size 0; }
}
// <input type="range"> styling // <input type="range"> styling
// --------------------------------------- // ---------------------------------------
.volume-thumb() { .volume-thumb() {
@ -86,7 +100,7 @@
.seek-thumb() { .seek-thumb() {
background: transparent; background: transparent;
border: 0; border: 0;
width: 2px; width: (@control-spacing * 2);
height: @control-spacing; height: @control-spacing;
} }
.seek-track() { .seek-track() {
@ -112,7 +126,6 @@
position: relative; position: relative;
max-width: 100%; max-width: 100%;
min-width: 290px; min-width: 290px;
overflow: hidden; // For the controls
// border-box everything // border-box everything
// http://paulirish.com/2012/box-sizing-border-box-ftw/ // http://paulirish.com/2012/box-sizing-border-box-ftw/
@ -172,15 +185,15 @@
text-align: center; text-align: center;
// Layout // Layout
&-sound { &-right {
display: block; display: block;
margin: @control-spacing auto 0; margin: @control-spacing auto 0;
} }
@media (min-width: @bp-control-split) { @media (min-width: @bp-control-split) {
&-playback { &-left {
float: left; float: left;
} }
&-sound { &-right {
float: right; float: right;
margin-top: 0; margin-top: 0;
} }
@ -220,10 +233,11 @@
overflow: hidden; overflow: hidden;
} }
// Specificity for overriding .inverted
button:focus, button:focus,
button:hover, button:hover,
input:focus + label, [type="checkbox"]:focus + label,
input + label:hover { [type="checkbox"] + label:hover {
background: @control-bg-hover; background: @control-bg-hover;
color: @control-color-hover; color: @control-color-hover;
} }
@ -247,6 +261,54 @@
} }
} }
// Tooltips
&-tooltip {
visibility: hidden;
position: absolute;
z-index: 2;
bottom: 100%;
margin-bottom: @tooltip-padding;
padding: @tooltip-padding (@tooltip-padding * 1.5);
opacity: 0;
background: @tooltip-bg;
border-radius: @tooltip-radius;
color: @tooltip-color;
font-size: @font-size-small;
line-height: 1.5;
font-weight: 600;
transform: translate(-50%, (@tooltip-padding * 3));
transition: transform .2s .2s ease, opacity .2s .2s ease;
&::after {
content: "";
display: block;
position: absolute;
left: 50%;
bottom: -@tooltip-arrow-size;
margin-left: -@tooltip-arrow-size;
width: 0;
height: 0;
transition: inherit;
border-style: solid;
border-width: @tooltip-arrow-size @tooltip-arrow-size 0 @tooltip-arrow-size;
border-color: @controls-bg transparent transparent;
}
}
label:hover .player-tooltip,
input:focus + label .player-tooltip,
button:hover .player-tooltip,
button:focus .player-tooltip {
visibility: visible;
opacity: 1;
transform: translate(-50%, 0);
}
label:hover .player-tooltip,
button:hover .player-tooltip {
z-index: 3;
}
// Player progress // Player progress
// <progress> element // <progress> element
&-progress { &-progress {
@ -284,11 +346,9 @@
// Inherit from currentColor; // Inherit from currentColor;
&::-webkit-progress-value { &::-webkit-progress-value {
background: currentColor; background: currentColor;
transition: width .1s ease;
} }
&::-moz-progress-bar { &::-moz-progress-bar {
background: currentColor; background: currentColor;
transition: width .1s ease;
} }
} }
&-played[value] { &-played[value] {
@ -303,7 +363,7 @@
// <input[type='range']> element // <input[type='range']> element
// Specificity is for bootstrap compatibility // Specificity is for bootstrap compatibility
&-seek[type=range] { &-seek[type=range] {
z-index: 3; z-index: 4;
cursor: pointer; cursor: pointer;
outline: 0; outline: 0;
@ -347,6 +407,24 @@
} }
} }
// Loading state
&.loading .player-progress-buffer {
animation: progress 1s linear infinite;
background-size: @progress-loading-size @progress-loading-size;
background-repeat: repeat-x;
background-color: @progress-buffered-bg;
background-image: linear-gradient(
-45deg,
@progress-loading-bg 25%,
transparent 25%,
transparent 50%,
@progress-loading-bg 50%,
@progress-loading-bg 75%,
transparent 75%,
transparent);
color: transparent;
}
// States // States
&-controls [data-player='pause'], &-controls [data-player='pause'],
&.playing .player-controls [data-player='play'] { &.playing .player-controls [data-player='play'] {
@ -419,6 +497,20 @@
} }
} }
// Hide sound controls on iOS
// It's not supported to change volume using JavaScript:
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
&.ios &-volume,
&.ios [data-player='mute'],
&.ios [data-player='mute'] + label,
&-audio.ios &-controls-right {
display: none;
}
// Center buttons so it looks less odd
&-audio.ios &-controls-left {
float: none;
}
// Full screen mode // Full screen mode
&-fullscreen, &-fullscreen,
&.fullscreen-active { &.fullscreen-active {
@ -432,13 +524,13 @@
z-index: 10000000; z-index: 10000000;
background: #000; background: #000;
video {
height: 100%;
}
.player-video-wrapper { .player-video-wrapper {
height: 100%; height: 100%;
width: 100%; width: 100%;
video {
height: 100%;
}
.player-captions { .player-captions {
top: auto; top: auto;
bottom: 90px; bottom: 90px;

View File

@ -21,15 +21,23 @@ $controls-bg: $gray-dark;
$control-bg-hover: $blue; $control-bg-hover: $blue;
$control-color: $gray-light; $control-color: $gray-light;
$control-color-inactive: $gray; $control-color-inactive: $gray;
$control-color-focus: #fff;
$control-color-hover: #fff; $control-color-hover: #fff;
// Tooltips
$tooltip-bg: $controls-bg;
$tooltip-color: #fff;
$tooltip-padding: $control-spacing;
$tooltip-arrow-size: 5px;
$tooltip-radius: 3px;
// Progress // Progress
$progress-bg: lighten($gray, 10%); $progress-bg: lighten($gray, 10%);
$progress-playing-bg: $blue; $progress-playing-bg: $blue;
$progress-buffered-bg: $gray; $progress-buffered-bg: $gray;
$progress-loading-size: 40px;
$progress-loading-bg: rgba(0,0,0, .15);
// Range // Volume
$volume-track-height: 6px; $volume-track-height: 6px;
$volume-track-bg: $gray; $volume-track-bg: $gray;
$volume-thumb-height: ($volume-track-height * 2); $volume-thumb-height: ($volume-track-height * 2);
@ -61,7 +69,6 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
&:after { content: ""; display: table; } &:after { content: ""; display: table; }
&:after { clear: both; } &:after { clear: both; }
} }
// Tab focus styles // Tab focus styles
@mixin tab-focus() @mixin tab-focus()
{ {
@ -69,6 +76,12 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
outline-offset: 0; outline-offset: 0;
} }
// Animation
// ---------------------------------------
@keyframes progress {
to { background-position: $progress-loading-size 0; }
}
// <input type="range"> styling // <input type="range"> styling
// --------------------------------------- // ---------------------------------------
@mixin volume-thumb() @mixin volume-thumb()
@ -88,29 +101,31 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
border: 0; border: 0;
border-radius: ($volume-track-height / 2); border-radius: ($volume-track-height / 2);
} }
@mixin seek-thumb() { @mixin seek-thumb()
{
background: transparent; background: transparent;
border: 0; border: 0;
width: 2px; width: ($control-spacing * 2);
height: $control-spacing; height: $control-spacing;
} }
@mixin seek-track() { @mixin seek-track()
{
background: none; background: none;
border: 0; border: 0;
} }
// Font smoothing // Font smoothing
// --------------------------------------- // ---------------------------------------
@mixin font-smoothing($mode: on) when ($mode = on) @mixin font-smoothing($mode: on)
{ {
@if $mode == 'on' {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} } @else if $mode == 'off' {
@mixin font-smoothing($mode: on) when ($mode = off)
{
-moz-osx-font-smoothing: auto; -moz-osx-font-smoothing: auto;
-webkit-font-smoothing: subpixel-antialiased; -webkit-font-smoothing: subpixel-antialiased;
} }
}
// Styles // Styles
// ------------------------------- // -------------------------------
@ -119,7 +134,6 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
position: relative; position: relative;
max-width: 100%; max-width: 100%;
min-width: 290px; min-width: 290px;
overflow: hidden; // For the controls
// border-box everything // border-box everything
// http://paulirish.com/2012/box-sizing-border-box-ftw/ // http://paulirish.com/2012/box-sizing-border-box-ftw/
@ -179,15 +193,15 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
text-align: center; text-align: center;
// Layout // Layout
&-sound { &-right {
display: block; display: block;
margin: $control-spacing auto 0; margin: $control-spacing auto 0;
} }
@media (min-width: $bp-control-split) { @media (min-width: $bp-control-split) {
&-playback { &-left {
float: left; float: left;
} }
&-sound { &-right {
float: right; float: right;
margin-top: 0; margin-top: 0;
} }
@ -226,16 +240,19 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
background: transparent; background: transparent;
overflow: hidden; overflow: hidden;
} }
input:focus + label,
button:focus { // Specificity for overriding .inverted
@include tab-focus(); button:focus,
color: $control-color-focus;
}
button:hover, button:hover,
input + label:hover { [type="checkbox"]:focus + label,
[type="checkbox"] + label:hover {
background: $control-bg-hover; background: $control-bg-hover;
color: $control-color-hover; color: $control-color-hover;
} }
button:focus,
input:focus + label {
outline: 0;
}
.icon-exit-fullscreen, .icon-exit-fullscreen,
.icon-muted, .icon-muted,
.icon-captions-on { .icon-captions-on {
@ -252,6 +269,54 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
} }
} }
// Tooltips
&-tooltip {
visibility: hidden;
position: absolute;
z-index: 2;
bottom: 100%;
margin-bottom: $tooltip-padding;
padding: $tooltip-padding ($tooltip-padding * 1.5);
opacity: 0;
background: $tooltip-bg;
border-radius: $tooltip-radius;
color: $tooltip-color;
font-size: $font-size-small;
line-height: 1.5;
font-weight: 600;
transform: translate(-50%, ($tooltip-padding * 3));
transition: transform .2s .2s ease, opacity .2s .2s ease;
&::after {
content: "";
display: block;
position: absolute;
left: 50%;
bottom: -$tooltip-arrow-size;
margin-left: -$tooltip-arrow-size;
width: 0;
height: 0;
transition: inherit;
border-style: solid;
border-width: $tooltip-arrow-size $tooltip-arrow-size 0 $tooltip-arrow-size;
border-color: $controls-bg transparent transparent;
}
}
label:hover .player-tooltip,
input:focus + label .player-tooltip,
button:hover .player-tooltip,
button:focus .player-tooltip {
visibility: visible;
opacity: 1;
transform: translate(-50%, 0);
}
label:hover .player-tooltip,
button:hover .player-tooltip {
z-index: 3;
}
// Player progress // Player progress
// <progress> element // <progress> element
&-progress { &-progress {
@ -270,8 +335,9 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
left: 0; left: 0;
top: 0; top: 0;
width: 100%; width: 100%;
height: 100%; height: $control-spacing;
margin: 0; margin: 0;
padding: 0;
vertical-align: top; vertical-align: top;
-webkit-appearance: none; -webkit-appearance: none;
@ -279,7 +345,6 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
border: none; border: none;
background: transparent; background: transparent;
} }
&-buffer[value], &-buffer[value],
&-played[value] { &-played[value] {
&::-webkit-progress-bar { &::-webkit-progress-bar {
@ -289,11 +354,9 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
// Inherit from currentColor; // Inherit from currentColor;
&::-webkit-progress-value { &::-webkit-progress-value {
background: currentColor; background: currentColor;
transition: width .1s ease;
} }
&::-moz-progress-bar { &::-moz-progress-bar {
background: currentColor; background: currentColor;
transition: width .1s ease;
} }
} }
&-played[value] { &-played[value] {
@ -308,7 +371,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
// <input[type='range']> element // <input[type='range']> element
// Specificity is for bootstrap compatibility // Specificity is for bootstrap compatibility
&-seek[type=range] { &-seek[type=range] {
z-index: 3; z-index: 4;
cursor: pointer; cursor: pointer;
outline: 0; outline: 0;
@ -352,6 +415,24 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
} }
} }
// Loading state
&.loading .player-progress-buffer {
animation: progress 1s linear infinite;
background-size: $progress-loading-size $progress-loading-size;
background-repeat: repeat-x;
background-color: $progress-buffered-bg;
background-image: linear-gradient(
-45deg,
$progress-loading-bg 25%,
transparent 25%,
transparent 50%,
$progress-loading-bg 50%,
$progress-loading-bg 75%,
transparent 75%,
transparent);
color: transparent;
}
// States // States
&-controls [data-player='pause'], &-controls [data-player='pause'],
&.playing .player-controls [data-player='play'] { &.playing .player-controls [data-player='play'] {
@ -365,6 +446,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
// <input[type='range']> element // <input[type='range']> element
// Specificity is for bootstrap compatibility // Specificity is for bootstrap compatibility
&-volume[type=range] { &-volume[type=range] {
display: inline-block;
vertical-align: middle; vertical-align: middle;
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
@ -376,20 +458,20 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
// Webkit // Webkit
&::-webkit-slider-runnable-track { &::-webkit-slider-runnable-track {
@include range-track(); @include volume-track();
} }
&::-webkit-slider-thumb { &::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
margin-top: -(($volume-thumb-height - $volume-track-height) / 2); margin-top: -(($volume-thumb-height - $volume-track-height) / 2);
@include range-thumb(); @include volume-thumb();
} }
// Mozilla // Mozilla
&::-moz-range-track { &::-moz-range-track {
@include range-track(); @include volume-track();
} }
&::-moz-range-thumb { &::-moz-range-thumb {
@include range-thumb(); @include volume-thumb();
} }
// Microsoft // Microsoft
@ -402,10 +484,10 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
} }
&::-ms-fill-lower, &::-ms-fill-lower,
&::-ms-fill-upper { &::-ms-fill-upper {
@include range-track(); @include volume-track();
} }
&::-ms-thumb { &::-ms-thumb {
@include range-thumb(); @include volume-thumb();
} }
&:focus { &:focus {
@ -423,6 +505,20 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
} }
} }
// Hide sound controls on iOS
// It's not supported to change volume using JavaScript:
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
&.ios &-volume,
&.ios [data-player='mute'],
&.ios [data-player='mute'] + label,
&-audio.ios &-controls-right {
display: none;
}
// Center buttons so it looks less odd
&-audio.ios &-controls-left {
float: none;
}
// Full screen mode // Full screen mode
&-fullscreen, &-fullscreen,
&.fullscreen-active { &.fullscreen-active {
@ -436,13 +532,13 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
z-index: 10000000; z-index: 10000000;
background: #000; background: #000;
video {
height: 100%;
}
.player-video-wrapper { .player-video-wrapper {
height: 100%; height: 100%;
width: 100%; width: 100%;
video {
height: 100%;
}
.player-captions { .player-captions {
top: auto; top: auto;
bottom: 90px; bottom: 90px;