Compare commits

..

4 Commits

Author SHA1 Message Date
e0562752ea Merge pull request #795 from frogg/patch-1
Added link that explains Webkit's autoplay blocker
2018-03-10 23:29:22 +11:00
e6db374a72 Added link that explains Webkit's autoplay blocker 2018-02-24 16:19:55 +01:00
ab7f277a1b Merge pull request #769 from redxtech/add-vue-plyr-to-readme
Add vue-plyr to readme
2018-02-06 10:51:42 +11:00
d5a1a7ca1c Add vue-plyr to readme 2018-01-28 23:03:05 -07:00
148 changed files with 13229 additions and 36726 deletions

View File

@ -1,10 +0,0 @@
# See editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -1,41 +0,0 @@
{
"parser": "babel-eslint",
"extends": ["airbnb-base", "prettier"],
"env": {
"browser": true,
"es6": true
},
"globals": { "Plyr": false, "jQuery": false },
"rules": {
"no-const-assign": 1,
"no-shadow": 0,
"no-this-before-super": 1,
"no-undef": 1,
"no-unreachable": 1,
"no-unused-vars": 1,
"constructor-super": 1,
"valid-typeof": 1,
"indent": [2, 4, { "SwitchCase": 1 }],
"quotes": [2, "single", "avoid-escape"],
"semi": [2, "always"],
"eqeqeq": [2, "always"],
"one-var": [2, "never"],
"comma-dangle": [2, "always-multiline"],
"no-restricted-globals": [
"error",
{
"name": "event",
"message": "Use local parameter instead."
},
{
"name": "error",
"message": "Use local parameter instead."
}
],
"array-bracket-newline": [2, { "minItems": 2 }],
"array-element-newline": [2, { "minItems": 2 }]
},
"parserOptions": {
"sourceType": "module"
}
}

View File

@ -1,17 +1,28 @@
<!---
Please use this issue template as it makes replicating and fixing the issue easier!
<!---
Please use this issue template as it makes replicating and fixing the issue easier!
--->
### Expected behaviour
- [ ] Issue does not already exist
- [ ] Issue observed on https://plyr.io
### Expected behaviour
### Actual behaviour
### Environment
- Browser:
- Version:
- Version:
- Operating System:
- Version:
- Version:
### Steps to reproduce
-
Players affected:
- [ ] HTML5 Video
- [ ] HTML5 Audio
- [ ] YouTube
- [ ] Vimeo
### Steps to reproduce
-
### Relevant links

14
.gitignore vendored
View File

@ -1,11 +1,11 @@
node_modules
*.sublime-project
*.sublime-workspace
.DS_Store
aws.json
docs/index.dev.html
*.mp4
!dist/blank.mp4
index-*.html
npm-debug.log
*.webm
/package-lock.json
.idea/
index-dev.html
notes.txt
*.vtt
docs/index.dev.php

11
.jsbeautifyrc Normal file
View File

@ -0,0 +1,11 @@
{
"html": {
"allowed_file_extensions": []
},
"css": {
"allowed_file_extensions": []
},
"js": {
"allowed_file_extensions": []
}
}

55
.jshintrc Normal file
View File

@ -0,0 +1,55 @@
{
// Settings
"passfail" : false, // Stop on first error.
"maxerr" : 100, // Maximum error before stopping.
// Predefined globals whom JSHint will ignore.
"browser" : true, // Standard browser globals e.g. `window`, `document`.
"node" : false,
"rhino" : false,
"couch" : false,
"wsh" : false, // Windows Scripting Host.
"jquery" : false,
// Development.
"debug" : true, // Allow debugger statements e.g. browser breakpoints.
"devel" : true, // Allow developments statements e.g. `console.log();`.
// ECMAScript 5.
"strict" : false, // Require `use strict` pragma in every file.
"globalstrict" : false, // Allow global "use strict" (also enables 'strict').
// The Good Parts.
"asi" : true, // Tolerate Automatic Semicolon Insertion (no semicolons).
"laxbreak" : true, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons.
"bitwise" : false, // Prohibit bitwise operators (&, |, ^, etc.).
"boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.
"curly" : true, // Require {} for every new block or scope.
"eqeqeq" : true, // Require triple equals i.e. `===`.
"eqnull" : false, // Tolerate use of `== null`.
"evil" : false, // Tolerate use of `eval`.
"expr" : false, // Tolerate `ExpressionStatement` as Programs.
"forin" : false, // Tolerate `for in` loops without `hasOwnPrototype`.
"immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
"latedef" : false, // Prohipit variable use before definition.
"loopfunc" : true, // Allow functions to be defined within loops.
"noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`.
"regexp" : true, // Prohibit `.` and `[^...]` in regular expressions.
"regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`.
"scripturl" : true, // Tolerate script-targeted URLs.
"shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`.
"supernew" : false, // Tolerate `new function () { ... };` and `new Object;`.
"undef" : true, // Require all non-global variables be declared before they are used.
// Personal styling preferences.
"newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`.
"noempty" : true, // Prohibit use of empty blocks.
"nonew" : true, // Prohibit use of constructors for side-effects.
"nomen" : true, // Prohibit use of initial or trailing underbars in names.
"onevar" : false, // Allow only one `var` statement per function.
"plusplus" : false, // Prohibit use of `++` & `--`.
"sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
"trailing" : true, // Prohibit trailing whitespaces.
"white" : true, // Check against strict whitespace and indentation rules.
"indent" : 4 // Specify indentation spacing
}

View File

@ -1,4 +0,0 @@
demo
.github
.vscode
*.code-workspace

View File

@ -1,7 +0,0 @@
{
"useTabs": false,
"tabWidth": 4,
"printWidth": 160,
"singleQuote": true,
"trailingComma": "all"
}

View File

@ -1,24 +0,0 @@
{
"plugins": ["stylelint-selector-bem-pattern", "stylelint-scss"],
"extends": ["stylelint-config-sass-guidelines", "stylelint-config-prettier"],
"rules": {
"selector-class-pattern": null,
"selector-no-qualifying-type": [
true,
{
"ignore": ["attribute", "class"]
}
],
"indentation": 4,
"string-quotes": "single",
"max-nesting-depth": 2,
"plugin/selector-bem-pattern": {
"preset": "bem",
"componentName": "(([a-z0-9]+(?!-$)-?)+)",
"componentSelectors": {
"initial": "\\.{componentName}(((__|--)(([a-z0-9\\[\\]'=]+(?!-$)-?)+))+)?$"
},
"ignoreSelectors": [".*\\.has-.*", ".*\\.is-.*"]
}
}
}

View File

@ -1,12 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
"dbaeumer.vscode-eslint",
"wix.vscode-import-cost",
"esbenp.prettier-vscode",
"shinnn.stylelint",
"wayou.vscode-todo-highlight"
]
}

View File

@ -1 +1,2 @@
{}
{
}

View File

@ -6,7 +6,7 @@
"Audio",
"Video",
"HTML5 Audio",
"HTML5 Video"
"HTml5 Video"
],
"authors": [
"Sam Potts <sam@potts.es>"
@ -30,4 +30,4 @@
"url": "git://github.com/sampotts/plyr.git"
},
"license": "MIT"
}
}

View File

@ -1,20 +1,24 @@
{
"plyr": {
"sass": {
"plyr.css": "src/sass/plyr.scss"
"less": {
"plyr.css": ["src/less/plyr.less"]
},
"scss": {
"plyr.css": ["src/scss/plyr.scss"]
},
"js": {
"plyr.js": "src/js/plyr.js",
"plyr.polyfilled.js": "src/js/plyr.polyfilled.js"
"plyr.js": ["src/js/plyr.js"]
}
},
"demo": {
"sass": {
"demo.css": "demo/src/sass/bundles/demo.scss",
"error.css": "demo/src/sass/bundles/error.csss"
"less": {
"demo.css": ["demo/src/less/demo.less"]
},
"js": {
"demo.js": "demo/src/js/demo.js"
"demo.js": [
"demo/src/js/lib/classlist.js",
"demo/src/js/main.js"
]
}
}
}

File diff suppressed because it is too large Load Diff

2
demo/dist/demo.css vendored

File diff suppressed because one or more lines are too long

243
demo/dist/demo.js vendored
View File

@ -1,242 +1 @@
(function () {
'use strict';
// ==========================================================================
// Plyr.io demo
// This code is purely for the https://plyr.io website
// Please see readme.md in the root or github.com/sampotts/plyr
// ==========================================================================
document.addEventListener('DOMContentLoaded', function () {
if (window.shr) {
window.shr.setup({
count: {
classname: 'button__count'
}
});
}
// Setup tab focus
var tabClassName = 'tab-focus';
// Remove class on blur
document.addEventListener('focusout', function (event) {
event.target.classList.remove(tabClassName);
});
// Add classname to tabbed elements
document.addEventListener('keydown', function (event) {
if (event.keyCode !== 9) {
return;
}
// Delay the adding of classname until the focus has changed
// This event fires before the focusin event
window.setTimeout(function () {
document.activeElement.classList.add(tabClassName);
}, 0);
});
// Setup the player
var player = new Plyr('#player', {
debug: true,
title: 'View From A Blue Moon',
// iconUrl: '../dist/plyr.svg',
keyboard: {
global: true
},
tooltips: {
controls: true
},
captions: {
active: true
},
keys: {
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c'
},
ads: {
enabled: true
}
});
// Expose for testing
window.player = player;
// Setup type toggle
var buttons = document.querySelectorAll('[data-source]');
var types = {
video: 'video',
audio: 'audio',
youtube: 'youtube',
vimeo: 'vimeo'
};
var currentType = window.location.hash.replace('#', '');
var historySupport = window.history && window.history.pushState;
// Toggle class on an element
function toggleClass(element, className, state) {
if (element) {
element.classList[state ? 'add' : 'remove'](className);
}
}
// Set a new source
function newSource(type, init) {
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
if (!(type in types) || !init && type === currentType || !currentType.length && type === types.video) {
return;
}
switch (type) {
case types.video:
player.source = {
type: 'video',
title: 'View From A Blue Moon',
sources: [{
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4',
type: 'video/mp4'
}],
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
tracks: [{
kind: 'captions',
label: 'English',
srclang: 'en',
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
default: true
}, {
kind: 'captions',
label: 'French',
srclang: 'fr',
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt'
}]
};
break;
case types.audio:
player.source = {
type: 'audio',
title: 'Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;',
sources: [{
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
type: 'audio/mp3'
}, {
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
type: 'audio/ogg'
}]
};
break;
case types.youtube:
player.source = {
type: 'video',
title: 'View From A Blue Moon',
sources: [{
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
provider: 'youtube'
}]
};
break;
case types.vimeo:
player.source = {
type: 'video',
sources: [{
src: 'https://vimeo.com/76979871',
provider: 'vimeo'
}]
};
break;
default:
break;
}
// Set the current type for next time
currentType = type;
// Remove active classes
Array.from(buttons).forEach(function (button) {
return toggleClass(button.parentElement, 'active', false);
});
// Set active on parent
toggleClass(document.querySelector('[data-source="' + type + '"]'), 'active', true);
// Show cite
Array.from(document.querySelectorAll('.plyr__cite')).forEach(function (cite) {
cite.setAttribute('hidden', '');
});
document.querySelector('.plyr__cite--' + type).removeAttribute('hidden');
}
// Bind to each button
Array.from(buttons).forEach(function (button) {
button.addEventListener('click', function () {
var type = button.getAttribute('data-source');
newSource(type);
if (historySupport) {
window.history.pushState({ type: type }, '', '#' + type);
}
});
});
// List for backwards/forwards
window.addEventListener('popstate', function (event) {
if (event.state && 'type' in event.state) {
newSource(event.state.type);
}
});
// On load
if (historySupport) {
var video = !currentType.length;
// If there's no current type set, assume video
if (video) {
currentType = types.video;
}
// Replace current history state
if (currentType in types) {
window.history.replaceState({
type: currentType
}, '', video ? '' : '#' + currentType);
}
// If it's not video, load the source
if (currentType !== types.video) {
newSource(currentType, true);
}
}
});
// Google analytics
// For demo site (https://plyr.io) only
/* eslint-disable */
if (window.location.host === 'plyr.io') {
(function (i, s, o, g, r, a, m) {
i.GoogleAnalyticsObject = r;
i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments);
};
i[r].l = 1 * new Date();
a = s.createElement(o);
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
window.ga('create', 'UA-40881672-11', 'auto');
window.ga('send', 'pageview');
}
/* eslint-enable */
}());
//# sourceMappingURL=demo.js.map
"document"in self&&("classList"in document.createElement("_")?function(){"use strict";var t=document.createElement("_");if(t.classList.add("c1","c2"),!t.classList.contains("c2")){var e=function(t){var e=DOMTokenList.prototype[t];DOMTokenList.prototype[t]=function(t){var i,o=arguments.length;for(i=0;i<o;i++)t=arguments[i],e.call(this,t)}};e("add"),e("remove")}if(t.classList.toggle("c3",!1),t.classList.contains("c3")){var i=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(t,e){return 1 in arguments&&!this.contains(t)==!e?e:i.call(this,t)}}t=null}():function(t){"use strict";if("Element"in t){var e=t.Element.prototype,i=Object,o=String.prototype.trim||function(){return this.replace(/^\s+|\s+$/g,"")},s=Array.prototype.indexOf||function(t){for(var e=0,i=this.length;e<i;e++)if(e in this&&this[e]===t)return e;return-1},n=function(t,e){this.name=t,this.code=DOMException[t],this.message=e},r=function(t,e){if(""===e)throw new n("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(e))throw new n("INVALID_CHARACTER_ERR","String contains an invalid character");return s.call(t,e)},a=function(t){for(var e=o.call(t.getAttribute("class")||""),i=e?e.split(/\s+/):[],s=0,n=i.length;s<n;s++)this.push(i[s]);this._updateClassName=function(){t.setAttribute("class",this.toString())}},c=a.prototype=[],l=function(){return new a(this)};if(n.prototype=Error.prototype,c.item=function(t){return this[t]||null},c.contains=function(t){return t+="",-1!==r(this,t)},c.add=function(){var t,e=arguments,i=0,o=e.length,s=!1;do{t=e[i]+"",-1===r(this,t)&&(this.push(t),s=!0)}while(++i<o);s&&this._updateClassName()},c.remove=function(){var t,e,i=arguments,o=0,s=i.length,n=!1;do{for(t=i[o]+"",e=r(this,t);-1!==e;)this.splice(e,1),n=!0,e=r(this,t)}while(++o<s);n&&this._updateClassName()},c.toggle=function(t,e){t+="";var i=this.contains(t),o=i?!0!==e&&"remove":!1!==e&&"add";return o&&this[o](t),!0===e||!1===e?e:!i},c.toString=function(){return this.join(" ")},i.defineProperty){var u={get:l,enumerable:!0,configurable:!0};try{i.defineProperty(e,"classList",u)}catch(t){-2146823252===t.number&&(u.enumerable=!1,i.defineProperty(e,"classList",u))}}else i.prototype.__defineGetter__&&e.__defineGetter__("classList",l)}}(self)),function(){function t(t,e,i){if(t)if(t.classList)t.classList[i?"add":"remove"](e);else{var o=(" "+t.className+" ").replace(/\s+/g," ").replace(" "+e+" ","");t.className=o+(i?" "+e:"")}}function e(e,i){if(e in n&&(i||e!==r)&&(r.length||e!==n.video)){switch(e){case n.video:o.source({type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"},{src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.webm",type:"video/webm"}],poster:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0}]});break;case n.audio:o.source({type:"audio",title:"Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]});break;case n.youtube:o.source({type:"video",title:"View From A Blue Moon",sources:[{src:"bTqVqk7FSmY",type:"youtube"}]});break;case n.vimeo:o.source({type:"video",title:"View From A Blue Moon",sources:[{src:"147865858",type:"vimeo"}]})}r=e;for(var a=s.length-1;a>=0;a--)t(s[a].parentElement,"active",!1);t(document.querySelector('[data-source="'+e+'"]').parentElement,"active",!0)}}var i=plyr.setup({debug:!0,title:"Video demo",iconUrl:"../dist/plyr.svg",tooltips:{controls:!0},captions:{defaultActive:!0}});plyr.loadSprite("dist/demo.svg");for(var o=i[0],s=document.querySelectorAll("[data-source]"),n={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),a=window.history&&window.history.pushState,c=s.length-1;c>=0;c--)s[c].addEventListener("click",function(){var t=this.getAttribute("data-source");e(t),a&&history.pushState({type:t},"","#"+t)});if(window.addEventListener("popstate",function(t){t.state&&"type"in t.state&&e(t.state.type)}),a){var l=!r.length;l&&(r=n.video),r in n&&history.replaceState({type:r},"",l?"":"#"+r),r!==n.video&&e(r,!0)}}(),document.domain.indexOf("plyr.io")>-1&&(!function(t,e,i,o,s,n,r){t.GoogleAnalyticsObject=s,t.ga=t.ga||function(){(t.ga.q=t.ga.q||[]).push(arguments)},t.ga.l=1*new Date,n=e.createElement(i),r=e.getElementsByTagName(i)[0],n.async=1,n.src="//www.google-analytics.com/analytics.js",r.parentNode.insertBefore(n,r)}(window,document,"script",0,"ga"),ga("create","UA-40881672-11","auto"),ga("send","pageview"));

File diff suppressed because one or more lines are too long

View File

