Compare commits

...

43 Commits

Author SHA1 Message Date
e49c417e54 Version bump 2015-07-20 22:32:29 +10:00
b39961ec49 Tidying 2015-07-20 22:28:27 +10:00
8894b4c7b9 Merge branch 'master' into develop
# Conflicts:
#	changelog.md
#	dist/plyr.js
2015-07-20 22:25:31 +10:00
cdf3deb458 YouTube playback, docs update 2015-07-20 22:24:06 +10:00
b5fc21239b Version bump 2015-07-20 14:45:32 +10:00
93cc9edd9a Added icon prefix option for when using default controls 2015-07-20 14:44:51 +10:00
dcd9ca3a93 Started on source swap 2015-07-12 23:57:36 +10:00
c202cc1ffb More work on YouTube 2015-07-12 23:41:56 +10:00
093af22942 More work on YouTube playback 2015-07-12 21:17:56 +10:00
9d966e41b1 Built 2015-06-27 16:21:54 +10:00
240aa7aa5f Merge branch 'master' into develop
# Conflicts:
#	dist/plyr.css
#	dist/plyr.js
2015-06-27 16:21:38 +10:00
654e9cd623 Docs tweaks 2015-06-08 22:12:08 +10:00
73c3888309 Removed log 2015-06-08 21:50:04 +10:00
4f0633fdc1 SASS for previous change 2015-06-08 21:46:47 +10:00
f41854ebe7 Minor tweak to hiding controls in fullscreen 2015-06-08 21:43:49 +10:00
f398266206 Chrome Canary Fix 2015-06-07 23:20:33 +10:00
4c17f98520 TogglePlay API method (Fixes #86), Volume border (Fixes #87), Chrome Subs (Fixes #90) 2015-06-07 23:00:26 +10:00
398815857f Merge branch 'develop' of github.com:selz/plyr into develop
# Conflicts:
#	.gitignore
2015-05-23 19:58:53 +10:00
4c5020a396 Ignore test video 2015-05-23 19:58:06 +10:00
df84ce6e90 Docs 2015-05-18 15:11:16 +10:00
7161378da1 Bug fix 2015-05-18 15:10:40 +10:00
224b612ae7 Bug fix 2015-05-18 14:46:21 +10:00
19d7522722 Fixes bug with 1.1.8 volume 2015-05-18 14:43:45 +10:00
ceace2a678 setVolume() API method improvements (Fixes #83) 2015-05-18 13:50:44 +10:00
d627454b2a Restore classname on destroy 2015-05-17 17:33:46 +10:00
7ccbfad6ad New API methods (fixes #77), Fix for non strict mode (fixes #78) 2015-05-17 17:23:09 +10:00
91f8a158d2 Update index.html 2015-04-19 23:47:46 +10:00
8925dcde01 Update readme.md 2015-04-19 23:45:25 +10:00
3d1a586314 Working on YouTube playback 2015-04-15 23:50:03 +10:00
d04b278802 Merge branch 'master' into develop
# Conflicts:
#	src/js/plyr.js
2015-04-15 21:51:59 +10:00
7345f656c1 Started on plugin setup 2015-04-15 21:47:50 +10:00
530abac3a7 Update readme.md 2015-04-15 01:15:05 +10:00
e702d9a881 Update readme.md 2015-04-15 01:14:36 +10:00
b0aeccb793 Update readme.md 2015-04-11 11:19:19 +10:00
43d8d748ce Merge pull request #76 from robinpoort/feature/sass-variables
Make SCSS variables optional
2015-04-11 09:29:04 +10:00
b43ea5c7d3 adding !default to variables so they are easily overwritable 2015-04-10 15:13:02 +02:00
f56759222d Update readme.md 2015-04-10 13:38:49 +10:00
c8b7bb570c Update readme.md 2015-04-10 13:37:55 +10:00
d536b29550 Update readme.md 2015-04-10 13:36:51 +10:00
ecbda018c5 Merge branch 'master' of github.com:selz/plyr
# Conflicts:
#	src/js/plyr.js
2015-04-10 07:17:23 +10:00
5187311ff0 Bug fix for isFullscreen() in Mozilla (Fixes #38) 2015-04-10 07:16:53 +10:00
d9a94ac7b0 Version bump 2015-04-08 13:42:34 +10:00
3526e322ef Minor bug fixes 2015-04-06 19:42:26 +10:00
27 changed files with 1342 additions and 484 deletions

View File

@ -1,12 +1,48 @@
# Changelog # Changelog
## v1.2.0
- Added YouTube support.
## v1.1.13
- Added icon prefix option for when using default controls
## v1.1.13
- Logic tweaks for hiding controls in fullscreen
## v1.1.12
- Bug fix for Chrome Canary
## v1.1.11
- Bug fix
## v1.1.10
- Bug fix
## v1.1.9
- Bug fix for 1.1.8
## v1.1.8
- setVolume API method improvements (Fixes #83)
## v1.1.7
- Restore classname on destroy()
## v1.1.6
- New API methods (fixes #77), Fix for non strict mode (fixes #78)
## v1.1.5
- Fix for incorrect `isFullscreen()` return value in Mozilla (Fixes #38)
## v1.1.4
- Minor bug fixes
## v1.1.3 ## v1.1.3
- Fixes for random id used in controls with multiple instances and one call to setup - Fixes for random id used in controls with multiple instances and one call to setup
- Audio player UI improvements - Audio player UI improvements
## v1.1.2 ## v1.1.2
- Added an onSetup callback option - Added an onSetup callback option
- Added fullscreen API methods toggleFullscreen() (must be user iniated), and isFullscreen() - Added fullscreen API methods `toggleFullscreen()` (must be user iniated), and `isFullscreen()`
## v1.1.1 ## v1.1.1
- Fix for unsupported browser handling - Fix for unsupported browser handling
@ -16,7 +52,7 @@
- Added config option to set which controls are shown (if using the default controls html) and better handling of missing controls - Added config option to set which controls are shown (if using the default controls html) and better handling of missing controls
## v1.0.31 ## v1.0.31
- Display duration on metadataloaded - Display duration on `metadataloaded`
## v1.0.30 ## v1.0.30
- Fixed bug with media longer than 60 minutes (Fixes #69) - Fixed bug with media longer than 60 minutes (Fixes #69)

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
dist/sprite.svg vendored

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

2
docs/dist/docs.css 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

@ -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.1.3/docs.css"> <link rel="stylesheet" href="//cdn.plyr.io/1.2.0/docs.css">
</head> </head>
<body> <body>
<main> <main>

View File

@ -8,39 +8,61 @@
<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.1.3/plyr.css"> <link rel="stylesheet" href="https://cdn.plyr.io/1.2.0/plyr.css?1">
<!-- Docs styles --> <!-- Docs styles -->
<link rel="stylesheet" href="//cdn.plyr.io/1.1.3/docs.css"> <link rel="stylesheet" href="https://cdn.plyr.io/1.2.0/docs.css?2">
</head> </head>
<body> <body>
<header> <header>
<h1>Plyr</h1> <h1>Plyr</h1>
<p>A simple HTML5 media player with custom controls and WebVTT captions.</p> <p>A simple HTML5 media player with custom controls and WebVTT captions by <a href="https://twitter.com/sam_potts" target="_blank">@sam_potts</a></p>
<a href="https://github.com/selz/plyr" target="_blank" class="btn">Download on GitHub</a> <nav>
<ul>
<li>
<a href="https://github.com/selz/plyr" target="_blank" class="btn btn-primary">Download on GitHub</a>
<span class="btn-count js-stargazers-count">&hellip;</span>
</li>
<li>
<a href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts" target="_blank" class="btn js-popup" data-window-height="250" data-window-width="500">Tweet</a>
<span class="btn-count js-tweet-count">&hellip;</span>
</li>
</ul>
</nav>
</header> </header>
<main> <main role="main" id="main">
<section class="example-video"> <nav class="btn-bar nav-panel">
<ul>
<li><a href="#video" class="btn active btn-small">Video</a></li>
<li><a href="#youtube" class="btn btn-small">YouTube</a></li>
<li><a href="#audio" class="btn btn-small">Audio</a></li>
</ul>
</nav>
<div class="panels">
<section class="panel example-video active" id="video">
<div class="player"> <div class="player">
<video poster="//cdn.selz.com/plyr/1.0/poster.jpg" controls crossorigin> <video poster="https://cdn.plyr.io/static/poster.jpg" controls crossorigin>
<!-- Video files --> <!-- Video files -->
<source src="//cdn.selz.com/plyr/1.0/movie.mp4" type="video/mp4"> <source src="https://cdn.selz.com/plyr/1.0/movie.mp4" type="video/mp4">
<source src="//cdn.selz.com/plyr/1.0/movie.webm" type="video/webm"> <source src="https://cdn.selz.com/plyr/1.0/movie.webm" type="video/webm">
<!-- Text track file --> <!-- Text track file -->
<track kind="captions" label="English" srclang="en" src="//cdn.selz.com/plyr/1.0/en.vtt" default> <track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.0/en.vtt" default>
<!-- Fallback for browsers that don't support the <video> element --> <!-- Fallback for browsers that don't support the <video> element -->
<div> <a href="https://cdn.selz.com/plyr/1.0/movie.mp4">Download</a>
<a href="//cdn.selz.com/plyr/1.0/movie.mp4">Download</a>
</div>
</video> </video>
</div> </div>
<small>Big Buck Bunny. More info can be found at <a href="https://peach.blender.org" target="_blank">peach.blender.org</a>.</small> <small>Big Buck Bunny. More info can be found at <a href="https://peach.blender.org" target="_blank">peach.blender.org</a>.</small>
</section> </section>
<section class="panel example-video" id="youtube">
<section class="example-audio"> <div class="player">
<div data-video-id="Au87oAJ2jeE" data-type="youtube"></div>
</div>
<small>Envato's "Made By" interview of <a href="https://www.youtube.com/watch?v=Au87oAJ2jeE" target="_blank">Dan Cederholm</a> from <a href="https://dribbble.com" target="_blank">Dribbble</a>.</small>
</section>
<section class="panel example-audio" id="audio">
<div class="player"> <div class="player">
<audio controls> <audio controls>
<!-- Audio files --> <!-- Audio files -->
@ -48,22 +70,14 @@
<source src="//cdn.selz.com/plyr/1.0/logistics-96-sample.ogg" type="audio/ogg"> <source src="//cdn.selz.com/plyr/1.0/logistics-96-sample.ogg" type="audio/ogg">
<!-- Fallback for browsers that don't support the <audio> element --> <!-- Fallback for browsers that don't support the <audio> element -->
<div>
<a href="//cdn.selz.com/plyr/1.0/logistics-96-sample.mp3">Download</a> <a href="//cdn.selz.com/plyr/1.0/logistics-96-sample.mp3">Download</a>
</div>
</audio> </audio>
</div> </div>
<small>"96" by Logistics, which can be purchased from <a href="https://www.hospitalrecords.com/shop/artist/logistics" target="_blank">Hospital Records</a>.</small> <small>"96" by Logistics, which can be purchased from <a href="https://www.hospitalrecords.com/shop/artist/logistics" target="_blank">Hospital Records</a>.</small>
</section> </section>
</div>
</main> </main>
<footer>
<p>Used by &hellip;</p>
<a href="https://selz.com" target="_blank" class="logo">
<img src="https://d33i624pw6jj68.cloudfront.net/static/img/logos/selz.svg" alt="Selz">
</a>
</footer>
<!-- 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>
@ -83,13 +97,13 @@
b.insertBefore(c, b.childNodes[0]); b.insertBefore(c, b.childNodes[0]);
} }
} }
})(document, "https://cdn.plyr.io/1.1.3/sprite.svg"); })(document, "https://cdn.plyr.io/1.2.0/sprite.svg");
</script> </script>
<!-- Plyr core script --> <!-- Plyr core script -->
<script src="//cdn.plyr.io/1.1.3/plyr.js"></script> <script src="https://cdn.plyr.io/1.2.0/plyr.js?1"></script>
<!-- Docs script --> <!-- Docs script -->
<script src="//cdn.plyr.io/1.1.3/docs.js"></script> <script src="https://cdn.plyr.io/1.2.0/docs.js?1"></script>
</body> </body>
</html> </html>

