Merge branch 'beta'
# Conflicts: # readme.md
							
								
								
									
										10
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,10 @@ | ||||
| # 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 | ||||
							
								
								
									
										41
									
								
								.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,41 @@ | ||||
| { | ||||
|     "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" | ||||
|     } | ||||
| } | ||||
							
								
								
									
										11
									
								
								.github/issue_template.md
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -2,9 +2,6 @@ | ||||
| Please use this issue template as it makes replicating and fixing the issue easier! | ||||
| ---> | ||||
|  | ||||
| - [ ] Issue does not already exist | ||||
| - [ ] Issue observed on https://plyr.io | ||||
|  | ||||
| ### Expected behaviour | ||||
|  | ||||
| ### Actual behaviour | ||||
| @ -16,13 +13,5 @@ Please use this issue template as it makes replicating and fixing the issue easi | ||||
| - Operating System: | ||||
| - Version: | ||||
|  | ||||
| Players affected: | ||||
| - [ ] HTML5 Video | ||||
| - [ ] HTML5 Audio | ||||
| - [ ] YouTube | ||||
| - [ ] Vimeo | ||||
|  | ||||
| ### Steps to reproduce | ||||
| - | ||||
|  | ||||
| ### Relevant links | ||||
|  | ||||
							
								
								
									
										14
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,11 +1,11 @@ | ||||
| node_modules | ||||
| *.sublime-project | ||||
| *.sublime-workspace | ||||
| .DS_Store | ||||
| aws.json | ||||
| docs/index.dev.html | ||||
| *.mp4 | ||||
| index-dev.html | ||||
| notes.txt | ||||
| *.vtt | ||||
| docs/index.dev.php | ||||
| !dist/blank.mp4 | ||||
| index-*.html | ||||
| npm-debug.log | ||||
| *.webm | ||||
| /package-lock.json | ||||
| .idea/ | ||||
|  | ||||
|  | ||||
| @ -1,11 +0,0 @@ | ||||
| { | ||||
|     "html": { | ||||
|         "allowed_file_extensions": [] | ||||
|     }, | ||||
|     "css": { | ||||
|         "allowed_file_extensions": [] | ||||
|     }, | ||||
|     "js": { | ||||
|         "allowed_file_extensions": [] | ||||
|     } | ||||
| } | ||||
							
								
								
									
										55
									
								
								.jshintrc
									
									
									
									
									
								
							
							
						
						| @ -1,55 +0,0 @@ | ||||
| { | ||||
|     // 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 | ||||
| } | ||||
							
								
								
									
										4
									
								
								.npmignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,4 @@ | ||||
| demo | ||||
| .github | ||||
| .vscode | ||||
| *.code-workspace | ||||
							
								
								
									
										7
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,7 @@ | ||||
| { | ||||
|     "useTabs": false, | ||||
|     "tabWidth": 4, | ||||
|     "printWidth": 160, | ||||
|     "singleQuote": true, | ||||
|     "trailingComma": "all" | ||||
| } | ||||
							
								
								
									
										24
									
								
								.stylelintrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,24 @@ | ||||