@ -1,2 +0,0 @@
!function(){"use strict";var e,t,o,i,r,a;document.addEventListener("DOMContentLoaded",function(){window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var e=new Plyr("#player",{debug:!0,title:"View From A Blue Moon",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"},ads:{enabled:!0}});window.player=e;var t=document.querySelectorAll("[data-source]"),o={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},i=window.location.hash.replace("#",""),r=window.history&&window.history.pushState;function a(e,t,o){e&&e.classList[o?"add":"remove"](t)}function n(r,n){if(r in o&&(n||r!==i)&&(i.length||r!==o.video)){switch(r){case o.video:e.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"}],poster:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt",default:!0},{kind:"captions",label:"French",srclang:"fr",src:"https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt"}]};break;case o.audio:e.source={type:"audio",title:"Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;",sources:[{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]};break;case o.youtube:e.source={type:"video",title:"View From A Blue Moon",sources:[{src:"https://youtube.com/watch?v=bTqVqk7FSmY",provider:"youtube"}]};break;case o.vimeo:e.source={type:"video",sources:[{src:"https://vimeo.com/76979871",provider:"vimeo"}]}}i=r,Array.from(t).forEach(function(e){return a(e.parentElement,"active",!1)}),a(document.querySelector('[data-source="'+r+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+r).removeAttribute("hidden")}}if(Array.from(t).forEach(function(e){e.addEventListener("click",function(){var t=e.getAttribute("data-source");n(t),r&&window.history.pushState({type:t},"","#"+t)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&n(e.state.type)}),r){var s=!i.length;s&&(i=o.video),i in o&&window.history.replaceState({type:i},"",s?"":"#"+i),i!==o.video&&n(i,!0)}}),"plyr.io"===window.location.host&&(e=window,t=document,o="script",i="ga",e.GoogleAnalyticsObject=i,e.ga=e.ga||function(){(e.ga.q=e.ga.q||[]).push(arguments)},e.ga.l=1*new Date,r=t.createElement(o),a=t.getElementsByTagName(o)[0],r.async=1,r.src="//www.google-analytics.com/analytics.js",a.parentNode.insertBefore(r,a),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"))}();
//# sourceMappingURL=demo.min.js.map

File diff suppressed because one or more lines are too long

1
demo/dist/demo.svg vendored Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg"><symbol id="icon-github" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M8 .2c-4.4 0-8 3.6-8 8 0 3.5 2.3 6.5 5.5 7.6.4.1.5-.2.5-.4V14c-2.2.5-2.7-1-2.7-1-.4-.9-.9-1.2-.9-1.2-.7-.5.1-.5.1-.5.8.1 1.2.8 1.2.8.7 1.3 1.9.9 2.3.7.1-.5.3-.9.5-1.1-1.8-.2-3.6-.9-3.6-4 0-.9.3-1.6.8-2.1-.1-.2-.4-1 .1-2.1 0 0 .7-.2 2.2.8.6-.2 1.3-.3 2-.3s1.4.1 2 .3c1.5-1 2.2-.8 2.2-.8.4 1.1.2 1.9.1 2.1.5.6.8 1.3.8 2.1 0 3.1-1.9 3.7-3.7 3.9.3.4.6.9.6 1.6v2.2c0 .2.1.5.6.4 3.2-1.1 5.5-4.1 5.5-7.6-.1-4.4-3.7-8-8.1-8z"/></symbol><symbol id="icon-twitter" viewBox="0 0 16 16"><title>Twitter</title><path d="M16 3c-.6.3-1.2.4-1.9.5.7-.4 1.2-1 1.4-1.8-.6.4-1.3.6-2.1.8-.6-.6-1.5-1-2.4-1-1.7 0-3.2 1.5-3.2 3.3 0 .3 0 .5.1.7-2.7-.1-5.2-1.4-6.8-3.4-.3.5-.4 1-.4 1.7 0 1.1.6 2.1 1.5 2.7-.5 0-1-.2-1.5-.4C.7 7.7 1.8 9 3.3 9.3c-.3.1-.6.1-.9.1-.2 0-.4 0-.6-.1.4 1.3 1.6 2.3 3.1 2.3-1.1.9-2.5 1.4-4.1 1.4H0c1.5.9 3.2 1.5 5 1.5 6 0 9.3-5 9.3-9.3v-.4C15 4.3 15.6 3.7 16 3z"/></symbol><symbol id="icon-vimeo" viewBox="0 0 16 16"><path d="M16 4.3c-.1 1.6-1.2 3.7-3.3 6.4-2.2 2.8-4 4.2-5.5 4.2-.9 0-1.7-.9-2.4-2.6C4 9.9 3.4 5 2 5c-.1 0-.5.3-1.2.8l-.8-1c.8-.7 3.5-3.4 4.7-3.5 1.2-.1 2 .7 2.3 2.5.3 2 .8 6.1 1.8 6.1.9 0 2.5-3.4 2.6-4 .1-.9-.3-1.9-2.3-1.1.8-2.6 2.3-3.8 4.5-3.8 1.7.1 2.5 1.2 2.4 3.3z"/></symbol><symbol id="icon-youtube" viewBox="0 0 16 16"><path d="M15.8 4.8c-.2-1.3-.8-2.2-2.2-2.4C11.4 2 8 2 8 2s-3.4 0-5.6.4C1 2.6.3 3.5.2 4.8 0 6.1 0 8 0 8s0 1.9.2 3.2c.2 1.3.8 2.2 2.2 2.4C4.6 14 8 14 8 14s3.4 0 5.6-.4c1.4-.3 2-1.1 2.2-2.4C16 9.9 16 8 16 8s0-1.9-.2-3.2zM6 11V5l5 3-5 3z"/></symbol></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -7,18 +7,18 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Docs styles -->
<link rel="stylesheet" href="dist/error.css">
<link rel="stylesheet" href="dist/demo.css">
<!-- Preload -->
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2">
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/avenir-medium.woff2">
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/avenir-bold.woff2">
</head>
<body>
<main>
<h1>Doh.</h1>
<p>Looks like something went wrong.</p>
<a href="javascript:history.back()" class="button">Go back</a>
<a href="http://plyr.io" class="btn btn--primary">Back to plyr.io</a>
</main>
</body>

View File

@ -3,95 +3,66 @@
<head>
<meta charset="utf-8" />
<title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title>
<meta name="description" property="og:description" content="A simple HTML5 media player with custom controls and WebVTT captions.">
<title>Plyr - A simple HTML5 media player</title>
<meta name="description" content="A simple HTML5 media player with custom controls and WebVTT captions.">
<meta name="author" content="Sam Potts">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Icons -->
<link rel="icon" href="https://cdn.plyr.io/static/icons/favicon.ico">
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16">
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png">
<!-- Opengraph -->
<meta property="og:title" content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player">
<meta property="og:site_name" content="Plyr">
<meta property="og:url" content="https://plyr.io">
<meta property="og:image" content="https://cdn.plyr.io/static/icons/1200x630.png">
<!-- Twitter -->
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@sam_potts">
<meta name="twitter:creator" content="@sam_potts">
<meta name="twitter:card" content="summary_large_image">
<!-- Styles -->
<link rel="stylesheet" href="../dist/plyr.css">
<!-- Docs styles -->
<link rel="stylesheet" href="dist/demo.css">
<!-- Preload -->
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2">
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/avenir-medium.woff2">
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/avenir-bold.woff2">
</head>
<body>
<div class="grid">
<header>
<h1>Plyr</h1>
<p>A simple, accessible and customisable media player for
<button type="button" class="faux-link" data-source="video">
<svg class="icon">
<title>HTML5</title>
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
</svg>Video</button>,
<button type="button" class="faux-link" data-source="audio">
<svg class="icon">
<title>HTML5</title>
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
</svg>Audio</button>,
<button type="button" class="faux-link" data-source="youtube">
<svg class="icon" role="presentation">
<title>YouTube</title>
<path d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
M6,11V5l5,3L6,11z"></path>
</svg>YouTube</button> and
<button type="button" class="faux-link" data-source="vimeo">
<svg class="icon" role="presentation">
<title>Vimeo</title>
<path d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"></path>
</svg>Vimeo</button>
</p>
<p>Premium video monitization from
<a href="https://vi.ai/publisher-video-monetization/" target="_blank" class="no-border">
<img src="https://cdn.plyr.io/static/vi-logo-24x24.svg" alt="ai.vi">
<span class="sr-only">ai.vi</span>
</a>
</p>
<div class="call-to-action">
<span class="button--with-count">
<a href="https://github.com/sampotts/plyr" target="_blank" class="button" data-shr-network="github">
<svg class="icon" role="presentation">
<title>GitHub</title>
<path d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
C5.9,15.9,6,15.6,6,15.4c0-0.2,0-0.7,0-1.4C3.8,14.5,3.3,13,3.3,13c-0.4-0.9-0.9-1.2-0.9-1.2c-0.7-0.5,0.1-0.5,0.1-0.5
c0.8,0.1,1.2,0.8,1.2,0.8C4.4,13.4,5.6,13,6,12.8c0.1-0.5,0.3-0.9,0.5-1.1c-1.8-0.2-3.6-0.9-3.6-4c0-0.9,0.3-1.6,0.8-2.1
c-0.1-0.2-0.4-1,0.1-2.1c0,0,0.7-0.2,2.2,0.8c0.6-0.2,1.3-0.3,2-0.3c0.7,0,1.4,0.1,2,0.3c1.5-1,2.2-0.8,2.2-0.8
c0.4,1.1,0.2,1.9,0.1,2.1c0.5,0.6,0.8,1.3,0.8,2.1c0,3.1-1.9,3.7-3.7,3.9C9.7,12,10,12.5,10,13.2c0,1.1,0,1.9,0,2.2
c0,0.2,0.1,0.5,0.6,0.4c3.2-1.1,5.5-4.1,5.5-7.6C16,3.8,12.4,0.2,8,0.2z"></path>
</svg>
Download on GitHub
<header>
<h1>Plyr</h1>
<p>A simple, accessible HTML5 media player by <a href="https://twitter.com/sam_potts" target="_blank">@sam_potts</a></p>
<nav>
<ul>
<li>
<a href="https://github.com/sampotts/plyr" target="_blank" class="btn btn--large btn--primary" data-shr-network="github">
<svg class="icon">
<use xlink:href="#icon-github" />
</svg>Download on GitHub
</a>
</span>
</div>
</header>
</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 btn--large btn--twitter" data-shr-network="twitter">
<svg class="icon">
<use xlink:href="#icon-twitter" />
</svg>Tweet
</a>
</li>
</ul>
</nav>
</header>
<main>
<video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
<main role="main" id="main">
<nav class="btn__bar">
<ul>
<li class="active">
<button type="button" class="btn" data-source="video">Video</button>
</li>
<li>
<button type="button" class="btn" data-source="audio">Audio</button>
</li>
<li>
<button type="button" class="btn btn--youtube" data-source="youtube"><svg class="icon"><use xlink:href="#icon-youtube"/></svg>YouTube</button>
</li>
<li>
<button type="button" class="btn btn--vimeo" data-source="vimeo"><svg class="icon"><use xlink:href="#icon-vimeo"/></svg>Vimeo</button>
</li>
</ul>
</nav>
<section>
<video poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg?v1" controls crossorigin>
<!-- Video files -->
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" type="video/mp4">
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.webm" type="video/webm">
@ -99,89 +70,34 @@
<!-- Text track file -->
<track kind="captions" label="English" srclang="en" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
default>
<track kind="captions" label="Français" srclang="fr" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
<!-- Fallback for browsers that don't support the <video> element -->
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" download>Download</a>
</video>
<ul>
<li class="plyr__cite plyr__cite--video" hidden>
<small>
<svg class="icon">
<title>HTML5</title>
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
</svg>
<a href="http://viewfromabluemoon.com/" target="_blank">View From A Blue Moon</a> &copy; Brainfarm
</small>
</li>
<li class="plyr__cite plyr__cite--audio" hidden>
<small>
<svg class="icon" title="HTML5">
<title>HTML5</title>
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
</svg>
<a href="http://www.kishibashi.com/" target="_blank">Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;</a> &copy; Kishi Bashi
</small>
</li>
<li class="plyr__cite plyr__cite--youtube" hidden>
<small>
<a href="https://www.youtube.com/watch?v=bTqVqk7FSmY" target="_blank">View From A Blue Moon</a> on&nbsp;
<span class="color--youtube">
<svg class="icon" role="presentation">
<title>YouTube</title>
<path d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
M6,11V5l5,3L6,11z"></path>
</svg>YouTube
</span>
</small>
</li>
<li class="plyr__cite plyr__cite--vimeo" hidden>
<small>
<a href="https://vimeo.com/ondemand/viewfromabluemoon4k" target="_blank">View From A Blue Moon</a> on&nbsp;
<span class="color--vimeo">
<svg class="icon" role="presentation">
<title>Vimeo</title>
<path d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"></path>
</svg>Vimeo
</span>
</small>
</li>
<li class="plyr__cite plyr__cite--video"><small><a href="http://viewfromabluemoon.com/" target="_blank">View From A Blue Moon</a> &copy; Brainfarm</small></li>
<li class="plyr__cite plyr__cite--audio"><small><a href="http://www.kishibashi.com/" target="_blank">Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;</a> &copy; Kishi Bashi</small></li>
<li class="plyr__cite plyr__cite--youtube"><small><a href="https://www.youtube.com/watch?v=bTqVqk7FSmY" target="_blank">View From A Blue Moon</a> on <span class="color--youtube"><svg class="icon"><use xlink:href="#icon-youtube"/></svg>YouTube</span></small></li>
<li class="plyr__cite plyr__cite--vimeo"><small><a href="https://vimeo.com/ondemand/viewfromabluemoon4k" target="_blank">View From A Blue Moon</a> on <span class="color--vimeo"><svg class="icon"><use xlink:href="#icon-vimeo"/></svg>Vimeo</span></small></li>
</ul>
</main>
</div>
<aside>
<svg class="icon">
<title>Twitter</title>
<path d="M16,3c-0.6,0.3-1.2,0.4-1.9,0.5c0.7-0.4,1.2-1,1.4-1.8c-0.6,0.4-1.3,0.6-2.1,0.8c-0.6-0.6-1.5-1-2.4-1
C9.3,1.5,7.8,3,7.8,4.8c0,0.3,0,0.5,0.1,0.7C5.2,5.4,2.7,4.1,1.1,2.1c-0.3,0.5-0.4,1-0.4,1.7c0,1.1,0.6,2.1,1.5,2.7
c-0.5,0-1-0.2-1.5-0.4c0,0,0,0,0,0c0,1.6,1.1,2.9,2.6,3.2C3,9.4,2.7,9.4,2.4,9.4c-0.2,0-0.4,0-0.6-0.1c0.4,1.3,1.6,2.3,3.1,2.3
c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"></path>
</svg>
<p>If you think Plyr's good,
<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" data-shr-network="twitter">tweet it</a>
</p>
</aside>
<!-- Polyfills -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent"></script>
</section>
</main>
<!-- Plyr core script -->
<script src="../dist/plyr.js"></script>
<!-- Sharing libary (https://shr.one) -->
<script src="https://cdn.shr.one/1.0.1/shr.js"></script>
<!-- Docs script -->
<script src="dist/demo.js"></script>
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
<script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async></script>
<!-- Docs script -->
<script src="dist/demo.js"></script>
<!-- Sharing libary (https://shr.one) -->
<script src="https://cdn.shr.one/1.0.1/shr.js"></script>
<script>
if (window.shr) { window.shr.setup({ count: { classname: 'btn__count' } }); }
</script>
</body>
</html>

View File

@ -1,29 +0,0 @@
WEBVTT FILE
1
00:00:09.500 --> 00:00:12.000
The ocean floor rises 5 miles to the shores
2
00:00:12.001 --> 00:00:16.500
of what people call, the seven mile miracle
3
00:00:25.500 --> 00:00:28.000
What would it be like to be born on this island?
4
00:00:32.500 --> 00:00:34.500
To grow up on these shores
5
00:00:37.500 --> 00:00:40.000
To witness this water, every day
6
00:00:43.500 --> 00:00:46.000
You're about to meet someone, who did
7
00:02:45.500 --> 00:02:49.000
This is a film about John John Florence

View File

@ -1,29 +0,0 @@
WEBVTT FILE
1
00:00:09.500 --> 00:00:12.000
Le fond de l'océan monte 5 miles des rives
2
00:00:12.001 --> 00:00:16.500
de ce que les gens appellent le miracle de sept mile
3
00:00:25.500 --> 00:00:28.000
Que serait-il d'être né sur cette île?
4
00:00:32.500 --> 00:00:34.500
Pour grandir sur ces rivages
5
00:00:37.500 --> 00:00:40.000
Pour assister à cette eau, tous les jours
6
00:00:43.500 --> 00:00:46.000
Vous êtes sur le point de rencontrer quelqu'un, qui ne
7
00:02:45.500 --> 00:02:49.000
Ceci est un film sur John John Florence

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

View File

@ -1,245 +0,0 @@
// ==========================================================================
// Plyr.io demo
// This code is purely for the https://plyr.io website
// Please see readme.md in the root or github.com/sampotts/plyr
// ==========================================================================
document.addEventListener('DOMContentLoaded', () => {
if (window.shr) {
window.shr.setup({
count: {
classname: 'button__count',
},
});
}
// Setup tab focus
const tabClassName = 'tab-focus';
// Remove class on blur
document.addEventListener('focusout', event => {
event.target.classList.remove(tabClassName);
});
// Add classname to tabbed elements
document.addEventListener('keydown', event => {
if (event.keyCode !== 9) {
return;
}
// Delay the adding of classname until the focus has changed
// This event fires before the focusin event
window.setTimeout(() => {
document.activeElement.classList.add(tabClassName);
}, 0);
});
// Setup the player
const player = new Plyr('#player', {
debug: true,
title: 'View From A Blue Moon',
// iconUrl: '../dist/plyr.svg',
keyboard: {
global: true,
},
tooltips: {
controls: true,
},
captions: {
active: true,
},
keys: {
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
},
ads: {
enabled: true,
},
});
// Expose for testing
window.player = player;
// Setup type toggle
const buttons = document.querySelectorAll('[data-source]');
const types = {
video: 'video',
audio: 'audio',
youtube: 'youtube',
vimeo: 'vimeo',
};
let currentType = window.location.hash.replace('#', '');
const historySupport = window.history && window.history.pushState;
// Toggle class on an element
function toggleClass(element, className, state) {
if (element) {
element.classList[state ? 'add' : 'remove'](className);
}
}
// Set a new source
function newSource(type, init) {
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
if (!(type in types) || (!init && type === currentType) || (!currentType.length && type === types.video)) {
return;
}
switch (type) {
case types.video:
player.source = {
type: 'video',
title: 'View From A Blue Moon',
sources: [{
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4',
type: 'video/mp4',
}],
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
tracks: [
{
kind: 'captions',
label: 'English',
srclang: 'en',
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
default: true,
},
{
kind: 'captions',
label: 'French',
srclang: 'fr',
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
},
],
};
break;
case types.audio:
player.source = {
type: 'audio',
title: 'Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;',
sources: [
{
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
type: 'audio/mp3',
},
{
src: 'https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg',
type: 'audio/ogg',
},
],
};
break;
case types.youtube:
player.source = {
type: 'video',
title: 'View From A Blue Moon',
sources: [{
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
provider: 'youtube',
}],
};
break;
case types.vimeo:
player.source = {
type: 'video',
sources: [{
src: 'https://vimeo.com/76979871',
provider: 'vimeo',
}],
};
break;
default:
break;
}
// Set the current type for next time
currentType = type;
// Remove active classes
Array.from(buttons).forEach(button => toggleClass(button.parentElement, 'active', false));
// Set active on parent
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
// Show cite
Array.from(document.querySelectorAll('.plyr__cite')).forEach(cite => {
cite.setAttribute('hidden', '');
});
document.querySelector(`.plyr__cite--${type}`).removeAttribute('hidden');
}
// Bind to each button
Array.from(buttons).forEach(button => {
button.addEventListener('click', () => {
const type = button.getAttribute('data-source');
newSource(type);
if (historySupport) {
window.history.pushState({ type }, '', `#${type}`);
}
});
});
// List for backwards/forwards
window.addEventListener('popstate', event => {
if (event.state && 'type' in event.state) {
newSource(event.state.type);
}
});
// On load
if (historySupport) {
const video = !currentType.length;
// If there's no current type set, assume video
if (video) {
currentType = types.video;
}
// Replace current history state
if (currentType in types) {
window.history.replaceState(
{
type: currentType,
},
'',
video ? '' : `#${currentType}`,
);
}
// If it's not video, load the source
if (currentType !== types.video) {
newSource(currentType, true);
}
}
});
// Google analytics
// For demo site (https://plyr.io) only
/* eslint-disable */
if (window.location.host === 'plyr.io') {
(function(i, s, o, g, r, a, m) {
i.GoogleAnalyticsObject = r;
i[r] =
i[r] ||
function() {
(i[r].q = i[r].q || []).push(arguments);
};
i[r].l = 1 * new Date();
a = s.createElement(o);
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
window.ga('create', 'UA-40881672-11', 'auto');
window.ga('send', 'pageview');
}
/* eslint-enable */

View File

@ -0,0 +1,237 @@
/*
* classList.js: Cross-browser full element.classList implementation.
* 1.1.20150312
*
* By Eli Grey, http://eligrey.com
* License: Dedicated to the public domain.
* See https://github.com/eligrey/classList.js/blob/master/LICENSE.md
*/
/*global self, document, DOMException */
/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */
if ("document" in self) {
// Full polyfill for browsers with no classList support
if (!("classList" in document.createElement("_"))) {
(function (view) {
"use strict";
if (!('Element' in view)) return;
var
classListProp = "classList"
, protoProp = "prototype"
, elemCtrProto = view.Element[protoProp]
, objCtr = Object
, strTrim = String[protoProp].trim || function () {
return this.replace(/^\s+|\s+$/g, "");
}
, arrIndexOf = Array[protoProp].indexOf || function (item) {
var
i = 0
, len = this.length
;
for (; i < len; i++) {
if (i in this && this[i] === item) {
return i;
}
}
return -1;
}
// Vendors: please allow content code to instantiate DOMExceptions
, DOMEx = function (type, message) {
this.name = type;
this.code = DOMException[type];
this.message = message;
}
, checkTokenAndGetIndex = function (classList, token) {
if (token === "") {
throw new DOMEx(
"SYNTAX_ERR"
, "An invalid or illegal string was specified"
);
}
if (/\s/.test(token)) {
throw new DOMEx(
"INVALID_CHARACTER_ERR"
, "String contains an invalid character"
);
}
return arrIndexOf.call(classList, token);
}
, ClassList = function (elem) {
var
trimmedClasses = strTrim.call(elem.getAttribute("class") || "")
, classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
, i = 0
, len = classes.length
;
for (; i < len; i++) {
this.push(classes[i]);
}
this._updateClassName = function () {
elem.setAttribute("class", this.toString());
};
}
, classListProto = ClassList[protoProp] = []
, classListGetter = function () {
return new ClassList(this);
}
;
// Most DOMException implementations don't allow calling DOMException's toString()
// on non-DOMExceptions. Error's toString() is sufficient here.
DOMEx[protoProp] = Error[protoProp];
classListProto.item = function (i) {
return this[i] || null;
};
classListProto.contains = function (token) {
token += "";
return checkTokenAndGetIndex(this, token) !== -1;
};
classListProto.add = function () {
var
tokens = arguments
, i = 0
, l = tokens.length
, token
, updated = false
;
do {
token = tokens[i] + "";
if (checkTokenAndGetIndex(this, token) === -1) {
this.push(token);
updated = true;
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.remove = function () {
var
tokens = arguments
, i = 0
, l = tokens.length
, token
, updated = false
, index
;
do {
token = tokens[i] + "";
index = checkTokenAndGetIndex(this, token);
while (index !== -1) {
this.splice(index, 1);
updated = true;
index = checkTokenAndGetIndex(this, token);
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.toggle = function (token, force) {
token += "";
var
result = this.contains(token)
, method = result ?
force !== true && "remove"
:
force !== false && "add"
;
if (method) {
this[method](token);
}
if (force === true || force === false) {
return force;
} else {
return !result;
}
};
classListProto.toString = function () {
return this.join(" ");
};
if (objCtr.defineProperty) {
var classListPropDesc = {
get: classListGetter
, enumerable: true
, configurable: true
};
try {
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
} catch (ex) { // IE 8 doesn't support enumerable:true
if (ex.number === -0x7FF5EC54) {
classListPropDesc.enumerable = false;
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
}
}
} else if (objCtr[protoProp].__defineGetter__) {
elemCtrProto.__defineGetter__(classListProp, classListGetter);
}
}(self));
} else {
// There is full or partial native classList support, so just check if we need
// to normalize the add/remove and toggle APIs.
(function () {
"use strict";
var testElement = document.createElement("_");
testElement.classList.add("c1", "c2");
// Polyfill for IE 10/11 and Firefox <26, where classList.add and
// classList.remove exist but support only one argument at a time.
if (!testElement.classList.contains("c2")) {
var createMethod = function(method) {
var original = DOMTokenList.prototype[method];
DOMTokenList.prototype[method] = function(token) {
var i, len = arguments.length;
for (i = 0; i < len; i++) {
token = arguments[i];
original.call(this, token);
}
};
};
createMethod('add');
createMethod('remove');
}
testElement.classList.toggle("c3", false);
// Polyfill for IE 10 and Firefox <24, where classList.toggle does not
// support the second argument.
if (testElement.classList.contains("c3")) {
var _toggle = DOMTokenList.prototype.toggle;
DOMTokenList.prototype.toggle = function(token, force) {
if (1 in arguments && !this.contains(token) === !force) {
return force;
} else {
return _toggle.call(this, token);
}
};
}
testElement = null;
}());
}
}

203
demo/src/js/main.js Normal file
View File

@ -0,0 +1,203 @@
// ==========================================================================
// Plyr.io demo
// This code is purely for the plyr.io website
// Please see readme.md in the root or github.com/selz/plyr
// ==========================================================================
/*global plyr*/
// General functions
(function() {
//document.body.addEventListener('ready', function(event) { console.log(event); });
// Setup the player
var instances = plyr.setup({
debug: true,
title: "Video demo",
iconUrl: "../dist/plyr.svg",
tooltips: {
controls: true
},
captions: {
defaultActive: true
}
});
plyr.loadSprite("dist/demo.svg");
// Plyr returns an array regardless
var player = instances[0];
// Setup type toggle
var buttons = document.querySelectorAll("[data-source]"),
types = {
video: "video",
audio: "audio",
youtube: "youtube",
vimeo: "vimeo"
},
currentType = window.location.hash.replace("#", ""),
historySupport = window.history && window.history.pushState;
// Bind to each button
for (var i = buttons.length - 1; i >= 0; i--) {
buttons[i].addEventListener("click", function() {
var type = this.getAttribute("data-source");
newSource(type);
if (historySupport) {
history.pushState({ type: type }, "", "#" + type);
}
});
}
// List for backwards/forwards
window.addEventListener("popstate", function(event) {
if (event.state && "type" in event.state) {
newSource(event.state.type);
}
});
// On load
if (historySupport) {
var video = !currentType.length;
// If there's no current type set, assume video
if (video) {
currentType = types.video;
}
// Replace current history state
if (currentType in types) {
history.replaceState({ type: currentType }, "", video ? "" : "#" + currentType);
}
// If it's not video, load the source
if (currentType !== types.video) {
newSource(currentType, true);
}
}
// Toggle class on an element
function toggleClass(element, className, state) {
if (element) {
if (element.classList) {
element.classList[state ? "add" : "remove"](className);
} else {
var name = (" " + element.className + " ").replace(/\s+/g, " ").replace(" " + className + " ", "");
element.className = name + (state ? " " + className : "");
}
}
}
// Set a new source
function newSource(type, init) {
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
if (!(type in types) || (!init && type === currentType) || (!currentType.length && type === types.video)) {
return;
}
switch (type) {
case types.video:
player.source({
type: "video",
title: "View From A Blue Moon",
sources: [
{
src: "https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4",
type: "video/mp4"
},
{
src: "https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.webm",
type: "video/webm"
}
],
poster: "https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg",
tracks: [
{
kind: "captions",
label: "English",
srclang: "en",
src: "https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt",
default: true
}
]
});
break;
case types.audio:
player.source({
type: "audio",
title: "Kishi Bashi &ndash; &ldquo;It All Began With A Burst&rdquo;",
sources: [
{
src: "https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",
type: "audio/mp3"
},
{
src: "https://cdn.plyr.io/static/demo/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",
type: "audio/ogg"
}
]
});
break;
case types.youtube:
player.source({
type: "video",
title: "View From A Blue Moon",
sources: [
{
src: "bTqVqk7FSmY",
type: "youtube"
}
]
});
break;
case types.vimeo:
player.source({
type: "video",
title: "View From A Blue Moon",
sources: [
{
src: "147865858",
type: "vimeo"
}
]
});
break;
}
// Set the current type for next time
currentType = type;
// Remove active classes
for (var x = buttons.length - 1; x >= 0; x--) {
toggleClass(buttons[x].parentElement, "active", false);
}
// Set active on parent
toggleClass(document.querySelector('[data-source="' + type + '"]').parentElement, "active", true);
}
})();
// Google analytics
// For demo site (http://[www.]plyr.io) only
if (document.domain.indexOf("plyr.io") > -1) {
(function(i, s, o, g, r, a, m) {
i.GoogleAnalyticsObject = r;
(i[r] =
i[r] ||
function() {
(i[r].q = i[r].q || []).push(arguments);
}),
(i[r].l = 1 * new Date());
(a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
})(window, document, "script", "//www.google-analytics.com/analytics.js", "ga");
ga("create", "UA-40881672-11", "auto");
ga("send", "pageview");
}

View File

@ -0,0 +1,48 @@
// ==========================================================================
// Base layout
// ==========================================================================
// BORDER-BOX ALL THE THINGS!
// http://paulirish.com/2012/box-sizing-border-box-ftw/
*, *::after, *::before {
box-sizing: border-box;
}
// Hidden
[hidden] {
display: none;
}
// Base
html {
height: 100%;
background: @body-background fixed;
}
body {
margin: 0;
padding: (@padding-base / 2);
}
// 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 {
max-width: @example-width-video;
margin: 0 auto @padding-base;
@media (min-width: @screen-sm) {
margin-bottom: (@padding-base * 2);
}
}

View File

@ -0,0 +1,172 @@
// ==========================================================================
// 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;
white-space: nowrap;
&::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;
}
&.active .btn {
&:extend(.btn--primary);
box-shadow: inset 0 1px 1px rgba(0,0,0, .2);
position: relative;
z-index: 1;
.icon {
color: inherit;
}
}
&.active + li .btn:hover {
z-index: 0;
}
}
.btn {
position: relative;
display: block;
border-radius: 0;
&:hover,
&:focus {
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;
user-select: none;
font-weight: @font-weight-bold;
}
// Buttons
.btn {
padding: (@padding-base / 2) ((@padding-base / 2) + 2);
background: linear-gradient(lighten(@off-white, 2%), darken(@off-white, 3%));
border: 1px solid @gray-light;
box-shadow: 0 1px 1px rgba(0,0,0, .05);
text-shadow: 0 1px 1px #fff;
color: @gray;
transition: background .1s ease, color .1s ease;
.font-size(@font-size-small);
&:hover,
&:focus {
border-color: darken(@gray-light, 8%);
color: @gray;
outline: 0;
}
}
// Sizes
.btn--large {
padding: (@padding-base / 2) @padding-base;
.font-size();
}
// Styles
.btn--primary {
background-image: linear-gradient(@link-color, darken(@link-color, 5%));
background-color: @link-color;
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--youtube .icon {
color: @color-youtube;
}
.btn--vimeo .icon {
color: @color-vimeo;
}
.btn--twitter .icon {
color: @color-twitter;
}
// Count bubble
.btn__count {
position: relative;
margin-left: (@padding-base / 2);
padding: (@padding-base / 2) (@padding-base * .75);
background: #fff;
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

@ -7,24 +7,13 @@ html.error,
.error body {
height: 100%;
}
html.error {
background: $page-background;
background-attachment: fixed;
}
.error body {
align-items: center;
display: flex;
width: 100%;
display: table;
table-layout: fixed;
}
.error main {
padding: $spacing-base;
text-align: center;
display: table-cell;
width: 100%;
p {
@include font-size($font-size-large);
}
}
vertical-align: middle;
}

View File

@ -10,34 +10,31 @@ video {
// Example players
.plyr {
border-radius: $border-radius-base;
box-shadow: 0 2px 5px rgba(#000, 0.2);
margin: $spacing-base auto;
&.plyr--audio {
max-width: 480px;
}
margin: 0 auto;
border-radius: @border-radius-large;
}
.plyr--audio {
max-width: @example-width-audio;
}
.plyr__video-wrapper::after {
border: 1px solid rgba(#000, 0.15);
border-radius: inherit;
bottom: 0;
content: '';
left: 0;
content: "";
pointer-events: none;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
border: 1px solid fade(#000, 15%);
border-radius: inherit;
}
// Style full supported player
.plyr__cite {
display: none;
margin-top: $spacing-base;
margin-top: @padding-base;
.icon {
margin-right: ceil($spacing-base / 6);
margin-right: (@padding-base / 4);
}
}

View File

@ -4,20 +4,23 @@
// Base size icon styles
.icon {
fill: currentColor;
height: $icon-size;
vertical-align: -3px;
width: $icon-size;
fill: currentColor;
width: @icon-size;
height: @icon-size;
vertical-align: -3px;
}
// Within elements
a svg,
button svg,
label svg {
pointer-events: none;
pointer-events: none;
}
a .icon,
.btn .icon {
margin-right: floor($spacing-base / 3);
margin-right: (@padding-base / 2);
}
.btn:not(.btn-large) .icon {
width: (@icon-size - 2);
height: (@icon-size - 2);
}

View File

@ -0,0 +1,75 @@
// ==========================================================================
// Typography
// ==========================================================================
// Base
html {
font-size: 100%;
}
body {
font-family: "Avenir", "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 1.5;
text-align: center;
color: @gray;
font-weight: @font-weight-base;
.font-smoothing();
}
// Headings
h1,
h2 {
letter-spacing: -.025em;
color: @brand-primary;
margin: 0 0 (@padding-base / 2);
line-height: 1.2;
font-weight: @font-weight-bold;
}
h1 {
.font-size(@font-size-h1);
}
// Paragraph and small
p,
small {
margin: 0 0 @padding-base;
}
small {
display: block;
padding: 0 (@padding-base / 2);
.font-size(@font-size-small);
}
// Lists
ul,
li {
list-style: none;
margin: 0;
padding: 0;
}
// Links
a {
text-decoration: none;
color: @link-color;
border-bottom: 1px dotted currentColor;
transition: background .3s ease, color .3s ease, border .3s ease;
&:hover,
&:focus {
color: @gray-dark;
border-bottom-color: rgba(0,0,0,0);
}
&:focus {
.tab-focus();
}
&.logo {
border: 0;
}
}
.color--vimeo {
color: @color-vimeo;
}
.color--youtube {
color: @color-youtube;
}

26
demo/src/less/demo.less Normal file
View File

@ -0,0 +1,26 @@
// ==========================================================================
// Plyr.io Demo Page
// ==========================================================================
// CSS Reset
@import "lib/normalize.less";
// Mixins
@import "lib/mixins.less";
// Variables
@import "variables.less";
// Animation
@import "lib/animation.less";
// Type
@import "lib/fontface.less";
@import "components/type.less";
// Components
@import "components/base.less";
@import "components/icons.less";
@import "components/buttons.less";
@import "components/error.less";
@import "components/examples.less";

View File

@ -3,11 +3,7 @@
// ==========================================================================
// Fade
@keyframes fadein {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade-in {
0% { opacity: 0 }
100% { opacity: 1 }
}

View File

@ -0,0 +1,18 @@
// ==========================================================================
// Fonts
// ==========================================================================
@font-face {
font-family: 'Avenir';
src: url('https://cdn.plyr.io/static/fonts/avenir-medium.woff2') format('woff2'), url('https://cdn.plyr.io/static/fonts/avenir-medium.woff') format('woff');
font-style: normal;
font-weight: @font-weight-base;
font-display: swap;
}
@font-face {
font-family: 'Avenir';
src: url('https://cdn.plyr.io/static/fonts/avenir-bold.woff2') format('woff2'), url('https://cdn.plyr.io/static/fonts/avenir-bold.woff') format('woff');
font-style: normal;
font-weight: @font-weight-bold;
font-display: swap;
}

View File

@ -0,0 +1,41 @@
// ==========================================================================
// Mixins
// ==========================================================================
// Contain floats: nicolasgallagher.com/micro-clearfix-hack/
// ---------------------------------------
.clearfix() {
zoom: 1;
&:before,
&:after { content: ""; display: table; }
&:after { clear: both; }
}
// Webkit-style focus
// ---------------------------------------
.tab-focus() {
// Default
outline: thin dotted @gray-dark;
// Webkit
outline-offset: 1px;
}
// Use rems for font sizing
// Leave <body> at 100%/16px
// ---------------------------------------
.font-size(@font-size: 16){
@rem: round((@font-size / 16), 3);
font-size: (@font-size * 1px);
font-size: ~"@{rem}rem";
}
// 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;
}

406
demo/src/less/lib/normalize.less vendored Normal file
View File

@ -0,0 +1,406 @@
/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
/* ==========================================================================
HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined in IE 8/9.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
display: block;
}
/**
* Correct `inline-block` display not defined in IE 8/9.
*/
audio,
canvas,
video {
display: inline-block;
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9.
* Hide the `template` element in IE, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* ==========================================================================
Base
========================================================================== */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* ==========================================================================
Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background: transparent;
}
/**
* Address `outline` inconsistency between Chrome and other browsers.
*/
a:focus {
outline: thin dotted;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* ==========================================================================
Typography
========================================================================== */
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari 5, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9, Safari 5, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari 5 and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Correct font family set oddly in Safari 5 and Chrome.
*/
code,
kbd,
pre,
samp {
font-family: monospace, serif;
font-size: 1em;
}
/**
* Improve readability of pre-formatted text in all browsers.
*/
pre {
white-space: pre-wrap;
}
/**
* Set consistent quote types.
*/
q {
quotes: "\201C" "\201D" "\2018" "\2019";
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* ==========================================================================
Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9.
*/
img {
border: 0;
}
/**
* Correct overflow displayed oddly in IE 9.
*/
svg:not(:root) {
overflow: hidden;
}
/* ==========================================================================
Figures
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari 5.
*/
figure {
margin: 0;
}
/* ==========================================================================
Forms
========================================================================== */
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* 1. Correct font family not being inherited in all browsers.
* 2. Correct font size not being inherited in all browsers.
* 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
*/
button,
input,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 2 */
margin: 0; /* 3 */
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
button,
input {
line-height: normal;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
* Correct `select` style inheritance in Firefox 4+ and Opera.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari 5 and Chrome
* on OS X.
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* 1. Remove default vertical scrollbar in IE 8/9.
* 2. Improve readability and alignment in all browsers.
*/
textarea {
overflow: auto; /* 1 */
vertical-align: top; /* 2 */
}
/* ==========================================================================
Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}

View File

@ -0,0 +1,48 @@
// ==========================================================================
// Variables
// ==========================================================================
// Colors
@gray-dark: #343f4a;
@gray: #55646b;
@gray-light: #cbd0d3;
@gray-lighter: #dbe3e8;
@off-white: #f2f5f7;
@brand-primary: #3498db;
@brand-secondary: #02BD9B;
// Brands
@color-twitter: #4BAAF4;
@color-youtube: #cc181e;
@color-vimeo: #19b7ed;
// Base
@body-background: @off-white; //linear-gradient(to left top, @brand-secondary, @brand-primary);
// Type
@font-size-base: 16;
@font-size-small: 14;
@font-size-h1: 64;
@font-weight-base: 500;
@font-weight-bold: 700;
// Elements
@link-color: @brand-primary;
@padding-base: 20px;
@arrow-size: 8px;
// Icons
@icon-size: 18px;
// Breakpoints
@screen-sm: 480px;
@screen-md: 768px;
// Radii
@border-radius-base: 4px;
@border-radius-large: 6px;
// Examples
@example-width-audio: 520px;
@example-width-video: 1200px;

View File

@ -1,46 +0,0 @@
// ==========================================================================
// Plyr.io Demo Page
// ==========================================================================
@charset 'UTF-8';
// Settings
@import '../settings/breakpoints';
@import '../settings/colors';
@import '../settings/cosmetic';
@import '../settings/icons';
@import '../settings/layout';
@import '../settings/plyr';
@import '../settings/spacing';
@import '../settings/type';
// Libs
@import '../lib/fontface';
@import '../lib/animation';
@import '../lib/mixins';
@import '../lib/normalize';
@import '../lib/reset';
// Layout
@import '../layout/core';
@import '../layout/grid';
// Type
@import '../type/base';
@import '../type/headings';
// Components
@import '../components/buttons';
@import '../components/header';
@import '../components/icons';
@import '../components/links';
@import '../components/lists';
@import '../components/media';
@import '../components/navigation';
@import '../components/players';
// Plyr
@import '../../../../src/sass/plyr';
// Utils
@import '../utilities/cosmetic';
@import '../utilities/hidden';

View File

@ -1,29 +0,0 @@
// ==========================================================================
// Plyr.io Error Page
// ==========================================================================
@charset 'UTF-8';
// Libs
@import '../lib/fontface';
@import '../lib/mixins';
@import '../lib/normalize';
@import '../lib/reset';
// Settings
@import '../settings/colors';
@import '../settings/cosmetic';
@import '../settings/icons';
@import '../settings/layout';
@import '../settings/spacing';
@import '../settings/type';
// Layout
@import '../layout/error';
// Type
@import '../type/base';
@import '../type/headings';
// Components
@import '../components/buttons';
@import '../components/links';

View File

@ -1,83 +0,0 @@
// ==========================================================================
// Buttons
// ==========================================================================
// Shared
.button,
.button__count {
align-items: center;
background: $color-button-background;
border: 0;
border-radius: $border-radius-base;
box-shadow: 0 1px 1px rgba(#000, 0.1);
color: $color-button-text;
display: inline-flex;
padding: ($spacing-base * 0.75);
position: relative;
text-shadow: none;
user-select: none;
vertical-align: middle;
}
// Buttons
.button {
font-weight: $font-weight-bold;
padding-left: $spacing-base;
padding-right: $spacing-base;
transition: all 0.2s ease;
&:hover,
&:focus {
color: $gray-dark;
// Remove the underline/border
&::after {
display: none;
}
}
&:hover {
box-shadow: 0 2px 2px rgba(#000, 0.1);
transform: translateY(-1px);
}
&:focus {
outline: 0;
}
&.tab-focus {
@include tab-focus();
}
&:active {
transform: translateY(1px);
}
}
// Button group
.button--with-count {
display: inline-flex;
.button .icon {
flex-shrink: 0;
}
}
// Count bubble
.button__count {
animation: fadein 0.2s ease;
margin-left: ($spacing-base / 2);
&::before {
border: $arrow-size solid transparent;
border-left-width: 0;
border-right-color: $color-button-background;
content: '';
height: 0;
position: absolute;
right: 100%;
top: 50%;
transform: translateY(-50%);
width: 0;
}
}

View File

@ -1,19 +0,0 @@
// ==========================================================================
// Header
// ==========================================================================
header {
padding-bottom: $spacing-base;
text-align: center;
.call-to-action {
margin-top: ($spacing-base * 1.5);
}
@media only screen and (min-width: $screen-md) {
margin-right: ($spacing-base * 3);
max-width: 360px;
padding-bottom: ($spacing-base * 2);
text-align: left;
}
}

View File

@ -1,49 +0,0 @@
// ==========================================================================
// Links
// ==========================================================================
// Make a <button> look like an <a>
button.faux-link {
@extend a; // stylelint-disable-line
@include cancel-button-styles();
}
// Links
a {
border-bottom: 1px dotted currentColor;
color: $color-link;
font-weight: $font-weight-bold;
position: relative;
text-decoration: none;
transition: all 0.2s ease;
&::after {
background: currentColor;
content: '';
height: 1px;
left: 50%;
position: absolute;
top: 100%;
transform: translateX(-50%);
transition: width 0.2s ease;
width: 0;
}
&:hover,
&:focus {
border-bottom-color: transparent;
outline: 0;
&::after {
width: 100%;
}
}
&.tab-focus {
@include tab-focus();
}
&.no-border::after {
display: none;
}
}

View File

@ -1,11 +0,0 @@
// ==========================================================================
// Lists
// ==========================================================================
// Lists
ul,
li {
list-style: none;
margin: 0;
padding: 0;
}

View File

@ -1,10 +0,0 @@
// ==========================================================================
// Basic media
// ==========================================================================
img,
video,
audio {
max-width: 100%;
vertical-align: middle;
}

View File

@ -1,9 +0,0 @@
// ==========================================================================
// Navigation
// ==========================================================================
nav {
display: flex;
justify-content: center;
margin-bottom: $spacing-base;
}

View File

@ -1,64 +0,0 @@
// ==========================================================================
// Core
// ==========================================================================
html,
body {
display: flex;
width: 100%;
}
html {
background: $page-background;
background-attachment: fixed;
height: 100%;
}
body {
align-items: center;
display: flex;
flex-direction: column;
min-height: 100%;
}
.grid {
flex: 1;
overflow: auto;
}
main {
margin: auto;
padding-bottom: 1px; // Collapsing margins
text-align: center;
}
aside {
align-items: center;
background: #fff;
color: $gray;
display: flex;
flex-shrink: 0;
justify-content: center;
padding: ($spacing-base * 0.75);
position: relative;
text-align: center;
text-shadow: none;
width: 100%;
.icon {
fill: $color-twitter;
margin-right: ($spacing-base / 2);
}
p {
margin: 0;
}
a {
color: $color-twitter;
&.tab-focus {
@include tab-focus($color-twitter);
}
}
}

View File

@ -1,19 +0,0 @@
// ==========================================================================
// Super basic grid
// ==========================================================================
.grid {
margin: 0 auto;
padding: $spacing-base;
@media only screen and (min-width: $screen-md) {
align-items: center;
display: flex;
max-width: $container-max-width;
width: 100%;
> * {
flex: 1;
}
}
}

View File

@ -1,45 +0,0 @@
// ==========================================================================
// Fonts
// ==========================================================================
@font-face {
font-display: swap;
font-family: 'Gordita';
font-style: normal;
font-weight: $font-weight-light;
src: url('https://cdn.plyr.io/static/fonts/gordita-light.woff2') format('woff2'), url('https://cdn.plyr.io/static/fonts/gordita-light.woff') format('woff');
}
@font-face {
font-display: swap;
font-family: 'Gordita';
font-style: normal;
font-weight: $font-weight-regular;
src: url('https://cdn.plyr.io/static/fonts/gordita-regular.woff2') format('woff2'),
url('https://cdn.plyr.io/static/fonts/gordita-regular.woff') format('woff');
}
@font-face {
font-display: swap;
font-family: 'Gordita';
font-style: normal;
font-weight: $font-weight-medium;
src: url('https://cdn.plyr.io/static/fonts/gordita-medium.woff2') format('woff2'),
url('https://cdn.plyr.io/static/fonts/gordita-medium.woff') format('woff');
}
@font-face {
font-display: swap;
font-family: 'Gordita';
font-style: normal;
font-weight: $font-weight-bold;
src: url('https://cdn.plyr.io/static/fonts/gordita-bold.woff2') format('woff2'), url('https://cdn.plyr.io/static/fonts/gordita-bold.woff') format('woff');
}
@font-face {
font-display: swap;
font-family: 'Gordita';
font-style: normal;
font-weight: $font-weight-black;
src: url('https://cdn.plyr.io/static/fonts/gordita-black.woff2') format('woff2'), url('https://cdn.plyr.io/static/fonts/gordita-black.woff') format('woff');
}

View File

@ -1,54 +0,0 @@
// ==========================================================================
// Mixins
// ==========================================================================
// Convert a <button> into an <a>
// ---------------------------------------
@mixin cancel-button-styles() {
background: transparent;
border: 0;
border-radius: 0;
cursor: pointer;
font: inherit;
line-height: $line-height-base;
margin: 0;
padding: 0;
position: relative;
text-align: inherit;
text-shadow: inherit;
-moz-user-select: text; // stylelint-disable-line
vertical-align: baseline;
width: auto;
}
// Nicer focus styles
// ---------------------------------------
@mixin tab-focus($color: $tab-focus-default-color) {
box-shadow: 0 0 0 3px rgba($color, 0.35);
outline: 0;
}
// Use rems for font sizing
// Leave <body> at 100%/16px
// ---------------------------------------
@function calculate-rem($size) {
$rem: $size / 16;
@return #{$rem}rem;
}
@mixin font-size($size: 16) {
font-size: $size * 1px; // Fallback in px
font-size: calculate-rem($size);
}
// Font smoothing
// ---------------------------------------
@mixin font-smoothing($enabled: true) {
@if $enabled {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
} @else {
-moz-osx-font-smoothing: auto;
-webkit-font-smoothing: subpixel-antialiased;
}
}

View File

@ -1,450 +0,0 @@
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in
* IE on Windows Phone and in iOS.
*/
html {
line-height: 1.15; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers (opinionated).
*/
body {
margin: 0;
}
/**
* Add the correct display in IE 9-.
*/
article,
aside,
footer,
header,
nav,
section {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* Add the correct display in IE 9-.
* 1. Add the correct display in IE.
*/
figcaption,
figure,
main {
/* 1 */
display: block;
}
/**
* Add the correct margin in IE 8.
*/
figure {
margin: 1em 40px;
}
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* 1. Remove the gray background on active links in IE 10.
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
*/
a {
background-color: transparent; /* 1 */
-webkit-text-decoration-skip: objects; /* 2 */
}
/**
* 1. Remove the bottom border in Chrome 57- and Firefox 39-.
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
*/
b,
strong {
font-weight: inherit;
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font style in Android 4.3-.
*/
dfn {
font-style: italic;
}
/**
* Add the correct background and color in IE 9-.
*/
mark {
background-color: #ff0;
color: #000;
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Add the correct display in IE 9-.
*/
audio,
video {
display: inline-block;
}
/**
* Add the correct display in iOS 4-7.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Remove the border on images inside links in IE 10-.
*/
img {
border-style: none;
}
/**
* Hide the overflow in IE.
*/
svg:not(:root) {
overflow: hidden;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers (opinionated).
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: sans-serif; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input {
/* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select {
/* 1 */
text-transform: none;
}
/**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
* controls in Android 4.
* 2. Correct the inability to style clickable types in iOS and Safari.
*/
button,
html [type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button; /* 2 */
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type='button']::-moz-focus-inner,
[type='reset']::-moz-focus-inner,
[type='submit']::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type='button']:-moz-focusring,
[type='reset']:-moz-focusring,
[type='submit']:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* 1. Add the correct display in IE 9-.
* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Remove the default vertical scrollbar in IE.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10-.
* 2. Remove the padding in IE 10-.
*/
[type='checkbox'],
[type='radio'] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type='number']::-webkit-inner-spin-button,
[type='number']::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
*/
[type='search']::-webkit-search-cancel-button,
[type='search']::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in IE 9-.
* 1. Add the correct display in Edge, IE, and Firefox.
*/
details,
menu {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Scripting
========================================================================== */
/**
* Add the correct display in IE 9-.
*/
canvas {
display: inline-block;
}
/**
* Add the correct display in IE.
*/
template {
display: none;
}
/* Hidden
========================================================================== */
/**
* Add the correct display in IE 10-.
*/
[hidden] {
display: none;
}

View File

@ -1,11 +0,0 @@
// ==========================================================================
// Resets
// ==========================================================================
// BORDER-BOX ALL THE THINGS!
// http://paulirish.com/2012/box-sizing-border-box-ftw/
*,
*::after,
*::before {
box-sizing: border-box;
}

View File

@ -1,6 +0,0 @@
// ==========================================================================
// Breakpoints
// ==========================================================================
$screen-sm: 480px;
$screen-md: 768px;

View File

@ -1,32 +0,0 @@
// ==========================================================================
// Colors
// ==========================================================================
// Greyscale
$gray-dark: #343f4a;
$gray: #55646b;
$gray-light: #cbd0d3;
$gray-lighter: #dbe3e8;
$off-white: #f2f5f7;
// Text
$color-text: #fff;
// Plyr
$color-brand-primary: #1aafff;
// Brands
$color-twitter: #4baaf4;
$color-youtube: #cc181e;
$color-vimeo: #19b7ed;
// Elements
$color-link: #fff;
$color-background: $color-brand-primary;
// Buttons
$color-button-background: #fff;
$color-button-text: $gray;
// Focus
$tab-focus-default-color: #fff;

View File

@ -1,12 +0,0 @@
// ==========================================================================
// Misc cosmetic
// ==========================================================================
// Button count arrow size
$arrow-size: 5px;
// Radii
$border-radius-base: 4px;
// Background
$page-background: linear-gradient(to left top, lighten($color-background, 10%), darken($color-background, 20%));

View File

@ -1,5 +0,0 @@
// ==========================================================================
// Icons
// ==========================================================================
$icon-size: 16px;

View File

@ -1,5 +0,0 @@
// ==========================================================================
// Layout
// ==========================================================================
$container-max-width: 1280px;

View File

@ -1,18 +0,0 @@
// ==========================================================================
// Plyr Settings
// ==========================================================================
// Font
$plyr-font-family: inherit;
// Sizes
$plyr-font-size-base: 13px;
$plyr-font-size-small: 12px;
$plyr-font-size-time: 11px;
$plyr-font-size-badges: 9px;
// Captions
$plyr-font-size-captions-base: $plyr-font-size-base;
$plyr-font-size-captions-small: $plyr-font-size-small;
$plyr-font-size-captions-medium: 18px;
$plyr-font-size-captions-large: 21px;

View File

@ -1,5 +0,0 @@
// ==========================================================================
// Colors
// ==========================================================================
$spacing-base: 20px;

View File

@ -1,20 +0,0 @@
// ==========================================================================
// Typography
// ==========================================================================
$font-sans-serif: 'Gordita', 'Avenir', 'Helvetica Neue', sans-serif;
$font-size-base: 15;
$font-size-small: 13;
$font-size-large: 18;
$font-size-h1: 64;
$font-weight-light: 300;
$font-weight-regular: 400;
$font-weight-medium: 500;
$font-weight-bold: 600;
$font-weight-black: 900;
$line-height-base: 1.75;
$letter-spacing-headings: -0.025em;

View File

@ -1,35 +0,0 @@
// ==========================================================================
// Base
// ==========================================================================
// Set to 100% for rem sizing
html {
font-size: 100%;
}
body {
@include font-smoothing();
@include font-size($font-size-base);
color: $color-text;
font-family: $font-sans-serif;
font-weight: $font-weight-medium;
line-height: $line-height-base;
text-shadow: 0 1px 1px rgba(#000, 0.15);
}
button,
input,
select,
textarea {
font: inherit;
}
p,
small {
margin: 0 0 $spacing-base;
}
small {
@include font-size($font-size-small);
display: block;
}

View File

@ -1,10 +0,0 @@
// ==========================================================================
// Headings
// ==========================================================================
h1 {
@include font-size($font-size-h1);
font-weight: $font-weight-bold;
letter-spacing: $letter-spacing-headings;
margin: 0 0 ($spacing-base / 2);
}

View File

@ -1,7 +0,0 @@
// ==========================================================================
// Misc cosmetic
// ==========================================================================
.no-border {
border: 0;
}

View File

@ -1,20 +0,0 @@
// ==========================================================================
// Hidden
// ==========================================================================
[hidden] {
display: none;
}
// Hide only visually, but have it available for screen readers: h5bp.com/v
.sr-only {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
opacity: 0.001;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}

12
demo/src/sprite/icon-github.svg Executable file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
C5.9,15.9,6,15.6,6,15.4c0-0.2,0-0.7,0-1.4C3.8,14.5,3.3,13,3.3,13c-0.4-0.9-0.9-1.2-0.9-1.2c-0.7-0.5,0.1-0.5,0.1-0.5
c0.8,0.1,1.2,0.8,1.2,0.8C4.4,13.4,5.6,13,6,12.8c0.1-0.5,0.3-0.9,0.5-1.1c-1.8-0.2-3.6-0.9-3.6-4c0-0.9,0.3-1.6,0.8-2.1
c-0.1-0.2-0.4-1,0.1-2.1c0,0,0.7-0.2,2.2,0.8c0.6-0.2,1.3-0.3,2-0.3c0.7,0,1.4,0.1,2,0.3c1.5-1,2.2-0.8,2.2-0.8
c0.4,1.1,0.2,1.9,0.1,2.1c0.5,0.6,0.8,1.3,0.8,2.1c0,3.1-1.9,3.7-3.7,3.9C9.7,12,10,12.5,10,13.2c0,1.1,0,1.9,0,2.2
c0,0.2,0.1,0.5,0.6,0.4c3.2-1.1,5.5-4.1,5.5-7.6C16,3.8,12.4,0.2,8,0.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<title>Twitter</title>
<path d="M16,3c-0.6,0.3-1.2,0.4-1.9,0.5c0.7-0.4,1.2-1,1.4-1.8c-0.6,0.4-1.3,0.6-2.1,0.8c-0.6-0.6-1.5-1-2.4-1
C9.3,1.5,7.8,3,7.8,4.8c0,0.3,0,0.5,0.1,0.7C5.2,5.4,2.7,4.1,1.1,2.1c-0.3,0.5-0.4,1-0.4,1.7c0,1.1,0.6,2.1,1.5,2.7
c-0.5,0-1-0.2-1.5-0.4c0,0,0,0,0,0c0,1.6,1.1,2.9,2.6,3.2C3,9.4,2.7,9.4,2.4,9.4c-0.2,0-0.4,0-0.6-0.1c0.4,1.3,1.6,2.3,3.1,2.3
c-1.1,0.9-2.5,1.4-4.1,1.4c-0.3,0-0.5,0-0.8,0c1.5,0.9,3.2,1.5,5,1.5c6,0,9.3-5,9.3-9.3c0-0.1,0-0.3,0-0.4C15,4.3,15.6,3.7,16,3z"/>
</svg>

After

Width:  |  Height:  |  Size: 981 B

9
demo/src/sprite/icon-vimeo.svg Executable file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<path d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 779 B

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<path d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
M6,11V5l5,3L6,11z"/>
</svg>

After

Width:  |  Height:  |  Size: 739 B

BIN
dist/blank.mp4 vendored

Binary file not shown.

2
dist/plyr.css vendored

File diff suppressed because one or more lines are too long

7193
dist/plyr.js vendored

File diff suppressed because one or more lines are too long

1
dist/plyr.js.map vendored

File diff suppressed because one or more lines are too long

2
dist/plyr.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

12390
dist/plyr.polyfilled.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/plyr.svg vendored
View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg"><symbol id="plyr-airplay" viewBox="0 0 18 18"><path d="M16 1H2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3v-2H3V3h12v8h-2v2h3a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z"/><path d="M4 17h10l-5-6z"/></symbol><symbol id="plyr-captions-off" viewBox="0 0 18 18"><path d="M1 1c-.6 0-1 .4-1 1v11c0 .6.4 1 1 1h4.6l2.7 2.7c.2.2.4.3.7.3.3 0 .5-.1.7-.3l2.7-2.7H17c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1H1zm4.52 10.15c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41C8.47 4.96 7.46 3.76 5.5 3.76c-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69zm7.57 0c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41c-.28-1.15-1.29-2.35-3.25-2.35-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69z" fill-rule="evenodd" fill-opacity=".5"/></symbol><symbol id="plyr-captions-on" viewBox="0 0 18 18"><path d="M1 1c-.6 0-1 .4-1 1v11c0 .6.4 1 1 1h4.6l2.7 2.7c.2.2.4.3.7.3.3 0 .5-.1.7-.3l2.7-2.7H17c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1H1zm4.52 10.15c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41C8.47 4.96 7.46 3.76 5.5 3.76c-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69zm7.57 0c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41c-.28-1.15-1.29-2.35-3.25-2.35-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69z" fill-rule="evenodd"/></symbol><symbol id="plyr-enter-fullscreen" viewBox="0 0 18 18"><path d="M10 3h3.6l-4 4L11 8.4l4-4V8h2V1h-7zM7 9.6l-4 4V10H1v7h7v-2H4.4l4-4z"/></symbol><symbol id="plyr-exit-fullscreen" viewBox="0 0 18 18"><path d="M1 12h3.6l-4 4L2 17.4l4-4V17h2v-7H1zM16 .6l-4 4V1h-2v7h7V6h-3.6l4-4z"/></symbol><symbol id="plyr-fast-forward" viewBox="0 0 18 18"><path d="M7.875 7.171L0 1v16l7.875-6.171V17L18 9 7.875 1z"/></symbol><symbol id="plyr-muted" viewBox="0 0 18 18"><path d="M12.4 12.5l2.1-2.1 2.1 2.1 1.4-1.4L15.9 9 18 6.9l-1.4-1.4-2.1 2.1-2.1-2.1L11 6.9 13.1 9 11 11.1zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z"/></symbol><symbol id="plyr-pause" viewBox="0 0 18 18"><path d="M6 1H3c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1zM12 1c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1h-3z"/></symbol><symbol id="plyr-pip" viewBox="0 0 18 18"><path d="M13.293 3.293L7.022 9.564l1.414 1.414 6.271-6.271L17 7V1h-6z"/><path d="M13 15H3V5h5V3H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-6h-2v5z"/></symbol><symbol id="plyr-play" viewBox="0 0 18 18"><path d="M15.562 8.1L3.87.225C3.052-.337 2 .225 2 1.125v15.75c0 .9 1.052 1.462 1.87.9L15.563 9.9c.584-.45.584-1.35 0-1.8z"/></symbol><symbol id="plyr-restart" viewBox="0 0 18 18"><path d="M9.7 1.2l.7 6.4 2.1-2.1c1.9 1.9 1.9 5.1 0 7-.9 1-2.2 1.5-3.5 1.5-1.3 0-2.6-.5-3.5-1.5-1.9-1.9-1.9-5.1 0-7 .6-.6 1.4-1.1 2.3-1.3l-.6-1.9C6 2.6 4.9 3.2 4 4.1 1.3 6.8 1.3 11.2 4 14c1.3 1.3 3.1 2 4.9 2 1.9 0 3.6-.7 4.9-2 2.7-2.7 2.7-7.1 0-9.9L16 1.9l-6.3-.7z"/></symbol><symbol id="plyr-rewind" viewBox="0 0 18 18"><path d="M10.125 1L0 9l10.125 8v-6.171L18 17V1l-7.875 6.171z"/></symbol><symbol id="plyr-settings" viewBox="0 0 18 18"><path d="M16.135 7.784a2 2 0 0 1-1.23-2.969c.322-.536.225-.998-.094-1.316l-.31-.31c-.318-.318-.78-.415-1.316-.094a2 2 0 0 1-2.969-1.23C10.065 1.258 9.669 1 9.219 1h-.438c-.45 0-.845.258-.997.865a2 2 0 0 1-2.969 1.23c-.536-.322-.999-.225-1.317.093l-.31.31c-.318.318-.415.781-.093 1.317a2 2 0 0 1-1.23 2.969C1.26 7.935 1 8.33 1 8.781v.438c0 .45.258.845.865.997a2 2 0 0 1 1.23 2.969c-.322.536-.225.998.094 1.316l.31.31c.319.319.782.415 1.316.094a2 2 0 0 1 2.969 1.23c.151.607.547.865.997.865h.438c.45 0 .845-.258.997-.865a2 2 0 0 1 2.969-1.23c.535.321.997.225 1.316-.094l.31-.31c.318-.318.415-.781.094-1.316a2 2 0 0 1 1.23-2.969c.607-.151.865-.547.865-.997v-.438c0-.451-.26-.846-.865-.997zM9 12a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol><symbol id="plyr-volume" viewBox="0 0 18 18"><path d="M15.6 3.3c-.4-.4-1-.4-1.4 0-.4.4-.4 1 0 1.4C15.4 5.9 16 7.4 16 9c0 1.6-.6 3.1-1.8 4.3-.4.4-.4 1 0 1.4.2.2.5.3.7.3.3 0 .5-.1.7-.3C17.1 13.2 18 11.2 18 9s-.9-4.2-2.4-5.7z"/><path d="M11.282 5.282a.909.909 0 0 0 0 1.316c.735.735.995 1.458.995 2.402 0 .936-.425 1.917-.995 2.487a.909.909 0 0 0 0 1.316c.145.145.636.262 1.018.156a.725.725 0 0 0 .298-.156C13.773 11.733 14.13 10.16 14.13 9c0-.17-.002-.34-.011-.51-.053-.992-.319-2.005-1.522-3.208a.909.909 0 0 0-1.316 0zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z"/></symbol></svg>
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg"><symbol id="plyr-captions-off" viewBox="0 0 18 18"><path d="M1 1c-.6 0-1 .4-1 1v11c0 .6.4 1 1 1h4.6l2.7 2.7c.2.2.4.3.7.3.3 0 .5-.1.7-.3l2.7-2.7H17c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1H1zm4.52 10.15c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41C8.47 4.96 7.46 3.76 5.5 3.76c-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69zm7.57 0c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41c-.28-1.15-1.29-2.35-3.25-2.35-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69z" fill-rule="evenodd" fill-opacity=".5"/></symbol><symbol id="plyr-captions-on" viewBox="0 0 18 18"><path d="M1 1c-.6 0-1 .4-1 1v11c0 .6.4 1 1 1h4.6l2.7 2.7c.2.2.4.3.7.3.3 0 .5-.1.7-.3l2.7-2.7H17c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1H1zm4.52 10.15c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41C8.47 4.96 7.46 3.76 5.5 3.76c-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69zm7.57 0c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41c-.28-1.15-1.29-2.35-3.25-2.35-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69z" fill-rule="evenodd"/></symbol><symbol id="plyr-enter-fullscreen" viewBox="0 0 18 18"><path d="M10 3h3.6l-4 4L11 8.4l4-4V8h2V1h-7zM7 9.6l-4 4V10H1v7h7v-2H4.4l4-4z"/></symbol><symbol id="plyr-exit-fullscreen" viewBox="0 0 18 18"><path d="M1 12h3.6l-4 4L2 17.4l4-4V17h2v-7H1zM16 .6l-4 4V1h-2v7h7V6h-3.6l4-4z"/></symbol><symbol id="plyr-fast-forward" viewBox="0 0 18 18"><path d="M7.875 7.171L0 1v16l7.875-6.171V17L18 9 7.875 1z"/></symbol><symbol id="plyr-muted" viewBox="0 0 18 18"><path d="M12.4 12.5l2.1-2.1 2.1 2.1 1.4-1.4L15.9 9 18 6.9l-1.4-1.4-2.1 2.1-2.1-2.1L11 6.9 13.1 9 11 11.1zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z"/></symbol><symbol id="plyr-pause" viewBox="0 0 18 18"><path d="M6 1H3c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1zM12 1c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1h-3z"/></symbol><symbol id="plyr-play" viewBox="0 0 18 18"><path d="M15.562 8.1L3.87.225C3.052-.337 2 .225 2 1.125v15.75c0 .9 1.052 1.462 1.87.9L15.563 9.9c.584-.45.584-1.35 0-1.8z"/></symbol><symbol id="plyr-restart" viewBox="0 0 18 18"><path d="M9.7 1.2l.7 6.4 2.1-2.1c1.9 1.9 1.9 5.1 0 7-.9 1-2.2 1.5-3.5 1.5-1.3 0-2.6-.5-3.5-1.5-1.9-1.9-1.9-5.1 0-7 .6-.6 1.4-1.1 2.3-1.3l-.6-1.9C6 2.6 4.9 3.2 4 4.1 1.3 6.8 1.3 11.2 4 14c1.3 1.3 3.1 2 4.9 2 1.9 0 3.6-.7 4.9-2 2.7-2.7 2.7-7.1 0-9.9L16 1.9l-6.3-.7z"/></symbol><symbol id="plyr-rewind" viewBox="0 0 18 18"><path d="M10.125 1L0 9l10.125 8v-6.171L18 17V1l-7.875 6.171z"/></symbol><symbol id="plyr-volume" viewBox="0 0 18 18"><path d="M15.6 3.3c-.4-.4-1-.4-1.4 0-.4.4-.4 1 0 1.4C15.4 5.9 16 7.4 16 9c0 1.6-.6 3.1-1.8 4.3-.4.4-.4 1 0 1.4.2.2.5.3.7.3.3 0 .5-.1.7-.3C17.1 13.2 18 11.2 18 9s-.9-4.2-2.4-5.7z"/><path d="M11.282 5.282a.909.909 0 0 0 0 1.316c.735.735.995 1.458.995 2.402 0 .936-.425 1.917-.995 2.487a.909.909 0 0 0 0 1.316c.145.145.636.262 1.018.156a.725.725 0 0 0 .298-.156C13.773 11.733 14.13 10.16 14.13 9c0-.17-.002-.34-.011-.51-.053-.992-.319-2.005-1.522-3.208a.909.909 0 0 0-1.316 0zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z"/></symbol></svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -1,407 +1,353 @@
// ==========================================================================
// Gulp build script
// ==========================================================================
/* global require, __dirname */
/* eslint no-console: "off" */
/*global require, __dirname,Buffer*/
/*jshint -W079 */
const del = require('del');
const path = require('path');
const gulp = require('gulp');
const gutil = require('gulp-util');
const concat = require('gulp-concat');
const filter = require('gulp-filter');
const sass = require('gulp-sass');
const cleancss = require('gulp-clean-css');
const run = require('run-sequence');
const prefix = require('gulp-autoprefixer');
const gitbranch = require('git-branch');
const svgstore = require('gulp-svgstore');
const svgmin = require('gulp-svgmin');
const rename = require('gulp-rename');
const s3 = require('gulp-s3');
const replace = require('gulp-replace');
const open = require('gulp-open');
const size = require('gulp-size');
const rollup = require('gulp-better-rollup');
const babel = require('rollup-plugin-babel');
const sourcemaps = require('gulp-sourcemaps');
const uglify = require('gulp-uglify-es').default;
const commonjs = require('rollup-plugin-commonjs');
const resolve = require('rollup-plugin-node-resolve');
var fs = require('fs'),
path = require('path'),
gulp = require('gulp'),
gutil = require('gulp-util'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
less = require('gulp-less'),
sass = require('gulp-sass'),
cleanCSS = require('gulp-clean-css'),
run = require('run-sequence'),
prefix = require('gulp-autoprefixer'),
svgstore = require('gulp-svgstore'),
svgmin = require('gulp-svgmin'),
rename = require('gulp-rename'),
s3 = require('gulp-s3'),
replace = require('gulp-replace'),
open = require('gulp-open'),
size = require('gulp-size'),
through = require('through2');
const bundles = require('./bundles.json');
const pkg = require('./package.json');
var root = __dirname,
paths = {
plyr: {
// Source paths
src: {
less: path.join(root, 'src/less/**/*'),
scss: path.join(root, 'src/scss/**/*'),
js: path.join(root, 'src/js/**/*'),
sprite: path.join(root, 'src/sprite/*.svg'),
},
// Output paths
output: path.join(root, 'dist/'),
},
demo: {
// Source paths
src: {
less: path.join(root, 'demo/src/less/**/*'),
js: path.join(root, 'demo/src/js/**/*'),
sprite: path.join(root, 'demo/src/sprite/**/*'),
},
// Output paths
output: path.join(root, 'demo/dist/'),
// Demo
root: path.join(root, 'demo/'),
},
upload: [path.join(root, 'dist/**'), path.join(root, 'demo/dist/**')],
},
// Task arrays
tasks = {
less: [],
scss: [],
js: [],
sprite: [],
},
// Fetch bundles from JSON
bundles = loadJSON(path.join(root, 'bundles.json')),
package = loadJSON(path.join(root, 'package.json'));
// Get AWS config
let aws = {};
try {
aws = require('./aws.json'); //eslint-disable-line
} catch (e) {
// Do nothing
// Load json
function loadJSON(path) {
try {
return JSON.parse(fs.readFileSync(path));
} catch (err) {
return {};
}
}
const minSuffix = '.min';
// Create a file from a string
// http://stackoverflow.com/questions/23230569/how-do-you-create-a-file-from-a-string-in-gulp
function createFile(filename, string) {
var src = require('stream').Readable({
objectMode: true,
});
src._read = function() {
this.push(
new gutil.File({
cwd: '',
base: '',
path: filename,
contents: new Buffer(string),
// stats also required for some functions
// https://nodejs.org/api/fs.html#fs_class_fs_stats
stat: {
size: string.length,
},
}),
);
this.push(null);
};
return src;
}
// Paths
const root = __dirname;
const paths = {
plyr: {
// Source paths
src: {
sass: path.join(root, 'src/sass/**/*.scss'),
js: path.join(root, 'src/js/**/*'),
sprite: path.join(root, 'src/sprite/*.svg'),
},
var build = {
js: function(files, bundle) {
for (var key in files) {
(function(key) {
var name = 'js-' + key;
tasks.js.push(name);
// Output paths
output: path.join(root, 'dist/'),
gulp.task(name, function() {
return gulp
.src(bundles[bundle].js[key])
.pipe(concat(key))
.pipe(uglify().on('error', gutil.log))
.pipe(gulp.dest(paths[bundle].output));
});
})(key);
}
},
demo: {
// Source paths
src: {
sass: path.join(root, 'demo/src/sass/**/*.scss'),
js: path.join(root, 'demo/src/js/**/*'),
},
less: function(files, bundle) {
for (var key in files) {
(function(key) {
var name = 'less-' + key;
tasks.less.push(name);
// Output paths
output: path.join(root, 'demo/dist/'),
// Demo
root: path.join(root, 'demo/'),
gulp.task(name, function() {
return gulp
.src(bundles[bundle].less[key])
.pipe(less())
.on('error', gutil.log)
.pipe(concat(key))
.pipe(prefix(['last 2 versions'], { cascade: true }))
.pipe(cleanCSS())
.pipe(gulp.dest(paths[bundle].output));
});
})(key);
}
},
upload: [
path.join(root, `dist/*${minSuffix}.js`),
path.join(root, 'dist/*.css'),
path.join(root, 'dist/*.svg'),
path.join(root, 'demo/dist/**'),
],
};
scss: function(files, bundle) {
for (var key in files) {
(function(key) {
var name = 'scss-' + key;
tasks.scss.push(name);
// Task arrays
const tasks = {
sass: [],
js: [],
sprite: [],
clean: ['clean'],
};
// Size plugin
const sizeOptions = { showFiles: true, gzip: true };
// Browserlist
const browsers = ['> 1%'];
// Babel config
const babelrc = {
presets: [[
'env',
{
targets: {
browsers,
},
useBuiltIns: true,
modules: false,
},
]],
plugins: ['external-helpers'],
babelrc: false,
exclude: 'node_modules/**',
};
// Clean out /dist
gulp.task('clean', () => {
const dirs = [
paths.plyr.output,
paths.demo.output,
].map(dir => path.join(dir, '**/*'));
// Don't delete the mp4
dirs.push(`!${path.join(paths.plyr.output, '**/*.mp4')}`);
del(dirs);
});
const build = {
js(files, bundle, options) {
Object.keys(files).forEach(key => {
const name = `js:${key}`;
tasks.js.push(name);
const { output } = paths[bundle];
gulp.task(name, () =>
gulp
.src(bundles[bundle].js[key])
.pipe(sourcemaps.init())
.pipe(concat(key))
.pipe(
rollup(
{
plugins: [
resolve(),
commonjs(),
babel(babelrc),
],
},
options,
),
)
.pipe(sourcemaps.write(''))
.pipe(gulp.dest(output))
.pipe(filter('**/*.js'))
.pipe(uglify())
.pipe(size(sizeOptions))
.pipe(rename({ suffix: minSuffix }))
.pipe(sourcemaps.write(''))
.pipe(gulp.dest(output)),
);
});
gulp.task(name, function() {
return gulp
.src(bundles[bundle].scss[key])
.pipe(sass())
.on('error', gutil.log)
.pipe(concat(key))
.pipe(prefix(['last 2 versions'], { cascade: true }))
.pipe(cleanCSS())
.pipe(gulp.dest(paths[bundle].output));
});
})(key);
}
},
sass(files, bundle) {
Object.keys(files).forEach(key => {
const name = `sass:${key}`;
tasks.sass.push(name);
gulp.task(name, () =>
gulp
.src(bundles[bundle].sass[key])
.pipe(sass())
.on('error', gutil.log)
.pipe(concat(key))
.pipe(prefix(browsers, { cascade: false }))
.pipe(cleancss())
.pipe(size(sizeOptions))
.pipe(gulp.dest(paths[bundle].output)),
);
});
},
sprite(bundle) {
const name = `svg:sprite:${bundle}`;
sprite: function(bundle) {
var name = 'sprite-' + bundle;
tasks.sprite.push(name);
// Process Icons
gulp.task(name, () =>
gulp
gulp.task(name, function() {
return gulp
.src(paths[bundle].src.sprite)
.pipe(
svgmin({
plugins: [{
removeDesc: true,
}],
plugins: [
{
removeDesc: true,
},
],
}),
)
.pipe(svgstore())
.pipe(rename({ basename: bundle }))
.pipe(size(sizeOptions))
.pipe(gulp.dest(paths[bundle].output)),
);
.pipe(gulp.dest(paths[bundle].output));
});
},
};
// Plyr core files
build.js(bundles.plyr.js, 'plyr', { name: 'Plyr', format: 'umd' });
build.sass(bundles.plyr.sass, 'plyr');
build.js(bundles.plyr.js, 'plyr');
build.less(bundles.plyr.less, 'plyr');
build.scss(bundles.plyr.scss, 'plyr');
build.sprite('plyr');
// Demo files
build.sass(bundles.demo.sass, 'demo');
build.js(bundles.demo.js, 'demo', { format: 'iife' });
build.less(bundles.demo.less, 'demo');
build.js(bundles.demo.js, 'demo');
build.sprite('demo');
// Build all JS
gulp.task('js', () => {
gulp.task('js', function() {
run(tasks.js);
});
// Build SCSS (for testing, default is LESS)
gulp.task('scss', function() {
run(tasks.scss);
});
// Watch for file changes
gulp.task('watch', () => {
gulp.task('watch', function() {
// Plyr core
gulp.watch(paths.plyr.src.js, tasks.js);
gulp.watch(paths.plyr.src.sass, tasks.sass);
gulp.watch(paths.plyr.src.less, tasks.less);
gulp.watch(paths.plyr.src.sprite, tasks.sprite);
// Demo
gulp.watch(paths.demo.src.js, tasks.js);
gulp.watch(paths.demo.src.sass, tasks.sass);
gulp.watch(paths.demo.src.less, tasks.less);
gulp.watch(paths.demo.src.sprite, tasks.sprite);
});
// Default gulp task
gulp.task('default', () => {
run(tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'watch');
gulp.task('default', function() {
run(tasks.js, tasks.less, tasks.sprite, 'watch');
});
// Publish a version to CDN and demo
// --------------------------------------------
// If aws is setup
if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
const { version } = pkg;
// Get branch info
const branch = {
current: gitbranch.sync(),
master: 'master',
beta: 'beta',
};
const allowed = [
branch.master,
branch.beta,
];
const maxAge = 31536000; // 1 year
const options = {
// Some options
var aws = loadJSON(path.join(root, 'aws.json')),
version = package.version,
maxAge = 31536000, // seconds 1 year
options = {
cdn: {
headers: {
'Cache-Control': `max-age=${maxAge}`,
'Cache-Control': 'max-age=' + maxAge,
Vary: 'Accept-Encoding',
},
},
demo: {
uploadPath: branch.current === branch.beta ? 'beta/' : null,
headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
Vary: 'Accept-Encoding',
},
},
symlinks(ver, filename) {
symlinks: function(version, filename) {
return {
headers: {
// http://stackoverflow.com/questions/2272835/amazon-s3-object-redirect
'x-amz-website-redirect-location': `/${ver}/${filename}`,
'x-amz-website-redirect-location': '/' + version + '/' + filename,
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
},
};
},
};
const regex = '(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*).(?:0|[1-9][0-9]*)(?:-[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?';
const semver = new RegExp(`v${regex}`, 'gi');
const localPath = new RegExp('(../)?dist', 'gi');
const versionPath = `https://${aws.cdn.domain}/${version}`;
const cdnpath = new RegExp(`${aws.cdn.domain}/${regex}/`, 'gi');
gulp.task('version', () => {
console.log(`Updating versions to '${version}'...`);
// Replace versioned URLs in source
const files = [
'plyr.js',
'plyr.polyfilled.js',
'defaults.js',
];
gulp
.src(files.map(file => path.join(root, `src/js/${file}`)))
.pipe(replace(semver, `v${version}`))
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
.pipe(gulp.dest(path.join(root, 'src/js/')));
});
// Publish version to CDN bucket
gulp.task('cdn', () => {
if (!allowed.includes(branch.current)) {
console.error(`Must be on ${allowed.join(', ')} to publish! (current: ${branch.current})`);
return null;
}
console.log(`Uploading '${version}' to ${aws.cdn.domain}...`);
// Upload to CDN
return gulp
.src(paths.upload)
.pipe(
rename(p => {
p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line
p.dirname = p.dirname.replace('.', version); // eslint-disable-line
}),
)
.pipe(
size({
showFiles: true,
gzip: true,
}),
)
.pipe(replace(localPath, versionPath))
.pipe(s3(aws.cdn, options.cdn));
});
// Publish to demo bucket
gulp.task('demo', () => {
if (!allowed.includes(branch.current)) {
console.error(`Must be on ${allowed.join(', ')} to publish! (current: ${branch.current})`);
return null;
}
console.log(`Uploading '${version}' demo to ${aws.demo.domain}...`);
// Replace versioned files in readme.md
gulp
.src([`${root}/readme.md`])
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
.pipe(gulp.dest(root));
// Replace local file paths with remote paths in demo HTML
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
const index = `${paths.demo.root}index.html`;
const error = `${paths.demo.root}error.html`;
const pages = [index];
if (branch.current === branch.master) {
pages.push(error);
}
gulp
.src(pages)
.pipe(replace(localPath, versionPath))
.pipe(s3(aws.demo, options.demo));
// Only update CDN for master (prod)
if (branch.current !== branch.master) {
return null;
}
// Upload error.html to cdn (as well as demo site)
return gulp
.src([error])
.pipe(replace(localPath, versionPath))
.pipe(s3(aws.cdn, options.demo));
});
// Update symlinks for latest
/* gulp.task("symlinks", function () {
console.log("Updating symlinks...");
return gulp.src(paths.upload)
.pipe(through.obj(function (chunk, enc, callback) {
if (chunk.stat.isFile()) {
// Get the filename
var filename = chunk.path.split("/").reverse()[0];
// Create the 0 byte redirect files to upload
createFile(filename, "")
.pipe(rename(function (path) {
path.dirname = path.dirname.replace(".", "latest");
}))
// Upload to S3 with correct headers
.pipe(s3(aws.cdn, options.symlinks(version, filename)));
}
callback(null, chunk);
}));
}); */
// Open the demo site to check it's sweet
gulp.task('open', () => {
console.log(`Opening ${aws.demo.domain}...`);
// A file must be specified or gulp will skip the task
// Doesn't matter which file since we set the URL above
// Weird, I know...
return gulp.src([`${paths.demo.root}index.html`]).pipe(
open('', {
url: `http://${aws.demo.domain}`,
}),
);
});
// Do everything
gulp.task('publish', () => {
run('version', tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'cdn', 'demo');
});
// If aws is setup
if ('cdn' in aws) {
var regex = '(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*).(?:0|[1-9][0-9]*)(?:-[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?',
cdnpath = new RegExp(aws.cdn.domain + '/' + regex, 'gi'),
semver = new RegExp('v' + regex, 'gi'),
localPath = new RegExp('(../)?dist', 'gi'),
versionPath = 'https://' + aws.cdn.domain + '/' + version;
}
// Publish version to CDN bucket
gulp.task('cdn', function() {
console.log('Uploading ' + version + ' to ' + aws.cdn.domain + '...');
// Upload to CDN
return gulp
.src(paths.upload)
.pipe(
size({
showFiles: true,
gzip: true,
}),
)
.pipe(
rename(function(path) {
path.dirname = path.dirname.replace('.', version);
}),
)
.pipe(replace(localPath, versionPath))
.pipe(s3(aws.cdn, options.cdn));
});
// Publish to demo bucket
gulp.task('demo', function() {
console.log('Uploading ' + version + ' demo to ' + aws.demo.domain + '...');
// Replace versioned files in readme.md
gulp
.src([root + '/readme.md'])
.pipe(replace(cdnpath, aws.cdn.domain + '/' + version))
.pipe(gulp.dest(root));
// Replace versioned files in plyr.js
gulp
.src(path.join(root, 'src/js/plyr.js'))
.pipe(replace(semver, 'v' + version))
.pipe(replace(cdnpath, aws.cdn.domain + '/' + version))
.pipe(gulp.dest(path.join(root, 'src/js/')));
// Replace local file paths with remote paths in demo HTML
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
gulp
.src([paths.demo.root + '*.html'])
.pipe(replace(localPath, versionPath))
.pipe(s3(aws.demo, options.demo));
// Upload error.html to cdn (as well as demo site)
return gulp
.src([paths.demo.root + 'error.html'])
.pipe(replace(localPath, versionPath))
.pipe(s3(aws.cdn, options.demo));
});
// Open the demo site to check it's sweet
gulp.task('symlinks', function() {
console.log('Updating symlinks...');
return gulp.src(paths.upload).pipe(
through.obj(function(chunk, enc, callback) {
if (chunk.stat.isFile()) {
// Get the filename
var filename = chunk.path.split('/').reverse()[0];
// Create the 0 byte redirect files to upload
createFile(filename, '')
.pipe(
rename(function(path) {
path.dirname = path.dirname.replace('.', 'latest');
}),
)
// Upload to S3 with correct headers
.pipe(s3(aws.cdn, options.symlinks(version, filename)));
}
callback(null, chunk);
}),
);
});
// Open the demo site to check it's sweet
gulp.task('open', function() {
console.log('Opening ' + aws.demo.domain + '...');
// A file must be specified or gulp will skip the task
// Doesn't matter which file since we set the URL above
// Weird, I know...
return gulp.src([paths.demo.root + 'index.html']).pipe(
open('', {
url: 'http://' + aws.demo.domain,
}),
);
});
// Do everything
gulp.task('publish', function() {
run(tasks.js, tasks.less, tasks.sprite, 'cdn', 'demo', 'symlinks');
});

4604
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +1,28 @@
{
"name": "plyr",
"version": "3.0.0-beta.14",
"version": "2.0.18",
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
"homepage": "https://plyr.io",
"main": "./dist/plyr.js",
"browser": "./dist/plyr.min.js",
"sass": "./src/sass/plyr.scss",
"style": "./dist/plyr.css",
"homepage": "http://plyr.io",
"main": "src/js/plyr.js",
"dependencies": {},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.1",
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.6.1",
"del": "^3.0.0",
"eslint": "^4.18.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.8.0",
"git-branch": "^1.0.0",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^4.1.0",
"gulp-better-rollup": "^3.0.0",
"gulp-clean-css": "^3.9.2",
"gulp-autoprefixer": "^4.0.0",
"gulp-clean-css": "^3.9.0",
"gulp-concat": "^2.6.1",
"gulp-filter": "^5.1.0",
"gulp-open": "^2.1.0",
"gulp-less": "^3.3.2",
"gulp-open": "^2.0.0",
"gulp-rename": "^1.2.2",
"gulp-replace": "^0.6.1",
"gulp-s3": "^0.11.0",
"gulp-sass": "^3.1.0",
"gulp-size": "^3.0.0",
"gulp-sourcemaps": "^2.6.4",
"gulp-size": "^2.1.0",
"gulp-svgmin": "^1.2.4",
"gulp-svgstore": "^6.1.1",
"gulp-uglify-es": "^1.0.0",
"gulp-svgstore": "^6.1.0",
"gulp-uglify": "^3.0.0",
"gulp-util": "^3.0.8",
"rollup-plugin-babel": "^3.0.3",
"rollup-plugin-commonjs": "^8.3.0",
"rollup-plugin-node-resolve": "^3.0.3",
"run-sequence": "^2.2.1",
"stylelint": "^8.4.0",
"stylelint-config-prettier": "^2.0.0",
"stylelint-config-sass-guidelines": "^4.1.0",
"stylelint-config-standard": "^18.0.0",
"stylelint-order": "^0.8.0",
"stylelint-scss": "^2.3.0",
"stylelint-selector-bem-pattern": "^2.0.0"
"run-sequence": "^2.2.0",
"through2": "^2.0.3"
},
"keywords": [
"HTML5 Video",
@ -70,9 +47,5 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Sam Potts <sam@potts.es>",
"dependencies": {
"babel-polyfill": "^6.26.0",
"custom-event-polyfill": "^0.3.0"
}
"author": "Sam Potts <sam@potts.es>"
}

View File

@ -1,31 +0,0 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
// Exclude from the editor
"files.exclude": {
"**/node_modules": true
},
// Exclude from search
"search.exclude": {
"dist/": true
},
// Linting
"stylelint.enable": true,
"css.validate": false,
"scss.validate": false,
"javascript.validate.enable": false,
// Prettier
"prettier.eslintIntegration": true,
"prettier.stylelintIntegration": true,
// Formatting
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.formatOnSave": true,
// Trim on save
"files.trimTrailingWhitespace": true
}
}

1373
readme.md

File diff suppressed because it is too large Load Diff

View File

@ -1,200 +0,0 @@
// ==========================================================================
// Plyr Captions
// ==========================================================================
import support from './support';
import utils from './utils';
import controls from './controls';
const captions = {
// Setup captions
setup() {
// Requires UI support
if (!this.supported.ui) {
return;
}
// Set default language if not set
const stored = this.storage.get('language');
if (!utils.is.empty(stored)) {
this.captions.language = stored;
}
if (utils.is.empty(this.captions.language)) {
this.captions.language = this.config.captions.language.toLowerCase();
}
// Set captions enabled state if not set
if (!utils.is.boolean(this.captions.active)) {
const active = this.storage.get('captions');
if (utils.is.boolean(active)) {
this.captions.active = active;
} else {
this.captions.active = this.config.captions.active;
}
}
// Only Vimeo and HTML5 video supported at this point
if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
// Clear menu and hide
if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this);
}
return;
}
// Inject the container
if (!utils.is.element(this.elements.captions)) {
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
utils.insertAfter(this.elements.captions, this.elements.wrapper);
}
// Set the class hook
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
// If no caption file exists, hide container for caption text
if (utils.is.empty(captions.getTracks.call(this))) {
return;
}
// Set language
captions.setLanguage.call(this);
// Enable UI
captions.show.call(this);
// Set available languages in list
if (utils.is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
controls.setCaptionsMenu.call(this);
}
},
// Set the captions language
setLanguage() {
// Setup HTML5 track rendering
if (this.isHTML5 && this.isVideo) {
captions.getTracks.call(this).forEach(track => {
// Show track
utils.on(track, 'cuechange', event => captions.setCue.call(this, event));
// Turn off native caption rendering to avoid double captions
// eslint-disable-next-line
track.mode = 'hidden';
});
// Get current track
const currentTrack = captions.getCurrentTrack.call(this);
// Check if suported kind
if (utils.is.track(currentTrack)) {
// If we change the active track while a cue is already displayed we need to update it
if (Array.from(currentTrack.activeCues || []).length) {
captions.setCue.call(this, currentTrack);
}
}
} else if (this.isVimeo && this.captions.active) {
this.embed.enableTextTrack(this.language);
}
},
// Get the tracks
getTracks() {
// Return empty array at least
if (utils.is.nullOrUndefined(this.media)) {
return [];
}
// Only get accepted kinds
return Array.from(this.media.textTracks || []).filter(track => [
'captions',
'subtitles',
].includes(track.kind));
},
// Get the current track for the current language
getCurrentTrack() {
return captions.getTracks.call(this).find(track => track.language.toLowerCase() === this.language);
},
// Display active caption if it contains text
setCue(input) {
// Get the track from the event if needed
const track = utils.is.event(input) ? input.target : input;
const { activeCues } = track;
const active = activeCues.length && activeCues[0];
const currentTrack = captions.getCurrentTrack.call(this);
// Only display current track
if (track !== currentTrack) {
return;
}
// Display a cue, if there is one
if (utils.is.cue(active)) {
captions.setText.call(this, active.getCueAsHTML());
} else {
captions.setText.call(this, null);
}
utils.dispatchEvent.call(this, this.media, 'cuechange');
},
// Set the current caption
setText(input) {
// Requires UI
if (!this.supported.ui) {
return;
}
if (utils.is.element(this.elements.captions)) {
const content = utils.createElement('span');
// Empty the container
utils.emptyElement(this.elements.captions);
// Default to empty
const caption = !utils.is.nullOrUndefined(input) ? input : '';
// Set the span content
if (utils.is.string(caption)) {
content.textContent = caption.trim();
} else {
content.appendChild(caption);
}
// Set new caption text
this.elements.captions.appendChild(content);
} else {
this.debug.warn('No captions element to render to');
}
},
// Display captions container and button (for initialization)
show() {
// If there's no caption toggle, bail
if (!utils.is.element(this.elements.buttons.captions)) {
return;
}
// Try to load the value from storage
let active = this.storage.get('captions');
// Otherwise fall back to the default config
if (!utils.is.boolean(active)) {
({ active } = this.config.captions);
} else {
this.captions.active = active;
}
if (active) {
utils.toggleClass(this.elements.container, this.config.classNames.captions.active, true);
utils.toggleState(this.elements.buttons.captions, true);
}
},
};
export default captions;

View File

@ -1,28 +0,0 @@
// ==========================================================================
// Console wrapper
// ==========================================================================
const noop = () => {};
export default class Console {
constructor(enabled = false) {
this.enabled = window.console && enabled;
if (this.enabled) {
this.log('Debugging enabled');
}
}
get log() {
// eslint-disable-next-line no-console
return this.enabled ? Function.prototype.bind.call(console.log, console) : noop;
}
get warn() {
// eslint-disable-next-line no-console
return this.enabled ? Function.prototype.bind.call(console.warn, console) : noop;
}
get error() {
// eslint-disable-next-line no-console
return this.enabled ? Function.prototype.bind.call(console.error, console) : noop;
}
}

1236
src/js/controls.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,382 +0,0 @@
// ==========================================================================
// Plyr default config
// ==========================================================================
const defaults = {
// Disable
enabled: true,
// Custom media title
title: '',
// Logging to console
debug: false,
// Auto play (if supported)
autoplay: false,
// Only allow one media playing at once (vimeo only)
autopause: true,
// Default time to skip when rewind/fast forward
seekTime: 10,
// Default volume
volume: 1,
muted: false,
// Pass a custom duration
duration: null,
// Display the media duration on load in the current time position
// If you have opted to display both duration and currentTime, this is ignored
displayDuration: true,
// Invert the current time to be a countdown
invertTime: true,
// Clicking the currentTime inverts it's value to show time left rather than elapsed
toggleInvert: true,
// Aspect ratio (for embeds)
ratio: '16:9',
// Click video container to play/pause
clickToPlay: true,
// Auto hide the controls
hideControls: true,
// Revert to poster on finish (HTML5 - will cause reload)
showPosterOnEnd: false,
// Disable the standard context menu
disableContextMenu: true,
// Sprite (for icons)
loadSprite: true,
iconPrefix: 'plyr',
iconUrl: 'https://cdn.plyr.io/3.0.0-beta.14/plyr.svg',
// Blank video (used to prevent errors on source change)
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
// Quality default
quality: {
default: 'default',
options: [
'hd2160',
'hd1440',
'hd1080',
'hd720',
'large',
'medium',
'small',
'tiny',
'default',
],
},
// Set loops
loop: {
active: false,
// start: null,
// end: null,
},
// Speed default and options to display
speed: {
selected: 1,
options: [
0.5,
0.75,
1,
1.25,
1.5,
1.75,
2,
],
},
// Keyboard shortcut settings
keyboard: {
focused: true,
global: false,
},
// Display tooltips
tooltips: {
controls: false,
seek: true,
},
// Captions settings
captions: {
active: false,
language: window.navigator.language.split('-')[0],
},
// Fullscreen settings
fullscreen: {
enabled: true, // Allow fullscreen?
fallback: true, // Fallback for vintage browsers
iosNative: false, // Use the native fullscreen in iOS (disables custom controls)
},
// Local storage
storage: {
enabled: true,
key: 'plyr',
},
// Default controls
controls: [
'play-large',
'play',
'progress',
'current-time',
'mute',
'volume',
'captions',
'settings',
'pip',
'airplay',
'fullscreen',
],
settings: [
'captions',
'quality',
'speed',
],
// Localisation
i18n: {
restart: 'Restart',
rewind: 'Rewind {seektime} secs',
play: 'Play',
pause: 'Pause',
forward: 'Forward {seektime} secs',
seek: 'Seek',
played: 'Played',
buffered: 'Buffered',
currentTime: 'Current time',
duration: 'Duration',
volume: 'Volume',
mute: 'Mute',
unmute: 'Unmute',
enableCaptions: 'Enable captions',
disableCaptions: 'Disable captions',
enterFullscreen: 'Enter fullscreen',
exitFullscreen: 'Exit fullscreen',
frameTitle: 'Player for {title}',
captions: 'Captions',
settings: 'Settings',
speed: 'Speed',
quality: 'Quality',
loop: 'Loop',
start: 'Start',
end: 'End',
all: 'All',
reset: 'Reset',
none: 'None',
disabled: 'Disabled',
advertisment: 'Ad',
},
// URLs
urls: {
vimeo: {
api: 'https://player.vimeo.com/api/player.js',
},
youtube: {
api: 'https://www.youtube.com/iframe_api',
},
googleIMA: {
api: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
},
},
// Custom control listeners
listeners: {
seek: null,
play: null,
pause: null,
restart: null,
rewind: null,
forward: null,
mute: null,
volume: null,
captions: null,
fullscreen: null,
pip: null,
airplay: null,
speed: null,
quality: null,
loop: null,
language: null,
},
// Events to watch and bubble
events: [
// Events to watch on HTML5 media elements and bubble
// https://developer.mozilla.org/en/docs/Web/Guide/Events/Media_events
'ended',
'progress',
'stalled',
'playing',
'waiting',
'canplay',
'canplaythrough',
'loadstart',
'loadeddata',
'loadedmetadata',
'timeupdate',
'volumechange',
'play',
'pause',
'error',
'seeking',
'seeked',
'emptied',
'ratechange',
'cuechange',
// Custom events
'enterfullscreen',
'exitfullscreen',
'captionsenabled',
'captionsdisabled',
'languagechange',
'controlshidden',
'controlsshown',
'ready',
// YouTube
'statechange',
'qualitychange',
'qualityrequested',
// Ads
'adsloaded',
'adscontentpause',
'adsconentresume',
'adstarted',
'adsmidpoint',
'adscomplete',
'adsallcomplete',
'adsimpression',
'adsclick',
],
// Selectors
// Change these to match your template if using custom HTML
selectors: {
editable: 'input, textarea, select, [contenteditable]',
container: '.plyr',
controls: {
container: null,
wrapper: '.plyr__controls',
},
labels: '[data-plyr]',
buttons: {
play: '[data-plyr="play"]',
pause: '[data-plyr="pause"]',
restart: '[data-plyr="restart"]',
rewind: '[data-plyr="rewind"]',
forward: '[data-plyr="fast-forward"]',
mute: '[data-plyr="mute"]',
captions: '[data-plyr="captions"]',
fullscreen: '[data-plyr="fullscreen"]',
pip: '[data-plyr="pip"]',
airplay: '[data-plyr="airplay"]',
settings: '[data-plyr="settings"]',
loop: '[data-plyr="loop"]',
},
inputs: {
seek: '[data-plyr="seek"]',
volume: '[data-plyr="volume"]',
speed: '[data-plyr="speed"]',
language: '[data-plyr="language"]',
quality: '[data-plyr="quality"]',
},
display: {
currentTime: '.plyr__time--current',
duration: '.plyr__time--duration',
buffer: '.plyr__progress--buffer',
played: '.plyr__progress--played',
loop: '.plyr__progress--loop',
volume: '.plyr__volume--display',
},
progress: '.plyr__progress',
captions: '.plyr__captions',
menu: {
quality: '.js-plyr__menu__list--quality',
},
},
// Class hooks added to the player in different states
classNames: {
video: 'plyr__video-wrapper',
embed: 'plyr__video-embed',
ads: 'plyr__ads',
control: 'plyr__control',
type: 'plyr--{0}',
provider: 'plyr--{0}',
stopped: 'plyr--stopped',
playing: 'plyr--playing',
loading: 'plyr--loading',
error: 'plyr--has-error',
hover: 'plyr--hover',
tooltip: 'plyr__tooltip',
cues: 'plyr__cues',
hidden: 'plyr__sr-only',
hideControls: 'plyr--hide-controls',
isIos: 'plyr--is-ios',
isTouch: 'plyr--is-touch',
uiSupported: 'plyr--full-ui',
noTransition: 'plyr--no-transition',
menu: {
value: 'plyr__menu__value',
badge: 'plyr__badge',
open: 'plyr--menu-open',
},
captions: {
enabled: 'plyr--captions-enabled',
active: 'plyr--captions-active',
},
fullscreen: {
enabled: 'plyr--fullscreen-enabled',
fallback: 'plyr--fullscreen-fallback',
},
pip: {
supported: 'plyr--pip-supported',
active: 'plyr--pip-active',
},
airplay: {
supported: 'plyr--airplay-supported',
active: 'plyr--airplay-active',
},
tabFocus: 'plyr__tab-focus',
},
// Embed attributes
attributes: {
embed: {
provider: 'data-plyr-provider',
id: 'data-plyr-embed-id',
},
},
// API keys
keys: {
google: null,
},
// Advertisements plugin
// Tag is not required as publisher is determined by vi.ai using the domain
ads: {
enabled: false,
},
};
export default defaults;

View File

@ -1,204 +0,0 @@
// ==========================================================================
// Fullscreen wrapper
// ==========================================================================
import utils from './utils';
const browser = utils.getBrowser();
function onChange() {
if (!this.enabled) {
return;
}
// Update toggle button
const button = this.player.elements.buttons.fullscreen;
if (utils.is.element(button)) {
utils.toggleState(button, this.active);
}
// Trigger an event
utils.dispatchEvent(this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);
// Trap focus in container
if (!browser.isIos) {
utils.trapFocus.call(this.player, this.target, this.active);
}
}
function toggleFallback(toggle = false) {
// Store or restore scroll position
if (toggle) {
this.scrollPosition = {
x: window.scrollX || 0,
y: window.scrollY || 0,
};
} else {
window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
}
// Toggle scroll
document.body.style.overflow = toggle ? 'hidden' : '';
// Toggle class hook
utils.toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
// Toggle button and fire events
onChange.call(this);
}
class Fullscreen {
constructor(player) {
// Keep reference to parent
this.player = player;
// Get prefix
this.prefix = Fullscreen.prefix;
// Scroll position
this.scrollPosition = { x: 0, y: 0 };
// Register event listeners
// Handle event (incase user presses escape etc)
utils.on(document, this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, () => {
// TODO: Filter for target??
onChange.call(this);
});
// Fullscreen toggle on double click
utils.on(this.player.elements.container, 'dblclick', () => {
this.toggle();
});
// Prevent double click on controls bubbling up
utils.on(this.player.elements.controls, 'dblclick', event => event.stopPropagation());
// Update the UI
this.update();
}
// Determine if native supported
static get native() {
return !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled);
}
// Get the prefix for handlers
static get prefix() {
// No prefix
if (utils.is.function(document.cancelFullScreen)) {
return false;
}
// Check for fullscreen support by vendor prefix
let value = '';
const prefixes = [
'webkit',
'moz',
'ms',
];
prefixes.some(pre => {
if (utils.is.function(document[`${pre}CancelFullScreen`])) {
value = pre;
return true;
} else if (utils.is.function(document.msExitFullscreen)) {
value = 'ms';
return true;
}
return false;
});
return value;
}
// Determine if fullscreen is enabled
get enabled() {
const fallback = this.player.config.fullscreen.fallback && !utils.inFrame();
return (Fullscreen.native || fallback) && this.player.config.fullscreen.enabled && this.player.supported.ui && this.player.isVideo;
}
// Get active state
get active() {
if (!this.enabled) {
return false;
}
// Fallback using classname
if (!Fullscreen.native) {
return utils.hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
}
const element = !this.prefix ? document.fullscreenElement : document[`${this.prefix}FullscreenElement`];
return element === this.target;
}
// Get target element
get target() {
return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
}
// Update UI
update() {
if (this.enabled) {
this.player.debug.log(`${Fullscreen.native ? 'Native' : 'Fallback'} fullscreen enabled`);
} else {
this.player.debug.log('Fullscreen not supported and fallback disabled');
}
// Add styling hook to show button
utils.toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled);
}
// Make an element fullscreen
enter() {
if (!this.enabled) {
return;
}
// iOS native fullscreen doesn't need the request step
if (browser.isIos && this.player.config.fullscreen.iosNative) {
if (this.player.playing) {
this.target.webkitEnterFullscreen();
}
} else if (!Fullscreen.native) {
toggleFallback.call(this, true);
} else if (!this.prefix) {
this.target.requestFullScreen();
} else if (!utils.is.empty(this.prefix)) {
this.target[`${this.prefix}${this.prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen'}`]();
}
}
// Bail from fullscreen
exit() {
if (!this.enabled) {
return;
}
// iOS native fullscreen
if (browser.isIos && this.player.config.fullscreen.iosNative) {
this.target.webkitExitFullscreen();
this.player.play();
} else if (!Fullscreen.native) {
toggleFallback.call(this, false);
} else if (!this.prefix) {
document.cancelFullScreen();
} else if (!utils.is.empty(this.prefix)) {
document[`${this.prefix}${this.prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen'}`]();
}
}
// Toggle state
toggle() {
if (!this.active) {
this.enter();
} else {
this.exit();
}
}
}
export default Fullscreen;

View File

@ -1,578 +0,0 @@
// ==========================================================================
// Plyr Event Listeners
// ==========================================================================
import support from './support';
import utils from './utils';
import controls from './controls';
import ui from './ui';
// Sniff out the browser
const browser = utils.getBrowser();
const listeners = {
// Global listeners
global() {
let last = null;
// Get the key code for an event
const getKeyCode = event => (event.keyCode ? event.keyCode : event.which);
// Handle key press
const handleKey = event => {
const code = getKeyCode(event);
const pressed = event.type === 'keydown';
const repeat = pressed && code === last;
// Bail if a modifier key is set
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
return;
}
// If the event is bubbled from the media element
// Firefox doesn't get the keycode for whatever reason
if (!utils.is.number(code)) {
return;
}
// Seek by the number keys
const seekByKey = () => {
// Divide the max duration into 10th's and times by the number value
this.currentTime = this.duration / 10 * (code - 48);
};
// Handle the key on keydown
// Reset on keyup
if (pressed) {
// Which keycodes should we prevent default
const preventDefault = [
48,
49,
50,
51,
52,
53,
54,
56,
57,
32,
75,
38,
40,
77,
39,
37,
70,
67,
73,
76,
79,
];
// Check focused element
// and if the focused element is not editable (e.g. text input)
// and any that accept key input http://webaim.org/techniques/keyboard/
const focused = utils.getFocusElement();
if (utils.is.element(focused) && utils.matches(focused, this.config.selectors.editable)) {
return;
}
// If the code is found prevent default (e.g. prevent scrolling for arrows)
if (preventDefault.includes(code)) {
event.preventDefault();
event.stopPropagation();
}
switch (code) {
case 48:
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
// 0-9
if (!repeat) {
seekByKey();
}
break;
case 32:
case 75:
// Space and K key
if (!repeat) {
this.togglePlay();
}
break;
case 38:
// Arrow up
this.increaseVolume(0.1);
break;
case 40:
// Arrow down
this.decreaseVolume(0.1);
break;
case 77:
// M key
if (!repeat) {
this.muted = !this.muted;
}
break;
case 39:
// Arrow forward
this.forward();
break;
case 37:
// Arrow back
this.rewind();
break;
case 70:
// F key
this.fullscreen.toggle();
break;
case 67:
// C key
if (!repeat) {
this.toggleCaptions();
}
break;
case 76:
// L key
this.loop = !this.loop;
break;
/* case 73:
this.setLoop('start');
break;
case 76:
this.setLoop();
break;
case 79:
this.setLoop('end');
break; */
default:
break;
}
// Escape is handle natively when in full screen
// So we only need to worry about non native
if (!this.fullscreen.enabled && this.fullscreen.active && code === 27) {
this.fullscreen.toggle();
}
// Store last code for next cycle
last = code;
} else {
last = null;
}
};
// Keyboard shortcuts
if (this.config.keyboard.global) {
utils.on(window, 'keydown keyup', handleKey, false);
} else if (this.config.keyboard.focused) {
utils.on(this.elements.container, 'keydown keyup', handleKey, false);
}
// Detect tab focus
// Remove class on blur/focusout
utils.on(this.elements.container, 'focusout', event => {
utils.toggleClass(event.target, this.config.classNames.tabFocus, false);
});
// Add classname to tabbed elements
utils.on(this.elements.container, 'keydown', event => {
if (event.keyCode !== 9) {
return;
}
// Delay the adding of classname until the focus has changed
// This event fires before the focusin event
window.setTimeout(() => {
utils.toggleClass(utils.getFocusElement(), this.config.classNames.tabFocus, true);
}, 0);
});
// Toggle controls visibility based on mouse movement
if (this.config.hideControls) {
// Toggle controls on mouse events and entering fullscreen
utils.on(this.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', event => {
this.toggleControls(event);
});
}
},
// Listen for media events
media() {
// Time change on media
utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event));
// Display duration
utils.on(this.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this, event));
// Check for audio tracks on load
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
utils.on(this.media, 'loadeddata', () => {
utils.toggleHidden(this.elements.volume, !this.hasAudio);
utils.toggleHidden(this.elements.buttons.mute, !this.hasAudio);
});
// Handle the media finishing
utils.on(this.media, 'ended', () => {
// Show poster on end
if (this.isHTML5 && this.isVideo && this.config.showPosterOnEnd) {
// Restart
this.restart();
// Re-load media
this.media.load();
}
});
// Check for buffer progress
utils.on(this.media, 'progress playing', event => ui.updateProgress.call(this, event));
// Handle native mute
utils.on(this.media, 'volumechange', event => ui.updateVolume.call(this, event));
// Handle native play/pause
utils.on(this.media, 'playing play pause ended', event => ui.checkPlaying.call(this, event));
// Loading
utils.on(this.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this, event));
// Check if media failed to load
// utils.on(this.media, 'play', event => ui.checkFailed.call(this, event));
// Click video
if (this.supported.ui && this.config.clickToPlay && !this.isAudio) {
// Re-fetch the wrapper
const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`);
// Bail if there's no wrapper (this should never happen)
if (!utils.is.element(wrapper)) {
return;
}
// On click play, pause ore restart
utils.on(wrapper, 'click', () => {
// Touch devices will just show controls (if we're hiding controls)
if (this.config.hideControls && support.touch && !this.paused) {
return;
}
if (this.paused) {
this.play();
} else if (this.ended) {
this.restart();
this.play();
} else {
this.pause();
}
});
}
// Disable right click
if (this.supported.ui && this.config.disableContextMenu) {
utils.on(
this.media,
'contextmenu',
event => {
event.preventDefault();
},
false,
);
}
// Volume change
utils.on(this.media, 'volumechange', () => {
// Save to storage
this.storage.set({ volume: this.volume, muted: this.muted });
});
// Speed change
utils.on(this.media, 'ratechange', () => {
// Update UI
controls.updateSetting.call(this, 'speed');
// Save to storage
this.storage.set({ speed: this.speed });
});
// Quality change
utils.on(this.media, 'qualitychange', () => {
// Update UI
controls.updateSetting.call(this, 'quality');
// Save to storage
this.storage.set({ quality: this.quality });
});
// Caption language change
utils.on(this.media, 'languagechange', () => {
// Update UI
controls.updateSetting.call(this, 'captions');
// Save to storage
this.storage.set({ language: this.language });
});
// Captions toggle
utils.on(this.media, 'captionsenabled captionsdisabled', () => {
// Update UI
controls.updateSetting.call(this, 'captions');
// Save to storage
this.storage.set({ captions: this.captions.active });
});
// Proxy events to container
// Bubble up key events for Edge
utils.on(this.media, this.config.events.concat([
'keyup',
'keydown',
]).join(' '), event => {
let detail = {};
// Get error details from media
if (event.type === 'error') {
detail = this.media.error;
}
utils.dispatchEvent.call(this, this.elements.container, event.type, true, detail);
});
},
// Listen for control events
controls() {
// IE doesn't support input event, so we fallback to change
const inputEvent = browser.isIE ? 'change' : 'input';
// Trigger custom and default handlers
const proxy = (event, handlerKey, defaultHandler) => {
const customHandler = this.config.listeners[handlerKey];
// Execute custom handler
if (utils.is.function(customHandler)) {
customHandler.call(this, event);
}
// Only call default handler if not prevented in custom handler
if (!event.defaultPrevented && utils.is.function(defaultHandler)) {
defaultHandler.call(this, event);
}
};
// Play/pause toggle
utils.on(this.elements.buttons.play, 'click', event =>
proxy(event, 'play', () => {
this.togglePlay();
}),
);
// Pause
utils.on(this.elements.buttons.restart, 'click', event =>
proxy(event, 'restart', () => {
this.restart();
}),
);
// Rewind
utils.on(this.elements.buttons.rewind, 'click', event =>
proxy(event, 'rewind', () => {
this.rewind();
}),
);
// Rewind
utils.on(this.elements.buttons.forward, 'click', event =>
proxy(event, 'forward', () => {
this.forward();
}),
);
// Mute toggle
utils.on(this.elements.buttons.mute, 'click', event =>
proxy(event, 'mute', () => {
this.muted = !this.muted;
}),
);
// Captions toggle
utils.on(this.elements.buttons.captions, 'click', event =>
proxy(event, 'captions', () => {
this.toggleCaptions();
}),
);
// Fullscreen toggle
utils.on(this.elements.buttons.fullscreen, 'click', event =>
proxy(event, 'fullscreen', () => {
this.fullscreen.toggle();
}),
);
// Picture-in-Picture
utils.on(this.elements.buttons.pip, 'click', event =>
proxy(event, 'pip', () => {
this.pip = 'toggle';
}),
);
// Airplay
utils.on(this.elements.buttons.airplay, 'click', event =>
proxy(event, 'airplay', () => {
this.airplay();
}),
);
// Settings menu
utils.on(this.elements.buttons.settings, 'click', event => {
controls.toggleMenu.call(this, event);
});
// Click anywhere closes menu
utils.on(document.documentElement, 'click', event => {
controls.toggleMenu.call(this, event);
});
// Settings menu
utils.on(this.elements.settings.form, 'click', event => {
event.stopPropagation();
// Settings menu items - use event delegation as items are added/removed
if (utils.matches(event.target, this.config.selectors.inputs.language)) {
proxy(event, 'language', () => {
this.language = event.target.value;
});
} else if (utils.matches(event.target, this.config.selectors.inputs.quality)) {
proxy(event, 'quality', () => {
this.quality = event.target.value;
});
} else if (utils.matches(event.target, this.config.selectors.inputs.speed)) {
proxy(event, 'speed', () => {
this.speed = parseFloat(event.target.value);
});
} else {
controls.showTab.call(this, event);
}
});
// Seek
utils.on(this.elements.inputs.seek, inputEvent, event =>
proxy(event, 'seek', () => {
this.currentTime = event.target.value / event.target.max * this.duration;
}),
);
// Current time invert
// Only if one time element is used for both currentTime and duration
if (this.config.toggleInvert && !utils.is.element(this.elements.display.duration)) {
utils.on(this.elements.display.currentTime, 'click', () => {
// Do nothing if we're at the start
if (this.currentTime === 0) {
return;
}
this.config.invertTime = !this.config.invertTime;
ui.timeUpdate.call(this);
});
}
// Volume
utils.on(this.elements.inputs.volume, inputEvent, event =>
proxy(event, 'volume', () => {
this.volume = event.target.value;
}),
);
// Polyfill for lower fill in <input type="range"> for webkit
if (browser.isWebkit) {
utils.on(utils.getElements.call(this, 'input[type="range"]'), 'input', event => {
controls.updateRangeFill.call(this, event.target);
});
}
// Seek tooltip
utils.on(this.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this, event));
// Toggle controls visibility based on mouse movement
if (this.config.hideControls) {
// Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.elements.controls, 'mouseenter mouseleave', event => {
this.elements.controls.hover = event.type === 'mouseenter';
});
// Watch for cursor over controls so they don't hide when trying to interact
utils.on(this.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
this.elements.controls.pressed = [
'mousedown',
'touchstart',
].includes(event.type);
});
// Focus in/out on controls
utils.on(this.elements.controls, 'focusin focusout', event => {
this.toggleControls(event);
});
}
// Mouse wheel for volume
utils.on(
this.elements.inputs.volume,
'wheel',
event =>
proxy(event, 'volume', () => {
// Detect "natural" scroll - suppored on OS X Safari only
// Other browsers on OS X will be inverted until support improves
const inverted = event.webkitDirectionInvertedFromDevice;
const step = 1 / 50;
let direction = 0;
// Scroll down (or up on natural) to decrease
if (event.deltaY < 0 || event.deltaX > 0) {
if (inverted) {
this.decreaseVolume(step);
direction = -1;
} else {
this.increaseVolume(step);
direction = 1;
}
}
// Scroll up (or down on natural) to increase
if (event.deltaY > 0 || event.deltaX < 0) {
if (inverted) {
this.increaseVolume(step);
direction = 1;
} else {
this.decreaseVolume(step);
direction = -1;
}
}
// Don't break page scrolling at max and min
if ((direction === 1 && this.media.volume < 1) || (direction === -1 && this.media.volume > 0)) {
event.preventDefault();
}
}),
false,
);
},
};
export default listeners;

View File

@ -1,106 +0,0 @@
// ==========================================================================
// Plyr Media
// ==========================================================================
import support from './support';
import utils from './utils';
import youtube from './plugins/youtube';
import vimeo from './plugins/vimeo';
import ui from './ui';
// Sniff out the browser
const browser = utils.getBrowser();
const media = {
// Setup media
setup() {
// If there's no media, bail
if (!this.media) {
this.debug.warn('No media element found!');
return;
}
// Add type class
utils.toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', this.type), true);
// Add provider class
utils.toggleClass(this.elements.container, this.config.classNames.provider.replace('{0}', this.provider), true);
// Add video class for embeds
// This will require changes if audio embeds are added
if (this.isEmbed) {
utils.toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', 'video'), true);
}
if (this.supported.ui) {
// Check for picture-in-picture support
utils.toggleClass(this.elements.container, this.config.classNames.pip.supported, support.pip && this.isHTML5 && this.isVideo);
// Check for airplay support
utils.toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5);
// If there's no autoplay attribute, assume the video is stopped and add state class
utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.config.autoplay);
// Add iOS class
utils.toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos);
// Add touch class
utils.toggleClass(this.elements.container, this.config.classNames.isTouch, support.touch);
}
// Inject the player wrapper
if (this.isVideo) {
// Create the wrapper div
this.elements.wrapper = utils.createElement('div', {
class: this.config.classNames.video,
});
// Wrap the video in a container
utils.wrap(this.media, this.elements.wrapper);
}
if (this.isEmbed) {
switch (this.provider) {
case 'youtube':
youtube.setup.call(this);
break;
case 'vimeo':
vimeo.setup.call(this);
break;
default:
break;
}
} else if (this.isHTML5) {
ui.setTitle.call(this);
}
},
// Cancel current network requests
// See https://github.com/sampotts/plyr/issues/174
cancelRequests() {
if (!this.isHTML5) {
return;
}
// Remove child sources
utils.removeElement(this.media.querySelectorAll('source'));
// Set blank video src attribute
// This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
// Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
this.media.setAttribute('src', this.config.blankVideo);
// Load the new empty source
// This will cancel existing requests
// See https://github.com/sampotts/plyr/issues/174
this.media.load();
// Debugging
this.debug.log('Cancelled network requests');
},
};
export default media;

View File

@ -1,576 +0,0 @@
// ==========================================================================
// Advertisement plugin using Google IMA HTML5 SDK
// Create an account with our ad partner, vi here:
// https://www.vi.ai/publisher-video-monetization/
// ==========================================================================
/* global google */
import utils from '../utils';
// Build the default tag URL
const getTagUrl = () => {
const params = {
AV_PUBLISHERID: '58c25bb0073ef448b1087ad6',
AV_CHANNELID: '5a0458dc28a06145e4519d21',
AV_URL: '127.0.0.1:3000',
cb: 1,
AV_WIDTH: 640,
AV_HEIGHT: 480,
};
const base = 'https://go.aniview.com/api/adserver6/vast/';
return `${base}?${utils.buildUrlParams(params)}`;
};
class Ads {
/**
* Ads constructor.
* @param {object} player
* @return {Ads}
*/
constructor(player) {
this.player = player;
this.enabled = player.config.ads.enabled;
this.playing = false;
this.initialized = false;
this.blocked = false;
this.enabled = utils.is.url(player.config.ads.tag);
// Check if a tag URL is provided.
if (!this.enabled) {
return;
}
// Check if the Google IMA3 SDK is loaded or load it ourselves
if (!utils.is.object(window.google)) {
utils.loadScript(
player.config.urls.googleIMA.api,
() => {
this.ready();
},
() => {
// Script failed to load or is blocked
this.blocked = true;
this.player.debug.log('Ads error: Google IMA SDK failed to load');
},
);
} else {
this.ready();
}
}
/**
* Get the ads instance ready.
*/
ready() {
this.elements = {
container: null,
displayContainer: null,
};
this.manager = null;
this.loader = null;
this.cuePoints = null;
this.events = {};
this.safetyTimer = null;
this.countdownTimer = null;
// Set listeners on the Plyr instance
this.listeners();
// Start ticking our safety timer. If the whole advertisement
// thing doesn't resolve within our set time; we bail
this.startSafetyTimer(12000, 'ready()');
// Setup a simple promise to resolve if the IMA loader is ready
this.loaderPromise = new Promise(resolve => {
this.on('ADS_LOADER_LOADED', () => resolve());
});
// Setup a promise to resolve if the IMA manager is ready
this.managerPromise = new Promise(resolve => {
this.on('ADS_MANAGER_LOADED', () => resolve());
});
// Clear the safety timer
this.managerPromise.then(() => {
this.clearSafetyTimer('onAdsManagerLoaded()');
});
// Setup the IMA SDK
this.setupIMA();
}
/**
* In order for the SDK to display ads for our video, we need to tell it where to put them,
* so here we define our ad container. This div is set up to render on top of the video player.
* Using the code below, we tell the SDK to render ads within that div. We also provide a
* handle to the content video player - the SDK will poll the current time of our player to
* properly place mid-rolls. After we create the ad display container, we initialize it. On
* mobile devices, this initialization is done as the result of a user action.
*/
setupIMA() {
// Create the container for our advertisements
this.elements.container = utils.createElement('div', {
class: this.player.config.classNames.ads,
hidden: '',
});
this.player.elements.container.appendChild(this.elements.container);
// So we can run VPAID2
google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED);
// Set language
google.ima.settings.setLocale(this.player.config.ads.language);
// We assume the adContainer is the video container of the plyr element
// that will house the ads
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container);
// Request video ads to be pre-loaded
this.requestAds();
}
/**
* Request advertisements
*/
requestAds() {
const { container } = this.player.elements;
try {
// Create ads loader
this.loader = new google.ima.AdsLoader(this.elements.displayContainer);
// Listen and respond to ads loaded and error events
this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false);
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
// Request video ads
const request = new google.ima.AdsRequest();
request.adTagUrl = getTagUrl();
// Specify the linear and nonlinear slot sizes. This helps the SDK
// to select the correct creative if multiple are returned
request.linearAdSlotWidth = container.offsetWidth;
request.linearAdSlotHeight = container.offsetHeight;
request.nonLinearAdSlotWidth = container.offsetWidth;
request.nonLinearAdSlotHeight = container.offsetHeight;
// We only overlay ads as we only support video.
request.forceNonLinearFullSlot = false;
this.loader.requestAds(request);
this.handleEventListeners('ADS_LOADER_LOADED');
} catch (e) {
this.onAdError(e);
}
}
/**
* Update the ad countdown
* @param {boolean} start
*/
pollCountdown(start = false) {
if (!start) {
window.clearInterval(this.countdownTimer);
this.elements.container.removeAttribute('data-badge-text');
return;
}
const update = () => {
const time = utils.formatTime(this.manager.getRemainingTime());
const label = `${this.player.config.i18n.advertisment} - ${time}`;
this.elements.container.setAttribute('data-badge-text', label);
};
this.countdownTimer = window.setInterval(update, 100);
}
/**
* This method is called whenever the ads are ready inside the AdDisplayContainer
* @param {Event} adsManagerLoadedEvent
*/
onAdsManagerLoaded(adsManagerLoadedEvent) {
// Get the ads manager
const settings = new google.ima.AdsRenderingSettings();
// Tell the SDK to save and restore content video state on our behalf
settings.restoreCustomPlaybackStateOnAdBreakComplete = true;
settings.enablePreloading = true;
// The SDK is polling currentTime on the contentPlayback. And needs a duration
// so it can determine when to start the mid- and post-roll
this.manager = adsManagerLoadedEvent.getAdsManager(this.player, settings);
// Get the cue points for any mid-rolls by filtering out the pre- and post-roll
this.cuePoints = this.manager.getCuePoints();
// Add advertisement cue's within the time line if available
this.cuePoints.forEach(cuePoint => {
if (cuePoint !== 0 && cuePoint !== -1) {
const seekElement = this.player.elements.progress;
if (seekElement) {
const cuePercentage = 100 / this.player.duration * cuePoint;
const cue = utils.createElement('span', {
class: this.player.config.classNames.cues,
});
cue.style.left = `${cuePercentage.toString()}%`;
seekElement.appendChild(cue);
}
}
});
// Get skippable state
// TODO: Skip button
// this.manager.getAdSkippableState();
// Set volume to match player
this.manager.setVolume(this.player.volume);
// Add listeners to the required events
// Advertisement error events
this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error));
// Advertisement regular events
Object.keys(google.ima.AdEvent.Type).forEach(type => {
this.manager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event));
});
// Resolve our adsManager
this.handleEventListeners('ADS_MANAGER_LOADED');
}
/**
* This is where all the event handling takes place. Retrieve the ad from the event. Some
* events (e.g. ALL_ADS_COMPLETED) don't have the ad object associated
* https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdEvent.Type
* @param {Event} event
*/
onAdEvent(event) {
const { container } = this.player.elements;
// Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED)
// don't have ad object associated
const ad = event.getAd();
// Proxy event
const dispatchEvent = type => {
utils.dispatchEvent.call(this.player, this.player.media, `ads${type}`);
};
switch (event.type) {
case google.ima.AdEvent.Type.LOADED:
// This is the first event sent for an ad - it is possible to determine whether the
// ad is a video ad or an overlay
this.handleEventListeners('LOADED');
// Bubble event
dispatchEvent('loaded');
// Start countdown
this.pollCountdown(true);
if (!ad.isLinear()) {
// Position AdDisplayContainer correctly for overlay
ad.width = container.offsetWidth;
ad.height = container.offsetHeight;
}
// console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex());
// console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset());
break;
case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
// All ads for the current videos are done. We can now request new advertisements
// in case the video is re-played
this.handleEventListeners('ALL_ADS_COMPLETED');
// Fire event
dispatchEvent('allcomplete');
// TODO: Example for what happens when a next video in a playlist would be loaded.
// So here we load a new video when all ads are done.
// Then we load new ads within a new adsManager. When the video
// Is started - after - the ads are loaded, then we get ads.
// You can also easily test cancelling and reloading by running
// player.ads.cancel() and player.ads.play from the console I guess.
// this.player.source = {
// type: 'video',
// title: 'View From A Blue Moon',
// sources: [{
// src:
// 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4', type:
// 'video/mp4', }], poster:
// 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg', tracks:
// [ { kind: 'captions', label: 'English', srclang: 'en', src:
// 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
// default: true, }, { kind: 'captions', label: 'French', srclang: 'fr', src:
// 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt', }, ],
// };
// TODO: So there is still this thing where a video should only be allowed to start
// playing when the IMA SDK is ready or has failed
this.loadAds();
break;
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
// This event indicates the ad has started - the video player can adjust the UI,
// for example display a pause button and remaining time. Fired when content should
// be paused. This usually happens right before an ad is about to cover the content
this.handleEventListeners('CONTENT_PAUSE_REQUESTED');
dispatchEvent('contentpause');
this.pauseContent();
break;
case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED:
// This event indicates the ad has finished - the video player can perform
// appropriate UI actions, such as removing the timer for remaining time detection.
// Fired when content should be resumed. This usually happens when an ad finishes
// or collapses
this.handleEventListeners('CONTENT_RESUME_REQUESTED');
dispatchEvent('contentresume');
this.pollCountdown();
this.resumeContent();
break;
case google.ima.AdEvent.Type.STARTED:
dispatchEvent('started');
break;
case google.ima.AdEvent.Type.MIDPOINT:
dispatchEvent('midpoint');
break;
case google.ima.AdEvent.Type.COMPLETE:
dispatchEvent('complete');
break;
case google.ima.AdEvent.Type.IMPRESSION:
dispatchEvent('impression');
break;
case google.ima.AdEvent.Type.CLICK:
dispatchEvent('click');
break;
default:
break;
}
}
/**
* Any ad error handling comes through here
* @param {Event} event
*/
onAdError(event) {
this.cancel();
this.player.debug.log('Ads error', event);
}
/**
* Setup hooks for Plyr and window events. This ensures
* the mid- and post-roll launch at the correct time. And
* resize the advertisement when the player resizes
*/
listeners() {
const { container } = this.player.elements;
let time;
// Add listeners to the required events
this.player.on('ended', () => {
this.loader.contentComplete();
});
this.player.on('seeking', () => {
time = this.player.currentTime;
return time;
});
this.player.on('seeked', () => {
const seekedTime = this.player.currentTime;
this.cuePoints.forEach((cuePoint, index) => {
if (time < cuePoint && cuePoint < seekedTime) {
this.manager.discardAdBreak();
this.cuePoints.splice(index, 1);
}
});
});
// Listen to the resizing of the window. And resize ad accordingly
// TODO: eventually implement ResizeObserver
window.addEventListener('resize', () => {
this.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
});
}
/**
* Initialize the adsManager and start playing advertisements
*/
play() {
const { container } = this.player.elements;
if (!this.managerPromise) {
return;
}
// Play the requested advertisement whenever the adsManager is ready
this.managerPromise.then(() => {
// Initialize the container. Must be done via a user action on mobile devices
this.elements.displayContainer.initialize();
try {
if (!this.initialized) {
// Initialize the ads manager. Ad rules playlist will start at this time
this.manager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
// Call play to start showing the ad. Single video and overlay ads will
// start at this time; the call will be ignored for ad rules
this.manager.start();
}
this.initialized = true;
} catch (adError) {
// An error may be thrown if there was a problem with the
// VAST response
this.onAdError(adError);
}
});
}
/**
* Resume our video.
*/
resumeContent() {
// Hide our ad container
utils.toggleHidden(this.elements.container, true);
// Ad is stopped
this.playing = false;
// Play our video
if (this.player.currentTime < this.player.duration) {
this.player.play();
}
}
/**
* Pause our video
*/
pauseContent() {
// Show our ad container.
utils.toggleHidden(this.elements.container, false);
// Ad is playing.
this.playing = true;
// Pause our video.
this.player.pause();
}
/**
* Destroy the adsManager so we can grab new ads after this. If we don't then we're not
* allowed to call new ads based on google policies, as they interpret this as an accidental
* video requests. https://developers.google.com/interactive-
* media-ads/docs/sdks/android/faq#8
*/
cancel() {
// Pause our video
if (this.initialized) {
this.resumeContent();
}
// Tell our instance that we're done for now
this.handleEventListeners('ERROR');
// Re-create our adsManager
this.loadAds();
}
/**
* Re-create our adsManager
*/
loadAds() {
// Tell our adsManager to go bye bye
this.managerPromise.then(() => {
// Destroy our adsManager
if (this.manager) {
this.manager.destroy();
}
// Re-set our adsManager promises
this.managerPromise = new Promise(resolve => {
this.on('ADS_MANAGER_LOADED', () => resolve());
this.player.debug.log(this.manager);
});
// Now request some new advertisements
this.requestAds();
});
}
/**
* Handles callbacks after an ad event was invoked
* @param {string} event - Event type
*/
handleEventListeners(event) {
if (utils.is.function(this.events[event])) {
this.events[event].call(this);
}
}
/**
* Add event listeners
* @param {string} event - Event type
* @param {function} callback - Callback for when event occurs
* @return {Ads}
*/
on(event, callback) {
this.events[event] = callback;
return this;
}
/**
* Setup a safety timer for when the ad network doesn't respond for whatever reason.
* The advertisement has 12 seconds to get its things together. We stop this timer when the
* advertisement is playing, or when a user action is required to start, then we clear the
* timer on ad ready
* @param {number} time
* @param {string} from
*/
startSafetyTimer(time, from) {
this.player.debug.log(`Safety timer invoked from: ${from}`);
this.safetyTimer = window.setTimeout(() => {
this.cancel();
this.clearSafetyTimer('startSafetyTimer()');
}, time);
}
/**
* Clear our safety timer(s)
* @param {string} from
*/
clearSafetyTimer(from) {
if (!utils.is.nullOrUndefined(this.safetyTimer)) {
this.player.debug.log(`Safety timer cleared from: ${from}`);
clearTimeout(this.safetyTimer);
this.safetyTimer = null;
}
}
}
export default Ads;

View File

@ -1,318 +0,0 @@
// ==========================================================================
// Vimeo plugin
// ==========================================================================
import utils from './../utils';
import captions from './../captions';
import ui from './../ui';
const vimeo = {
setup() {
// Add embed class for responsive
utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
// Set intial ratio
vimeo.setAspectRatio.call(this);
// Load the API if not already
if (!utils.is.object(window.Vimeo)) {
utils.loadScript(this.config.urls.vimeo.api, () => {
vimeo.ready.call(this);
});
} else {
vimeo.ready.call(this);
}
},
// Set aspect ratio
// For Vimeo we have an extra 300% height <div> to hide the standard controls and UI
setAspectRatio(input) {
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
const padding = 100 / ratio[0] * ratio[1];
const height = 200;
const offset = (height - padding) / (height / 50);
this.elements.wrapper.style.paddingBottom = `${padding}%`;
this.media.style.transform = `translateY(-${offset}%)`;
},
// API Ready
ready() {
const player = this;
// Get Vimeo params for the iframe
const options = {
loop: player.config.loop.active,
autoplay: player.autoplay,
byline: false,
portrait: false,
title: false,
speed: true,
transparent: 0,
gesture: 'media',
};
const params = utils.buildUrlParams(options);
// Get the source URL or ID
let source = player.media.getAttribute('src');
// Get from <div> if needed
if (utils.is.empty(source)) {
source = player.media.getAttribute(this.config.attributes.embed.id);
}
const id = utils.parseVimeoId(source);
// Build an iframe
const iframe = utils.createElement('iframe');
const src = `https://player.vimeo.com/video/${id}?${params}`;
iframe.setAttribute('src', src);
iframe.setAttribute('allowfullscreen', '');
iframe.setAttribute('allowtransparency', '');
iframe.setAttribute('allow', 'autoplay');
// Inject the package
const wrapper = utils.createElement('div');
wrapper.appendChild(iframe);
player.media = utils.replaceElement(wrapper, player.media);
// Setup instance
// https://github.com/vimeo/player.js
player.embed = new window.Vimeo.Player(iframe);
player.media.paused = true;
player.media.currentTime = 0;
// Create a faux HTML5 API using the Vimeo API
player.media.play = () => {
player.embed.play().then(() => {
player.media.paused = false;
});
};
player.media.pause = () => {
player.embed.pause().then(() => {
player.media.paused = true;
});
};
player.media.stop = () => {
player.embed.stop().then(() => {
player.media.paused = true;
player.currentTime = 0;
});
};
// Seeking
let { currentTime } = player.media;
Object.defineProperty(player.media, 'currentTime', {
get() {
return currentTime;
},
set(time) {
// Get current paused state
// Vimeo will automatically play on seek
const { paused } = player.media;
// Set seeking flag
player.media.seeking = true;
// Trigger seeking
utils.dispatchEvent.call(player, player.media, 'seeking');
// Seek after events
player.embed.setCurrentTime(time);
// Restore pause state
if (paused) {
player.pause();
}
},
});
// Playback speed
let speed = player.config.speed.selected;
Object.defineProperty(player.media, 'playbackRate', {
get() {
return speed;
},
set(input) {
player.embed.setPlaybackRate(input).then(() => {
speed = input;
utils.dispatchEvent.call(player, player.media, 'ratechange');
});
},
});
// Volume
let { volume } = player.config;
Object.defineProperty(player.media, 'volume', {
get() {
return volume;
},
set(input) {
player.embed.setVolume(input).then(() => {
volume = input;
utils.dispatchEvent.call(player, player.media, 'volumechange');
});
},
});
// Muted
let { muted } = player.config;
Object.defineProperty(player.media, 'muted', {
get() {
return muted;
},
set(input) {
const toggle = utils.is.boolean(input) ? input : false;
player.embed.setVolume(toggle ? 0 : player.config.volume).then(() => {
muted = toggle;
utils.dispatchEvent.call(player, player.media, 'volumechange');
});
},
});
// Loop
let { loop } = player.config;
Object.defineProperty(player.media, 'loop', {
get() {
return loop;
},
set(input) {
const toggle = utils.is.boolean(input) ? input : player.config.loop.active;
player.embed.setLoop(toggle).then(() => {
loop = toggle;
});
},
});
// Source
let currentSrc;
player.embed.getVideoUrl().then(value => {
currentSrc = value;
});
Object.defineProperty(player.media, 'currentSrc', {
get() {
return currentSrc;
},
});
// Ended
Object.defineProperty(player.media, 'ended', {
get() {
return player.currentTime === player.duration;
},
});
// Set aspect ratio based on video size
Promise.all([
player.embed.getVideoWidth(),
player.embed.getVideoHeight(),
]).then(dimensions => {
const ratio = utils.getAspectRatio(dimensions[0], dimensions[1]);
vimeo.setAspectRatio.call(this, ratio);
});
// Set autopause
player.embed.setAutopause(player.config.autopause).then(state => {
player.config.autopause = state;
});
// Get title
player.embed.getVideoTitle().then(title => {
player.config.title = title;
ui.setTitle.call(this);
});
// Get current time
player.embed.getCurrentTime().then(value => {
currentTime = value;
utils.dispatchEvent.call(player, player.media, 'timeupdate');
});
// Get duration
player.embed.getDuration().then(value => {
player.media.duration = value;
utils.dispatchEvent.call(player, player.media, 'durationchange');
});
// Get captions
player.embed.getTextTracks().then(tracks => {
player.media.textTracks = tracks;
captions.setup.call(player);
});
player.embed.on('cuechange', data => {
let cue = null;
if (data.cues.length) {
cue = utils.stripHTML(data.cues[0].text);
}
captions.setText.call(player, cue);
});
player.embed.on('loaded', () => {
if (utils.is.element(player.embed.element) && player.supported.ui) {
const frame = player.embed.element;
// Fix keyboard focus issues
// https://github.com/sampotts/plyr/issues/317
frame.setAttribute('tabindex', -1);
}
});
player.embed.on('play', () => {
// Only fire play if paused before
if (player.media.paused) {
utils.dispatchEvent.call(player, player.media, 'play');
}
player.media.paused = false;
utils.dispatchEvent.call(player, player.media, 'playing');
});
player.embed.on('pause', () => {
player.media.paused = true;
utils.dispatchEvent.call(player, player.media, 'pause');
});
player.embed.on('timeupdate', data => {
player.media.seeking = false;
currentTime = data.seconds;
utils.dispatchEvent.call(player, player.media, 'timeupdate');
});
player.embed.on('progress', data => {
player.media.buffered = data.percent;
utils.dispatchEvent.call(player, player.media, 'progress');
// Check all loaded
if (parseInt(data.percent, 10) === 1) {
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
}
});
player.embed.on('seeked', () => {
player.media.seeking = false;
utils.dispatchEvent.call(player, player.media, 'seeked');
utils.dispatchEvent.call(player, player.media, 'play');
});
player.embed.on('ended', () => {
player.media.paused = true;
utils.dispatchEvent.call(player, player.media, 'ended');
});
player.embed.on('error', detail => {
player.media.error = detail;
utils.dispatchEvent.call(player, player.media, 'error');
});
// Rebuild UI
window.setTimeout(() => ui.build.call(player), 0);
},
};
export default vimeo;

View File

@ -1,418 +0,0 @@
// ==========================================================================
// YouTube plugin
// ==========================================================================
import utils from './../utils';
import controls from './../controls';
import ui from './../ui';
const youtube = {
setup() {
// Add embed class for responsive
utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
// Set aspect ratio
youtube.setAspectRatio.call(this);
// Setup API
if (utils.is.object(window.YT) && utils.is.function(window.YT.Player)) {
youtube.ready.call(this);
} else {
// Load the API
utils.loadScript(this.config.urls.youtube.api);
// Setup callback for the API
// YouTube has it's own system of course...
window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || [];
// Add to queue
window.onYouTubeReadyCallbacks.push(() => {
youtube.ready.call(this);
});
// Set callback to process queue
window.onYouTubeIframeAPIReady = () => {
window.onYouTubeReadyCallbacks.forEach(callback => {
callback();
});
};
}
},
// Get the media title
getTitle(videoId) {
// Try via undocumented API method first
// This method disappears now and then though...
// https://github.com/sampotts/plyr/issues/709
if (utils.is.function(this.embed.getVideoData)) {
const { title } = this.embed.getVideoData();
if (utils.is.empty(title)) {
this.config.title = title;
ui.setTitle.call(this);
return;
}
}
// Or via Google API
const key = this.config.keys.google;
if (utils.is.string(key) && !utils.is.empty(key)) {
const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${key}&fields=items(snippet(title))&part=snippet`;
utils
.fetch(url)
.then(result => {
if (utils.is.object(result)) {
this.config.title = result.items[0].snippet.title;
ui.setTitle.call(this);
}
})
.catch(() => {});
}
},
// Set aspect ratio
setAspectRatio() {
const ratio = this.config.ratio.split(':');
this.elements.wrapper.style.paddingBottom = `${100 / ratio[0] * ratio[1]}%`;
},
// API ready
ready() {
const player = this;
// Ignore already setup (race condition)
const currentId = player.media.getAttribute('id');
if (!utils.is.empty(currentId) && currentId.startsWith('youtube-')) {
return;
}
// Get the source URL or ID
let source = player.media.getAttribute('src');
// Get from <div> if needed
if (utils.is.empty(source)) {
source = player.media.getAttribute(this.config.attributes.embed.id);
}
// Replace the <iframe> with a <div> due to YouTube API issues
const videoId = utils.parseYouTubeId(source);
const id = utils.generateId(player.provider);
const container = utils.createElement('div', { id });
player.media = utils.replaceElement(container, player.media);
// Setup instance
// https://developers.google.com/youtube/iframe_api_reference
player.embed = new window.YT.Player(id, {
videoId,
playerVars: {
autoplay: player.config.autoplay ? 1 : 0, // Autoplay
controls: player.supported.ui ? 0 : 1, // Only show controls if not fully supported
rel: 0, // No related vids
showinfo: 0, // Hide info
iv_load_policy: 3, // Hide annotations
modestbranding: 1, // Hide logos as much as possible (they still show one in the corner when paused)
disablekb: 1, // Disable keyboard as we handle it
playsinline: 1, // Allow iOS inline playback
// Tracking for stats
// origin: window ? `${window.location.protocol}//${window.location.host}` : null,
widget_referrer: window ? window.location.href : null,
// Captions are flaky on YouTube
cc_load_policy: player.captions.active ? 1 : 0,
cc_lang_pref: player.config.captions.language,
},
events: {
onError(event) {
// If we've already fired an error, don't do it again
// YouTube fires onError twice
if (utils.is.object(player.media.error)) {
return;
}
const detail = {
code: event.data,
};
// Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
switch (event.data) {
case 2:
detail.message =
'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.';
break;
case 5:
detail.message =
'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.';
break;
case 100:
detail.message =
'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.';
break;
case 101:
case 150:
detail.message = 'The owner of the requested video does not allow it to be played in embedded players.';
break;
default:
detail.message = 'An unknown error occured';
break;
}
player.media.error = detail;
utils.dispatchEvent.call(player, player.media, 'error');
},
onPlaybackQualityChange(event) {
// Get the instance
const instance = event.target;
// Get current quality
player.media.quality = instance.getPlaybackQuality();
utils.dispatchEvent.call(player, player.media, 'qualitychange');
},
onPlaybackRateChange(event) {
// Get the instance
const instance = event.target;
// Get current speed
player.media.playbackRate = instance.getPlaybackRate();
utils.dispatchEvent.call(player, player.media, 'ratechange');
},
onReady(event) {
// Get the instance
const instance = event.target;
// Get the title
youtube.getTitle.call(player, videoId);
// Create a faux HTML5 API using the YouTube API
player.media.play = () => {
instance.playVideo();
player.media.paused = false;
};
player.media.pause = () => {
instance.pauseVideo();
player.media.paused = true;
};
player.media.stop = () => {
instance.stopVideo();
player.media.paused = true;
};
player.media.duration = instance.getDuration();
player.media.paused = true;
// Seeking
player.media.currentTime = 0;
Object.defineProperty(player.media, 'currentTime', {
get() {
return Number(instance.getCurrentTime());
},
set(time) {
// Set seeking flag
player.media.seeking = true;
// Trigger seeking
utils.dispatchEvent.call(player, player.media, 'seeking');
// Seek after events sent
instance.seekTo(time);
},
});
// Playback speed
Object.defineProperty(player.media, 'playbackRate', {
get() {
return instance.getPlaybackRate();
},
set(input) {
instance.setPlaybackRate(input);
},
});
// Quality
Object.defineProperty(player.media, 'quality', {
get() {
return instance.getPlaybackQuality();
},
set(input) {
// Trigger request event
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
quality: input,
});
instance.setPlaybackQuality(input);
},
});
// Volume
let { volume } = player.config;
Object.defineProperty(player.media, 'volume', {
get() {
return volume;
},
set(input) {
volume = input;
instance.setVolume(volume * 100);
utils.dispatchEvent.call(player, player.media, 'volumechange');
},
});
// Muted
let { muted } = player.config;
Object.defineProperty(player.media, 'muted', {
get() {
return muted;
},
set(input) {
const toggle = utils.is.boolean(input) ? input : muted;
muted = toggle;
instance[toggle ? 'mute' : 'unMute']();
utils.dispatchEvent.call(player, player.media, 'volumechange');
},
});
// Source
Object.defineProperty(player.media, 'currentSrc', {
get() {
return instance.getVideoUrl();
},
});
// Ended
Object.defineProperty(player.media, 'ended', {
get() {
return player.currentTime === player.duration;
},
});
// Get available speeds
player.options.speed = instance.getAvailablePlaybackRates();
// Set the tabindex to avoid focus entering iframe
if (player.supported.ui) {
player.media.setAttribute('tabindex', -1);
}
utils.dispatchEvent.call(player, player.media, 'timeupdate');
utils.dispatchEvent.call(player, player.media, 'durationchange');
// Reset timer
window.clearInterval(player.timers.buffering);
// Setup buffering
player.timers.buffering = window.setInterval(() => {
// Get loaded % from YouTube
player.media.buffered = instance.getVideoLoadedFraction();
// Trigger progress only when we actually buffer something
if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
utils.dispatchEvent.call(player, player.media, 'progress');
}
// Set last buffer point
player.media.lastBuffered = player.media.buffered;
// Bail if we're at 100%
if (player.media.buffered === 1) {
window.clearInterval(player.timers.buffering);
// Trigger event
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
}
}, 200);
// Rebuild UI
window.setTimeout(() => ui.build.call(player), 50);
},
onStateChange(event) {
// Get the instance
const instance = event.target;
// Reset timer
window.clearInterval(player.timers.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;
// YouTube doesn't support loop for a single video, so mimick it.
if (player.media.loop) {
// YouTube needs a call to `stopVideo` before playing again
instance.stopVideo();
instance.playVideo();
} else {
utils.dispatchEvent.call(player, player.media, 'ended');
}
break;
case 1:
// If we were seeking, fire seeked event
if (player.media.seeking) {
utils.dispatchEvent.call(player, player.media, 'seeked');
}
player.media.seeking = false;
// Only fire play if paused before
if (player.media.paused) {
utils.dispatchEvent.call(player, player.media, 'play');
}
player.media.paused = false;
utils.dispatchEvent.call(player, player.media, 'playing');
// Poll to get playback progress
player.timers.playing = window.setInterval(() => {
utils.dispatchEvent.call(player, player.media, 'timeupdate');
}, 50);
// Check duration again due to YouTube bug
// https://github.com/sampotts/plyr/issues/374
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
if (player.media.duration !== instance.getDuration()) {
player.media.duration = instance.getDuration();
utils.dispatchEvent.call(player, player.media, 'durationchange');
}
// Get quality
controls.setQualityMenu.call(player, instance.getAvailableQualityLevels());
break;
case 2:
player.media.paused = true;
utils.dispatchEvent.call(player, player.media, 'pause');
break;
default:
break;
}
utils.dispatchEvent.call(player, player.elements.container, 'statechange', false, {
code: event.data,
});
},
},
});
},
};
export default youtube;

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +0,0 @@
// ==========================================================================
// Plyr Polyfilled Build
// plyr.js v3.0.0-beta.14
// https://github.com/sampotts/plyr
// License: The MIT License (MIT)
// ==========================================================================
import 'babel-polyfill';
import 'custom-event-polyfill';
import Plyr from './plyr';
export default Plyr;

View File

@ -1,148 +0,0 @@
// ==========================================================================
// Plyr source update
// ==========================================================================
import { providers } from './types';
import utils from './utils';
import media from './media';
import ui from './ui';
import support from './support';
const source = {
// Add elements to HTML5 media (source, tracks, etc)
insertElements(type, attributes) {
if (utils.is.string(attributes)) {
utils.insertElement(type, this.media, {
src: attributes,
});
} else if (utils.is.array(attributes)) {
attributes.forEach(attribute => {
utils.insertElement(type, this.media, attribute);
});
}
},
// Update source
// Sources are not checked for support so be careful
change(input) {
if (!utils.is.object(input) || !('sources' in input) || !input.sources.length) {
this.debug.warn('Invalid source format');
return;
}
// Cancel current network requests
media.cancelRequests.call(this);
// Destroy instance and re-setup
this.destroy.call(
this,
() => {
// TODO: Reset menus here
// Remove elements
utils.removeElement(this.media);
this.media = null;
// Reset class name
if (utils.is.element(this.elements.container)) {
this.elements.container.removeAttribute('class');
}
// Set the type and provider
this.type = input.type;
this.provider = !utils.is.empty(input.sources[0].provider) ? input.sources[0].provider : providers.html5;
// Check for support
this.supported = support.check(this.type, this.provider, this.config.inline);
// Create new markup
switch (`${this.provider}:${this.type}`) {
case 'html5:video':
this.media = utils.createElement('video');
break;
case 'html5:audio':
this.media = utils.createElement('audio');
break;
case 'youtube:video':
case 'vimeo:video':
this.media = utils.createElement('div', {
src: input.sources[0].src,
});
break;
default:
break;
}
// Inject the new element
this.elements.container.appendChild(this.media);
// Autoplay the new source?
if (utils.is.boolean(input.autoplay)) {
this.config.autoplay = input.autoplay;
}
// Set attributes for audio and video
if (this.isHTML5) {
if (this.config.crossorigin) {
this.media.setAttribute('crossorigin', '');
}
if (this.config.autoplay) {
this.media.setAttribute('autoplay', '');
}
if ('poster' in input) {
this.media.setAttribute('poster', input.poster);
}
if (this.config.loop.active) {
this.media.setAttribute('loop', '');
}
if (this.config.muted) {
this.media.setAttribute('muted', '');
}
if (this.config.inline) {
this.media.setAttribute('playsinline', '');
}
}
// Restore class hook
ui.addStyleHook.call(this);
// Set new sources for html5
if (this.isHTML5) {
source.insertElements.call(this, 'source', input.sources);
}
// Set video title
this.config.title = input.title;
// Set up from scratch
media.setup.call(this);
// HTML5 stuff
if (this.isHTML5) {
// Setup captions
if ('tracks' in input) {
source.insertElements.call(this, 'track', input.tracks);
}
// Load HTML5 sources
this.media.load();
}
// If HTML5 or embed but not fully supported, setupInterface and call ready now
if (this.isHTML5 || (this.isEmbed && !this.supported.ui)) {
// Setup interface
ui.build.call(this);
}
// Update the fullscreen support
this.fullscreen.update();
},
true,
);
},
};
export default source;

View File

@ -1,71 +0,0 @@
// ==========================================================================
// Plyr storage
// ==========================================================================
import utils from './utils';
class Storage {
constructor(player) {
this.enabled = player.config.storage.enabled;
this.key = player.config.storage.key;
}
// Check for actual support (see if we can use it)
static get supported() {
if (!('localStorage' in window)) {
return false;
}
const test = '___test';
// Try to use it (it might be disabled, e.g. user is in private mode)
// see: https://github.com/sampotts/plyr/issues/131
try {
window.localStorage.setItem(test, test);
window.localStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
get(key) {
const store = window.localStorage.getItem(this.key);
if (!Storage.supported || utils.is.empty(store)) {
return null;
}
const json = JSON.parse(store);
return utils.is.string(key) && key.length ? json[key] : json;
}
set(object) {
// Bail if we don't have localStorage support or it's disabled
if (!Storage.supported || !this.enabled) {
return;
}
// Can only store objectst
if (!utils.is.object(object)) {
return;
}
// Get current storage
let storage = this.get();
// Default to empty object
if (utils.is.empty(storage)) {
storage = {};
}
// Update the working copy of the values
utils.extend(storage, object);
// Update storage
window.localStorage.setItem(this.key, JSON.stringify(storage));
}
}
export default Storage;

Some files were not shown because too many files have changed in this diff Show More