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! | 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 | ### Expected behaviour | ||||||
|  |  | ||||||
| ### Actual behaviour | ### Actual behaviour | ||||||
| @ -16,13 +13,5 @@ Please use this issue template as it makes replicating and fixing the issue easi | |||||||
| - Operating System: | - Operating System: | ||||||
| - Version: | - Version: | ||||||
|  |  | ||||||
| Players affected: |  | ||||||
| - [ ] HTML5 Video |  | ||||||
| - [ ] HTML5 Audio |  | ||||||
| - [ ] YouTube |  | ||||||
| - [ ] Vimeo |  | ||||||
|  |  | ||||||
| ### Steps to reproduce | ### Steps to reproduce | ||||||
| - | - | ||||||
|  |  | ||||||
| ### Relevant links |  | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,11 +1,11 @@ | |||||||
| node_modules | node_modules | ||||||
| *.sublime-project |  | ||||||
| *.sublime-workspace |  | ||||||
| .DS_Store | .DS_Store | ||||||
| aws.json | aws.json | ||||||
| docs/index.dev.html |  | ||||||
| *.mp4 | *.mp4 | ||||||
| index-dev.html | !dist/blank.mp4 | ||||||
| notes.txt | index-*.html | ||||||
| *.vtt | npm-debug.log | ||||||
| docs/index.dev.php | *.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", |         "Audio", | ||||||
|         "Video", |         "Video", | ||||||
|         "HTML5 Audio", |         "HTML5 Audio", | ||||||
|         "HTml5 Video" |         "HTML5 Video" | ||||||
|     ], |     ], | ||||||
|     "authors": [ |     "authors": [ | ||||||
|         "Sam Potts <sam@potts.es>" |         "Sam Potts <sam@potts.es>" | ||||||
|  | |||||||
							
								
								
									
										20
									
								
								bundles.json
									
									
									
									
									
								
							
							
						
						| @ -1,24 +1,20 @@ | |||||||
| { | { | ||||||
|     "plyr": { |     "plyr": { | ||||||
|         "less": { |         "sass": { | ||||||
|             "plyr.css":     ["src/less/plyr.less"] |             "plyr.css": "src/sass/plyr.scss" | ||||||
|         }, |  | ||||||
|         "scss": { |  | ||||||
|             "plyr.css":     ["src/scss/plyr.scss"] |  | ||||||
|         }, |         }, | ||||||
|         "js": { |         "js": { | ||||||
|             "plyr.js":      ["src/js/plyr.js"] |             "plyr.js": "src/js/plyr.js", | ||||||
|  |             "plyr.polyfilled.js": "src/js/plyr.polyfilled.js" | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     "demo": { |     "demo": { | ||||||
|         "less": { |         "sass": { | ||||||
|             "demo.css":     ["demo/src/less/demo.less"] |             "demo.css": "demo/src/sass/bundles/demo.scss", | ||||||
|  |             "error.css": "demo/src/sass/bundles/error.csss" | ||||||
|         }, |         }, | ||||||
|         "js": { |         "js": { | ||||||
|             "demo.js": [ |             "demo.js": "demo/src/js/demo.js" | ||||||
|                 "demo/src/js/lib/classlist.js", |  | ||||||
|                 "demo/src/js/main.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"> |     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|  |  | ||||||
|     <!-- Docs styles --> |     <!-- Docs styles --> | ||||||
|     <link rel="stylesheet" href="dist/demo.css"> |     <link rel="stylesheet" href="dist/error.css"> | ||||||
|  |  | ||||||
|     <!-- Preload --> |     <!-- 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/gordita-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-bold.woff2"> | ||||||
| </head> | </head> | ||||||
|  |  | ||||||
| <body> | <body> | ||||||
|     <main> |     <main> | ||||||
|         <h1>Doh.</h1> |         <h1>Doh.</h1> | ||||||
|         <p>Looks like something went wrong.</p> |         <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> |     </main> | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										192
									
								
								demo/index.html
									
									
									
									
									
								
							
							
						
						| @ -3,66 +3,95 @@ | |||||||
|  |  | ||||||
| <head> | <head> | ||||||
|     <meta charset="utf-8" /> |     <meta charset="utf-8" /> | ||||||
|     <title>Plyr - A simple HTML5 media player</title> |     <title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title> | ||||||
|     <meta name="description" content="A simple HTML5 media player with custom controls and WebVTT captions."> |     <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="author" content="Sam Potts"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> |     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|  |  | ||||||
|     <!-- Styles --> |     <!-- Icons --> | ||||||
|     <link rel="stylesheet" href="../dist/plyr.css"> |     <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 --> |     <!-- Docs styles --> | ||||||
|     <link rel="stylesheet" href="dist/demo.css"> |     <link rel="stylesheet" href="dist/demo.css"> | ||||||
|  |  | ||||||
|     <!-- Preload --> |     <!-- 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/gordita-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-bold.woff2"> | ||||||
| </head> | </head> | ||||||
|  |  | ||||||
| <body> | <body> | ||||||
|  |     <div class="grid"> | ||||||
|         <header> |         <header> | ||||||
|             <h1>Plyr</h1> |             <h1>Plyr</h1> | ||||||
|         <p>A simple, accessible HTML5 media player by <a href="https://twitter.com/sam_potts" target="_blank">@sam_potts</a></p> |             <p>A simple, accessible and customisable media player for | ||||||
|         <nav> |                 <button type="button" class="faux-link" data-source="video"> | ||||||
|             <ul> |  | ||||||
|                 <li> |  | ||||||
|                     <a href="https://github.com/sampotts/plyr" target="_blank" class="btn btn--large btn--primary" data-shr-network="github"> |  | ||||||
|                     <svg class="icon"> |                     <svg class="icon"> | ||||||
|                             <use xlink:href="#icon-github" /> |                         <title>HTML5</title> | ||||||
|                         </svg>Download on GitHub |                         <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> | ||||||
|                     </a> |                     </svg>Video</button>, | ||||||
|                 </li> |                 <button type="button" class="faux-link" data-source="audio"> | ||||||
|                 <li> |  | ||||||
|                     <a href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts" |  | ||||||
|                         target="_blank" class="btn btn--large btn--twitter" data-shr-network="twitter"> |  | ||||||
|                     <svg class="icon"> |                     <svg class="icon"> | ||||||
|                             <use xlink:href="#icon-twitter" /> |                         <title>HTML5</title> | ||||||
|                         </svg>Tweet |                         <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> |                 </a> | ||||||
|                 </li> |             </p> | ||||||
|             </ul> |  | ||||||
|         </nav> |             <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> |         </header> | ||||||
|  |  | ||||||
|     <main role="main" id="main"> |         <main> | ||||||
|         <nav class="btn__bar"> |             <video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player"> | ||||||
|             <ul> |  | ||||||
|                 <li class="active"> |  | ||||||
|                     <button type="button" class="btn" data-source="video">Video</button> |  | ||||||
|                 </li> |  | ||||||
|                 <li> |  | ||||||
|                     <button type="button" class="btn" data-source="audio">Audio</button> |  | ||||||
|                 </li> |  | ||||||
|                 <li> |  | ||||||
|                     <button type="button" class="btn btn--youtube" data-source="youtube"><svg class="icon"><use xlink:href="#icon-youtube"/></svg>YouTube</button> |  | ||||||
|                 </li> |  | ||||||
|                 <li> |  | ||||||
|                     <button type="button" class="btn btn--vimeo" data-source="vimeo"><svg class="icon"><use xlink:href="#icon-vimeo"/></svg>Vimeo</button> |  | ||||||
|                 </li> |  | ||||||
|             </ul> |  | ||||||
|         </nav> |  | ||||||
|         <section> |  | ||||||
|             <video poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg?v1" controls crossorigin> |  | ||||||
|                 <!-- Video files --> |                 <!-- 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.mp4" type="video/mp4"> | ||||||
|                 <source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.webm" type="video/webm"> |                 <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 --> |                 <!-- 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" |                 <track kind="captions" label="English" srclang="en" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt" | ||||||
|                     default> |                     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 --> |                 <!-- 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> |                 <a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" download>Download</a> | ||||||
|             </video> |             </video> | ||||||
|  |  | ||||||
|             <ul> |             <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--video" hidden> | ||||||
|                 <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> |                     <small> | ||||||
|                 <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> |                         <svg class="icon"> | ||||||
|                 <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> |                             <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> |             </ul> | ||||||
|         </section> |  | ||||||
|         </main> |         </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 --> |     <!-- Plyr core script --> | ||||||
|     <script src="../dist/plyr.js"></script> |     <script src="../dist/plyr.js"></script> | ||||||
|  |  | ||||||
|     <!-- Docs script --> |     <!-- Sharing libary (https://shr.one) --> | ||||||
|     <script src="dist/demo.js"></script> |     <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) --> |     <!-- 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> |     <script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async></script> | ||||||
|  |  | ||||||
|     <!-- Sharing libary (https://shr.one) --> |     <!-- Docs script --> | ||||||
|     <script src="https://cdn.shr.one/1.0.1/shr.js"></script> |     <script src="dist/demo.js"></script> | ||||||
|     <script> |  | ||||||
|         if (window.shr) { window.shr.setup({ count: { classname: 'btn__count' } }); } |  | ||||||
|     </script> |  | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
| </html> | </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 | // Base size icon styles | ||||||
| .icon { | .icon { | ||||||
|     fill: currentColor; |     fill: currentColor; | ||||||
| 	width: @icon-size; |     height: $icon-size; | ||||||
| 	height: @icon-size; |  | ||||||
|     vertical-align: -3px; |     vertical-align: -3px; | ||||||
|  |     width: $icon-size; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Within elements | // Within elements | ||||||
| @ -16,11 +16,8 @@ button svg, | |||||||
| label svg { | label svg { | ||||||
|     pointer-events: none; |     pointer-events: none; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| a .icon, | a .icon, | ||||||
| .btn .icon { | .btn .icon { | ||||||
| 	margin-right: (@padding-base / 2); |     margin-right: floor($spacing-base / 3); | ||||||
| } |  | ||||||
| .btn:not(.btn-large) .icon { |  | ||||||
| 	width: (@icon-size - 2); |  | ||||||
| 	height: (@icon-size - 2); |  | ||||||
| } | } | ||||||
							
								
								
									
										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 | // Example players | ||||||
| .plyr { | .plyr { | ||||||
|     margin: 0 auto; |     border-radius: $border-radius-base; | ||||||
|     border-radius: @border-radius-large; |     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 { | .plyr__video-wrapper::after { | ||||||
|     content: ""; |     border: 1px solid rgba(#000, 0.15); | ||||||
|  |     border-radius: inherit; | ||||||
|  |     bottom: 0; | ||||||
|  |     content: ''; | ||||||
|  |     left: 0; | ||||||
|     pointer-events: none; |     pointer-events: none; | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     top: 0; |  | ||||||
|     bottom: 0; |  | ||||||
|     left: 0; |  | ||||||
|     right: 0; |     right: 0; | ||||||
|     border: 1px solid fade(#000, 15%); |     top: 0; | ||||||
|     border-radius: inherit; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Style full supported player | // Style full supported player | ||||||
| .plyr__cite { | .plyr__cite { | ||||||
|     display: none; |     display: none; | ||||||
|     margin-top: @padding-base; |     margin-top: $spacing-base; | ||||||
| 
 | 
 | ||||||
|     .icon { |     .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 { | .error body { | ||||||
|     height: 100%; |     height: 100%; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | html.error { | ||||||
|  |     background: $page-background; | ||||||
|  |     background-attachment: fixed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .error body { | .error body { | ||||||
|  |     align-items: center; | ||||||
|  |     display: flex; | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     display: table; |  | ||||||
|     table-layout: fixed; |  | ||||||
| } | } | ||||||
|  | 
 | ||||||
| .error main { | .error main { | ||||||
|     display: table-cell; |     padding: $spacing-base; | ||||||
|  |     text-align: center; | ||||||
|     width: 100%; |     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 | // Fade | ||||||
| @keyframes fade-in { | @keyframes fadein { | ||||||
| 	0% 		{ opacity: 0 } |     0% { | ||||||
| 	100% 	{ opacity: 1 } |         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 | // Gulp build script | ||||||
| // ========================================================================== | // ========================================================================== | ||||||
| /*global require, __dirname,Buffer*/ | /* global require, __dirname */ | ||||||
| /*jshint -W079 */ | /* eslint no-console: "off" */ | ||||||
|  |  | ||||||
| var fs = require('fs'), | const del = require('del'); | ||||||
|     path = require('path'), | const path = require('path'); | ||||||
|     gulp = require('gulp'), | const gulp = require('gulp'); | ||||||
|     gutil = require('gulp-util'), | const gutil = require('gulp-util'); | ||||||
|     concat = require('gulp-concat'), | const concat = require('gulp-concat'); | ||||||
|     uglify = require('gulp-uglify'), | const filter = require('gulp-filter'); | ||||||
|     less = require('gulp-less'), | const sass = require('gulp-sass'); | ||||||
|     sass = require('gulp-sass'), | const cleancss = require('gulp-clean-css'); | ||||||
|     cleanCSS = require('gulp-clean-css'), | const run = require('run-sequence'); | ||||||
|     run = require('run-sequence'), | const prefix = require('gulp-autoprefixer'); | ||||||
|     prefix = require('gulp-autoprefixer'), | const gitbranch = require('git-branch'); | ||||||
|     svgstore = require('gulp-svgstore'), | const svgstore = require('gulp-svgstore'); | ||||||
|     svgmin = require('gulp-svgmin'), | const svgmin = require('gulp-svgmin'); | ||||||
|     rename = require('gulp-rename'), | const rename = require('gulp-rename'); | ||||||
|     s3 = require('gulp-s3'), | const s3 = require('gulp-s3'); | ||||||
|     replace = require('gulp-replace'), | const replace = require('gulp-replace'); | ||||||
|     open = require('gulp-open'), | const open = require('gulp-open'); | ||||||
|     size = require('gulp-size'), | const size = require('gulp-size'); | ||||||
|     through = require('through2'); | 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, | const bundles = require('./bundles.json'); | ||||||
|     paths = { | 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: { |     plyr: { | ||||||
|         // Source paths |         // Source paths | ||||||
|         src: { |         src: { | ||||||
|                 less: path.join(root, 'src/less/**/*'), |             sass: path.join(root, 'src/sass/**/*.scss'), | ||||||
|                 scss: path.join(root, 'src/scss/**/*'), |  | ||||||
|             js: path.join(root, 'src/js/**/*'), |             js: path.join(root, 'src/js/**/*'), | ||||||
|             sprite: path.join(root, 'src/sprite/*.svg'), |             sprite: path.join(root, 'src/sprite/*.svg'), | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         // Output paths |         // Output paths | ||||||
|         output: path.join(root, 'dist/'), |         output: path.join(root, 'dist/'), | ||||||
|     }, |     }, | ||||||
|     demo: { |     demo: { | ||||||
|         // Source paths |         // Source paths | ||||||
|         src: { |         src: { | ||||||
|                 less: path.join(root, 'demo/src/less/**/*'), |             sass: path.join(root, 'demo/src/sass/**/*.scss'), | ||||||
|             js: path.join(root, 'demo/src/js/**/*'), |             js: path.join(root, 'demo/src/js/**/*'), | ||||||
|                 sprite: path.join(root, 'demo/src/sprite/**/*'), |  | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|         // Output paths |         // Output paths | ||||||
|         output: path.join(root, 'demo/dist/'), |         output: path.join(root, 'demo/dist/'), | ||||||
|  |  | ||||||
|         // Demo |         // Demo | ||||||
|         root: path.join(root, '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 | // Task arrays | ||||||
|     tasks = { | const tasks = { | ||||||
|         less: [], |     sass: [], | ||||||
|         scss: [], |  | ||||||
|     js: [], |     js: [], | ||||||
|     sprite: [], |     sprite: [], | ||||||
|     }, |     clean: ['clean'], | ||||||
|     // 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); |  | ||||||
| }; | }; | ||||||
|     return src; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var build = { | // Size plugin | ||||||
|     js: function(files, bundle) { | const sizeOptions = { showFiles: true, gzip: true }; | ||||||
|         for (var key in files) { |  | ||||||
|             (function(key) { | // Browserlist | ||||||
|                 var name = 'js-' + key; | 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); |             tasks.js.push(name); | ||||||
|  |             const { output } = paths[bundle]; | ||||||
|  |  | ||||||
|                 gulp.task(name, function() { |             gulp.task(name, () => | ||||||
|                     return gulp |                 gulp | ||||||
|                     .src(bundles[bundle].js[key]) |                     .src(bundles[bundle].js[key]) | ||||||
|  |                     .pipe(sourcemaps.init()) | ||||||
|                     .pipe(concat(key)) |                     .pipe(concat(key)) | ||||||
|                         .pipe(uglify().on('error', gutil.log)) |                     .pipe( | ||||||
|                         .pipe(gulp.dest(paths[bundle].output)); |                         rollup( | ||||||
|                 }); |                             { | ||||||
|             })(key); |                                 plugins: [ | ||||||
|         } |                                     resolve(), | ||||||
|  |                                     commonjs(), | ||||||
|  |                                     babel(babelrc), | ||||||
|  |                                 ], | ||||||
|                             }, |                             }, | ||||||
|     less: function(files, bundle) { |                             options, | ||||||
|         for (var key in files) { |                         ), | ||||||
|             (function(key) { |                     ) | ||||||
|                 var name = 'less-' + key; |                     .pipe(sourcemaps.write('')) | ||||||
|                 tasks.less.push(name); |                     .pipe(gulp.dest(output)) | ||||||
|  |                     .pipe(filter('**/*.js')) | ||||||
|                 gulp.task(name, function() { |                     .pipe(uglify()) | ||||||
|                     return gulp |                     .pipe(size(sizeOptions)) | ||||||
|                         .src(bundles[bundle].less[key]) |                     .pipe(rename({ suffix: minSuffix })) | ||||||
|                         .pipe(less()) |                     .pipe(sourcemaps.write('')) | ||||||
|                         .on('error', gutil.log) |                     .pipe(gulp.dest(output)), | ||||||
|                         .pipe(concat(key)) |             ); | ||||||
|                         .pipe(prefix(['last 2 versions'], { cascade: true })) |  | ||||||
|                         .pipe(cleanCSS()) |  | ||||||
|                         .pipe(gulp.dest(paths[bundle].output)); |  | ||||||
|         }); |         }); | ||||||
|             })(key); |  | ||||||
|         } |  | ||||||
|     }, |     }, | ||||||
|     scss: function(files, bundle) { |     sass(files, bundle) { | ||||||
|         for (var key in files) { |         Object.keys(files).forEach(key => { | ||||||
|             (function(key) { |             const name = `sass:${key}`; | ||||||
|                 var name = 'scss-' + key; |             tasks.sass.push(name); | ||||||
|                 tasks.scss.push(name); |  | ||||||
|  |  | ||||||
|                 gulp.task(name, function() { |             gulp.task(name, () => | ||||||
|                     return gulp |                 gulp | ||||||
|                         .src(bundles[bundle].scss[key]) |                     .src(bundles[bundle].sass[key]) | ||||||
|                     .pipe(sass()) |                     .pipe(sass()) | ||||||
|                     .on('error', gutil.log) |                     .on('error', gutil.log) | ||||||
|                     .pipe(concat(key)) |                     .pipe(concat(key)) | ||||||
|                         .pipe(prefix(['last 2 versions'], { cascade: true })) |                     .pipe(prefix(browsers, { cascade: false })) | ||||||
|                         .pipe(cleanCSS()) |                     .pipe(cleancss()) | ||||||
|                         .pipe(gulp.dest(paths[bundle].output)); |                     .pipe(size(sizeOptions)) | ||||||
|  |                     .pipe(gulp.dest(paths[bundle].output)), | ||||||
|  |             ); | ||||||
|         }); |         }); | ||||||
|             })(key); |  | ||||||
|         } |  | ||||||
|     }, |     }, | ||||||
|     sprite: function(bundle) { |     sprite(bundle) { | ||||||
|         var name = 'sprite-' + bundle; |         const name = `svg:sprite:${bundle}`; | ||||||
|         tasks.sprite.push(name); |         tasks.sprite.push(name); | ||||||
|  |  | ||||||
|         // Process Icons |         // Process Icons | ||||||
|         gulp.task(name, function() { |         gulp.task(name, () => | ||||||
|             return gulp |             gulp | ||||||
|                 .src(paths[bundle].src.sprite) |                 .src(paths[bundle].src.sprite) | ||||||
|                 .pipe( |                 .pipe( | ||||||
|                     svgmin({ |                     svgmin({ | ||||||
|                         plugins: [ |                         plugins: [{ | ||||||
|                             { |  | ||||||
|                             removeDesc: true, |                             removeDesc: true, | ||||||
|                             }, |                         }], | ||||||
|                         ], |  | ||||||
|                     }), |                     }), | ||||||
|                 ) |                 ) | ||||||
|                 .pipe(svgstore()) |                 .pipe(svgstore()) | ||||||
|                 .pipe(rename({ basename: bundle })) |                 .pipe(rename({ basename: bundle })) | ||||||
|                 .pipe(gulp.dest(paths[bundle].output)); |                 .pipe(size(sizeOptions)) | ||||||
|         }); |                 .pipe(gulp.dest(paths[bundle].output)), | ||||||
|  |         ); | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // Plyr core files | // Plyr core files | ||||||
| build.js(bundles.plyr.js, 'plyr'); | build.js(bundles.plyr.js, 'plyr', { name: 'Plyr', format: 'umd' }); | ||||||
| build.less(bundles.plyr.less, 'plyr'); | build.sass(bundles.plyr.sass, 'plyr'); | ||||||
| build.scss(bundles.plyr.scss, 'plyr'); |  | ||||||
| build.sprite('plyr'); | build.sprite('plyr'); | ||||||
|  |  | ||||||
| // Demo files | // Demo files | ||||||
| build.less(bundles.demo.less, 'demo'); | build.sass(bundles.demo.sass, 'demo'); | ||||||
| build.js(bundles.demo.js, 'demo'); | build.js(bundles.demo.js, 'demo', { format: 'iife' }); | ||||||
| build.sprite('demo'); |  | ||||||
|  |  | ||||||
| // Build all JS | // Build all JS | ||||||
| gulp.task('js', function() { | gulp.task('js', () => { | ||||||
|     run(tasks.js); |     run(tasks.js); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // Build SCSS (for testing, default is LESS) |  | ||||||
| gulp.task('scss', function() { |  | ||||||
|     run(tasks.scss); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| // Watch for file changes | // Watch for file changes | ||||||
| gulp.task('watch', function() { | gulp.task('watch', () => { | ||||||
|     // Plyr core |     // Plyr core | ||||||
|     gulp.watch(paths.plyr.src.js, tasks.js); |     gulp.watch(paths.plyr.src.js, tasks.js); | ||||||
|     gulp.watch(paths.plyr.src.less, tasks.less); |     gulp.watch(paths.plyr.src.sass, tasks.sass); | ||||||
|     gulp.watch(paths.plyr.src.sprite, tasks.sprite); |     gulp.watch(paths.plyr.src.sprite, tasks.sprite); | ||||||
|  |  | ||||||
|     // Demo |     // Demo | ||||||
|     gulp.watch(paths.demo.src.js, tasks.js); |     gulp.watch(paths.demo.src.js, tasks.js); | ||||||
|     gulp.watch(paths.demo.src.less, tasks.less); |     gulp.watch(paths.demo.src.sass, tasks.sass); | ||||||
|     gulp.watch(paths.demo.src.sprite, tasks.sprite); |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // Default gulp task | // Default gulp task | ||||||
| gulp.task('default', function() { | gulp.task('default', () => { | ||||||
|     run(tasks.js, tasks.less, tasks.sprite, 'watch'); |     run(tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'watch'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // Publish a version to CDN and demo | // 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 |     // Get branch info | ||||||
| var aws = loadJSON(path.join(root, 'aws.json')), |     const branch = { | ||||||
|     version = package.version, |         current: gitbranch.sync(), | ||||||
|     maxAge = 31536000, // seconds 1 year |         master: 'master', | ||||||
|     options = { |         beta: 'beta', | ||||||
|  |     }; | ||||||
|  |     const allowed = [ | ||||||
|  |         branch.master, | ||||||
|  |         branch.beta, | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     const maxAge = 31536000; // 1 year | ||||||
|  |     const options = { | ||||||
|         cdn: { |         cdn: { | ||||||
|             headers: { |             headers: { | ||||||
|                 'Cache-Control': 'max-age=' + maxAge, |                 'Cache-Control': `max-age=${maxAge}`, | ||||||
|                 Vary: 'Accept-Encoding', |                 Vary: 'Accept-Encoding', | ||||||
|             }, |             }, | ||||||
|         }, |         }, | ||||||
|         demo: { |         demo: { | ||||||
|  |             uploadPath: branch.current === branch.beta ? 'beta/' : null, | ||||||
|             headers: { |             headers: { | ||||||
|                 'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0', |                 'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0', | ||||||
|                 Vary: 'Accept-Encoding', |                 Vary: 'Accept-Encoding', | ||||||
|             }, |             }, | ||||||
|         }, |         }, | ||||||
|         symlinks: function(version, filename) { |         symlinks(ver, filename) { | ||||||
|             return { |             return { | ||||||
|                 headers: { |                 headers: { | ||||||
|                     // http://stackoverflow.com/questions/2272835/amazon-s3-object-redirect |                     // 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', |                     'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0', | ||||||
|                 }, |                 }, | ||||||
|             }; |             }; | ||||||
|         }, |         }, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| // If aws is setup |     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\\-]+)*)?'; | ||||||
| if ('cdn' in aws) { |     const semver = new RegExp(`v${regex}`, 'gi'); | ||||||
|     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\\-]+)*)?', |     const localPath = new RegExp('(../)?dist', 'gi'); | ||||||
|         cdnpath = new RegExp(aws.cdn.domain + '/' + regex, 'gi'), |     const versionPath = `https://${aws.cdn.domain}/${version}`; | ||||||
|         semver = new RegExp('v' + regex, 'gi'), |     const cdnpath = new RegExp(`${aws.cdn.domain}/${regex}/`, 'gi'); | ||||||
|         localPath = new RegExp('(../)?dist', 'gi'), |  | ||||||
|         versionPath = 'https://' + aws.cdn.domain + '/' + version; |     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 |     // Publish version to CDN bucket | ||||||
| gulp.task('cdn', function() { |     gulp.task('cdn', () => { | ||||||
|     console.log('Uploading ' + version + ' to ' + aws.cdn.domain + '...'); |         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 |         // Upload to CDN | ||||||
|         return gulp |         return gulp | ||||||
|             .src(paths.upload) |             .src(paths.upload) | ||||||
|             .pipe( |             .pipe( | ||||||
|             size({ |                 rename(p => { | ||||||
|                 showFiles: true, |                     p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line | ||||||
|                 gzip: true, |                     p.dirname = p.dirname.replace('.', version); // eslint-disable-line | ||||||
|                 }), |                 }), | ||||||
|             ) |             ) | ||||||
|             .pipe( |             .pipe( | ||||||
|             rename(function(path) { |                 size({ | ||||||
|                 path.dirname = path.dirname.replace('.', version); |                     showFiles: true, | ||||||
|  |                     gzip: true, | ||||||
|                 }), |                 }), | ||||||
|             ) |             ) | ||||||
|             .pipe(replace(localPath, versionPath)) |             .pipe(replace(localPath, versionPath)) | ||||||
| @ -277,77 +322,86 @@ gulp.task('cdn', function() { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     // Publish to demo bucket |     // Publish to demo bucket | ||||||
| gulp.task('demo', function() { |     gulp.task('demo', () => { | ||||||
|     console.log('Uploading ' + version + ' demo to ' + aws.demo.domain + '...'); |         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 |         // Replace versioned files in readme.md | ||||||
|         gulp |         gulp | ||||||
|         .src([root + '/readme.md']) |             .src([`${root}/readme.md`]) | ||||||
|         .pipe(replace(cdnpath, aws.cdn.domain + '/' + version)) |             .pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`)) | ||||||
|             .pipe(gulp.dest(root)); |             .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 |         // 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" |         // 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 |         gulp | ||||||
|         .src([paths.demo.root + '*.html']) |             .src(pages) | ||||||
|             .pipe(replace(localPath, versionPath)) |             .pipe(replace(localPath, versionPath)) | ||||||
|             .pipe(s3(aws.demo, options.demo)); |             .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) |         // Upload error.html to cdn (as well as demo site) | ||||||
|         return gulp |         return gulp | ||||||
|         .src([paths.demo.root + 'error.html']) |             .src([error]) | ||||||
|             .pipe(replace(localPath, versionPath)) |             .pipe(replace(localPath, versionPath)) | ||||||
|             .pipe(s3(aws.cdn, options.demo)); |             .pipe(s3(aws.cdn, options.demo)); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| // Open the demo site to check it's sweet |     // Update symlinks for latest | ||||||
| gulp.task('symlinks', function() { |     /* gulp.task("symlinks", function () { | ||||||
|     console.log('Updating symlinks...'); |         console.log("Updating symlinks..."); | ||||||
|  |  | ||||||
|     return gulp.src(paths.upload).pipe( |         return gulp.src(paths.upload) | ||||||
|         through.obj(function(chunk, enc, callback) { |             .pipe(through.obj(function (chunk, enc, callback) { | ||||||
|                 if (chunk.stat.isFile()) { |                 if (chunk.stat.isFile()) { | ||||||
|                     // Get the filename |                     // 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 |                     // Create the 0 byte redirect files to upload | ||||||
|                 createFile(filename, '') |                     createFile(filename, "") | ||||||
|                     .pipe( |                         .pipe(rename(function (path) { | ||||||
|                         rename(function(path) { |                             path.dirname = path.dirname.replace(".", "latest"); | ||||||
|                             path.dirname = path.dirname.replace('.', 'latest'); |                         })) | ||||||
|                         }), |  | ||||||
|                     ) |  | ||||||
|                         // Upload to S3 with correct headers |                         // Upload to S3 with correct headers | ||||||
|                         .pipe(s3(aws.cdn, options.symlinks(version, filename))); |                         .pipe(s3(aws.cdn, options.symlinks(version, filename))); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 callback(null, chunk); |                 callback(null, chunk); | ||||||
|         }), |             })); | ||||||
|     ); |     }); */ | ||||||
| }); |  | ||||||
|  |  | ||||||
|     // Open the demo site to check it's sweet |     // Open the demo site to check it's sweet | ||||||
| gulp.task('open', function() { |     gulp.task('open', () => { | ||||||
|     console.log('Opening ' + aws.demo.domain + '...'); |         console.log(`Opening ${aws.demo.domain}...`); | ||||||
|  |  | ||||||
|         // A file must be specified or gulp will skip the task |         // A file must be specified or gulp will skip the task | ||||||
|         // Doesn't matter which file since we set the URL above |         // Doesn't matter which file since we set the URL above | ||||||
|         // Weird, I know... |         // Weird, I know... | ||||||
|     return gulp.src([paths.demo.root + 'index.html']).pipe( |         return gulp.src([`${paths.demo.root}index.html`]).pipe( | ||||||
|             open('', { |             open('', { | ||||||
|             url: 'http://' + aws.demo.domain, |                 url: `http://${aws.demo.domain}`, | ||||||
|             }), |             }), | ||||||
|         ); |         ); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     // Do everything |     // Do everything | ||||||
| gulp.task('publish', function() { |     gulp.task('publish', () => { | ||||||
|     run(tasks.js, tasks.less, tasks.sprite, 'cdn', 'demo', 'symlinks'); |         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", |     "name": "plyr", | ||||||
|     "version": "2.0.18", |     "version": "3.0.0-beta.20", | ||||||
|     "description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player", |     "description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player", | ||||||
|     "homepage": "http://plyr.io", |     "homepage": "https://plyr.io", | ||||||
|     "main": "src/js/plyr.js", |     "main": "./dist/plyr.js", | ||||||
|     "dependencies": {}, |     "browser": "./dist/plyr.min.js", | ||||||
|  |     "sass": "./src/sass/plyr.scss", | ||||||
|  |     "style": "./dist/plyr.css", | ||||||
|     "devDependencies": { |     "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": "^3.9.1", | ||||||
|         "gulp-autoprefixer": "^4.0.0", |         "gulp-autoprefixer": "^5.0.0", | ||||||
|         "gulp-clean-css": "^3.9.0", |         "gulp-better-rollup": "^3.0.0", | ||||||
|  |         "gulp-clean-css": "^3.9.3", | ||||||
|         "gulp-concat": "^2.6.1", |         "gulp-concat": "^2.6.1", | ||||||
|         "gulp-less": "^3.3.2", |         "gulp-filter": "^5.1.0", | ||||||
|         "gulp-open": "^2.0.0", |         "gulp-open": "^3.0.0", | ||||||
|         "gulp-rename": "^1.2.2", |         "gulp-rename": "^1.2.2", | ||||||
|         "gulp-replace": "^0.6.1", |         "gulp-replace": "^0.6.1", | ||||||
|         "gulp-s3": "^0.11.0", |         "gulp-s3": "^0.11.0", | ||||||
|         "gulp-sass": "^3.1.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-svgmin": "^1.2.4", | ||||||
|         "gulp-svgstore": "^6.1.0", |         "gulp-svgstore": "^6.1.1", | ||||||
|         "gulp-uglify": "^3.0.0", |         "gulp-uglify-es": "^1.0.1", | ||||||
|         "gulp-util": "^3.0.8", |         "gulp-util": "^3.0.8", | ||||||
|         "run-sequence": "^2.2.0", |         "rollup-plugin-babel": "^3.0.3", | ||||||
|         "through2": "^2.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": [ |     "keywords": [ | ||||||
|         "HTML5 Video", |         "HTML5 Video", | ||||||
| @ -47,5 +70,9 @@ | |||||||
|     "scripts": { |     "scripts": { | ||||||
|         "test": "echo \"Error: no test specified\" && exit 1" |         "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; | ||||||