YouTube, Captions and Control improvements

- Controls improvements (Fixes #103)
- YouTube bug fixes (Fixes #105)
- Internationalization support (Fixes #101)
- Captions legibility improvements
This commit is contained in:
Sam Potts 2015-08-08 14:37:12 +10:00
parent 12a737a49e
commit 38a206892b
14 changed files with 247 additions and 215 deletions

View File

@ -1,5 +1,11 @@
# Changelog
# v1.3.0
- Internationalization support (i18n) using default controls (required markup changes to controls)
- ARIA enhancements for controls (required markup changes to controls)
- Captions legibility improvements
- YouTube bug fixes
## v1.2.6
- SASS updates and fixes (cheers @ChristianPV)
@ -21,7 +27,7 @@
## v1.2.1
- Tooltip bug fix
## v1.2.0
# v1.2.0
- Added YouTube support
## v1.1.13

View File

@ -1,10 +1,36 @@
# Controls HTML
# Controls
This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs.
The demo Plyr setup uses a Hogan template. This purely to allow for localization at a later date. Check out `controls.html` in `/src/templates` to get an idea of how the default html is structured.
## Internationalization using default controls
## Requirements
You can provide an `i18n` object as one of your options when initialising the plugin which we be used when rendering the controls.
### Example
```javascript
i18n: {
restart: "Restart",
rewind: "Rewind {seektime} secs",
play: "Play",
pause: "Pause",
forward: "Forward {seektime} secs",
played: "played",
buffered: "buffered",
currentTime: "Current time",
duration: "Duration",
volume: "Volume",
toggleMute: "Toggle Mute",
toggleCaptions: "Toggle Captions",
toggleFullscreen: "Toggle Fullscreen"
}
```
Note: `{seektime}` will be replaced with your configured seek time or the default. For example "Forward {seektime} secs" would render as "Forward 10 secs".
## Using custom HTML
The example on [plyr.io](http://plyr.io) setup uses a Hogan template. Check out `controls.html` in `/src/templates` to get an idea of how the default html is structured.
The classes and data attributes used in your template should match the `selectors` option.
@ -15,7 +41,7 @@ You need to add several placeholders to your html template that are replaced whe
You can include only the controls you need when specifying custom html.
## Example
### Example
This is an example `html` option with all controls.
@ -62,20 +88,18 @@ This is an example `html` option with all controls.
"</span>",
"</span>",
"<span class='player-controls-right'>",
"<input class='inverted sr-only' id='mute{id}' type='checkbox' data-player='mute'>",
"<label id='mute{id}' for='mute{id}'>",
"<button type="button" data-player="mute">",
"<svg class='icon-muted'><use xlink:href='#icon-muted'></use></svg>",
"<svg><use xlink:href='#icon-volume'></use></svg>",
"<span class='sr-only'>Toggle Mute</span>",
"</label>",
"</button>",
"<label for='volume{id}' class='sr-only'>Volume</label>",
"<input id='volume{id}' class='player-volume' type='range' min='0' max='10' value='5' data-player='volume'>",
"<input class='sr-only' id='captions{id}' type='checkbox' data-player='captions'>",
"<label for='captions{id}'>",
"<button type="button" data-player="captions">",
"<svg class='icon-captions-on'><use xlink:href='#icon-captions-on'></use></svg>",
"<svg><use xlink:href='#icon-captions-off'></use></svg>",
"<span class='sr-only'>Toggle Captions</span>",
"</label>",
"</button>",
"<button type='button' data-player='fullscreen'>",
"<svg class='icon-exit-fullscreen'><use xlink:href='#icon-exit-fullscreen'></use></svg>",
"<svg><use xlink:href='#icon-enter-fullscreen'></use></svg>",

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

View File

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

View File

@ -8,10 +8,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Styles -->
<link rel="stylesheet" href="https://cdn.plyr.io/1.2.6/plyr.css?3">
<link rel="stylesheet" href="https://cdn.plyr.io/1.3.0/plyr.css?3">
<!-- Docs styles -->
<link rel="stylesheet" href="https://cdn.plyr.io/1.2.6/docs.css?1">
<link rel="stylesheet" href="https://cdn.plyr.io/1.3.0/docs.css?1">
</head>
<body>
<header>
@ -97,13 +97,13 @@
b.insertBefore(c, b.childNodes[0]);
}
}
})(document, "https://cdn.plyr.io/1.2.6/sprite.svg");
})(document, "https://cdn.plyr.io/1.3.0/sprite.svg");
</script>
<!-- Plyr core script -->
<script src="https://cdn.plyr.io/1.2.6/plyr.js?1"></script>
<script src="https://cdn.plyr.io/1.3.0/plyr.js?1"></script>
<!-- Docs script -->
<script src="https://cdn.plyr.io/1.2.6/docs.js?1"></script>
<script src="https://cdn.plyr.io/1.3.0/docs.js?1"></script>
</body>
</html>