View File

@ -7,12 +7,17 @@
// Setup the player // Setup the player
plyr.setup({ plyr.setup({
debug: true, debug: true,
volume: 9,
title: "Video demo", title: "Video demo",
html: templates.controls.render({}), html: templates.controls.render({}),
captions: { captions: {
defaultActive: true defaultActive: true
}, },
onSetup: function() { onSetup: function() {
if(!("media" in this)) {
return;
}
var player = this, var player = this,
type = player.media.tagName.toLowerCase(), type = player.media.tagName.toLowerCase(),
toggle = document.querySelector("[data-toggle='fullscreen']"); toggle = document.querySelector("[data-toggle='fullscreen']");
@ -25,6 +30,145 @@ plyr.setup({
} }
}); });
// General functions
(function() {
// Popup
function popup(event) {
// Prevent the link opening
if(event.target.nodeName.toLowerCase() == "a") {
if(event.preventDefault) {
event.preventDefault();
}
else {
event.returnValue = false;
}
}
var link = event.target,
url = link.href,
width = link.getAttribute("data-window-width") || 600,
height = link.getAttribute("data-window-height") || 600,
name = link.getAttribute("data-window-name") || "popup";
// If window exists, just focus it
if(window["window-"+name] && !window["window-"+name].closed) {
window["window-"+name].focus();
}
else {
// Get position
var left = window.screenLeft !== undefined ? window.screenLeft : screen.left;
var top = window.screenTop !== undefined ? window.screenTop : screen.top;
// Open in the centre of the screen
var x = (screen.width / 2) - (width / 2) + left,
y = (screen.height / 2) - (height / 2) + top;
// Open that window
window["window-"+name] = window.open(url, name, "top=" + y +",left="+ x +",width=" + width + ",height=" + height);
// Focus new window
window["window-"+name].focus();
}
}
// Trigger popups
document.querySelector(".js-popup").addEventListener("click", popup);
// Get JSONP
function getJSONP(url, callback) {
var name = "jsonp_callback_" + Math.round(100000 * Math.random());
// Cleanup to prevent memory leaks and hit original callback
window[name] = function(data) {
delete window[name];
document.body.removeChild(script);
callback(data);
};
// Create a faux script
var script = document.createElement("script");
script.setAttribute("src", url + (url.indexOf("?") >= 0 ? "&" : "?") + "callback=" + name);
// Inject to the body
document.body.appendChild(script);
}
// Get star count
var storageSupported = ("sessionStorage" in window),
selectors = {
github: ".js-stargazers-count",
twitter: ".js-tweet-count"
};
// Display the count next to the button
function displayCount(selector, count) {
document.querySelector(selector).innerHTML = count;
}
// Add star
function formatGitHubCount(count) {
return "&bigstar; " + count;
}
// Check if it's in session storage first
if(storageSupported && "github_stargazers" in window.sessionStorage) {
displayCount(selectors.github, formatGitHubCount(window.sessionStorage.github_stargazers));
}
else {
getJSONP("https://api.github.com/repos/selz/plyr?access_token=a46ac653210ba6a6be44260c29c333470c3fbbf5", function (json) {
if (json && typeof json.data.stargazers_count !== "undefined") {
// Update UI
displayCount(selectors.github, formatGitHubCount(json.data.stargazers_count));
// Store in session storage
window.sessionStorage.github_stargazers = json.data.stargazers_count;
}
});
}
// Get tweet count
if(storageSupported && "tweets" in window.sessionStorage) {
displayCount(selectors.twitter, window.sessionStorage.tweets);
}
else {
getJSONP("https://cdn.api.twitter.com/1/urls/count.json?url=plyr.io", function (json) {
if (json && typeof json.count !== "undefined") {
// Update UI
displayCount(selectors.twitter, json.count);
// Store in session storage
window.sessionStorage.tweets = json.count;
}
});
}
// Tabs
var tabs = document.querySelectorAll(".nav-panel a"),
panels = document.querySelectorAll(".panels > .panel"),
activeClass = "active";
for (var i = tabs.length - 1; i >= 0; i--) {
tabs[i].addEventListener("click", togglePanel);
}
function togglePanel(event) {
event.preventDefault();
var tab = event.target,
panel = document.querySelector(tab.getAttribute("href"));
for (var i = panels.length - 1; i >= 0; i--) {
panels[i].classList.remove(activeClass);
}
for (var x = tabs.length - 1; x >= 0; x--) {
tabs[x].classList.remove(activeClass);
}
panel.classList.add(activeClass);
event.target.classList.add(activeClass);
}
})();
// Google analytics // Google analytics
// For demo site (http://[www.]plyr.io) only // For demo site (http://[www.]plyr.io) only

View File

@ -0,0 +1,45 @@
// ==========================================================================
// Base layout
// ==========================================================================
// BORDER-BOX ALL THE THINGS!
// http://paulirish.com/2012/box-sizing-border-box-ftw/
*, *::after, *::before {
box-sizing: border-box;
}
// Base
html {
font-size: 100%;
}
body {
font-family: "Avenir", "Helvetica Neue", Helvetica, Arial, sans-serif;
background: @body-background;
line-height: 1.5;
text-align: center;
color: @gray;
.font-smoothing(on);
}
// Header
header {
padding: @padding-base;
margin-bottom: @padding-base;
p {
.font-size(18);
}
@media (min-width: @screen-sm) {
padding-top: (@padding-base * 3);
padding-bottom: (@padding-base * 3);
}
}
// Sections
section {
padding-bottom: @padding-base;
@media (min-width: @screen-sm) {
padding-bottom: (@padding-base * 2);
}
}

View File

@ -0,0 +1,144 @@
// ==========================================================================
// Buttons
// ==========================================================================
nav {
ul {
list-style: none;
margin: 0;
padding: 0;
font-size: 0;
}
li {
display: inline-block;
margin-top: (@padding-base / 2);
.font-size();
white-space: nowrap;
}
li + li {
margin-left: @padding-base;
}
}
// Tabs
.btn-bar {
position: relative;
margin: 0 auto @padding-base;
max-width: @example-width-video;
&::before {
content: "";
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1px;
background: @gray-lighter;
}
ul {
position: relative;
z-index: 1;
display: inline-block;
user-select: none;
}
li {
margin: 0;
&:first-child .btn {
border-radius: 4px 0 0 4px;
}
&:last-child .btn {
border-radius: 0 4px 4px 0;
}
& + li .btn {
margin-left: -1px;
}
}
.btn {
display: block;
border-radius: 0;
}
.active {
&:extend(.btn-primary);
}
.btn.active {
box-shadow: inset 0 1px 1px rgba(0,0,0, .2);
position: relative;
z-index: 1;
}
@media (min-width: 560px) {
margin-bottom: (@padding-base * 2);
}
}
// Shared
.btn,
.btn-count {
display: inline-block;
vertical-align: middle;
border-radius: @border-radius-base;
font-weight: 600;
user-select: none;
}
// Buttons
.btn {
padding: (@padding-base / 2) @padding-base;
background: @body-background;
border: 1px solid @gray-light;
box-shadow: inset 0 1px 0 #fff, 0 1px 1px rgba(0,0,0, .05);
text-shadow: 0 1px 1px #fff;
color: @gray;
&:hover,
&:focus {
background-color: #fff;
border-color: darken(@gray-light, 5%);
color: @link-color;
outline: 0;
}
}
.btn-primary {
background: linear-gradient(@link-color, darken(@link-color, 3%));
border-color: darken(@link-color, 10%);
box-shadow: 0 1px 1px rgba(0,0,0, .15);
text-shadow: 0 1px 1px rgba(0,0,0, .1);
color: #fff;
&:hover,
&:focus {
color: #fff;
border-color: darken(@link-color, 20%);
}
}
.btn-small {
padding-top: ceil(@padding-base / 3);
padding-bottom: ceil(@padding-base / 3);
}
// Count bubble
.btn-count {
position: relative;
margin-left: 6px;
padding: ((@padding-base / 2) - 1px);
background: @body-background;
border: 1px solid @gray-light;
&::before {
content: "";
position: absolute;
display: block;
width: @arrow-size;
height: @arrow-size;
left: 1px;
top: 50%;
margin-top: -(@arrow-size / 2);
background: inherit;
border: inherit;
border-width: 1px 0 0 1px;
transform: rotate(-45deg) translate(-50%, -50%);
}
}

View File

@ -0,0 +1,19 @@
// ==========================================================================
// Errors (AWS pages)
// ==========================================================================
// Error page
html.error,
.error body {
height: 100%;
}
.error body {
width: 100%;
display: table;
table-layout: fixed;
}
.error main {
display: table-cell;
width: 100%;
vertical-align: middle;
}

View File

@ -0,0 +1,39 @@
// ==========================================================================
// Examples
// ==========================================================================
// Example players
.example-audio .player,
.example-video .player {
margin: 0 auto @padding-base;
&-controls {
border-radius: 0 0 @border-radius-base @border-radius-base;
}
}
.example-audio .player {
max-width: @example-width-audio;
&-controls {
border-radius: @border-radius-base;
}
&-progress {
border-radius: @border-radius-base @border-radius-base 0 0;
overflow: hidden;
}
}
.example-video .player {
max-width: @example-width-video;
video,
iframe {
border-radius: @border-radius-base @border-radius-base 0 0;
}
iframe {
-webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC);
}
&-fullscreen,
&.fullscreen-active {
max-width: none;
}
}

View File

@ -0,0 +1,13 @@
// ==========================================================================
// Panels
// ==========================================================================
// Panels
.panel {
display: none;
&.active {
display: block;
animation: fade-in .3s ease;
}
}

View File

@ -0,0 +1,47 @@
// ==========================================================================
// Typography
// ==========================================================================
// Headings
h1,
h2 {
letter-spacing: -.025em;
color: #2E3C44;
margin: 0 0 (@padding-base / 2);
line-height: 1.2;
.font-smoothing();
}
h1 {
.font-size(64);
color: #3498DB;
}
// Paragraph and small
p,
small {
margin: 0 0 @padding-base;
}
small {
display: block;
padding: 0 (@padding-base / 2);
.font-size(14);
}
// Links
a {
text-decoration: none;
color: @link-color;
border-bottom: 1px solid currentColor;
transition: background .3s ease, color .3s ease;
&:hover,
&:focus {
color: @gray-dark;
}
&:focus {
.tab-focus();
}
&.logo {
border: 0;
}
}

View File

@ -2,163 +2,33 @@
// HTML5 Video Player Demo Page // HTML5 Video Player Demo Page
// ========================================================================== // ==========================================================================
// Reset // CSS Reset
@import "lib/normalize.less"; @import "lib/normalize.less";
// Mixins // Mixins
@import "lib/mixins.less"; @import "lib/mixins.less";
// Fonts - docs only!
@import "lib/fontface.less";
// Variables // Variables
// --------------------------------------- @import "variables.less";
// Colors
@blue: #3498DB;
@gray-dark: #343f4a;
@gray: #565d64;
@gray-light: #cbd0d3;
// Elements // Animation
@link-color: @blue; @import "lib/animation.less";
@padding-base: 20px;
// Breakpoints // Base layout
@screen-md: 768px; @import "components/base.less";
// BORDER-BOX ALL THE THINGS!
// http://paulirish.com/2012/box-sizing-border-box-ftw/
*, *::after, *::before {
box-sizing: border-box;
}
// Base
body {
font-family: "Avenir", "Helvetica Neue", Helvetica, Arial, sans-serif;
background: #fff;
line-height: 1.5;
text-align: center;
color: #6D797F;
}
// Error page
html.error,
.error body {
height: 100%;
}
.error body {
width: 100%;
display: table;
table-layout: fixed;
}
.error main {
display: table-cell;
width: 100%;
vertical-align: middle;
}
// Type // Type
h1, @import "lib/fontface.less";
h2 { @import "components/type.less";
letter-spacing: -.025em;
color: #2E3C44;
margin: 0 0 (@padding-base / 2);
line-height: 1.2;
.font-smoothing();
}
h1 {
.font-size(64);
color: #3498DB;
}
p,
small {
margin: 0 0 @padding-base;
}
small {
display: block;
padding: 0 (@padding-base / 2);
.font-size(14);
}
// Header // Buttons
header { @import "components/buttons.less";
padding: @padding-base;
margin-bottom: @padding-base;
p { // Panels
.font-size(18); @import "components/panels.less";
}
@media (min-width: 560px) {
padding-top: (@padding-base * 3);
padding-bottom: (@padding-base * 3);
}
}
// Sections // Error
section { @import "components/error.less";
padding-bottom: @padding-base;
@media (min-width: 560px) { // Examples
padding-bottom: (@padding-base * 2); @import "components/examples.less";
}
}
// Links & Buttons
a {
text-decoration: none;
color: @link-color;
border-bottom: 1px solid currentColor;
transition: all .3s ease;
&:hover,
&:focus {
color: #000;
}
&:focus {
.tab-focus();
}
&.logo {
border: 0;
}
}
.btn {
display: inline-block;
padding: (@padding-base / 2) (@padding-base * 1.5);
background: @link-color;
border: 0;
color: #fff;
.font-smoothing(on);
font-weight: 600;
border-radius: 3px;
user-select: none;
&:hover,
&:focus {
color: #fff;
background: darken(@link-color, 5%);
}
}
// Players
.example-audio .player {
max-width: 520px;
}
.example-video .player {
max-width: 1200px;
}
.example-audio .player,
.example-video .player {
margin: 0 auto @padding-base;
&-fullscreen,
&.fullscreen-active {
max-width: none;
}
}
// Footer
footer {
margin-bottom: @padding-base;
p {
margin-bottom: (@padding-base / 2);
}
}

View File

@ -0,0 +1,9 @@
// ==========================================================================
// Animations
// ==========================================================================
// Fade
@keyframes fade-in {
0% { opacity: 0 }
100% { opacity: 1 }
}

View File

@ -1,16 +1,18 @@
// ==========================================================================
// Fonts
// ==========================================================================
@font-face { @font-face {
font-family: "Avenir"; font-family: "Avenir";
src: url("//cdn.plyr.io/fonts/avenir-medium.woff2") format("woff2"), 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.woff") format("woff");
url("//cdn.plyr.io/fonts/avenir-medium.ttf") format("truetype");
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
} }
@font-face { @font-face {
font-family: "Avenir"; font-family: "Avenir";
src: url("//cdn.plyr.io/fonts/avenir-bold.woff2") format("woff2"), 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.woff") format("woff");
url("//cdn.plyr.io/fonts/avenir-bold.ttf") format("truetype");
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
} }

View File

@ -17,7 +17,6 @@
// Default // Default
outline: thin dotted @gray-dark; outline: thin dotted @gray-dark;
// Webkit // Webkit
//outline: 5px auto -webkit-focus-ring-color;
outline-offset: 1px; outline-offset: 1px;
} }
@ -25,7 +24,7 @@
// Leave <body> at 100%/16px // Leave <body> at 100%/16px
// --------------------------------------- // ---------------------------------------
.font-size(@font-size: 16){ .font-size(@font-size: 16){
@rem: round((@font-size / 16), 1); @rem: round((@font-size / 16), 3);
font-size: (@font-size * 1px); font-size: (@font-size * 1px);
font-size: ~"@{rem}rem"; font-size: ~"@{rem}rem";
} }