| { | ||||
|     "plugins": ["stylelint-selector-bem-pattern", "stylelint-scss"], | ||||
|     "extends": ["stylelint-config-recommended", "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-.*"] | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										12
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,12 @@ | ||||
| { | ||||
|     // 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" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,2 +1 @@ | ||||
| { | ||||
| } | ||||
| {} | ||||
| @ -6,7 +6,7 @@ | ||||
|         "Audio", | ||||
|         "Video", | ||||
|         "HTML5 Audio", | ||||
|         "HTml5 Video" | ||||
|         "HTML5 Video" | ||||
|     ], | ||||
|     "authors": [ | ||||
|         "Sam Potts <sam@potts.es>" | ||||
|  | ||||
							
								
								
									
										20
									
								
								bundles.json
									
									
									
									
									
								
							
							
						
						| @ -1,24 +1,20 @@ | ||||
| { | ||||
|     "plyr": { | ||||
|         "less": { | ||||
|             "plyr.css":     ["src/less/plyr.less"] | ||||
|         }, | ||||
|         "scss": { | ||||
|             "plyr.css":     ["src/scss/plyr.scss"] | ||||
|         "sass": { | ||||
|             "plyr.css": "src/sass/plyr.scss" | ||||
|         }, | ||||
|         "js": { | ||||
|             "plyr.js":      ["src/js/plyr.js"] | ||||
|             "plyr.js": "src/js/plyr.js", | ||||
|             "plyr.polyfilled.js": "src/js/plyr.polyfilled.js" | ||||
|         } | ||||
|     }, | ||||
|     "demo": { | ||||
|         "less": { | ||||
|             "demo.css":     ["demo/src/less/demo.less"] | ||||
|         "sass": { | ||||
|             "demo.css": "demo/src/sass/bundles/demo.scss", | ||||
|             "error.css": "demo/src/sass/bundles/error.csss" | ||||
|         }, | ||||
|         "js": { | ||||
|             "demo.js": [ | ||||
|                 "demo/src/js/lib/classlist.js", | ||||
|                 "demo/src/js/main.js" | ||||
|             ] | ||||
|             "demo.js": "demo/src/js/demo.js" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										749
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										2
									
								
								demo/dist/demo.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										244
									
								
								demo/dist/demo.js
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1 +1,243 @@ | ||||
| "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 – “It All Began With A Burst”",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")); | ||||
| (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 | ||||
|         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, | ||||
|             publisherId: '918848828995742' | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     // Expose for tinkering in the console | ||||
|     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 – “It All Began With A Burst”', | ||||
|                     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', 'https://www.google-analytics.com/analytics.js', 'ga'); | ||||
|     window.ga('create', 'UA-40881672-11', 'auto'); | ||||
|     window.ga('send', 'pageview'); | ||||
| } | ||||
| /* eslint-enable */ | ||||
|  | ||||
| }()); | ||||
|  | ||||
| //# sourceMappingURL=demo.js.map | ||||
|  | ||||
							
								
								
									
										1
									
								
								demo/dist/demo.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										2
									
								
								demo/dist/demo.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,2 @@ | ||||
| !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&&setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var e=new Plyr("#player",{debug:!0,title:"View From A Blue Moon",iconUrl:"../dist/plyr.svg",keyboard:{global:!0},tooltips:{controls:!0},captions:{active:!0},keys:{google:"AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c"},ads:{enabled:!0,publisherId:"918848828995742"}});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 – “It All Began With A Burst”",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="https://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 | ||||
							
								
								
									
										1
									
								
								demo/dist/demo.min.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										1
									
								
								demo/dist/demo.svg
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1 +0,0 @@ | ||||
| <?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> | ||||
| Before Width: | Height: | Size: 1.7 KiB | 
| @ -7,18 +7,18 @@ | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|  | ||||
|     <!-- Docs styles --> | ||||
|     <link rel="stylesheet" href="dist/demo.css"> | ||||
|     <link rel="stylesheet" href="dist/error.css"> | ||||
|  | ||||
|     <!-- Preload --> | ||||
|     <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"> | ||||
|     <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"> | ||||
| </head> | ||||
|  | ||||
| <body> | ||||
|     <main> | ||||
|         <h1>Doh.</h1> | ||||
|         <p>Looks like something went wrong.</p> | ||||
|         <a href="http://plyr.io" class="btn btn--primary">Back to plyr.io</a> | ||||
|         <a href="javascript:history.back()" class="button">Go back</a> | ||||
|     </main> | ||||
| </body> | ||||
|  | ||||
|  | ||||
							
								
								
									
										192
									
								
								demo/index.html
									
									
									
									
									
								
							
							
						
						| @ -3,66 +3,95 @@ | ||||
|  | ||||
| <head> | ||||
|     <meta charset="utf-8" /> | ||||
|     <title>Plyr - A simple HTML5 media player</title> | ||||
|     <meta name="description" content="A simple HTML5 media player with custom controls and WebVTT captions."> | ||||
|     <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."> | ||||
|     <meta name="author" content="Sam Potts"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|  | ||||
|     <!-- Styles --> | ||||
|     <link rel="stylesheet" href="../dist/plyr.css"> | ||||
|     <!-- 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"> | ||||
|  | ||||
|     <!-- 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/avenir-medium.woff2"> | ||||
|     <link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/avenir-bold.woff2"> | ||||
|     <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"> | ||||
| </head> | ||||
|  | ||||
| <body> | ||||
|     <div class="grid"> | ||||
|         <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"> | ||||
|             <p>A simple, accessible and customisable media player for | ||||
|                 <button type="button" class="faux-link" data-source="video"> | ||||
|                     <svg class="icon"> | ||||
|                             <use xlink:href="#icon-github" /> | ||||
|                         </svg>Download on GitHub | ||||
|                     </a> | ||||
|                 </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"> | ||||
|                         <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"> | ||||
|                             <use xlink:href="#icon-twitter" /> | ||||
|                         </svg>Tweet | ||||
|                         <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/?aid=plyrio" 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> | ||||
|                 </li> | ||||
|             </ul> | ||||
|         </nav> | ||||
|             </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 | ||||
|                     </a> | ||||
|                 </span> | ||||
|             </div> | ||||
|         </header> | ||||
|  | ||||
|     <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> | ||||
|         <main> | ||||
|             <video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player"> | ||||
|                 <!-- 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"> | ||||
| @ -70,34 +99,89 @@ | ||||
|                 <!-- 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"><small><a href="http://viewfromabluemoon.com/" target="_blank">View From A Blue Moon</a> © Brainfarm</small></li> | ||||
|                 <li class="plyr__cite plyr__cite--audio"><small><a href="http://www.kishibashi.com/" target="_blank">Kishi Bashi – “It All Began With A Burst”</a> © 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> | ||||
|                 <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> © 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 – “It All Began With A Burst”</a> © 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  | ||||
|                         <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  | ||||
|                         <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> | ||||
|             </ul> | ||||
|         </section> | ||||
|         </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> | ||||
|  | ||||
|     <!-- Plyr core script --> | ||||
|     <script src="../dist/plyr.js"></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> | ||||
|  | ||||
|     <!-- 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> | ||||
|  | ||||
|     <!-- 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> | ||||
|     <!-- Docs script --> | ||||
|     <script src="dist/demo.js"></script> | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
							
								
								
									
										29
									
								
								demo/media/View_From_A_Blue_Moon_Trailer-HD.en.vtt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,29 @@ | ||||
| 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 | ||||
							
								
								
									
										29
									
								
								demo/media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,29 @@ | ||||
| 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 | ||||
							
								
								
									
										
											BIN
										
									
								
								demo/media/View_From_A_Blue_Moon_Trailer-HD.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 154 KiB | 
							
								
								
									
										246
									
								
								demo/src/js/demo.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,246 @@ | ||||
| // ========================================================================== | ||||
| // 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 | ||||
|         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, | ||||
|             publisherId: '918848828995742', | ||||
|         }, | ||||
|     }); | ||||
|  | ||||
|     // Expose for tinkering in the console | ||||
|     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 – “It All Began With A Burst”', | ||||
|                     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', 'https://www.google-analytics.com/analytics.js', 'ga'); | ||||
|     window.ga('create', 'UA-40881672-11', 'auto'); | ||||
|     window.ga('send', 'pageview'); | ||||
| } | ||||
| /* eslint-enable */ | ||||
| @ -1,237 +0,0 @@ | ||||
| /* | ||||
|  * 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; | ||||
| }()); | ||||
|  | ||||
| } | ||||
|  | ||||
| } | ||||
| @ -1,203 +0,0 @@ | ||||
| // ========================================================================== | ||||
| // 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 – “It All Began With A Burst”", | ||||
|                     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"); | ||||
| } | ||||
| @ -1,48 +0,0 @@ | ||||
| // ========================================================================== | ||||
| // 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); | ||||
|     } | ||||
| } | ||||
| @ -1,172 +0,0 @@ | ||||
| // ========================================================================== | ||||
| // 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%); | ||||
| 	} | ||||
| } | ||||
| @ -1,75 +0,0 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| // ========================================================================== | ||||
| // 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"; | ||||
| @ -1,18 +0,0 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
| } | ||||
| @ -1,41 +0,0 @@ | ||||
| // ========================================================================== | ||||
| // 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
									
									
								
							
							
						
						| @ -1,406 +0,0 @@ | ||||
| /*! 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; | ||||
| } | ||||
| @ -1,48 +0,0 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
							
								
								
									
										46
									
								
								demo/src/sass/bundles/demo.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,46 @@ | ||||
| // ========================================================================== | ||||
| // 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'; | ||||
							
								
								
									
										29
									
								
								demo/src/sass/bundles/error.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,29 @@ | ||||
| // ========================================================================== | ||||
| // 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'; | ||||
							
								
								
									
										83
									
								
								demo/src/sass/components/buttons.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,83 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								demo/src/sass/components/header.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,19 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
|     } | ||||
| } | ||||
| @ -5,9 +5,9 @@ | ||||
| // Base size icon styles | ||||
| .icon { | ||||
|     fill: currentColor; | ||||
| 	width: @icon-size; | ||||
| 	height: @icon-size; | ||||
|     height: $icon-size; | ||||
|     vertical-align: -3px; | ||||
|     width: $icon-size; | ||||
| } | ||||
| 
 | ||||
| // Within elements | ||||
| @ -16,11 +16,8 @@ button svg, | ||||
| label svg { | ||||
|     pointer-events: none; | ||||
| } | ||||
| 
 | ||||
| a .icon, | ||||
| .btn .icon { | ||||
| 	margin-right: (@padding-base / 2); | ||||
| } | ||||
| .btn:not(.btn-large) .icon { | ||||
| 	width: (@icon-size - 2); | ||||
| 	height: (@icon-size - 2); | ||||
|     margin-right: floor($spacing-base / 3); | ||||
| } | ||||
							
								
								
									
										49
									
								
								demo/src/sass/components/links.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,49 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										11
									
								
								demo/src/sass/components/lists.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,11 @@ | ||||
| // ========================================================================== | ||||
| // Lists | ||||
| // ========================================================================== | ||||
|  | ||||
| // Lists | ||||
| ul, | ||||
| li { | ||||
|     list-style: none; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
| } | ||||
							
								
								
									
										10
									
								
								demo/src/sass/components/media.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,10 @@ | ||||
| // ========================================================================== | ||||
| // Basic media | ||||
| // ========================================================================== | ||||
|  | ||||
| img, | ||||
| video, | ||||
| audio { | ||||
|     max-width: 100%; | ||||
|     vertical-align: middle; | ||||
| } | ||||
							
								
								
									
										9
									
								
								demo/src/sass/components/navigation.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,9 @@ | ||||
| // ========================================================================== | ||||
| // Navigation | ||||
| // ========================================================================== | ||||
|  | ||||
| nav { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     margin-bottom: $spacing-base; | ||||
| } | ||||
| @ -10,31 +10,34 @@ video { | ||||
| 
 | ||||
| // Example players | ||||
| .plyr { | ||||
|     margin: 0 auto; | ||||
|     border-radius: @border-radius-large; | ||||
|     border-radius: $border-radius-base; | ||||
|     box-shadow: 0 2px 5px rgba(#000, 0.2); | ||||
|     margin: $spacing-base auto; | ||||
| 
 | ||||
|     &.plyr--audio { | ||||
|         max-width: 480px; | ||||
|     } | ||||
| .plyr--audio { | ||||
|     max-width: @example-width-audio; | ||||
| } | ||||
| 
 | ||||
| .plyr__video-wrapper::after { | ||||
|     content: ""; | ||||
|     border: 1px solid rgba(#000, 0.15); | ||||
|     border-radius: inherit; | ||||
|     bottom: 0; | ||||
|     content: ''; | ||||
|     left: 0; | ||||
|     pointer-events: none; | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     bottom: 0; | ||||
|     left: 0; | ||||
|     right: 0; | ||||
|     border: 1px solid fade(#000, 15%); | ||||
|     border-radius: inherit; | ||||
|     top: 0; | ||||
| } | ||||
| 
 | ||||
| // Style full supported player | ||||
| .plyr__cite { | ||||
|     display: none; | ||||
|     margin-top: @padding-base; | ||||
|     margin-top: $spacing-base; | ||||
| 
 | ||||
|     .icon { | ||||
|         margin-right: (@padding-base / 4); | ||||
|         margin-right: ceil($spacing-base / 6); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										64
									
								
								demo/src/sass/layout/core.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,64 @@ | ||||
| // ========================================================================== | ||||
| // 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -7,13 +7,24 @@ 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 { | ||||
|     display: table-cell; | ||||
|     padding: $spacing-base; | ||||
|     text-align: center; | ||||
|     width: 100%; | ||||
|     vertical-align: middle; | ||||
| 
 | ||||
|     p { | ||||
|         @include font-size($font-size-large); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								demo/src/sass/layout/grid.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,19 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -3,7 +3,11 @@ | ||||
| // ========================================================================== | ||||
| 
 | ||||
| // Fade | ||||
| @keyframes fade-in { | ||||
| 	0% 		{ opacity: 0 } | ||||
| 	100% 	{ opacity: 1 } | ||||
| @keyframes fadein { | ||||
|     0% { | ||||
|         opacity: 0; | ||||
|     } | ||||
|     100% { | ||||
|         opacity: 1; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										45
									
								
								demo/src/sass/lib/fontface.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,45 @@ | ||||
| // ========================================================================== | ||||
| // 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'); | ||||
| } | ||||
							
								
								
									
										54
									
								
								demo/src/sass/lib/mixins.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,54 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										450
									
								
								demo/src/sass/lib/normalize.scss
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,450 @@ | ||||
| /*! 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; | ||||
| } | ||||
							
								
								
									
										11
									
								
								demo/src/sass/lib/reset.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,11 @@ | ||||
| // ========================================================================== | ||||
| // Resets | ||||
| // ========================================================================== | ||||
|  | ||||
| // BORDER-BOX ALL THE THINGS! | ||||
| // http://paulirish.com/2012/box-sizing-border-box-ftw/ | ||||
| *, | ||||
| *::after, | ||||
| *::before { | ||||
|     box-sizing: border-box; | ||||
| } | ||||
							
								
								
									
										6
									
								
								demo/src/sass/settings/breakpoints.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,6 @@ | ||||
| // ========================================================================== | ||||
| // Breakpoints | ||||
| // ========================================================================== | ||||
|  | ||||
| $screen-sm: 480px; | ||||
| $screen-md: 768px; | ||||
							
								
								
									
										32
									
								
								demo/src/sass/settings/colors.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,32 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
							
								
								
									
										12
									
								
								demo/src/sass/settings/cosmetic.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,12 @@ | ||||
| // ========================================================================== | ||||
| // 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%)); | ||||
							
								
								
									
										5
									
								
								demo/src/sass/settings/icons.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| // ========================================================================== | ||||
| // Icons | ||||
| // ========================================================================== | ||||
|  | ||||
| $icon-size: 16px; | ||||
							
								
								
									
										5
									
								
								demo/src/sass/settings/layout.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| // ========================================================================== | ||||
| // Layout | ||||
| // ========================================================================== | ||||
|  | ||||
| $container-max-width: 1280px; | ||||
							
								
								
									
										18
									
								
								demo/src/sass/settings/plyr.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,18 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
							
								
								
									
										5
									
								
								demo/src/sass/settings/spacing.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,5 @@ | ||||
| // ========================================================================== | ||||
| // Colors | ||||
| // ========================================================================== | ||||
|  | ||||
| $spacing-base: 20px; | ||||
							
								
								
									
										20
									
								
								demo/src/sass/settings/type.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,20 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
							
								
								
									
										35
									
								
								demo/src/sass/type/base.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,35 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
| } | ||||
							
								
								
									
										10
									
								
								demo/src/sass/type/headings.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,10 @@ | ||||
| // ========================================================================== | ||||
| // Headings | ||||
| // ========================================================================== | ||||
|  | ||||
| h1 { | ||||
|     @include font-size($font-size-h1); | ||||
|     font-weight: $font-weight-bold; | ||||
|     letter-spacing: $letter-spacing-headings; | ||||
|     margin: 0 0 ($spacing-base / 2); | ||||
| } | ||||
							
								
								
									
										7
									
								
								demo/src/sass/utilities/cosmetic.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,7 @@ | ||||
| // ========================================================================== | ||||
| // Misc cosmetic | ||||
| // ========================================================================== | ||||
|  | ||||
| .no-border { | ||||
|     border: 0; | ||||
| } | ||||
							
								
								
									
										20
									
								
								demo/src/sass/utilities/hidden.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,20 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
| } | ||||
| @ -1,12 +0,0 @@ | ||||
| <?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> | ||||
| Before Width: | Height: | Size: 1.1 KiB | 
| @ -1,11 +0,0 @@ | ||||
| <?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> | ||||
| Before Width: | Height: | Size: 981 B | 
| @ -1,9 +0,0 @@ | ||||
| <?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> | ||||
| Before Width: | Height: | Size: 779 B | 
| @ -1,9 +0,0 @@ | ||||
| <?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> | ||||
| Before Width: | Height: | Size: 739 B | 
							
								
								
									
										
											BIN
										
									
								
								dist/blank.mp4
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										2
									
								
								dist/plyr.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										7323
									
								
								dist/plyr.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										1
									
								
								dist/plyr.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										2
									
								
								dist/plyr.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										1
									
								
								dist/plyr.min.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										13237
									
								
								dist/plyr.polyfilled.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										1
									
								
								dist/plyr.polyfilled.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										2
									
								
								dist/plyr.polyfilled.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										1
									
								
								dist/plyr.polyfilled.min.js.map
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										2
									
								
								dist/plyr.svg
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -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-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> | ||||
| <?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> | ||||
| Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 4.9 KiB | 
							
								
								
									
										446
									
								
								gulpfile.js
									
									
									
									
									
								
							
							
						
						| @ -1,275 +1,320 @@ | ||||
| // ========================================================================== | ||||
| // Gulp build script | ||||
| // ========================================================================== | ||||
| /*global require, __dirname,Buffer*/ | ||||
| /*jshint -W079 */ | ||||
| /* global require, __dirname */ | ||||
| /* eslint no-console: "off" */ | ||||
|  | ||||
| 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 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 root = __dirname, | ||||
|     paths = { | ||||
| const bundles = require('./bundles.json'); | ||||
| const pkg = require('./package.json'); | ||||
|  | ||||
| // Get AWS config | ||||
| let aws = {}; | ||||
| try { | ||||
|     aws = require('./aws.json'); //eslint-disable-line | ||||
| } catch (e) { | ||||
|     // Do nothing | ||||
| } | ||||
|  | ||||
| const minSuffix = '.min'; | ||||
|  | ||||
| // Paths | ||||
| const root = __dirname; | ||||
| const paths = { | ||||
|     plyr: { | ||||
|         // Source paths | ||||
|         src: { | ||||
|                 less: path.join(root, 'src/less/**/*'), | ||||
|                 scss: path.join(root, 'src/scss/**/*'), | ||||
|             sass: path.join(root, 'src/sass/**/*.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/**/*'), | ||||
|             sass: path.join(root, 'demo/src/sass/**/*.scss'), | ||||
|             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/**')], | ||||
|     }, | ||||
|     upload: [ | ||||
|         path.join(root, `dist/*${minSuffix}.js`), | ||||
|         path.join(root, 'dist/*.css'), | ||||
|         path.join(root, 'dist/*.svg'), | ||||
|         path.join(root, 'demo/dist/**'), | ||||
|     ], | ||||
| }; | ||||
|  | ||||
| // Task arrays | ||||
|     tasks = { | ||||
|         less: [], | ||||
|         scss: [], | ||||
| const tasks = { | ||||
|     sass: [], | ||||
|     js: [], | ||||
|     sprite: [], | ||||
|     }, | ||||
|     // Fetch bundles from JSON | ||||
|     bundles = loadJSON(path.join(root, 'bundles.json')), | ||||
|     package = loadJSON(path.join(root, 'package.json')); | ||||
|  | ||||
| // Load json | ||||
| function loadJSON(path) { | ||||
|     try { | ||||
|         return JSON.parse(fs.readFileSync(path)); | ||||
|     } catch (err) { | ||||
|         return {}; | ||||
|     } | ||||
| } | ||||
|  | ||||
| // 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); | ||||
|     clean: ['clean'], | ||||
| }; | ||||
|     return src; | ||||
| } | ||||
|  | ||||
| var build = { | ||||
|     js: function(files, bundle) { | ||||
|         for (var key in files) { | ||||
|             (function(key) { | ||||
|                 var name = 'js-' + key; | ||||
| // 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, function() { | ||||
|                     return gulp | ||||
|             gulp.task(name, () => | ||||
|                 gulp | ||||
|                     .src(bundles[bundle].js[key]) | ||||
|                     .pipe(sourcemaps.init()) | ||||
|                     .pipe(concat(key)) | ||||
|                         .pipe(uglify().on('error', gutil.log)) | ||||
|                         .pipe(gulp.dest(paths[bundle].output)); | ||||
|                 }); | ||||
|             })(key); | ||||
|         } | ||||
|                     .pipe( | ||||
|                         rollup( | ||||
|                             { | ||||
|                                 plugins: [ | ||||
|                                     resolve(), | ||||
|                                     commonjs(), | ||||
|                                     babel(babelrc), | ||||
|                                 ], | ||||
|                             }, | ||||
|     less: function(files, bundle) { | ||||
|         for (var key in files) { | ||||
|             (function(key) { | ||||
|                 var name = 'less-' + key; | ||||
|                 tasks.less.push(name); | ||||
|  | ||||
|                 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)); | ||||
|                             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)), | ||||
|             ); | ||||
|         }); | ||||
|             })(key); | ||||
|         } | ||||
|     }, | ||||
|     scss: function(files, bundle) { | ||||
|         for (var key in files) { | ||||
|             (function(key) { | ||||
|                 var name = 'scss-' + key; | ||||
|                 tasks.scss.push(name); | ||||
|     sass(files, bundle) { | ||||
|         Object.keys(files).forEach(key => { | ||||
|             const name = `sass:${key}`; | ||||
|             tasks.sass.push(name); | ||||
|  | ||||
|                 gulp.task(name, function() { | ||||
|                     return gulp | ||||
|                         .src(bundles[bundle].scss[key]) | ||||
|             gulp.task(name, () => | ||||
|                 gulp | ||||
|                     .src(bundles[bundle].sass[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)); | ||||
|                     .pipe(prefix(browsers, { cascade: false })) | ||||
|                     .pipe(cleancss()) | ||||
|                     .pipe(size(sizeOptions)) | ||||
|                     .pipe(gulp.dest(paths[bundle].output)), | ||||
|             ); | ||||
|         }); | ||||
|             })(key); | ||||
|         } | ||||
|     }, | ||||
|     sprite: function(bundle) { | ||||
|         var name = 'sprite-' + bundle; | ||||
|     sprite(bundle) { | ||||
|         const name = `svg:sprite:${bundle}`; | ||||
|         tasks.sprite.push(name); | ||||
|  | ||||
|         // Process Icons | ||||
|         gulp.task(name, function() { | ||||
|             return gulp | ||||
|         gulp.task(name, () => | ||||
|             gulp | ||||
|                 .src(paths[bundle].src.sprite) | ||||
|                 .pipe( | ||||
|                     svgmin({ | ||||
|                         plugins: [ | ||||
|                             { | ||||
|                         plugins: [{ | ||||
|                             removeDesc: true, | ||||
|                             }, | ||||
|                         ], | ||||
|                         }], | ||||
|                     }), | ||||
|                 ) | ||||
|                 .pipe(svgstore()) | ||||
|                 .pipe(rename({ basename: bundle })) | ||||
|                 .pipe(gulp.dest(paths[bundle].output)); | ||||
|         }); | ||||
|                 .pipe(size(sizeOptions)) | ||||
|                 .pipe(gulp.dest(paths[bundle].output)), | ||||
|         ); | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| // Plyr core files | ||||
| build.js(bundles.plyr.js, 'plyr'); | ||||
| build.less(bundles.plyr.less, 'plyr'); | ||||
| build.scss(bundles.plyr.scss, 'plyr'); | ||||
| build.js(bundles.plyr.js, 'plyr', { name: 'Plyr', format: 'umd' }); | ||||
| build.sass(bundles.plyr.sass, 'plyr'); | ||||
| build.sprite('plyr'); | ||||
|  | ||||
| // Demo files | ||||
| build.less(bundles.demo.less, 'demo'); | ||||
| build.js(bundles.demo.js, 'demo'); | ||||
| build.sprite('demo'); | ||||
| build.sass(bundles.demo.sass, 'demo'); | ||||
| build.js(bundles.demo.js, 'demo', { format: 'iife' }); | ||||
|  | ||||
| // Build all JS | ||||
| gulp.task('js', function() { | ||||
| gulp.task('js', () => { | ||||
|     run(tasks.js); | ||||
| }); | ||||
|  | ||||
| // Build SCSS (for testing, default is LESS) | ||||
| gulp.task('scss', function() { | ||||
|     run(tasks.scss); | ||||
| }); | ||||
|  | ||||
| // Watch for file changes | ||||
| gulp.task('watch', function() { | ||||
| gulp.task('watch', () => { | ||||
|     // Plyr core | ||||
|     gulp.watch(paths.plyr.src.js, tasks.js); | ||||
|     gulp.watch(paths.plyr.src.less, tasks.less); | ||||
|     gulp.watch(paths.plyr.src.sass, tasks.sass); | ||||
|     gulp.watch(paths.plyr.src.sprite, tasks.sprite); | ||||
|  | ||||
|     // Demo | ||||
|     gulp.watch(paths.demo.src.js, tasks.js); | ||||
|     gulp.watch(paths.demo.src.less, tasks.less); | ||||
|     gulp.watch(paths.demo.src.sprite, tasks.sprite); | ||||
|     gulp.watch(paths.demo.src.sass, tasks.sass); | ||||
| }); | ||||
|  | ||||
| // Default gulp task | ||||
| gulp.task('default', function() { | ||||
|     run(tasks.js, tasks.less, tasks.sprite, 'watch'); | ||||
| gulp.task('default', () => { | ||||
|     run(tasks.clean, tasks.js, tasks.sass, 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; | ||||
|  | ||||
| // Some options | ||||
| var aws = loadJSON(path.join(root, 'aws.json')), | ||||
|     version = package.version, | ||||
|     maxAge = 31536000, // seconds 1 year | ||||
|     options = { | ||||
|     // 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 = { | ||||
|         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: function(version, filename) { | ||||
|         symlinks(ver, filename) { | ||||
|             return { | ||||
|                 headers: { | ||||
|                     // http://stackoverflow.com/questions/2272835/amazon-s3-object-redirect | ||||
|                     'x-amz-website-redirect-location': '/' + version + '/' + filename, | ||||
|                     'x-amz-website-redirect-location': `/${ver}/${filename}`, | ||||
|                     'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0', | ||||
|                 }, | ||||
|             }; | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
| // 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; | ||||
| } | ||||
|     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', function() { | ||||
|     console.log('Uploading ' + version + ' to ' + aws.cdn.domain + '...'); | ||||
|     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( | ||||
|             size({ | ||||
|                 showFiles: true, | ||||
|                 gzip: true, | ||||
|                 rename(p => { | ||||
|                     p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line | ||||
|                     p.dirname = p.dirname.replace('.', version); // eslint-disable-line | ||||
|                 }), | ||||
|             ) | ||||
|             .pipe( | ||||
|             rename(function(path) { | ||||
|                 path.dirname = path.dirname.replace('.', version); | ||||
|                 size({ | ||||
|                     showFiles: true, | ||||
|                     gzip: true, | ||||
|                 }), | ||||
|             ) | ||||
|             .pipe(replace(localPath, versionPath)) | ||||
| @ -277,77 +322,86 @@ gulp.task('cdn', function() { | ||||
|     }); | ||||
|  | ||||
|     // Publish to demo bucket | ||||
| gulp.task('demo', function() { | ||||
|     console.log('Uploading ' + version + ' demo to ' + aws.demo.domain + '...'); | ||||
|     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)) | ||||
|             .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" | ||||
|         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([paths.demo.root + '*.html']) | ||||
|             .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([paths.demo.root + 'error.html']) | ||||
|             .src([error]) | ||||
|             .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...'); | ||||
|     // Update symlinks for latest | ||||
|     /* gulp.task("symlinks", function () { | ||||
|         console.log("Updating symlinks..."); | ||||
|  | ||||
|     return gulp.src(paths.upload).pipe( | ||||
|         through.obj(function(chunk, enc, callback) { | ||||
|         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]; | ||||
|                     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'); | ||||
|                         }), | ||||
|                     ) | ||||
|                     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 + '...'); | ||||
|     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( | ||||
|         return gulp.src([`${paths.demo.root}index.html`]).pipe( | ||||
|             open('', { | ||||
|             url: 'http://' + aws.demo.domain, | ||||
|                 url: `http://${aws.demo.domain}`, | ||||
|             }), | ||||
|         ); | ||||
|     }); | ||||
|  | ||||
|     // Do everything | ||||
| gulp.task('publish', function() { | ||||
|     run(tasks.js, tasks.less, tasks.sprite, 'cdn', 'demo', 'symlinks'); | ||||
|     gulp.task('publish', () => { | ||||
|         run('version', tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'cdn', 'demo'); | ||||
|     }); | ||||
| } | ||||
|  | ||||
							
								
								
									
										4604
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										55
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @ -1,28 +1,51 @@ | ||||
| { | ||||
|     "name": "plyr", | ||||
|     "version": "2.0.18", | ||||
|     "version": "3.0.0-beta.20", | ||||
|     "description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player", | ||||
|     "homepage": "http://plyr.io", | ||||
|     "main": "src/js/plyr.js", | ||||
|     "dependencies": {}, | ||||
|     "homepage": "https://plyr.io", | ||||
|     "main": "./dist/plyr.js", | ||||
|     "browser": "./dist/plyr.min.js", | ||||
|     "sass": "./src/sass/plyr.scss", | ||||
|     "style": "./dist/plyr.css", | ||||
|     "devDependencies": { | ||||
|         "babel-core": "^6.26.0", | ||||
|         "babel-eslint": "^8.2.2", | ||||
|         "babel-plugin-external-helpers": "^6.22.0", | ||||
|         "babel-preset-env": "^1.6.1", | ||||
|         "del": "^3.0.0", | ||||
|         "eslint": "^4.18.2", | ||||
|         "eslint-config-airbnb-base": "^12.1.0", | ||||
|         "eslint-config-prettier": "^2.9.0", | ||||
|         "eslint-plugin-import": "^2.9.0", | ||||
|         "git-branch": "^2.0.1", | ||||
|         "gulp": "^3.9.1", | ||||
|         "gulp-autoprefixer": "^4.0.0", | ||||
|         "gulp-clean-css": "^3.9.0", | ||||
|         "gulp-autoprefixer": "^5.0.0", | ||||
|         "gulp-better-rollup": "^3.0.0", | ||||
|         "gulp-clean-css": "^3.9.3", | ||||
|         "gulp-concat": "^2.6.1", | ||||
|         "gulp-less": "^3.3.2", | ||||
|         "gulp-open": "^2.0.0", | ||||
|         "gulp-filter": "^5.1.0", | ||||
|         "gulp-open": "^3.0.0", | ||||
|         "gulp-rename": "^1.2.2", | ||||
|         "gulp-replace": "^0.6.1", | ||||
|         "gulp-s3": "^0.11.0", | ||||
|         "gulp-sass": "^3.1.0", | ||||
|         "gulp-size": "^2.1.0", | ||||
|         "gulp-size": "^3.0.0", | ||||
|         "gulp-sourcemaps": "^2.6.4", | ||||
|         "gulp-svgmin": "^1.2.4", | ||||
|         "gulp-svgstore": "^6.1.0", | ||||
|         "gulp-uglify": "^3.0.0", | ||||
|         "gulp-svgstore": "^6.1.1", | ||||
|         "gulp-uglify-es": "^1.0.1", | ||||
|         "gulp-util": "^3.0.8", | ||||
|         "run-sequence": "^2.2.0", | ||||
|         "through2": "^2.0.3" | ||||
|         "rollup-plugin-babel": "^3.0.3", | ||||
|         "rollup-plugin-commonjs": "^8.4.1", | ||||
|         "rollup-plugin-node-resolve": "^3.2.0", | ||||
|         "run-sequence": "^2.2.1", | ||||
|         "stylelint": "^9.1.3", | ||||
|         "stylelint-config-prettier": "^3.0.4", | ||||
|         "stylelint-config-recommended": "^2.1.0", | ||||
|         "stylelint-config-sass-guidelines": "^5.0.0", | ||||
|         "stylelint-order": "^0.8.1", | ||||
|         "stylelint-scss": "^2.5.0", | ||||
|         "stylelint-selector-bem-pattern": "^2.0.0" | ||||
|     }, | ||||
|     "keywords": [ | ||||
|         "HTML5 Video", | ||||
| @ -47,5 +70,9 @@ | ||||
|     "scripts": { | ||||
|         "test": "echo \"Error: no test specified\" && exit 1" | ||||
|     }, | ||||
|     "author": "Sam Potts <sam@potts.es>" | ||||
|     "author": "Sam Potts <sam@potts.es>", | ||||
|     "dependencies": { | ||||
|         "babel-polyfill": "^6.26.0", | ||||
|         "custom-event-polyfill": "^0.3.0" | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										31
									
								
								plyr.code-workspace
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,31 @@ | ||||
| { | ||||
| 	"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 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										231
									
								
								src/js/captions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,231 @@ | ||||
| // ========================================================================== | ||||
| // Plyr Captions | ||||
| // TODO: Create as class | ||||
| // ========================================================================== | ||||
|  | ||||
| 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))); | ||||
|  | ||||
|         // Get tracks | ||||
|         const tracks = captions.getTracks.call(this); | ||||
|  | ||||
|         // If no caption file exists, hide container for caption text | ||||
|         if (utils.is.empty(tracks)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Get browser info | ||||
|         const browser = utils.getBrowser(); | ||||
|  | ||||
|         // Fix IE captions if CORS is used | ||||
|         // Fetch captions and inject as blobs instead (data URIs not supported!) | ||||
|         if (browser.isIE && window.URL) { | ||||
|             const elements = this.media.querySelectorAll('track'); | ||||
|  | ||||
|             Array.from(elements).forEach(track => { | ||||
|                 const src = track.getAttribute('src'); | ||||
|                 const href = utils.parseUrl(src); | ||||
|  | ||||
|                 if (href.hostname !== window.location.href.hostname && [ | ||||
|                     'http:', | ||||
|                     'https:', | ||||
|                 ].includes(href.protocol)) { | ||||
|                     utils | ||||
|                         .fetch(src, 'blob') | ||||
|                         .then(blob => { | ||||
|                             track.setAttribute('src', window.URL.createObjectURL(blob)); | ||||
|                         }) | ||||
|                         .catch(() => { | ||||
|                             utils.removeElement(track); | ||||
|                         }); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // 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; | ||||
							
								
								
									
										28
									
								
								src/js/console.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,28 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1257
									
								
								src/js/controls.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										383
									
								
								src/js/defaults.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,383 @@ | ||||
| // ========================================================================== | ||||
| // 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.20/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', | ||||
|         advertisement: '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', | ||||
|         'adscontentresume', | ||||
|         '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 | ||||
|     // Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio | ||||
|     ads: { | ||||
|         enabled: false, | ||||
|         publisherId: '', | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| export default defaults; | ||||
							
								
								
									
										204
									
								
								src/js/fullscreen.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,204 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
							
								
								
									
										595
									
								
								src/js/listeners.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,595 @@ | ||||
| // ========================================================================== | ||||
| // 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(); | ||||
|  | ||||
| class Listeners { | ||||
|     constructor(player) { | ||||
|         this.player = player; | ||||
|         this.lastKey = null; | ||||
|  | ||||
|         this.handleKey = this.handleKey.bind(this); | ||||
|         this.toggleMenu = this.toggleMenu.bind(this); | ||||
|     } | ||||
|  | ||||
|     // Handle key presses | ||||
|     handleKey(event) { | ||||
|         const code = event.keyCode ? event.keyCode : event.which; | ||||
|         const pressed = event.type === 'keydown'; | ||||
|         const repeat = pressed && code === this.lastKey; | ||||
|  | ||||
|         // 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.player.currentTime = this.player.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.player.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.player.togglePlay(); | ||||
|                     } | ||||
|                     break; | ||||
|  | ||||
|                 case 38: | ||||
|                     // Arrow up | ||||
|                     this.player.increaseVolume(0.1); | ||||
|                     break; | ||||
|  | ||||
|                 case 40: | ||||
|                     // Arrow down | ||||
|                     this.player.decreaseVolume(0.1); | ||||
|                     break; | ||||
|  | ||||
|                 case 77: | ||||
|                     // M key | ||||
|                     if (!repeat) { | ||||
|                         this.player.muted = !this.player.muted; | ||||
|                     } | ||||
|                     break; | ||||
|  | ||||
|                 case 39: | ||||
|                     // Arrow forward | ||||
|                     this.player.forward(); | ||||
|                     break; | ||||
|  | ||||
|                 case 37: | ||||
|                     // Arrow back | ||||
|                     this.player.rewind(); | ||||
|                     break; | ||||
|  | ||||
|                 case 70: | ||||
|                     // F key | ||||
|                     this.player.fullscreen.toggle(); | ||||
|                     break; | ||||
|  | ||||
|                 case 67: | ||||
|                     // C key | ||||
|                     if (!repeat) { | ||||
|                         this.player.toggleCaptions(); | ||||
|                     } | ||||
|                     break; | ||||
|  | ||||
|                 case 76: | ||||
|                     // L key | ||||
|                     this.player.loop = !this.player.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.player.fullscreen.enabled && this.player.fullscreen.active && code === 27) { | ||||
|                 this.player.fullscreen.toggle(); | ||||
|             } | ||||
|  | ||||
|             // Store last code for next cycle | ||||
|             this.lastKey = code; | ||||
|         } else { | ||||
|             this.lastKey = null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Toggle menu | ||||
|     toggleMenu(event) { | ||||
|         controls.toggleMenu.call(this.player, event); | ||||
|     } | ||||
|  | ||||
|     // Global window & document listeners | ||||
|     global(toggle = true) { | ||||
|         // Keyboard shortcuts | ||||
|         if (this.player.config.keyboard.global) { | ||||
|             utils.toggleListener(window, 'keydown keyup', this.handleKey, toggle, false); | ||||
|         } | ||||
|  | ||||
|         // Click anywhere closes menu | ||||
|         utils.toggleListener(document.body, 'click', this.toggleMenu, toggle); | ||||
|     } | ||||
|  | ||||
|     // Container listeners | ||||
|     container() { | ||||
|         // Keyboard shortcuts | ||||
|         if (!this.player.config.keyboard.global && this.player.config.keyboard.focused) { | ||||
|             utils.on(this.player.elements.container, 'keydown keyup', this.handleKey, false); | ||||
|         } | ||||
|  | ||||
|         // Detect tab focus | ||||
|         // Remove class on blur/focusout | ||||
|         utils.on(this.player.elements.container, 'focusout', event => { | ||||
|             utils.toggleClass(event.target, this.player.config.classNames.tabFocus, false); | ||||
|         }); | ||||
|  | ||||
|         // Add classname to tabbed elements | ||||
|         utils.on(this.player.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 | ||||
|             setTimeout(() => { | ||||
|                 utils.toggleClass(utils.getFocusElement(), this.player.config.classNames.tabFocus, true); | ||||
|             }, 0); | ||||
|         }); | ||||
|  | ||||
|         // Toggle controls visibility based on mouse movement | ||||
|         if (this.player.config.hideControls) { | ||||
|             // Toggle controls on mouse events and entering fullscreen | ||||
|             utils.on(this.player.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', event => { | ||||
|                 this.player.toggleControls(event); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Listen for media events | ||||
|     media() { | ||||
|         // Time change on media | ||||
|         utils.on(this.player.media, 'timeupdate seeking', event => ui.timeUpdate.call(this.player, event)); | ||||
|  | ||||
|         // Display duration | ||||
|         utils.on(this.player.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this.player, 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.player.media, 'loadeddata', () => { | ||||
|             utils.toggleHidden(this.player.elements.volume, !this.player.hasAudio); | ||||
|             utils.toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio); | ||||
|         }); | ||||
|  | ||||
|         // Handle the media finishing | ||||
|         utils.on(this.player.media, 'ended', () => { | ||||
|             // Show poster on end | ||||
|             if (this.player.isHTML5 && this.player.isVideo && this.player.config.showPosterOnEnd) { | ||||
|                 // Restart | ||||
|                 this.player.restart(); | ||||
|  | ||||
|                 // Re-load media | ||||
|                 this.player.media.load(); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // Check for buffer progress | ||||
|         utils.on(this.player.media, 'progress playing', event => ui.updateProgress.call(this.player, event)); | ||||
|  | ||||
|         // Handle native mute | ||||
|         utils.on(this.player.media, 'volumechange', event => ui.updateVolume.call(this.player, event)); | ||||
|  | ||||
|         // Handle native play/pause | ||||
|         utils.on(this.player.media, 'playing play pause ended', event => ui.checkPlaying.call(this.player, event)); | ||||
|  | ||||
|         // Loading | ||||
|         utils.on(this.player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(this.player, event)); | ||||
|  | ||||
|         // Check if media failed to load | ||||
|         // utils.on(this.player.media, 'play', event => ui.checkFailed.call(this.player, event)); | ||||
|  | ||||
|         // Click video | ||||
|         if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) { | ||||
|             // Re-fetch the wrapper | ||||
|             const wrapper = utils.getElement.call(this.player, `.${this.player.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.player.config.hideControls && support.touch && !this.player.paused) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 if (this.player.paused) { | ||||
|                     this.player.play(); | ||||
|                 } else if (this.player.ended) { | ||||
|                     this.player.restart(); | ||||
|                     this.player.play(); | ||||
|                 } else { | ||||
|                     this.player.pause(); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Disable right click | ||||
|         if (this.player.supported.ui && this.player.config.disableContextMenu) { | ||||
|             utils.on( | ||||
|                 this.player.media, | ||||
|                 'contextmenu', | ||||
|                 event => { | ||||
|                     event.preventDefault(); | ||||
|                 }, | ||||
|                 false, | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         // Volume change | ||||
|         utils.on(this.player.media, 'volumechange', () => { | ||||
|             // Save to storage | ||||
|             this.player.storage.set({ volume: this.player.volume, muted: this.player.muted }); | ||||
|         }); | ||||
|  | ||||
|         // Speed change | ||||
|         utils.on(this.player.media, 'ratechange', () => { | ||||
|             // Update UI | ||||
|             controls.updateSetting.call(this.player, 'speed'); | ||||
|  | ||||
|             // Save to storage | ||||
|             this.player.storage.set({ speed: this.player.speed }); | ||||
|         }); | ||||
|  | ||||
|         // Quality change | ||||
|         utils.on(this.player.media, 'qualitychange', () => { | ||||
|             // Update UI | ||||
|             controls.updateSetting.call(this.player, 'quality'); | ||||
|  | ||||
|             // Save to storage | ||||
|             this.player.storage.set({ quality: this.player.quality }); | ||||
|         }); | ||||
|  | ||||
|         // Caption language change | ||||
|         utils.on(this.player.media, 'languagechange', () => { | ||||
|             // Update UI | ||||
|             controls.updateSetting.call(this.player, 'captions'); | ||||
|  | ||||
|             // Save to storage | ||||
|             this.player.storage.set({ language: this.player.language }); | ||||
|         }); | ||||
|  | ||||
|         // Captions toggle | ||||
|         utils.on(this.player.media, 'captionsenabled captionsdisabled', () => { | ||||
|             // Update UI | ||||
|             controls.updateSetting.call(this.player, 'captions'); | ||||
|  | ||||
|             // Save to storage | ||||
|             this.player.storage.set({ captions: this.player.captions.active }); | ||||
|         }); | ||||
|  | ||||
|         // Proxy events to container | ||||
|         // Bubble up key events for Edge | ||||
|         utils.on(this.player.media, this.player.config.events.concat([ | ||||
|             'keyup', | ||||
|             'keydown', | ||||
|         ]).join(' '), event => { | ||||
|             let detail = {}; | ||||
|  | ||||
|             // Get error details from media | ||||
|             if (event.type === 'error') { | ||||
|                 detail = this.player.media.error; | ||||
|             } | ||||
|  | ||||
|             utils.dispatchEvent.call(this.player, this.player.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.player.config.listeners[handlerKey]; | ||||
|  | ||||
|             // Execute custom handler | ||||
|             if (utils.is.function(customHandler)) { | ||||
|                 customHandler.call(this.player, event); | ||||
|             } | ||||
|  | ||||
|             // Only call default handler if not prevented in custom handler | ||||
|             if (!event.defaultPrevented && utils.is.function(defaultHandler)) { | ||||
|                 defaultHandler.call(this.player, event); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         // Play/pause toggle | ||||
|         utils.on(this.player.elements.buttons.play, 'click', event => | ||||
|             proxy(event, 'play', () => { | ||||
|                 this.player.togglePlay(); | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         // Pause | ||||
|         utils.on(this.player.elements.buttons.restart, 'click', event => | ||||
|             proxy(event, 'restart', () => { | ||||
|                 this.player.restart(); | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         // Rewind | ||||
|         utils.on(this.player.elements.buttons.rewind, 'click', event => | ||||
|             proxy(event, 'rewind', () => { | ||||
|                 this.player.rewind(); | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         // Rewind | ||||
|         utils.on(this.player.elements.buttons.forward, 'click', event => | ||||
|             proxy(event, 'forward', () => { | ||||
|                 this.player.forward(); | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         // Mute toggle | ||||
|         utils.on(this.player.elements.buttons.mute, 'click', event => | ||||
|             proxy(event, 'mute', () => { | ||||
|                 this.player.muted = !this.player.muted; | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         // Captions toggle | ||||
|         utils.on(this.player.elements.buttons.captions, 'click', event => | ||||
|             proxy(event, 'captions', () => { | ||||
|                 this.player.toggleCaptions(); | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         // Fullscreen toggle | ||||
|         utils.on(this.player.elements.buttons.fullscreen, 'click', event => | ||||
|             proxy(event, 'fullscreen', () => { | ||||
|                 this.player.fullscreen.toggle(); | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         // Picture-in-Picture | ||||
|         utils.on(this.player.elements.buttons.pip, 'click', event => | ||||
|             proxy(event, 'pip', () => { | ||||
|                 this.player.pip = 'toggle'; | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         // Airplay | ||||
|         utils.on(this.player.elements.buttons.airplay, 'click', event => | ||||
|             proxy(event, 'airplay', () => { | ||||
|                 this.player.airplay(); | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         // Settings menu | ||||
|         utils.on(this.player.elements.buttons.settings, 'click', event => { | ||||
|             controls.toggleMenu.call(this.player, event); | ||||
|         }); | ||||
|  | ||||
|         // Settings menu | ||||
|         utils.on(this.player.elements.settings.form, 'click', event => { | ||||
|             event.stopPropagation(); | ||||
|  | ||||
|             // Settings menu items - use event delegation as items are added/removed | ||||
|             if (utils.matches(event.target, this.player.config.selectors.inputs.language)) { | ||||
|                 proxy(event, 'language', () => { | ||||
|                     this.player.language = event.target.value; | ||||
|                 }); | ||||
|             } else if (utils.matches(event.target, this.player.config.selectors.inputs.quality)) { | ||||
|                 proxy(event, 'quality', () => { | ||||
|                     this.player.quality = event.target.value; | ||||
|                 }); | ||||
|             } else if (utils.matches(event.target, this.player.config.selectors.inputs.speed)) { | ||||
|                 proxy(event, 'speed', () => { | ||||
|                     this.player.speed = parseFloat(event.target.value); | ||||
|                 }); | ||||
|             } else { | ||||
|                 controls.showTab.call(this.player, event); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // Seek | ||||
|         utils.on(this.player.elements.inputs.seek, inputEvent, event => | ||||
|             proxy(event, 'seek', () => { | ||||
|                 this.player.currentTime = event.target.value / event.target.max * this.player.duration; | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         // Current time invert | ||||
|         // Only if one time element is used for both currentTime and duration | ||||
|         if (this.player.config.toggleInvert && !utils.is.element(this.player.elements.display.duration)) { | ||||
|             utils.on(this.player.elements.display.currentTime, 'click', () => { | ||||
|                 // Do nothing if we're at the start | ||||
|                 if (this.player.currentTime === 0) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 this.player.config.invertTime = !this.player.config.invertTime; | ||||
|                 ui.timeUpdate.call(this.player); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Volume | ||||
|         utils.on(this.player.elements.inputs.volume, inputEvent, event => | ||||
|             proxy(event, 'volume', () => { | ||||
|                 this.player.volume = event.target.value; | ||||
|             }), | ||||
|         ); | ||||
|  | ||||
|         // Polyfill for lower fill in <input type="range"> for webkit | ||||
|         if (browser.isWebkit) { | ||||
|             utils.on(utils.getElements.call(this.player, 'input[type="range"]'), 'input', event => { | ||||
|                 controls.updateRangeFill.call(this.player, event.target); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Seek tooltip | ||||
|         utils.on(this.player.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this.player, event)); | ||||
|  | ||||
|         // Toggle controls visibility based on mouse movement | ||||
|         if (this.player.config.hideControls) { | ||||
|             // Watch for cursor over controls so they don't hide when trying to interact | ||||
|             utils.on(this.player.elements.controls, 'mouseenter mouseleave', event => { | ||||
|                 this.player.elements.controls.hover = event.type === 'mouseenter'; | ||||
|             }); | ||||
|  | ||||
|             // Watch for cursor over controls so they don't hide when trying to interact | ||||
|             utils.on(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => { | ||||
|                 this.player.elements.controls.pressed = [ | ||||
|                     'mousedown', | ||||
|                     'touchstart', | ||||
|                 ].includes(event.type); | ||||
|             }); | ||||
|  | ||||
|             // Focus in/out on controls | ||||
|             utils.on(this.player.elements.controls, 'focusin focusout', event => { | ||||
|                 this.player.toggleControls(event); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Mouse wheel for volume | ||||
|         utils.on( | ||||
|             this.player.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.player.decreaseVolume(step); | ||||
|                             direction = -1; | ||||
|                         } else { | ||||
|                             this.player.increaseVolume(step); | ||||
|                             direction = 1; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     // Scroll up (or down on natural) to increase | ||||
|                     if (event.deltaY > 0 || event.deltaX < 0) { | ||||
|                         if (inverted) { | ||||
|                             this.player.increaseVolume(step); | ||||
|                             direction = 1; | ||||
|                         } else { | ||||
|                             this.player.decreaseVolume(step); | ||||
|                             direction = -1; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     // Don't break page scrolling at max and min | ||||
|                     if ((direction === 1 && this.player.media.volume < 1) || (direction === -1 && this.player.media.volume > 0)) { | ||||
|                         event.preventDefault(); | ||||
|                     } | ||||
|                 }), | ||||
|             false, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     // Reset on destroy | ||||
|     clear() { | ||||
|         this.global(false); | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default Listeners; | ||||
							
								
								
									
										106
									
								
								src/js/media.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,106 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
							
								
								
									
										574
									
								
								src/js/plugins/ads.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,574 @@ | ||||
| // ========================================================================== | ||||
| // 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'; | ||||
|  | ||||
| class Ads { | ||||
|     /** | ||||
|      * Ads constructor. | ||||
|      * @param {object} player | ||||
|      * @return {Ads} | ||||
|      */ | ||||
|     constructor(player) { | ||||
|         this.player = player; | ||||
|         this.publisherId = player.config.ads.publisherId; | ||||
|         this.enabled = player.isHTML5 && player.isVideo && player.config.ads.enabled && utils.is.string(this.publisherId) && this.publisherId.length; | ||||
|         this.playing = false; | ||||
|         this.initialized = false; | ||||
|         this.elements = { | ||||
|             container: null, | ||||
|             displayContainer: null, | ||||
|         }; | ||||
|         this.manager = null; | ||||
|         this.loader = null; | ||||
|         this.cuePoints = null; | ||||
|         this.events = {}; | ||||
|         this.safetyTimer = null; | ||||
|         this.countdownTimer = null; | ||||
|  | ||||
|         // Setup a promise to resolve when the IMA manager is ready | ||||
|         this.managerPromise = new Promise((resolve, reject) => { | ||||
|             // The ad is loaded and ready | ||||
|             this.on('loaded', resolve); | ||||
|  | ||||
|             // Ads failed | ||||
|             this.on('error', reject); | ||||
|         }); | ||||
|  | ||||
|         this.load(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Load the IMA SDK | ||||
|      */ | ||||
|     load() { | ||||
|         if (this.enabled) { | ||||
|             // Check if the Google IMA3 SDK is loaded or load it ourselves | ||||
|             if (!utils.is.object(window.google) || !utils.is.object(window.google.ima)) { | ||||
|                 utils | ||||
|                     .loadScript(this.player.config.urls.googleIMA.api) | ||||
|                     .then(() => { | ||||
|                         this.ready(); | ||||
|                     }) | ||||
|                     .catch(() => { | ||||
|                         // Script failed to load or is blocked | ||||
|                         this.trigger('error', new Error('Google IMA SDK failed to load')); | ||||
|                     }); | ||||
|             } else { | ||||
|                 this.ready(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the ads instance ready | ||||
|      */ | ||||
|     ready() { | ||||
|         // Start ticking our safety timer. If the whole advertisement | ||||
|         // thing doesn't resolve within our set time; we bail | ||||
|         this.startSafetyTimer(12000, 'ready()'); | ||||
|  | ||||
|         // Clear the safety timer | ||||
|         this.managerPromise.then(() => { | ||||
|             this.clearSafetyTimer('onAdsManagerLoaded()'); | ||||
|         }); | ||||
|  | ||||
|         // Set listeners on the Plyr instance | ||||
|         this.listeners(); | ||||
|  | ||||
|         // Setup the IMA SDK | ||||
|         this.setupIMA(); | ||||
|     } | ||||
|  | ||||
|     // Build the default tag URL | ||||
|     get tagUrl() { | ||||
|         const params = { | ||||
|             AV_PUBLISHERID: '58c25bb0073ef448b1087ad6', | ||||
|             AV_CHANNELID: '5a0458dc28a06145e4519d21', | ||||
|             AV_URL: location.hostname, | ||||
|             cb: Date.now(), | ||||
|             AV_WIDTH: 640, | ||||
|             AV_HEIGHT: 480, | ||||
|             AV_CDIM2: this.publisherId, | ||||
|         }; | ||||
|  | ||||
|         const base = 'https://go.aniview.com/api/adserver6/vast/'; | ||||
|  | ||||
|         return `${base}?${utils.buildUrlParams(params)}`; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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, | ||||
|         }); | ||||
|         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 = this.tagUrl; | ||||
|  | ||||
|             // 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); | ||||
|         } catch (e) { | ||||
|             this.onAdError(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Update the ad countdown | ||||
|      * @param {boolean} start | ||||
|      */ | ||||
|     pollCountdown(start = false) { | ||||
|         if (!start) { | ||||
|             clearInterval(this.countdownTimer); | ||||
|             this.elements.container.removeAttribute('data-badge-text'); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         const update = () => { | ||||
|             const time = utils.formatTime(Math.max(this.manager.getRemainingTime(), 0)); | ||||
|             const label = `${this.player.config.i18n.advertisement} - ${time}`; | ||||
|             this.elements.container.setAttribute('data-badge-text', label); | ||||
|         }; | ||||
|  | ||||
|         this.countdownTimer = setInterval(update, 100); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * This method is called whenever the ads are ready inside the AdDisplayContainer | ||||
|      * @param {Event} adsManagerLoadedEvent | ||||
|      */ | ||||
|     onAdsManagerLoaded(event) { | ||||
|         // 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 = event.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 && cuePoint < this.player.duration) { | ||||
|                 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.trigger('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 => { | ||||
|             const event = `ads${type.replace(/_/g, '').toLowerCase()}`; | ||||
|             utils.dispatchEvent.call(this.player, this.player.media, event); | ||||
|         }; | ||||
|  | ||||
|         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.trigger('loaded'); | ||||
|  | ||||
|                 // Bubble event | ||||
|                 dispatchEvent(event.type); | ||||
|  | ||||
|                 // 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 | ||||
|  | ||||
|                 // Fire event | ||||
|                 dispatchEvent(event.type); | ||||
|  | ||||
|                 // 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 | ||||
|  | ||||
|                 dispatchEvent(event.type); | ||||
|  | ||||
|                 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 | ||||
|  | ||||
|                 dispatchEvent(event.type); | ||||
|  | ||||
|                 this.pollCountdown(); | ||||
|  | ||||
|                 this.resumeContent(); | ||||
|  | ||||
|                 break; | ||||
|  | ||||
|             case google.ima.AdEvent.Type.STARTED: | ||||
|             case google.ima.AdEvent.Type.MIDPOINT: | ||||
|             case google.ima.AdEvent.Type.COMPLETE: | ||||
|             case google.ima.AdEvent.Type.IMPRESSION: | ||||
|             case google.ima.AdEvent.Type.CLICK: | ||||
|                 dispatchEvent(event.type); | ||||
|                 break; | ||||
|  | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Any ad error handling comes through here | ||||
|      * @param {Event} event | ||||
|      */ | ||||
|     onAdError(event) { | ||||
|         this.cancel(); | ||||
|         this.player.debug.warn('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) { | ||||
|             this.resumeContent(); | ||||
|         } | ||||
|  | ||||
|         // 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); | ||||
|                 } | ||||
|             }) | ||||
|             .catch(() => {}); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Resume our video | ||||
|      */ | ||||
|     resumeContent() { | ||||
|         // Hide the advertisement container | ||||
|         this.elements.container.style.zIndex = ''; | ||||
|  | ||||
|         // Ad is stopped | ||||
|         this.playing = false; | ||||
|  | ||||
|         // Play our video | ||||
|         if (this.player.currentTime < this.player.duration) { | ||||
|             this.player.play(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Pause our video | ||||
|      */ | ||||
|     pauseContent() { | ||||
|         // Show the advertisement container | ||||
|         this.elements.container.style.zIndex = 3; | ||||
|  | ||||
|         // 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.trigger('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('loaded', resolve); | ||||
|                     this.player.debug.log(this.manager); | ||||
|                 }); | ||||
|  | ||||
|                 // Now request some new advertisements | ||||
|                 this.requestAds(); | ||||
|             }) | ||||
|             .catch(() => {}); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handles callbacks after an ad event was invoked | ||||
|      * @param {string} event - Event type | ||||
|      */ | ||||
|     trigger(event, ...args) { | ||||
|         const handlers = this.events[event]; | ||||
|  | ||||
|         if (utils.is.array(handlers)) { | ||||
|             handlers.forEach(handler => { | ||||
|                 if (utils.is.function(handler)) { | ||||
|                     handler.apply(this, args); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Add event listeners | ||||
|      * @param {string} event - Event type | ||||
|      * @param {function} callback - Callback for when event occurs | ||||
|      * @return {Ads} | ||||
|      */ | ||||
|     on(event, callback) { | ||||
|         if (!utils.is.array(this.events[event])) { | ||||
|             this.events[event] = []; | ||||
|         } | ||||
|  | ||||
|         this.events[event].push(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 = 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; | ||||
							
								
								
									
										323
									
								
								src/js/plugins/vimeo.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,323 @@ | ||||
| // ========================================================================== | ||||
| // 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) | ||||
|                 .then(() => { | ||||
|                     vimeo.ready.call(this); | ||||
|                 }) | ||||
|                 .catch(error => { | ||||
|                     this.debug.warn('Vimeo API failed to load', error); | ||||
|                 }); | ||||
|         } 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 | ||||
|         setTimeout(() => ui.build.call(player), 0); | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| export default vimeo; | ||||
							
								
								
									
										417
									
								
								src/js/plugins/youtube.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,417 @@ | ||||
| // ========================================================================== | ||||
| // 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).catch(error => { | ||||
|                 this.debug.warn('YouTube API failed to load', error); | ||||
|             }); | ||||
|  | ||||
|             // 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.pause = () => { | ||||
|                         instance.pauseVideo(); | ||||
|                     }; | ||||
|  | ||||
|                     player.media.stop = () => { | ||||
|                         instance.stopVideo(); | ||||
|                     }; | ||||
|  | ||||
|                     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 | ||||
|                     clearInterval(player.timers.buffering); | ||||
|  | ||||
|                     // Setup buffering | ||||
|                     player.timers.buffering = 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) { | ||||
|                             clearInterval(player.timers.buffering); | ||||
|  | ||||
|                             // Trigger event | ||||
|                             utils.dispatchEvent.call(player, player.media, 'canplaythrough'); | ||||
|                         } | ||||
|                     }, 200); | ||||
|  | ||||
|                     // Rebuild UI | ||||
|                     setTimeout(() => ui.build.call(player), 50); | ||||
|                 }, | ||||
|                 onStateChange(event) { | ||||
|                     // Get the instance | ||||
|                     const instance = event.target; | ||||
|  | ||||
|                     // Reset timer | ||||
|                     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 = 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; | ||||
							
								
								
									
										4693
									
								
								src/js/plyr.js
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										14
									
								
								src/js/plyr.polyfilled.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,14 @@ | ||||
| // ========================================================================== | ||||
| // Plyr Polyfilled Build | ||||
| // plyr.js v3.0.0-beta.20 | ||||
| // https://github.com/sampotts/plyr | ||||
| // License: The MIT License (MIT) | ||||
| // ========================================================================== | ||||
|  | ||||
| import 'babel-polyfill'; | ||||
|  | ||||
| import 'custom-event-polyfill'; | ||||
|  | ||||
| import Plyr from './plyr'; | ||||
|  | ||||
| export default Plyr; | ||||
							
								
								
									
										148
									
								
								src/js/source.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,148 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||
							
								
								
									
										71
									
								
								src/js/storage.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,71 @@ | ||||
| // ========================================================================== | ||||
| // 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; | ||||