View File

@ -40,23 +40,18 @@
</span>
</span>
<span class="player-controls-right">
<input class="inverted sr-only" id="mute{id}" type="checkbox" data-player="mute">
<label id="mute{id}" for="mute{id}">
<button type="button" data-player="mute">
<svg class="icon-muted"><use xlink:href="#icon-muted"></use></svg>
<svg><use xlink:href="#icon-volume"></use></svg>
<span class="sr-only">Toggle Mute</span>
</label>
</button>
<label for="volume{id}" class="sr-only">Volume</label>
<input id="volume{id}" class="player-volume" type="range" min="0" max="10" step="0.5" value="0" data-player="volume">
<input class="sr-only" id="captions{id}" type="checkbox" data-player="captions">
<label for="captions{id}">
<button type="button" data-player="captions">
<svg class="icon-captions-on"><use xlink:href="#icon-captions-on"></use></svg>
<svg><use xlink:href="#icon-captions-off"></use></svg>
<span class="sr-only">Toggle Captions</span>
</label>
</button>
<button type="button" data-player="fullscreen">
<svg class="icon-exit-fullscreen"><use xlink:href="#icon-exit-fullscreen"></use></svg>
<svg><use xlink:href="#icon-enter-fullscreen"></use></svg>

View File

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

View File