View File

@ -0,0 +1,30 @@
// ==========================================================================
// Variables
// ==========================================================================
// Colors
@blue: #3498db;
@gray-dark: #343f4a;
@gray: #55646b;
@gray-light: #cbd0d3;
@gray-lighter: #dbe3e8;
@off-white: #f2f5f7;
// Base
@body-background: @off-white;
// Elements
@link-color: @blue;
@padding-base: 20px;
@arrow-size: 8px;
// Breakpoints
@screen-sm: 480px;
@screen-md: 768px;
// Radii
@border-radius-base: 4px;
// Examples
@example-width-audio: 520px;
@example-width-video: 1200px;

View File

@ -185,12 +185,12 @@ gulp.task("watch", function () {
// Plyr core // Plyr core
gulp.watch(paths.plyr.src.js, tasks.js); gulp.watch(paths.plyr.src.js, tasks.js);
gulp.watch(paths.plyr.src.less, tasks.less); gulp.watch(paths.plyr.src.less, tasks.less);
gulp.watch(paths.plyr.src.sprite, "sprite"); gulp.watch(paths.plyr.src.sprite, ["sprite"]);
// Docs // Docs
gulp.watch(paths.docs.src.js, tasks.js); gulp.watch(paths.docs.src.js, tasks.js);
gulp.watch(paths.docs.src.less, tasks.less); gulp.watch(paths.docs.src.less, tasks.less);
gulp.watch(paths.docs.src.templates, "js"); gulp.watch(paths.docs.src.templates, ["js"]);
}); });
// Publish a version to CDN and docs // Publish a version to CDN and docs
@ -272,5 +272,5 @@ gulp.task("open", function () {
// Do everything // Do everything
gulp.task("publish", function () { gulp.task("publish", function () {
run("templates", tasks.js, tasks.less, "sprite", "cdn", "docs", "open"); run("templates", tasks.js, tasks.less, "sprite", "cdn", "docs");
}); });

View File

@ -1,18 +1,18 @@
{ {
"name": "plyr", "name": "plyr",
"version": "1.1.3", "version": "1.2.0",
"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",
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"gulp": "~3.8.6", "gulp": "^3.8.6",
"gulp-autoprefixer": "^0.0.8", "gulp-autoprefixer": "^0.0.8",
"gulp-concat": "~2.3.3", "gulp-concat": "^2.3.3",
"gulp-gzip": "^1.0.0", "gulp-gzip": "^1.0.0",
"gulp-hogan-compile": "^0.4.1", "gulp-hogan-compile": "^0.4.1",
"gulp-less": "~1.3.1", "gulp-less": "^1.3.1",
"gulp-minify-css": "~0.3.6", "gulp-minify-css": "^0.3.6",
"gulp-open": "^0.3.2", "gulp-open": "^0.3.2",
"gulp-rename": "^1.2.0", "gulp-rename": "^1.2.0",
"gulp-replace": "^0.5.3", "gulp-replace": "^0.5.3",
@ -21,8 +21,8 @@
"gulp-size": "^1.2.1", "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",
"gulp-util": "~2.2.20", "gulp-util": "^2.2.20",
"run-sequence": "^0.3.6" "run-sequence": "^0.3.6"
}, },
"scripts": { "scripts": {

112
readme.md
View File

@ -11,12 +11,13 @@ We wanted a lightweight, accessible and customisable media player that just supp
## Features ## Features
- **Accessible** - full support for captions and screen readers. - **Accessible** - full support for captions and screen readers.
- **Lightweight** - just 6.4KB minified and gzipped. - **Lightweight** - just 6.4KB minified and gzipped.
- **Customisable** - make the player look how you want with the markup you want. - **[Customisable](#html)** - make the player look how you want with the markup you want.
- **Semantic** - uses the *right* elements. `<input type="range">` for volume and `<progress>` for progress and well, `<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.
- **Audio & Video** - support for both formats. - **Audio & Video** - support for both formats.
- **API** - toggle playback, volume, seeking, and more. - **[Embed](#embed)** - support for YouTube (Vimeo soon).
- **Fullscreen** - supports native fullscreen with fallback to "full window" modes. - **[API](#api)** - toggle playback, volume, seeking, and more.
- **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes.
- **No dependencies** - written in vanilla JavaScript, no jQuery required. - **No dependencies** - written in vanilla JavaScript, no jQuery required.
Oh and yes, it works with Bootstrap. Oh and yes, it works with Bootstrap.
@ -25,11 +26,11 @@ Oh and yes, it works with Bootstrap.
Check out [the changelog](changelog.md) Check out [the changelog](changelog.md)
## Planned development ## Planned development
- Accept a string selector, a node, or a nodelist for the `container` property of `selectors`. - Playlists
- ~~YouTube~~ and Vimeo support
- Playback speed
- Multiple language captions (with selection) - Multiple language captions (with selection)
- Playlists (audio and video) - Audio captions
- Set source by API
- Tooltip option (for seeking and controls)
... and whatever else has been raised in [issues](https://github.com/Selz/plyr/issues) ... and whatever else has been raised in [issues](https://github.com/Selz/plyr/issues)
If you have any cool ideas or features, please let me know by [creating an issue](https://github.com/Selz/plyr/issues/new) or of course, forking and sending a pull request. If you have any cool ideas or features, please let me know by [creating an issue](https://github.com/Selz/plyr/issues/new) or of course, forking and sending a pull request.
@ -38,7 +39,7 @@ If you have any cool ideas or features, please let me know by [creating an issue
Check `docs/index.html` and `docs/dist/docs.js` for an example setup. Check `docs/index.html` and `docs/dist/docs.js` for an example setup.
**Heads up**, the example `index.html` file needs to be served from a webserver (such as Apache, Nginx, IIS or similar) unless you change the file sources to include http or https. e.g. change `//cdn.plyr.io/1.1.3/plyr.js` to `https://cdn.plyr.io/1.1.3/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.2.0/plyr.js` to `https://cdn.plyr.io/1.2.0/plyr.js`
### Bower ### Bower
If bower is your thang, you can grab Plyr using: If bower is your thang, you can grab Plyr using:
@ -55,14 +56,14 @@ ember addon:install ember-cli-plyr
More info is on [npm](https://www.npmjs.com/package/ember-cli-plyr) and [GitHub](https://github.com/louisrudner/ember-cli-plyr) More info is on [npm](https://www.npmjs.com/package/ember-cli-plyr) and [GitHub](https://github.com/louisrudner/ember-cli-plyr)
### CDN ### CDN
If you want to use our CDN, you can use the following. HTTPS (SSL) is supported. If you want to use our CDN, you can use the following:
```html ```html
<link rel="stylesheet" href="//cdn.plyr.io/1.1.3/plyr.css"> <link rel="stylesheet" href="https://cdn.plyr.io/1.2.0/plyr.css">
<script src="//cdn.plyr.io/1.1.3/plyr.js"></script> <script src="https://cdn.plyr.io/1.2.0/plyr.js"></script>
``` ```
You can also access the `sprite.svg` file at `//cdn.plyr.io/1.1.3/sprite.svg`. You can also access the `sprite.svg` file at `https://cdn.plyr.io/1.2.0/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.
@ -79,13 +80,13 @@ The SVG sprite for the controls icons is loaded in by AJAX to help with performa
(function(d, p){ (function(d, p){
var a = new XMLHttpRequest(), var a = new XMLHttpRequest(),
b = d.body; b = d.body;
a.open("GET",p,!0); a.open("GET", p, true);
a.send(); a.send();
a.onload = function(){ a.onload = function(){
var c = d.createElement("div"); var c = d.createElement("div");
c.style.display = "none"; c.style.display = "none";
c.innerHTML = a.responseText; c.innerHTML = a.responseText;
b.insertBefore(c,b.childNodes[0]) b.insertBefore(c, b.childNodes[0]);
} }
})(document, "dist/sprite.svg"); })(document, "dist/sprite.svg");
</script> </script>
@ -99,18 +100,16 @@ and the AJAX technique here:
The only extra markup that's needed to use plyr is a `<div>` wrapper. Replace the source, poster and captions with urls for your media. The only extra markup that's needed to use plyr is a `<div>` wrapper. Replace the source, poster and captions with urls for your media.
```html ```html
<div class="player"> <div class="player">
<video poster="//cdn.selz.com/plyr/1.0/poster.jpg" controls crossorigin> <video poster="https://cdn.selz.com/plyr/1.0/poster.jpg" controls crossorigin>
<!-- Video files --> <!-- Video files -->
<source src="//cdn.selz.com/plyr/1.0/movie.mp4" type="video/mp4"> <source src="https://cdn.selz.com/plyr/1.0/movie.mp4" type="video/mp4">
<source src="//cdn.selz.com/plyr/1.0/movie.webm" type="video/webm"> <source src="https://cdn.selz.com/plyr/1.0/movie.webm" type="video/webm">
<!-- Text track file --> <!-- Text track file -->
<track kind="captions" label="English captions" src="//cdn.selz.com/plyr/1.0/movie_captions_en.vtt" srclang="en" default> <track kind="captions" label="English captions" src="https://cdn.selz.com/plyr/1.0/movie_captions_en.vtt" srclang="en" default>
<!-- Fallback for browsers that don't support the <video> element --> <!-- Fallback for browsers that don't support the <video> element -->
<div> <a href="https://cdn.selz.com/plyr/1.0/movie.mp4">Download</a>
<a href="//cdn.selz.com/plyr/1.0/movie.mp4">Download</a>
</div>
</video> </video>
</div> </div>
``` ```
@ -120,17 +119,23 @@ And the same for `<audio>`
<div class="player"> <div class="player">
<audio controls> <audio controls>
<!-- Audio files --> <!-- Audio files -->
<source src="//cdn.selz.com/plyr/1.0/logistics-96-sample.mp3" type="audio/mp3"> <source src="https://cdn.selz.com/plyr/1.0/logistics-96-sample.mp3" type="audio/mp3">
<source src="//cdn.selz.com/plyr/1.0/logistics-96-sample.ogg" type="audio/ogg"> <source src="https://cdn.selz.com/plyr/1.0/logistics-96-sample.ogg" type="audio/ogg">
<!-- Fallback for browsers that don't support the <audio> element --> <!-- Fallback for browsers that don't support the <audio> element -->
<div> <a href="https://cdn.selz.com/plyr/1.0/logistics-96-sample.mp3">Download</a>
<a href="//cdn.selz.com/plyr/1.0/logistics-96-sample.mp3">Download</a>
</div>
</audio> </audio>
</div> </div>
``` ```
For YouTube, Plyr uses the standard YouTube API markup (an empty `<div>`):
```html
<div class="player">
<div data-video-id="L1h9xxCU20g" data-type="youtube"></div>
</div>
```
#### Cross Origin (CORS) #### Cross Origin (CORS)
You'll notice the `crossorigin` attribute on the example `<video>` and `<audio>` elements. This is because the media is loaded from another domain. If your media is hosted on another domain, you may need to add this attribute. You'll notice the `crossorigin` attribute on the example `<video>` and `<audio>` elements. This is because the media is loaded from another domain. If your media is hosted on another domain, you may need to add this attribute.
@ -138,20 +143,16 @@ More info on CORS here:
[https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) [https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)
### JavaScript ### JavaScript
Much of the behaviour of the player is configurable when initialising the library. Below is an example of a default instance. Much of the behaviour of the player is configurable when initialising the library. Here's an example of a default setup:
```html ```html
<script src="dist/plyr.js"></script> <script src="dist/plyr.js"></script>
<script> <script>plyr.setup();</script>
plyr.setup({
*options*
});
</script>
``` ```
#### Options #### Options
You can pass the following options to the setup method. You can pass the following options to the setup method using `plyr.setup({...})`.
<table class="table" width="100%"> <table class="table" width="100%">
<thead> <thead>
@ -181,6 +182,12 @@ You can pass the following options to the setup method.
<td><code>["restart", "rewind", "play", "fast-forward", "current-time", "duration", "mute", "volume", "captions", "fullscreen"]</code></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> <td>Toggle which control elements you would like to display when using the default controls html. If you specify a <code>html</code> option, this is redundant. The default value is to display everything.</td>
</tr> </tr>
<tr>
<td><code>iconPrefix</code></td>
<td>String</td>
<td><code>icon</code></td>
<td>Specify the id prefix for the icons used in the default controls (e.g. "icon-play" would be "icon"). This is to prevent clashes if you're using your own SVG defs file but with the default controls. Most people can ignore this option.</td>
</tr>
<tr> <tr>
<td><code>debug</code></td> <td><code>debug</code></td>
<td>Boolean</td> <td>Boolean</td>
@ -310,6 +317,11 @@ Here's a list of the methods supported:
<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>
<td><code>togglePlay()</code></td>
<td>Boolean</td>
<td>Toggles playback for the player based on either the boolean argument or it's current state.</td>
</tr>
<tr> <tr>
<td><code>toggleMute()</code></td> <td><code>toggleMute()</code></td>
<td>&mdash;</td> <td>&mdash;</td>
@ -333,7 +345,7 @@ Here's a list of the methods supported:
<tr> <tr>
<td><code>support(...)</code></td> <td><code>support(...)</code></td>
<td>String</td> <td>String</td>
<td>Determine if a player supports a certain MIME type.</td> <td>Determine if a player supports a certain MIME type. This is not supported for embedded content (YouTube).</td>
</tr> </tr>
<tr> <tr>
<td><code>source(...)</code></td> <td><code>source(...)</code></td>
@ -355,6 +367,16 @@ Here's a list of the methods supported:
<td>String</td> <td>String</td>
<td>Set the poster url. This is supported for the <code>video</code> element only.</td> <td>Set the poster url. This is supported for the <code>video</code> element only.</td>
</tr> </tr>
<tr>
<td><code>destroy()</code></td>
<td>&mdash;</td>
<td>Destroys the plyr UI and any media event listeners, effectively restoring to the previous state before <code>setup()</code> was called.</td>
</tr>
<tr>
<td><code>restore()</code></td>
<td>&mdash;</td>
<td>Reverses the effects of the <code>destroy()</code> method, restoring the UI and listeners.</td>
</tr>
</tbody> </tbody>
</table> </table>
@ -372,6 +394,26 @@ media.addEventListener("playing", function() {
A complete list of events can be found here: A complete list of events can be found here:
[Media Events - W3.org](http://www.w3.org/2010/05/video/mediaevents.html) [Media Events - W3.org](http://www.w3.org/2010/05/video/mediaevents.html)
## Embeds
Currently only YouTube is supported. Vimeo will be coming soon. Some HTML5 media events are triggered on the `media` property of the `plyr` object:
- `play`
- `pause`
- `timeupdate`
- `progress`
Due to the way the YouTube API works, the `timeupdate` and `progress` events are triggered by polling every 200ms so the event may trigger without an actual value change. Buffering progress is `media.buffered` in the `plyr` object. It is a a number between 0 and 1 that specifies the percentage of the video that the player shows as buffered.
```javascript
document.querySelector(".player").plyr.media.addEventListener("play", function() {
console.log("play");
});
```
The `.source()` API method can also be used but the video id must be passed as the argument.
Currently caption control is not supported but I will work on this.
## Fullscreen ## Fullscreen
Fullscreen in Plyr is supported for all browsers that [currently support it](http://caniuse.com/#feat=fullscreen). If you're using the default CSS, you can also use a "full browser" mode which will use the full browser window by adding the `player-fullscreen` class to your container. Fullscreen in Plyr is supported for all browsers that [currently support it](http://caniuse.com/#feat=fullscreen). If you're using the default CSS, you can also use a "full browser" mode which will use the full browser window by adding the `player-fullscreen` class to your container.
@ -423,7 +465,7 @@ There's an API method for checking support. You can call `plyr.supported()` and
If you find anything weird with Plyr, please let us know using the GitHub issues tracker. If you find anything weird with Plyr, please let us know using the GitHub issues tracker.
## Author ## Author
Plyr is developed by [@sam_potts](https://twitter.com/sam_potts) / [sampotts.me](http://sampotts.me) Plyr is developed by [@sam_potts](https://twitter.com/sam_potts) / [sampotts.me](http://sampotts.me) with help from the awesome [contributors](https://github.com/Selz/plyr/graphs/contributors)
## Mentions ## Mentions
- [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/) - [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)

View File

@ -1,6 +1,6 @@
// ========================================================================== // ==========================================================================
// Plyr // Plyr
// plyr.js v1.1.2 // plyr.js v1.2.0
// https://github.com/selz/plyr // https://github.com/selz/plyr
// License: The MIT License (MIT) // License: The MIT License (MIT)
// ========================================================================== // ==========================================================================
@ -9,6 +9,7 @@
(function (api) { (function (api) {
"use strict"; "use strict";
/*global YT*/
// Globals // Globals
var fullscreen, config; var fullscreen, config;
@ -22,6 +23,7 @@
click: true, click: true,
tooltips: false, tooltips: false,
displayDuration: true, displayDuration: true,
iconPrefix: "icon",
selectors: { selectors: {
container: ".player", container: ".player",
controls: ".player-controls", controls: ".player-controls",
@ -48,9 +50,9 @@
duration: ".player-duration" duration: ".player-duration"
}, },
classes: { classes: {
video: "player-video",
videoWrapper: "player-video-wrapper", videoWrapper: "player-video-wrapper",
audio: "player-audio", embedWrapper: "player-video-embed",
type: "player-{0}",
stopped: "stopped", stopped: "stopped",
playing: "playing", playing: "playing",
muted: "muted", muted: "muted",
@ -81,7 +83,7 @@
key: "plyr_volume" key: "plyr_volume"
}, },
controls: ["restart", "rewind", "play", "fast-forward", "current-time", "duration", "mute", "volume", "captions", "fullscreen"], controls: ["restart", "rewind", "play", "fast-forward", "current-time", "duration", "mute", "volume", "captions", "fullscreen"],
onSetup: function() {}, onSetup: function() {}
}; };
// Build the default HTML // Build the default HTML
@ -105,7 +107,7 @@
if(_inArray(config.controls, "restart")) { if(_inArray(config.controls, "restart")) {
html.push( html.push(
"<button type='button' data-player='restart'>", "<button type='button' data-player='restart'>",
"<svg><use xlink:href='#icon-restart'></use></svg>", "<svg><use xlink:href='#" + config.iconPrefix + "-restart'></use></svg>",
"<span class='sr-only'>Restart</span>", "<span class='sr-only'>Restart</span>",
"</button>" "</button>"
); );
@ -115,7 +117,7 @@
if(_inArray(config.controls, "rewind")) { if(_inArray(config.controls, "rewind")) {
html.push( html.push(
"<button type='button' data-player='rewind'>", "<button type='button' data-player='rewind'>",
"<svg><use xlink:href='#icon-rewind'></use></svg>", "<svg><use xlink:href='#" + config.iconPrefix + "-rewind'></use></svg>",
"<span class='sr-only'>Rewind {seektime} secs</span>", "<span class='sr-only'>Rewind {seektime} secs</span>",
"</button>" "</button>"
); );
@ -125,11 +127,11 @@
if(_inArray(config.controls, "play")) { if(_inArray(config.controls, "play")) {
html.push( html.push(
"<button type='button' data-player='play'>", "<button type='button' data-player='play'>",
"<svg><use xlink:href='#icon-play'></use></svg>", "<svg><use xlink:href='#" + config.iconPrefix + "-play'></use></svg>",
"<span class='sr-only'>Play</span>", "<span class='sr-only'>Play</span>",
"</button>", "</button>",
"<button type='button' data-player='pause'>", "<button type='button' data-player='pause'>",
"<svg><use xlink:href='#icon-pause'></use></svg>", "<svg><use xlink:href='#" + config.iconPrefix + "-pause'></use></svg>",
"<span class='sr-only'>Pause</span>", "<span class='sr-only'>Pause</span>",
"</button>" "</button>"
); );
@ -139,7 +141,7 @@
if(_inArray(config.controls, "fast-forward")) { if(_inArray(config.controls, "fast-forward")) {
html.push( html.push(
"<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='#" + config.iconPrefix + "-fast-forward'></use></svg>",
"<span class='sr-only'>Forward {seektime} secs</span>", "<span class='sr-only'>Forward {seektime} secs</span>",
"</button>" "</button>"
); );
@ -176,8 +178,8 @@
html.push( html.push(
"<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='#" + config.iconPrefix + "-muted'></use></svg>",
"<svg><use xlink:href='#icon-volume'></use></svg>", "<svg><use xlink:href='#" + config.iconPrefix + "-volume'></use></svg>",
"<span class='sr-only'>Toggle Mute</span>", "<span class='sr-only'>Toggle Mute</span>",
"</label>" "</label>"
); );
@ -196,8 +198,8 @@
html.push( html.push(
"<input class='sr-only' id='captions{id}' type='checkbox' data-player='captions'>", "<input class='sr-only' id='captions{id}' type='checkbox' data-player='captions'>",
"<label for='captions{id}'>", "<label for='captions{id}'>",
"<svg class='icon-captions-on'><use xlink:href='#icon-captions-on'></use></svg>", "<svg class='icon-captions-on'><use xlink:href='#" + config.iconPrefix + "-captions-on'></use></svg>",
"<svg><use xlink:href='#icon-captions-off'></use></svg>", "<svg><use xlink:href='#" + config.iconPrefix + "-captions-off'></use></svg>",
"<span class='sr-only'>Toggle Captions</span>", "<span class='sr-only'>Toggle Captions</span>",
"</label>" "</label>"
); );
@ -207,8 +209,8 @@
if(_inArray(config.controls, "fullscreen")) { if(_inArray(config.controls, "fullscreen")) {
html.push( html.push(
"<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='#" + config.iconPrefix + "-exit-fullscreen'></use></svg>",
"<svg><use xlink:href='#icon-enter-fullscreen'></use></svg>", "<svg><use xlink:href='#" + config.iconPrefix + "-enter-fullscreen'></use></svg>",
"<span class='sr-only'>Toggle Fullscreen</span>", "<span class='sr-only'>Toggle Fullscreen</span>",
"</button>" "</button>"
); );
@ -330,6 +332,18 @@
return false; return false;
} }
// Inject a script
function _injectScript(source) {
if(document.querySelectorAll("script[src='" + source + "']").length) {
return;
}
var tag = document.createElement("script");
tag.src = source;
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
// Element exists in an array // Element exists in an array
function _inArray(haystack, needle) { function _inArray(haystack, needle) {
return Array.prototype.indexOf && (haystack.indexOf(needle) != -1); return Array.prototype.indexOf && (haystack.indexOf(needle) != -1);
@ -366,12 +380,28 @@
// append it to the parent. // append it to the parent.
if (sibling) { if (sibling) {
parent.insertBefore(child, sibling); parent.insertBefore(child, sibling);
} else { }
else {
parent.appendChild(child); parent.appendChild(child);
} }
} }
} }
// Unwrap an element
// http://plainjs.com/javascript/manipulation/unwrap-a-dom-element-35/
function _unwrap(wrapper) {
// Get the element's parent node
var parent = wrapper.parentNode;
// Move all children out of the element
while (wrapper.firstChild) {
parent.insertBefore(wrapper.firstChild, wrapper);
}
// Remove the empty element
parent.removeChild(wrapper);
}
// Remove an element // Remove an element
function _remove(element) { function _remove(element) {
element.parentNode.removeChild(element); element.parentNode.removeChild(element);
@ -404,7 +434,7 @@
// Toggle event // Toggle event
function _toggleHandler(element, events, callback, toggle) { function _toggleHandler(element, events, callback, toggle) {
events = events.split(" "); var eventList = events.split(" ");
// If a nodelist is passed, call itself on each node // If a nodelist is passed, call itself on each node
if(element instanceof NodeList) { if(element instanceof NodeList) {
@ -417,8 +447,8 @@
} }
// If a single node is passed, bind the event listener // If a single node is passed, bind the event listener
for (var i = 0; i < events.length; i++) { for (var i = 0; i < eventList.length; i++) {
element[toggle ? "addEventListener" : "removeEventListener"](events[i], callback, false); element[toggle ? "addEventListener" : "removeEventListener"](eventList[i], callback, false);
} }
} }
@ -541,6 +571,8 @@
switch (this.prefix) { switch (this.prefix) {
case "": case "":
return document.fullscreenElement == element; return document.fullscreenElement == element;
case "moz":
return document.mozFullScreenElement == element;
default: default:
return document[this.prefix + "FullscreenElement"] == element; return document[this.prefix + "FullscreenElement"] == element;
} }
@ -798,7 +830,7 @@
player.media.removeAttribute("controls"); player.media.removeAttribute("controls");
// Add type class // Add type class
_toggleClass(player.container, config.classes[player.type], true); _toggleClass(player.container, config.classes.type.replace("{0}", player.type), true);
// 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));
@ -820,6 +852,11 @@
// Cache the container // Cache the container
player.videoContainer = wrapper; player.videoContainer = wrapper;
} }
// YouTube
if(player.type == "youtube") {
_setupYouTube(player.media.getAttribute("data-video-id"));
}
} }
// Autoplay // Autoplay
@ -828,6 +865,149 @@
} }
} }
// Setup YouTube
function _setupYouTube(id) {
// Remove old containers
var containers = _getElements("[id^='youtube']");
for (var i = containers.length - 1; i >= 0; i--) {
_remove(containers[i]);
}
// Create the YouTube container
var container = document.createElement("div");
container.setAttribute("id", "youtube-" + Math.floor(Math.random() * (10000)));
player.media.appendChild(container);
// Add embed class for responsive
_toggleClass(player.media, config.classes.videoWrapper, true);
_toggleClass(player.media, config.classes.embedWrapper, true);
if(typeof YT === "object") {
_YTReady(id, container);
}
else {
// Load the API
_injectScript("https://www.youtube.com/iframe_api");
// Setup callback for the API
window.onYouTubeIframeAPIReady = function () { _YTReady(id, container); }
}
}
// Handle API ready
function _YTReady(id, container) {
_log("YouTube API Ready");
// Setup timers object
// We have to poll YouTube for updates
if(!("timer" in player)) {
player.timer = {};
}
// Setup instance
// https://developers.google.com/youtube/iframe_api_reference
player.embed = new YT.Player(container.id, {
videoId: id,
playerVars: {
autoplay: 0,
controls: 0,
vq: "hd720",
rel: 0,
showinfo: 0,
iv_load_policy: 3,
cc_lang_pref: "en",
wmode: "transparent",
modestbranding: 1
},
events: {
onReady: function(event) {
// Get the instance
var instance = event.target;
// Create a faux HTML5 API using the YouTube API
player.media.play = function() { instance.playVideo(); };
player.media.pause = function() { instance.pauseVideo(); };
player.media.stop = function() { instance.stopVideo(); };
player.media.duration = instance.getDuration();
player.media.paused = (instance.getPlayerState() == 2);
player.media.currentTime = instance.getCurrentTime();
player.media.muted = instance.isMuted();
// Trigger timeupdate
_triggerEvent(player.media, "timeupdate");
// Reset timer
window.clearInterval(player.timer.buffering);
// Setup buffering
player.timer.buffering = window.setInterval(function() {
// Get loaded % from YouTube
player.media.buffered = instance.getVideoLoadedFraction();
// Trigger progress
_triggerEvent(player.media, "progress");
// Bail if we're at 100%
if(player.media.buffered === 1) {
window.clearInterval(player.timer.buffering);
}
}, 200);
// Only setup controls once
if(!player.container.querySelectorAll(config.selectors.controls).length) {
_setupInterface();
}
// Display duration if available
if(config.displayDuration) {
_displayDuration();
}
},
onStateChange: function(event) {
// Get the instance
var instance = event.target;
// Reset timer
window.clearInterval(player.timer.playing);
// Handle events
// -1 Unstarted
// 0 Ended
// 1 Playing
// 2 Paused
// 3 Buffering
// 5 Video cued
switch(event.data) {
case 0:
player.media.paused = true;
_triggerEvent(player.media, "ended");
break;
case 1:
player.media.paused = false;
_triggerEvent(player.media, "play");
// Poll to get playback progress
player.timer.playing = window.setInterval(function() {
// Set the current time
player.media.currentTime = instance.getCurrentTime();
// Trigger timeupdate
_triggerEvent(player.media, "timeupdate");
}, 200);
break;
case 2:
player.media.paused = true;
_triggerEvent(player.media, "pause");
break;
}
}
}
});
}
// Setup captions // Setup captions
function _setupCaptions() { function _setupCaptions() {
if(player.type === "video") { if(player.type === "video") {
@ -883,13 +1063,13 @@
// Enable UI // Enable UI
_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) // Disable unsupported browsers than report false positive
if ((player.browser.name === "IE" && player.browser.version === 10) || if ((player.browser.name === "IE" && player.browser.version >= 10) ||
(player.browser.name === "IE" && player.browser.version === 11) ||
(player.browser.name === "Firefox" && player.browser.version >= 31) || (player.browser.name === "Firefox" && player.browser.version >= 31) ||
(player.browser.name === "Chrome" && player.browser.version >= 43) ||
(player.browser.name === "Safari" && player.browser.version >= 7)) { (player.browser.name === "Safari" && player.browser.version >= 7)) {
// Debugging // Debugging
_log("Detected IE 10/11 or Firefox 31+ or Safari 7+."); _log("Detected unsupported browser for HTML5 captions. Using fallback.");
// Set to false so skips to "manual" captioning // Set to false so skips to "manual" captioning
player.usingTextTracks = false; player.usingTextTracks = false;
@ -978,7 +1158,7 @@
// Setup fullscreen // Setup fullscreen
function _setupFullscreen() { function _setupFullscreen() {
if(player.type === "video" && config.fullscreen.enabled) { if(player.type != "audio" && config.fullscreen.enabled) {
// Check for native support // Check for native support
var nativeSupport = fullscreen.supportsFullScreen; var nativeSupport = fullscreen.supportsFullScreen;
@ -1009,6 +1189,22 @@
player.media.pause(); player.media.pause();
} }
// Toggle playback
function _togglePlay(toggle) {
// Play
if(toggle === true) {
_play();
}
// Pause
else if(toggle === false) {
_pause();
}
// True toggle
else {
player.media[player.media.paused ? "play" : "pause"]();
}
}
// Rewind // Rewind
function _rewind(seekTime) { function _rewind(seekTime) {
// Use default if needed // Use default if needed
@ -1058,6 +1254,14 @@
} }
catch(e) {} catch(e) {}
// YouTube
if(player.type == "youtube") {
player.embed.seekTo(player.media.currentTime);
// Trigger timeupdate
_triggerEvent(player.media, "timeupdate");
}
// Logging // Logging
_log("Seeking to " + player.media.currentTime + " seconds"); _log("Seeking to " + player.media.currentTime + " seconds");
@ -1111,6 +1315,41 @@
// Set class hook // Set class hook
_toggleClass(player.container, config.classes.fullscreen.active, player.isFullscreen); _toggleClass(player.container, config.classes.fullscreen.active, player.isFullscreen);
// Toggle controls visibility based on mouse movement and location
var hoverTimer, isMouseOver = false;
// Show the player controls
function _showControls() {
// Set shown class
_toggleClass(player.controls, config.classes.hover, true);
// Clear timer every movement
window.clearTimeout(hoverTimer);
// If the mouse is not over the controls, set a timeout to hide them
if(!isMouseOver) {
hoverTimer = window.setTimeout(function() {
_toggleClass(player.controls, config.classes.hover, false);
}, 2000);
}
}
// Check mouse is over the controls
function _setMouseOver (event) {
isMouseOver = (event.type === "mouseenter");
}
if(config.fullscreen.hideControls) {
// Hide on entering full screen
_toggleClass(player.controls, config.classes.hover, false);
// Keep an eye on the mouse location in relation to controls
_toggleHandler(player.controls, "mouseenter mouseleave", _setMouseOver, player.isFullscreen);
// Show the controls on mouse move
_toggleHandler(player.container, "mousemove", _showControls, player.isFullscreen);
}
} }
// Bail from faux-fullscreen // Bail from faux-fullscreen
@ -1123,12 +1362,7 @@
// Set volume // Set volume
function _setVolume(volume) { function _setVolume(volume) {
// Bail if there's no volume element // Use default if no value specified
if(!player.volume) {
return;
}
// 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[config.storage.key] || config.volume; volume = window.localStorage[config.storage.key] || config.volume;
@ -1137,25 +1371,30 @@
volume = config.volume; volume = config.volume;
} }
} }
// Maximum is 10 // Maximum is 10
if(volume > 10) { if(volume > 10) {
volume = 10; volume = 10;
} }
// Minimum is 0
// If the controls are there if(volume < 0) {
if(player.supported.full) { volume = 0;
player.volume.value = volume;
} }
// Set the player volume // Set the player volume
player.media.volume = parseFloat(volume / 10); player.media.volume = parseFloat(volume / 10);
// Update the UI // YouTube
_checkMute(); if(player.type == "youtube") {
player.embed.setVolume(player.media.volume * 100);
// Store the volume in storage // Trigger timeupdate
if(config.storage.enabled && _storage().supported) { _triggerEvent(player.media, "volumechange");
window.localStorage.setItem(config.storage.key, volume); }
// Toggle muted state
if(player.media.muted && volume > 0) {
_toggleMute();
} }
} }
@ -1166,16 +1405,40 @@
muted = !player.media.muted; muted = !player.media.muted;
} }
// If the controls are there
if(player.supported.full) {
player.buttons.mute.checked = muted;
}
// Set mute on the player // Set mute on the player
player.media.muted = muted; player.media.muted = muted;
// Update UI // YouTube
_checkMute(); if(player.type === "youtube") {
player.embed[player.media.muted ? "mute" : "unMute"]();
// Trigger timeupdate
_triggerEvent(player.media, "volumechange");
}
}
// Update volume UI and storage
function _updateVolume() {
// Get the current volume
var volume = player.media.muted ? 0 : (player.media.volume * 10);
// Update the <input type="range"> if present
if(player.supported.full && player.volume) {
player.volume.value = volume;
}
// Store the volume in storage
if(config.storage.enabled && _storage().supported) {
window.localStorage.setItem(config.storage.key, volume);
}
// Toggle class if muted
_toggleClass(player.container, config.classes.muted, (volume === 0));
// Update checkbox for mute state
if(player.supported.full && player.buttons.mute) {
player.buttons.mute.checked = (volume === 0);
}
} }
// Toggle captions // Toggle captions
@ -1194,11 +1457,6 @@
_toggleClass(player.container, config.classes.captions.active, show); _toggleClass(player.container, config.classes.captions.active, show);
} }
// Check mute state
function _checkMute() {
_toggleClass(player.container, config.classes.muted, (player.media.volume === 0 || player.media.muted));
}
// Check if media is loading // Check if media is loading
function _checkLoading(event) { function _checkLoading(event) {
var loading = (event.type === "waiting"); var loading = (event.type === "waiting");
@ -1247,9 +1505,14 @@
value = (function() { value = (function() {
var buffered = player.media.buffered; var buffered = player.media.buffered;
if(buffered.length) { // HTML5
if(buffered && buffered.length) {
return _getPercentage(buffered.end(0), player.media.duration); return _getPercentage(buffered.end(0), player.media.duration);
} }
// YouTube returns between 0 and 1
else if(typeof buffered == "number") {
return (buffered * 100);
}
return 0; return 0;
})(); })();
@ -1343,6 +1606,22 @@
// Update source // Update source
// Sources are not checked for support so be careful // Sources are not checked for support so be careful
function _parseSource(sources) { function _parseSource(sources) {
// YouTube
if(player.type === "youtube" && typeof sources === "string") {
// Destroy YouTube instance
player.embed.destroy();
// Re-setup YouTube
// We don't use loadVideoBy[x] here since it has issues
_setupYouTube(sources);
// Update times
_timeUpdate();
// Bail
return;
}
// Pause playback (webkit freaks out) // Pause playback (webkit freaks out)
_pause(); _pause();
@ -1464,13 +1743,10 @@
}); });
// Check for buffer progress // Check for buffer progress
_on(player.media, "progress", _updateProgress); _on(player.media, "progress playing", _updateProgress);
// Also check on start of playing
_on(player.media, "playing", _updateProgress);
// Handle native mute // Handle native mute
_on(player.media, "volumechange", _checkMute); _on(player.media, "volumechange", _updateVolume);
// Handle native play/pause // Handle native play/pause
_on(player.media, "play pause", _checkPlaying); _on(player.media, "play pause", _checkPlaying);
@ -1485,27 +1761,68 @@
if(player.type === "video" && config.click) { if(player.type === "video" && config.click) {
_on(player.videoContainer, "click", function() { _on(player.videoContainer, "click", function() {
if(player.media.paused) { if(player.media.paused) {
_play(); _triggerEvent(player.buttons.play, "click");
} }
else if(player.media.ended) { else if(player.media.ended) {
_seek(); _seek();
_play(); _triggerEvent(player.buttons.play, "click");
} }
else { else {
_pause(); _triggerEvent(player.buttons.pause, "click");
} }
}); });
} }
// Bind to mouse hover
if(config.fullscreen.hideControls) {
_on(player.controls, "mouseenter mouseleave", function(event) {
_toggleClass(player.controls, config.classes.hover, (event.type === "mouseenter"));
})
}
} }
// Destroy an instance
// Event listeners are removed when elements are removed
// http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory
function _destroy() {
// Bail if the element is not initialized
if(!player.init) {
return null;
}
// Reset container classname
player.container.setAttribute("class", config.selectors.container.replace(".", ""));
// Remove init flag
player.init = false;
// Remove controls
_remove(_getElement(config.selectors.controls));
// YouTube
if(player.type === "youtube") {
player.embed.destroy();
return;
}
// If video, we need to remove some more
if(player.type === "video") {
// Remove captions
_remove(_getElement(config.selectors.captions));
// Remove video wrapper
_unwrap(player.videoContainer);
}
// Restore native video controls
player.media.setAttribute("controls", "");
// Clone the media element to remove listeners
// http://stackoverflow.com/questions/19469881/javascript-remove-all-event-listeners-of-specific-type
var clone = player.media.cloneNode(true);
player.media.parentNode.replaceChild(clone, player.media);
}
// Setup a player
function _init() { function _init() {
// Bail if the element is initialized
if(player.init) {
return null;
}
// Setup the fullscreen api // Setup the fullscreen api
fullscreen = _fullscreen(); fullscreen = _fullscreen();
@ -1513,10 +1830,19 @@
player.browser = _browserSniff(); player.browser = _browserSniff();
// Get the media element // Get the media element
player.media = player.container.querySelectorAll("audio, video")[0]; player.media = player.container.querySelectorAll("audio, video, div")[0];
// Set media type // Set media type
player.type = player.media.tagName.toLowerCase(); var tagName = player.media.tagName.toLowerCase();
switch(tagName) {
case "div":
player.type = player.media.getAttribute("data-type");
break;
default:
player.type = tagName;
break;
}
// Check for full support // Check for full support
player.supported = api.supported(player.type); player.supported = api.supported(player.type);
@ -1532,16 +1858,16 @@
// Setup media // Setup media
_setupMedia(); _setupMedia();
// If there's full support // Setup interface
if(player.supported.full) { if(player.type == "video" || player.type == "audio") {
// Inject custom controls // Bail if no support
_injectControls(); if(!player.supported.full) {
return;
// Find the elements
if(!_findElements()) {
return false;
} }
// Setup UI
_setupInterface();
// Display duration if available // Display duration if available
if(config.displayDuration) { if(config.displayDuration) {
_displayDuration(); _displayDuration();
@ -1549,12 +1875,27 @@
// Set up aria-label for Play button with the title option // Set up aria-label for Play button with the title option
_setupAria(); _setupAria();
}
// Successful setup
player.init = true;
}
function _setupInterface() {
// Inject custom controls
_injectControls();
// Find the elements
if(!_findElements()) {
return false;
}
// Captions // Captions
_setupCaptions(); _setupCaptions();
// Set volume // Set volume
_setVolume(); _setVolume();
_updateVolume();
// Setup fullscreen // Setup fullscreen
_setupFullscreen(); _setupFullscreen();
@ -1563,11 +1904,11 @@
_listeners(); _listeners();
} }
// Successful setup // Initialize instance
return true; _init();
}
if(!_init()) { // If init failed, return an empty object
if(!player.init) {
return {}; return {};
} }
@ -1582,11 +1923,14 @@
source: _parseSource, source: _parseSource,
poster: _updatePoster, poster: _updatePoster,
setVolume: _setVolume, setVolume: _setVolume,
togglePlay: _togglePlay,
toggleMute: _toggleMute, toggleMute: _toggleMute,
toggleCaptions: _toggleCaptions, toggleCaptions: _toggleCaptions,
toggleFullscreen: _toggleFullscreen, toggleFullscreen: _toggleFullscreen,
isFullscreen: function() { return player.isFullscreen || false; }, isFullscreen: function() { return player.isFullscreen || false; },
support: function(mimeType) { return _supportMime(player, mimeType); } support: function(mimeType) { return _supportMime(player, mimeType); },
destroy: _destroy,
restore: _init
} }
} }
@ -1610,6 +1954,11 @@
full = (basic && !oldIE); full = (basic && !oldIE);
break; break;
case "youtube":
basic = true;
full = !oldIE;
break;
default: default:
basic = (audio && video); basic = (audio && video);
full = (basic && !oldIE); full = (basic && !oldIE);

View File

@ -1,15 +1,18 @@
// ========================================================================== // ==========================================================================
// HTML5 Media Player // Plyr styles
// https://github.com/selz/plyr
// ========================================================================== // ==========================================================================
// Variables // Variables
// ------------------------------- // -------------------------------
// Colors // Colors
@blue: #3498DB; @blue: #3498DB;
@gray-dark: #343f4a; @gray-dark: #343F4A;
@gray: #565d64; @gray: #565D64;
@gray-light: #cbd0d3; @gray-light: #6B7D86;
@off-white: #f9fafb; @gray-lighter: #CBD0D3;
@off-white: #D6DADD;
// Font sizes // Font sizes
@font-size-small: 14px; @font-size-small: 14px;
@ -18,11 +21,10 @@
// Controls // Controls
@control-spacing: 10px; @control-spacing: 10px;
@controls-bg: @gray-dark; @controls-bg: #fff;
@control-bg-hover: @blue; @control-bg-hover: @blue;
@control-color: @gray-light; .contrast-control-color(@controls-bg);
@control-color-inactive: @gray; .contrast-control-color-hover(@control-bg-hover);
@control-color-hover: #fff;
// Tooltips // Tooltips
@tooltip-bg: @controls-bg; @tooltip-bg: @controls-bg;
@ -40,7 +42,7 @@
// Volume // Volume
@volume-track-height: 6px; @volume-track-height: 6px;
@volume-track-bg: @gray; @volume-track-bg: darken(@controls-bg, 10%);
@volume-thumb-height: (@volume-track-height * 2); @volume-thumb-height: (@volume-track-height * 2);
@volume-thumb-width: (@volume-track-height * 2); @volume-thumb-width: (@volume-track-height * 2);
@volume-thumb-bg: @control-color; @volume-thumb-bg: @control-color;
@ -50,18 +52,40 @@
@bp-control-split: 560px; // When controls split into left/right @bp-control-split: 560px; // When controls split into left/right
@bp-captions-large: 768px; // When captions jump to the larger font size @bp-captions-large: 768px; // When captions jump to the larger font size
// Utility classes & mixins // Animation
// ------------------------------- // ---------------------------------------
// Screen reader only
.sr-only { @keyframes progress {
position: absolute !important; to { background-position: @progress-loading-size 0; }
clip: rect(1px, 1px, 1px, 1px);
padding: 0 !important;
border: 0 !important;
height: 1px !important;
width: 1px !important;
overflow: hidden;
} }
// Mixins
// -------------------------------
// Contrast
.contrast-control-color(@color: "") when (lightness(@color) >= 65%) {
@control-color: @gray-light;
}
.contrast-control-color(@color: "") when (lightness(@color) < 65%) {
@control-color: @gray-lighter;
}
.contrast-control-color-hover(@color: "") when (lightness(@color) >= 65%) {
@control-color-hover: @gray;
}
.contrast-control-color-hover(@color: "") when (lightness(@color) < 65%) {
@control-color-hover: #fff;
}
// Font smoothing
.font-smoothing(@mode: on) when (@mode = on) {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
.font-smoothing(@mode: on) when (@mode = off) {
-moz-osx-font-smoothing: auto;
-webkit-font-smoothing: subpixel-antialiased;
}
// Contain floats: nicolasgallagher.com/micro-clearfix-hack/ // Contain floats: nicolasgallagher.com/micro-clearfix-hack/
.clearfix() { .clearfix() {
zoom: 1; zoom: 1;
@ -75,14 +99,7 @@
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() {
height: @volume-thumb-height; height: @volume-thumb-height;
width: @volume-thumb-width; width: @volume-thumb-width;
@ -109,15 +126,16 @@
border: 0; border: 0;
} }
// Font smoothing // Screen reader only
// --------------------------------------- // -------------------------------
.font-smoothing(@mode: on) when (@mode = on) { .sr-only {
-moz-osx-font-smoothing: grayscale; position: absolute !important;
-webkit-font-smoothing: antialiased; clip: rect(1px, 1px, 1px, 1px);
} padding: 0 !important;
.font-smoothing(@mode: on) when (@mode = off) { border: 0 !important;
-moz-osx-font-smoothing: auto; height: 1px !important;
-webkit-font-smoothing: subpixel-antialiased; width: 1px !important;
overflow: hidden;
} }
// Styles // Styles
@ -141,12 +159,28 @@
&-video-wrapper { &-video-wrapper {
position: relative; position: relative;
} }
video { video,
audio {
width: 100%; width: 100%;
height: auto; height: auto;
vertical-align: middle; vertical-align: middle;
} }
// For embeds
&-video-embed {
padding-bottom: 56.25%; /* 16:9 */
height: 0;
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
}
// Captions // Captions
&-captions { &-captions {
display: none; display: none;
@ -184,6 +218,7 @@
background: @controls-bg; background: @controls-bg;
line-height: 1; line-height: 1;
text-align: center; text-align: center;
box-shadow: 0 1px 1px rgba(red(@gray-dark), green(@gray-dark), blue(@gray-dark), .2);
// Layout // Layout
&-right { &-right {
@ -207,7 +242,7 @@
margin: 0 2px; margin: 0 2px;
padding: (@control-spacing / 2) @control-spacing; padding: (@control-spacing / 2) @control-spacing;
transition: background .3s ease; transition: background .3s ease, color .3s ease, opacity .3s ease;
border-radius: 3px; border-radius: 3px;
cursor: pointer; cursor: pointer;
@ -221,12 +256,13 @@
} }
input + label, input + label,
.inverted:checked + label { .inverted:checked + label {
color: @control-color-inactive; opacity: .5;
} }
button, button,
.inverted + label, .inverted + label,
input:checked + label { input:checked + label {
color: @control-color; color: @control-color;
opacity: 1;
} }
button { button {
border: 0; border: 0;
@ -241,6 +277,7 @@
[type="checkbox"] + label:hover { [type="checkbox"] + label:hover {
background: @control-bg-hover; background: @control-bg-hover;
color: @control-color-hover; color: @control-color-hover;
opacity: 1;
} }
button:focus, button:focus,
input:focus + label { input:focus + label {
@ -273,7 +310,6 @@
&::before { &::before {
content: "\2044"; content: "\2044";
margin-right: @control-spacing; margin-right: @control-spacing;
color: darken(@control-color, 30%);
} }
} }
} }
@ -463,7 +499,8 @@
margin: 0 @control-spacing 0 0; margin: 0 @control-spacing 0 0;
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
background: none; background: transparent;
border: none;
// Webkit // Webkit
&::-webkit-slider-runnable-track { &::-webkit-slider-runnable-track {
@ -563,12 +600,9 @@
top: auto; top: auto;
bottom: 90px; bottom: 90px;
@media (min-width: @bp-control-split) and (max-width: (@bp-captions-large - 1)) { @media (min-width: @bp-control-split) {
bottom: 60px; bottom: 60px;
} }
@media (min-width: @bp-captions-large) {
bottom: 80px;
}
} }
} }
.player-controls { .player-controls {
@ -581,11 +615,10 @@
// Hide controls when playing in full screen // Hide controls when playing in full screen
&.fullscreen-hide-controls.playing .player-controls { &.fullscreen-hide-controls.playing .player-controls {
transform: translateY(100%) translateY(@control-spacing / 2); transform: translateY(100%) translateY(@control-spacing / 2);
transition: transform .3s 1s ease; transition: transform .3s .2s ease;
&.hover { &.hover {
transform: translateY(0); transform: translateY(0);
transition-delay: 0;
} }
} }
} }

View File

@ -1,67 +1,98 @@
// ========================================================================== // ==========================================================================
// HTML5 Media Player // Plyr styles
// https://github.com/selz/plyr
// ========================================================================== // ==========================================================================
// Variables // Variables
// ------------------------------- // -------------------------------
// Colors // Colors
$blue: #3498DB; $blue: #3498DB !default;
$gray-dark: #343f4a; $gray-dark: #343F4A !default;
$gray: #565d64; $gray: #565D64 !default;
$gray-light: #cbd0d3; $gray-light: #6B7D86 !default;
$off-white: #f9fafb; $gray-lighter: #CBD0D3 !default;
$off-white: #D6DADD !default;
// Font sizes // Font sizes
$font-size-small: 14px; $font-size-small: 14px !default;
$font-size-base: 16px; $font-size-base: 16px !default;
$font-size-large: ceil(($font-size-base * 1.5)); $font-size-large: ceil(($font-size-base * 1.5)) !default;
// Controls // Controls
$control-spacing: 10px; $control-spacing: 10px !default;
$controls-bg: $gray-dark; $controls-bg: #fff !default;
$control-bg-hover: $blue; $control-bg-hover: @blue !default;
$control-color: $gray-light; .contrast-control-color($controls-bg);
$control-color-inactive: $gray; .contrast-control-color-hover($control-bg-hover);
$control-color-hover: #fff;
// Tooltips // Tooltips
$tooltip-bg: $controls-bg; $tooltip-bg: $controls-bg !default;
$tooltip-color: #fff; $tooltip-color: #fff !default;
$tooltip-padding: $control-spacing; $tooltip-padding: $control-spacing !default;
$tooltip-arrow-size: 5px; $tooltip-arrow-size: 5px !default;
$tooltip-radius: 3px; $tooltip-radius: 3px !default;
// Progress // Progress
$progress-bg: rgba(red($gray), green($gray), blue($gray), .2); $progress-bg: rgba(red($gray), green($gray), blue($gray), .2) !default;
$progress-playing-bg: $blue; $progress-playing-bg: $blue !default;
$progress-buffered-bg: rgba(red($gray), green($gray), blue($gray), .25); $progress-buffered-bg: rgba(red($gray), green($gray), blue($gray), .25) !default;
$progress-loading-size: 40px; $progress-loading-size: 40px !default;
$progress-loading-bg: rgba(0,0,0, .15); $progress-loading-bg: rgba(0,0,0, .15) !default;
// Volume // Volume
$volume-track-height: 6px; $volume-track-height: 6px !default;
$volume-track-bg: $gray; $volume-track-bg: darken($controls-bg, 10%) !default;
$volume-thumb-height: ($volume-track-height * 2); $volume-thumb-height: ($volume-track-height * 2) !default;
$volume-thumb-width: ($volume-track-height * 2); $volume-thumb-width: ($volume-track-height * 2) !default;
$volume-thumb-bg: $control-color; $volume-thumb-bg: $control-color !default;
$volume-thumb-bg-focus: $control-bg-hover; $volume-thumb-bg-focus: $control-bg-hover !default;
// Breakpoints // Breakpoints
$bp-control-split: 560px; // When controls split into left/right $bp-control-split: 560px !default; // When controls split into left/right
$bp-captions-large: 768px; // When captions jump to the larger font size $bp-captions-large: 768px !default; // When captions jump to the larger font size
// Utility classes & mixins // Animation
// ------------------------------- // ---------------------------------------
// Screen reader only
.sr-only { @keyframes progress {
position: absolute !important; to { background-position: $progress-loading-size 0; }
clip: rect(1px, 1px, 1px, 1px);
padding: 0 !important;
border: 0 !important;
height: 1px !important;
width: 1px !important;
overflow: hidden;
} }
// Mixins
// -------------------------------
// Contrast
@mixin contrast-control-color($color: "") {
@if (lightness($color) >= 65%) {
$control-color: $gray-light;
}
@else if(lightness(@color) < 65%) {
$control-color: $gray-lighter;
}
}
@mixin contrast-control-color-hover($color: "") {
@if (lightness($color) >= 65%) {
$control-color-hover: $gray;
}
@else if (lightness($color) < 65%) {
$control-color-hover: #fff;
}
}
// Font smoothing
@mixin font-smoothing($mode: on)
{
@if ($mode == 'on') {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
@else if ($mode == 'off') {
-moz-osx-font-smoothing: auto;
-webkit-font-smoothing: subpixel-antialiased;
}
}
// Contain floats: nicolasgallagher.com/micro-clearfix-hack/ // Contain floats: nicolasgallagher.com/micro-clearfix-hack/
@mixin clearfix() @mixin clearfix()
{ {
@ -77,14 +108,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
outline-offset: 0; outline-offset: 0;
} }
// Animation // Range mixins
// ---------------------------------------
@keyframes progress {
to { background-position: $progress-loading-size 0; }
}
// <input type="range"> styling
// ---------------------------------------
@mixin volume-thumb() @mixin volume-thumb()
{ {
height: $volume-thumb-height; height: $volume-thumb-height;
@ -115,17 +139,16 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
border: 0; border: 0;
} }
// Font smoothing // Screen reader only
// --------------------------------------- // -------------------------------
@mixin font-smoothing($mode: on) .sr-only {
{ position: absolute !important;
@if $mode == 'on' { clip: rect(1px, 1px, 1px, 1px);
-moz-osx-font-smoothing: grayscale; padding: 0 !important;
-webkit-font-smoothing: antialiased; border: 0 !important;
} @else if $mode == 'off' { height: 1px !important;
-moz-osx-font-smoothing: auto; width: 1px !important;
-webkit-font-smoothing: subpixel-antialiased; overflow: hidden;
}
} }
// Styles // Styles
@ -149,7 +172,8 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
&-video-wrapper { &-video-wrapper {
position: relative; position: relative;
} }
video { video,
audio {
width: 100%; width: 100%;
height: auto; height: auto;
vertical-align: middle; vertical-align: middle;
@ -192,6 +216,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
background: $controls-bg; background: $controls-bg;
line-height: 1; line-height: 1;
text-align: center; text-align: center;
box-shadow: 0 1px 1px rgba(red($gray-dark), green($gray-dark), blue($gray-dark), .2);
// Layout // Layout
&-right { &-right {
@ -215,7 +240,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
margin: 0 2px; margin: 0 2px;
padding: ($control-spacing / 2) $control-spacing; padding: ($control-spacing / 2) $control-spacing;
transition: background .3s ease; background .3s ease, color .3s ease, opacity .3s ease;
border-radius: 3px; border-radius: 3px;
cursor: pointer; cursor: pointer;
@ -229,12 +254,13 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
} }
input + label, input + label,
.inverted:checked + label { .inverted:checked + label {
color: $control-color-inactive; opacity: .5;
} }
button, button,
.inverted + label, .inverted + label,
input:checked + label { input:checked + label {
color: $control-color; color: $control-color;
opacity: 1;
} }
button { button {
border: 0; border: 0;
@ -249,6 +275,7 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
[type="checkbox"] + label:hover { [type="checkbox"] + label:hover {
background: $control-bg-hover; background: $control-bg-hover;
color: $control-color-hover; color: $control-color-hover;
opacity: 1;
} }
button:focus, button:focus,
input:focus + label { input:focus + label {
@ -281,7 +308,6 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
&::before { &::before {
content: "\2044"; content: "\2044";
margin-right: $control-spacing; margin-right: $control-spacing;
color: darken($control-color, 30%);
} }
} }
} }
@ -471,7 +497,8 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
margin: 0 $control-spacing 0 0; margin: 0 $control-spacing 0 0;
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
background: none; background: transparent;
border: none;
// Webkit // Webkit
&::-webkit-slider-runnable-track { &::-webkit-slider-runnable-track {
@ -571,12 +598,9 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
top: auto; top: auto;
bottom: 90px; bottom: 90px;
@media (min-width: $bp-control-split) and (max-width: ($bp-captions-large - 1)) { @media (min-width: $bp-control-split) {
bottom: 60px; bottom: 60px;
} }
@media (min-width: $bp-captions-large) {
bottom: 80px;
}
} }
} }
.player-controls { .player-controls {
@ -589,11 +613,10 @@ $bp-captions-large: 768px; // When captions jump to the larger font size
// Hide controls when playing in full screen // Hide controls when playing in full screen
&.fullscreen-hide-controls.playing .player-controls { &.fullscreen-hide-controls.playing .player-controls {
transform: translateY(100%) translateY($control-spacing / 2); transform: translateY(100%) translateY($control-spacing / 2);
transition: transform .3s 1s ease; transition: transform .3s .2s ease;
&.hover { &.hover {
transform: translateY(0); transform: translateY(0);
transition-delay: 0;
} }
} }
} }