@ -39,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.
**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.6/plyr.js` to `https://cdn.plyr.io/1.2.6/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.3.0/plyr.js` to `https://cdn.plyr.io/1.3.0/plyr.js`
### Bower
If bower is your thang, you can grab Plyr using:
@ -59,11 +59,11 @@ More info is on [npm](https://www.npmjs.com/package/ember-cli-plyr) and [GitHub]
If you want to use our CDN, you can use the following:
```html
<link rel="stylesheet" href="https://cdn.plyr.io/1.2.6/plyr.css">
<script src="https://cdn.plyr.io/1.2.6/plyr.js"></script>
<link rel="stylesheet" href="https://cdn.plyr.io/1.3.0/plyr.css">
<script src="https://cdn.plyr.io/1.3.0/plyr.js"></script>
```
You can also access the `sprite.svg` file at `https://cdn.plyr.io/1.2.6/sprite.svg`.
You can also access the `sprite.svg` file at `https://cdn.plyr.io/1.3.0/sprite.svg`.
### CSS
If you want to use the default css, add the `plyr.css` file from /dist into your head, or even better use `plyr.less` or `plyr.sass` file included in `/src` in your build to save a request.
@ -182,6 +182,12 @@ You can pass the following options to the setup method using `plyr.setup({...})`
<td><code>["restart", "rewind", "play", "fast-forward", "current-time", "duration", "mute", "volume", "captions", "fullscreen"]</code></td>
<td>Toggle which control elements you would like to display when using the default controls html. If you specify a <code>html</code> option, this is redundant. The default value is to display everything.</td>
</tr>
<tr>
<td><code>i18n</code></td>
<td>Object</td>
<td><code><a href="controls.md">See controls.md</a></code></td>
<td>Used for internationalisation (i18n) of the tooltips/labels within the buttons.</td>
</tr>
<tr>
<td><code>iconPrefix</code></td>
<td>String</td>

View File

@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
// plyr.js v1.2.6
// plyr.js v1.3.0
// https://github.com/selz/plyr
// License: The MIT License (MIT)
// ==========================================================================
@ -83,7 +83,21 @@
key: "plyr_volume"
},
controls: ["restart", "rewind", "play", "fast-forward", "current-time", "duration", "mute", "volume", "captions", "fullscreen"],
onSetup: function() {}
i18n: {
restart: "Restart",
rewind: "Rewind {seektime} secs",
play: "Play",
pause: "Pause",
forward: "Forward {seektime} secs",
played: "played",
buffered: "buffered",
currentTime: "Current time",
duration: "Duration",
volume: "Volume",
toggleMute: "Toggle Mute",
toggleCaptions: "Toggle Captions",
toggleFullscreen: "Toggle Fullscreen"
}
};
// Build the default HTML
@ -95,10 +109,10 @@
"<label for='seek{id}' class='sr-only'>Seek</label>",
"<input id='seek{id}' class='player-progress-seek' type='range' min='0' max='100' step='0.5' value='0' data-player='seek'>",
"<progress class='player-progress-played' max='100' value='0'>",
"<span>0</span>% played",
"<span>0</span>% " + config.i18n.played,
"</progress>",
"<progress class='player-progress-buffer' max='100' value='0'>",
"<span>0</span>% buffered",
"<span>0</span>% " + config.i18n.buffered,
"</progress>",
"</div>",
"<span class='player-controls-left'>"];
@ -108,7 +122,7 @@
html.push(
"<button type='button' data-player='restart'>",
"<svg><use xlink:href='#" + config.iconPrefix + "-restart'></use></svg>",
"<span class='sr-only'>Restart</span>",
"<span class='sr-only'>" + config.i18n.restart + "</span>",
"</button>"
);
}
@ -118,7 +132,7 @@
html.push(
"<button type='button' data-player='rewind'>",
"<svg><use xlink:href='#" + config.iconPrefix + "-rewind'></use></svg>",
"<span class='sr-only'>Rewind {seektime} secs</span>",
"<span class='sr-only'>" + config.i18n.rewind + "</span>",
"</button>"
);
}
@ -128,11 +142,11 @@
html.push(
"<button type='button' data-player='play'>",
"<svg><use xlink:href='#" + config.iconPrefix + "-play'></use></svg>",
"<span class='sr-only'>Play</span>",
"<span class='sr-only'>" + config.i18n.play + "</span>",
"</button>",
"<button type='button' data-player='pause'>",
"<svg><use xlink:href='#" + config.iconPrefix + "-pause'></use></svg>",
"<span class='sr-only'>Pause</span>",
"<span class='sr-only'>" + config.i18n.pause + "</span>",
"</button>"
);
}
@ -142,7 +156,7 @@
html.push(
"<button type='button' data-player='fast-forward'>",
"<svg><use xlink:href='#" + config.iconPrefix + "-fast-forward'></use></svg>",
"<span class='sr-only'>Forward {seektime} secs</span>",
"<span class='sr-only'>" + config.i18n.forward + "</span>",
"</button>"
);
}
@ -151,7 +165,7 @@
if(_inArray(config.controls, "current-time")) {
html.push(
"<span class='player-time'>",
"<span class='sr-only'>Current time</span>",
"<span class='sr-only'>" + config.i18n.currentTime + "</span>",
"<span class='player-current-time'>00:00</span>",
"</span>"
);
@ -161,7 +175,7 @@
if(_inArray(config.controls, "duration")) {
html.push(
"<span class='player-time'>",
"<span class='sr-only'>Duration</span>",
"<span class='sr-only'>" + config.i18n.duration + "</span>",
"<span class='player-duration'>00:00</span>",
"</span>"
);
@ -176,19 +190,18 @@
// Toggle mute button
if(_inArray(config.controls, "mute")) {
html.push(
"<input class='inverted sr-only' id='mute{id}' type='checkbox' data-player='mute'>",
"<label id='mute{id}' for='mute{id}'>",
"<button type='button' data-player='mute'>",
"<svg class='icon-muted'><use xlink:href='#" + config.iconPrefix + "-muted'></use></svg>",
"<svg><use xlink:href='#" + config.iconPrefix + "-volume'></use></svg>",
"<span class='sr-only'>Toggle Mute</span>",
"</label>"
"<span class='sr-only'>" + config.i18n.toggleMute + "</span>",
"</button>"
);
}
// Volume range control
if(_inArray(config.controls, "volume")) {
html.push(
"<label for='volume{id}' class='sr-only'>Volume</label>",
"<label for='volume{id}' class='sr-only'>" + config.i18n.volume + "</label>",
"<input id='volume{id}' class='player-volume' type='range' min='0' max='10' value='5' data-player='volume'>"
);
}
@ -196,12 +209,11 @@
// Toggle captions button
if(_inArray(config.controls, "captions")) {
html.push(
"<input class='sr-only' id='captions{id}' type='checkbox' data-player='captions'>",
"<label for='captions{id}'>",
"<button type='button' data-player='captions'>",
"<svg class='icon-captions-on'><use xlink:href='#" + config.iconPrefix + "-captions-on'></use></svg>",
"<svg><use xlink:href='#" + config.iconPrefix + "-captions-off'></use></svg>",
"<span class='sr-only'>Toggle Captions</span>",
"</label>"
"<span class='sr-only'>" + config.i18n.toggleCaptions + "</span>",
"</button>"
);
}
@ -211,7 +223,7 @@
"<button type='button' data-player='fullscreen'>",
"<svg class='icon-exit-fullscreen'><use xlink:href='#" + config.iconPrefix + "-exit-fullscreen'></use></svg>",
"<svg><use xlink:href='#" + config.iconPrefix + "-enter-fullscreen'></use></svg>",
"<span class='sr-only'>Toggle Fullscreen</span>",
"<span class='sr-only'>" + config.i18n.toggleFullscreen + "</span>",
"</button>"
);
}
@ -478,18 +490,15 @@
element.dispatchEvent(fauxEvent);
}
// Toggle checkbox
function _toggleCheckbox(event) {
// Only listen for return key
if(event.keyCode && event.keyCode != 13) {
return true;
}
// Toggle aria-pressed state on a toggle button
function _toggleState(target, state) {
// Get state
state = (typeof state === "boolean" ? state : !target.getAttribute("aria-pressed"));
// Toggle the checkbox
event.target.checked = !event.target.checked;
// Set the attribute on target
target.setAttribute("aria-pressed", state);
// Trigger change event
_triggerEvent(event.target, "change");
return state;
}
// Get percentage
@ -637,7 +646,7 @@
player.currentCaption = player.captions[player.subcount][1];
// Render the caption
player.captionsContainer.innerHTML = player.currentCaption;
player.captionsContainer.innerHTML = player.currentCaption.trim();
}
else {
// Clear the caption
@ -656,7 +665,7 @@
if (config.captions.defaultActive) {
_toggleClass(player.container, config.classes.captions.active, true);
player.buttons.captions.checked = true;
_toggleState(player.buttons.captions, true);
}
}
@ -795,15 +804,15 @@
}
}
// Setup aria attributes
function _setupAria() {
// Setup aria attribute for play
function _setupPlayAria() {
// If there's no play button, bail
if(!player.buttons.play) {
return;
}
// Find the current text
var label = player.buttons.play.innerText || "Play";
var label = player.buttons.play.innerText || config.i18n.play;
// If there's a media title set, use that for the label
if (typeof(config.title) !== "undefined" && config.title.length) {
@ -913,10 +922,10 @@
cc_lang_pref: "en",
wmode: "transparent",
modestbranding: 1,
disablekb: 1
disablekb: 1
},
events: {
'onReady': function(event) {
"onReady": function(event) {
// Get the instance
var instance = event.target;
@ -925,7 +934,7 @@
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.paused = true;
player.media.currentTime = instance.getCurrentTime();
player.media.muted = instance.isMuted();
@ -961,7 +970,7 @@
}
}
},
'onStateChange': function(event) {
"onStateChange": function(event) {
// Get the instance
var instance = event.target;
@ -1010,10 +1019,10 @@
function _setupCaptions() {
if(player.type === "video") {
// Inject the container
player.videoContainer.insertAdjacentHTML("afterbegin", "<div class='" + config.selectors.captions.replace(".", "") + "'></div>");
player.videoContainer.insertAdjacentHTML("afterbegin", "<div class='" + config.selectors.captions.replace(".", "") + "' aria-live='assertive'><span></span></div>");
// Cache selector
player.captionsContainer = _getElement(config.selectors.captions);
player.captionsContainer = _getElement(config.selectors.captions).querySelector("span");
// Determine if HTML5 textTracks is supported
player.usingTextTracks = false;
@ -1088,7 +1097,7 @@
// Display a cue, if there is one
if (this.activeCues[0] && this.activeCues[0].hasOwnProperty("text")) {
player.captionsContainer.appendChild(this.activeCues[0].getCueAsHTML());
player.captionsContainer.appendChild(this.activeCues[0].getCueAsHTML().trim());
}
});
}
@ -1170,6 +1179,9 @@
_log("Fullscreen not supported and fallback disabled.");
}
// Toggle state
_toggleState(player.buttons.fullscreen, false);
// Set control hide class hook
if(config.fullscreen.hideControls) {
_toggleClass(player.container, config.classes.fullscreen.hideControls, true);
@ -1224,7 +1236,8 @@
// Seek to time
// The input parameter can be an event or a number
function _seek(input) {
var targetTime = 0;
var targetTime = 0,
paused = player.media.paused;
// Explicit position
if (typeof input === "number") {
@ -1256,6 +1269,10 @@
if(player.type == "youtube") {
player.embed.seekTo(targetTime);
if(paused) {
_pause();
}
// Trigger timeupdate
_triggerEvent(player.media, "timeupdate");
}
@ -1314,6 +1331,9 @@
// Set class hook
_toggleClass(player.container, config.classes.fullscreen.active, player.isFullscreen);
// Set button state
_toggleState(player.buttons.fullscreen, player.isFullscreen);
// Toggle controls visibility based on mouse movement and location
var hoverTimer, isMouseOver = false;
@ -1399,10 +1419,13 @@
// Mute
function _toggleMute(muted) {
// If the method is called without parameter, toggle based on current value
if(typeof muted === "undefined") {
if(typeof muted !== "boolean") {
muted = !player.media.muted;
}
// Set button state
_toggleState(player.buttons.mute, muted);
// Set mute on the player
player.media.muted = muted;
@ -1435,7 +1458,7 @@
// Update checkbox for mute state
if(player.supported.full && player.buttons.mute) {
player.buttons.mute.checked = (volume === 0);
_toggleState(player.buttons.mute, (volume === 0));
}
}
@ -1447,11 +1470,14 @@
}
// If the method is called without parameter, toggle based on current value
if(typeof show === "undefined") {
if(typeof show !== "boolean") {
show = (player.container.className.indexOf(config.classes.captions.active) === -1);
player.buttons.captions.checked = show;
}
// Toggle state
_toggleState(player.buttons.captions, show);
// Add class hook
_toggleClass(player.container, config.classes.captions.active, show);
}
@ -1731,9 +1757,7 @@
});
// Mute
_on(player.buttons.mute, "change", function() {
_toggleMute(this.checked);
});
_on(player.buttons.mute, "click", _toggleMute);
// Fullscreen
_on(player.buttons.fullscreen, "click", _toggleFullscreen);
@ -1753,9 +1777,7 @@
_on(player.media, "loadedmetadata", _displayDuration);
// Captions
_on(player.buttons.captions, "change", function() {
_toggleCaptions(this.checked);
});
_on(player.buttons.captions, "click", _toggleCaptions);
// Handle the media finishing
_on(player.media, "ended", function() {
@ -1780,9 +1802,6 @@
// 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() {
@ -1900,7 +1919,7 @@
}
// Set up aria-label for Play button with the title option
_setupAria();
_setupPlayAria();
}
// Successful setup
@ -2026,7 +2045,9 @@
element.plyr = (Object.keys(instance).length ? instance : false);
// Callback
config.onSetup.apply(element.plyr);
if(typeof config.onSetup === "function") {
config.onSetup.apply(element.plyr);
}
}
// Add to return array even if it's already setup

View File

@ -17,7 +17,11 @@
// Font sizes
@font-size-small: 14px;
@font-size-base: 16px;
@font-size-large: ceil((@font-size-base * 1.5));
// Captions
@font-size-captions-base: ceil(@font-size-base * 1.25);
@font-size-captions-medium: ceil(@font-size-base * 1.5);
@font-size-captions-large: (@font-size-base * 2);
// Controls
@control-spacing: 10px;
@ -189,26 +193,31 @@
bottom: 0;
left: 0;
width: 100%;
padding: 20px;
min-height: 2.5em;
padding: (@control-spacing * 2) (@control-spacing * 2) (@control-spacing * 3);
color: #fff;
font-size: @font-size-base;
font-weight: 600;
text-shadow:
-1px -1px 0 @gray,
1px -1px 0 @gray,
-1px 1px 0 @gray,
1px 1px 0 @gray;
font-size: @font-size-captions-base;
text-align: center;
.font-smoothing();
span {
border-radius: 2px;
padding: 3px 10px;
background: rgba(0,0,0, .9);
}
span:empty {
display: none;
}
@media (min-width: @bp-captions-large) {
font-size: @font-size-large;
font-size: @font-size-captions-medium;
}
}
&.captions-active &-captions {
display: block;
}
&.fullscreen-active &-captions {
font-size: @font-size-captions-large;
}
// Player controls
&-controls {
@ -236,16 +245,19 @@
}
}
input + label,
// Buttons
button {
display: inline-block;
vertical-align: middle;
margin: 0 2px;
padding: (@control-spacing / 2) @control-spacing;
transition: background .3s ease, color .3s ease, opacity .3s ease;
overflow: hidden;
border: 0;
background: transparent;
border-radius: 3px;
cursor: pointer;
color: @control-color;
transition: background .3s ease, color .3s ease, opacity .3s ease;
svg {
width: 18px;
@ -254,41 +266,27 @@
fill: currentColor;
transition: fill .3s ease;
}
}
input + label,
.inverted:checked + label {
opacity: .5;
}
button,
.inverted + label,
input:checked + label {
color: @control-color;
opacity: 1;
}
button {
border: 0;
background: transparent;
overflow: hidden;
// Hover and tab focus
&.tab-focus,
&:hover {
background: @control-bg-hover;
color: @control-color-hover;
}
// Default focus
&:focus {
outline: 0;
}
}
// Specificity for overriding .inverted
button:focus,
button:hover,
[type="checkbox"]:focus + label,
[type="checkbox"] + label:hover {
background: @control-bg-hover;
color: @control-color-hover;
opacity: 1;
}
button:focus,
input:focus + label {
outline: 0;
}
// Hide toggle icons by default
.icon-exit-fullscreen,
.icon-muted,
.icon-captions-on {
display: none;
}
// Player time
.player-time {
display: inline-block;
vertical-align: middle;
@ -309,7 +307,7 @@
// Add a slash in before
&::before {
content: "\2044";
content: '\2044';
margin-right: @control-spacing;
}
}
@ -338,7 +336,7 @@
// Arrow
&::after {
content: "";
content: '';
position: absolute;
z-index: 1;
top: 100%;
@ -352,14 +350,11 @@
border-width: 0 1px 1px 0;
}
}
label:hover .player-tooltip,
input.tab-focus:focus + label .player-tooltip,
button:hover .player-tooltip,
button.tab-focus:focus .player-tooltip {
opacity: 1;
transform: translate(-50%, 0) scale(1);
}
label:hover .player-tooltip,
button:hover .player-tooltip {
z-index: 3;
}
@ -377,7 +372,7 @@
&-buffer[value],
&-played[value],
&-seek[type=range] {
&-seek[type='range'] {
position: absolute;
left: 0;
top: 0;
@ -417,7 +412,7 @@
// Seek control
// <input[type='range']> element
// Specificity is for bootstrap compatibility
&-seek[type=range] {
&-seek[type='range'] {
z-index: 4;
cursor: pointer;
outline: 0;
@ -492,7 +487,7 @@
// Volume control
// <input[type='range']> element
// Specificity is for bootstrap compatibility
&-volume[type="range"] {
&-volume[type='range'] {
display: inline-block;
vertical-align: middle;
-webkit-appearance: none;
@ -558,7 +553,6 @@
// 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;
}
@ -645,15 +639,11 @@
// Some options are hidden by default
[data-player='captions'],
[data-player='captions'] + label,
[data-player='fullscreen'],
[data-player='fullscreen'] + label {
[data-player='fullscreen'] {
display: none;
}
&.captions-enabled [data-player='captions'],
&.captions-enabled [data-player='captions'] + label,
&.fullscreen-enabled [data-player='fullscreen'],
&.fullscreen-enabled [data-player='fullscreen'] + label {
&.fullscreen-enabled [data-player='fullscreen'] {
display: inline-block;
}
}

View File

@ -17,7 +17,11 @@ $off-white: #D6DADD !default;
// Font sizes
$font-size-small: 14px !default;
$font-size-base: 16px !default;
$font-size-large: ceil(($font-size-base * 1.5)) !default;
// Captions
$font-size-captions-base: ceil(@font-size-base * 1.25) !default;
$font-size-captions-medium: ceil(@font-size-base * 1.5) !default;
$font-size-captions-large: (@font-size-base * 2) !default;
// Controls
$control-spacing: 10px !default;
@ -89,7 +93,7 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
{
zoom: 1;
&:before,
&:after { content: ""; display: table; }
&:after { content: ''; display: table; }
&:after { clear: both; }
}
// Tab focus styles
@ -192,26 +196,31 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
bottom: 0;
left: 0;
width: 100%;
padding: 20px;
min-height: 2.5em;
padding: ($control-spacing * 2) ($control-spacing * 2) ($control-spacing * 3);
color: #fff;
font-size: $font-size-base;
font-weight: 600;
text-shadow:
-1px -1px 0 $gray,
1px -1px 0 $gray,
-1px 1px 0 $gray,
1px 1px 0 $gray;
font-size: $font-size-captions-base;
text-align: center;
@include font-smoothing();
@media (min-width: $bp-captions-large) {
font-size: $font-size-large;
span {
border-radius: 2px;
padding: 3px 10px;
background: rgba(0,0,0, .9);
}
span:empty {
display: none;
}
@media (min-width: @bp-captions-large) {
font-size: $font-size-captions-medium;
}
}
&.captions-active &-captions {
display: block;
}
&.fullscreen-active &-captions {
font-size: $font-size-captions-large;
}
// Player controls
&-controls {
@ -239,16 +248,19 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
}
}
input + label,
// Buttons
button {
display: inline-block;
vertical-align: middle;
margin: 0 2px;
padding: ($control-spacing / 2) $control-spacing;
transition: background .3s ease, color .3s ease, opacity .3s ease;
overflow: hidden;
border: 0;
background: transparent;
border-radius: 3px;
cursor: pointer;
color: $control-color;
transition: background .3s ease, color .3s ease, opacity .3s ease;
svg {
width: 18px;
@ -257,41 +269,27 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
fill: currentColor;
transition: fill .3s ease;
}
}
input + label,
.inverted:checked + label {
opacity: .5;
}
button,
.inverted + label,
input:checked + label {
color: $control-color;
opacity: 1;
}
button {
border: 0;
background: transparent;
overflow: hidden;
// Hover and tab focus
&.tab-focus,
&:hover {
background: $control-bg-hover;
color: $control-color-hover;
}
// Default focus
&:focus {
outline: 0;
}
}
// Specificity for overriding .inverted
button:focus,
button:hover,
[type="checkbox"]:focus + label,
[type="checkbox"] + label:hover {
background: $control-bg-hover;
color: $control-color-hover;
opacity: 1;
}
button:focus,
input:focus + label {
outline: 0;
}
// Hide toggle icons by default
.icon-exit-fullscreen,
.icon-muted,
.icon-captions-on {
display: none;
}
// Time display
.player-time {
display: inline-block;
vertical-align: middle;
@ -312,7 +310,7 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
// Add a slash in before
&::before {
content: "\2044";
content: '\2044';
margin-right: $control-spacing;
}
}
@ -339,7 +337,7 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
transition: transform .2s .1s ease, opacity .2s .1s ease;
&::after {
content: "";
content: '';
display: block;
position: absolute;
left: 50%;
@ -353,14 +351,11 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
border-color: $controls-bg transparent transparent;
}
}
label:hover .player-tooltip,
input:focus + label .player-tooltip,
button:hover .player-tooltip,
button:focus .player-tooltip {
opacity: 1;
transform: translate(-50%, 0) scale(1);
}
label:hover .player-tooltip,
button:hover .player-tooltip {
z-index: 3;
}
@ -378,7 +373,7 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
&-buffer[value],
&-played[value],
&-seek[type=range] {
&-seek[type='range'] {
position: absolute;
left: 0;
top: 0;
@ -418,7 +413,7 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
// Seek control
// <input[type='range']> element
// Specificity is for bootstrap compatibility
&-seek[type=range] {
&-seek[type='range'] {
z-index: 4;
cursor: pointer;
outline: 0;
@ -493,7 +488,7 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
// Volume control
// <input[type='range']> element
// Specificity is for bootstrap compatibility
&-volume[type=range] {
&-volume[type='range'] {
display: inline-block;
vertical-align: middle;
-webkit-appearance: none;
@ -559,7 +554,6 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
// 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;
}
@ -646,15 +640,11 @@ $bp-captions-large: 768px !default; // When captions jump to the larger
// Some options are hidden by default
[data-player='captions'],
[data-player='captions'] + label,
[data-player='fullscreen'],
[data-player='fullscreen'] + label {
[data-player='fullscreen'] {
display: none;
}
&.captions-enabled [data-player='captions'],
&.captions-enabled [data-player='captions'] + label,
&.fullscreen-enabled [data-player='fullscreen'],
&.fullscreen-enabled [data-player='fullscreen'] + label {
&.fullscreen-enabled [data-player='fullscreen'] {
display: inline-block;
}
}