Compare commits
431 Commits
v2.0.4
...
v3.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
b4e22e2e7b | |||
d4234da9aa | |||
1f53b27d4e | |||
cc128e6088 | |||
3c8acb4e9e | |||
ebf53d14b1 | |||
b298587c0b | |||
26e9aaceb8 | |||
384f3d6eda | |||
3aa5747c90 | |||
5671235fd9 | |||
1dd5c9efd9 | |||
5fad152cbf | |||
f33ca846f2 | |||
986c802acd | |||
98dd818bf6 | |||
021f6c8460 | |||
392f837398 | |||
6831c30534 | |||
ed6048034b | |||
8af312fe3c | |||
d87ada4f58 | |||
31c8166562 | |||
0cb2f95888 | |||
896ea7c689 | |||
1d1eb02bd7 | |||
3583165b30 | |||
d822f0c6bf | |||
9e52296dc6 | |||
12a7a4142c | |||
8348f79742 | |||
ec73d34bd3 | |||
1cef48d4f8 | |||
c24c05226d | |||
4b0005c28e | |||
8064405dbc | |||
22e8892993 | |||
cfc86bcb7c | |||
5f96ec6ac2 | |||
bbdf225d7b | |||
025fc1090b | |||
d9ec1d1b8e | |||
2e5d06ad85 | |||
bacd049eb0 | |||
6fd7b26bb2 | |||
1df79cf7f8 | |||
e6badacf0d | |||
a0d9d5eca8 | |||
8786377a75 | |||
f0322e8d94 | |||
3f2ac3fca0 | |||
9872207e87 | |||
58d942e737 | |||
dd190155c4 | |||
ebb3b83b27 | |||
79a43120ae | |||
2b36ab7ef5 | |||
eb38e0394d | |||
b79ffacd9c | |||
e14e2cfaff | |||
04119b27e6 | |||
92cb9e22e2 | |||
888c6773d0 | |||
cfd653f02a | |||
6b9106ddb1 | |||
838bd49a1d | |||
800e0dedfb | |||
965fc0b2f5 | |||
98ac98b4c2 | |||
178b0d8020 | |||
3bd1c1ff56 | |||
7d4bf91a35 | |||
ac3d1d1242 | |||
742018a716 | |||
a2ef691b4b | |||
9e0c406a4a | |||
f3df7aba15 | |||
6bc3592381 | |||
6864149989 | |||
aab53fa91f | |||
423f67b4f4 | |||
82f81f4f73 | |||
c8990bd379 | |||
e6cc4ba1f5 | |||
de54929bb7 | |||
fd77831303 | |||
cda574e627 | |||
fda1977119 | |||
f7bf0961cf | |||
61325bbad1 | |||
921cefd212 | |||
de6f0f1b77 | |||
7382553a78 | |||
4b82e89845 | |||
d3b31e595a | |||
f33bc5a5c6 | |||
f518ec108b | |||
edfc6cd475 | |||
92cd67effb | |||
da1e987444 | |||
2d4a166218 | |||
feae00224e | |||
000afdd300 | |||
0dc9681ae8 | |||
dc391c98c6 | |||
4b62a5c74d | |||
3f744ef63a | |||
5a244b7fed | |||
4dca4bf93c | |||
6984d6fb16 | |||
d7a1c44281 | |||
c64b8f6940 | |||
9c4b53d761 | |||
022b436c3f | |||
6058bb0245 | |||
3ade86537d | |||
d80be3b903 | |||
1a26681d87 | |||
9171297764 | |||
ebd1a52e5d | |||
743c84188e | |||
4879bea4a0 | |||
66917fd39b | |||
fd97607e55 | |||
f878581c8f | |||
f9602acf61 | |||
86a5724bdb | |||
63c963d726 | |||
88bfa35b99 | |||
bb66be98da | |||
c948e95ade | |||
1a5f4b1b9e | |||
3f41a0cf54 | |||
966ca1acc3 | |||
e2c7491ccd | |||
0ec0addbda | |||
fae4ca12e0 | |||
84505da84b | |||
2497e25b3c | |||
9c4b7e7094 | |||
3a9beaed59 | |||
1578525ee3 | |||
62c1b368cf | |||
f8ecea8fb7 | |||
0068710740 | |||
d206b8ab34 | |||
51c011b733 | |||
5fe477340b | |||
45df319823 | |||
6bebbe4153 | |||
374de800a4 | |||
60084a17f8 | |||
4d417d0396 | |||
e2d8bd2b73 | |||
1c693df00b | |||
8aaa932050 | |||
3cd6c2acf6 | |||
3930ebb339 | |||
b102714067 | |||
13d3037a53 | |||
1e7a60ee5c | |||
8d80f6b05d | |||
3c99357f06 | |||
597b3fe0db | |||
a757acad15 | |||
7913cf837f | |||
e98d356ede | |||
c9c20345c0 | |||
aca06be7ac | |||
550cc6a19b | |||
e44e93c117 | |||
a06a8150ef | |||
e787934ee6 | |||
be4b5ef4db | |||
55c5b8b07e | |||
a924d40620 | |||
0074e77a7c | |||
f3141690ee | |||
7ac5b0e18b | |||
d920de2a25 | |||
5abf7e9535 | |||
650ee2c18f | |||
069c8093ae | |||
1cc2930dc0 | |||
95734cf7cc | |||
3d50936b47 | |||
7234e2f5a3 | |||
dd9d5c8898 | |||
e06b689f5c | |||
71db66d802 | |||
959b5a20e3 | |||
9de5c0cf39 | |||
1a3868a6c6 | |||
378aa159b8 | |||
6876a9163d | |||
34d28a69ad | |||
0715d3ae67 | |||
542095f5eb | |||
57517a9dcc | |||
751708ff23 | |||
fcf944788a | |||
36a84c5c2d | |||
4392abfc49 | |||
3238b6a36a | |||
18a5edef0e | |||
1b3a6b340d | |||
b3759e966d | |||
34172b9aee | |||
02cb093f7b | |||
d6977473b1 | |||
15cffad89e | |||
a21ffe983c | |||
0d96601d1a | |||
9fb79f29fd | |||
032d73408f | |||
7959297a98 | |||
6dd010ea34 | |||
f89ddb2f1b | |||
2e5cc272e3 | |||
ebd13c63e8 | |||
ec091266aa | |||
ad105877ed | |||
c84f666b55 | |||
72412d26a5 | |||
bf02c69daa | |||
2bba1f30e2 | |||
01f50f03f1 | |||
f50f0eb63d | |||
2196665495 | |||
0148c76c30 | |||
3fe0c7c84b | |||
18b2cbb2e9 | |||
f6a7555a1d | |||
f7144dc0cb | |||
a9957211f4 | |||
7b60e473fa | |||
b114a6f42e | |||
dfc516baf6 | |||
b0d3bcd15a | |||
03c3960e93 | |||
d0ceefe827 | |||
e756664750 | |||
21f444ed61 | |||
40b4f8e840 | |||
bc61dac4c5 | |||
3605989226 | |||
a49d77afab | |||
7daa08c32f | |||
aaab9ad082 | |||
86c9004183 | |||
857dfe838c | |||
6828bc00f8 | |||
e1a19faf26 | |||
e95c81cc5b | |||
f3bda17fa0 | |||
c0eee58732 | |||
bcf88cd024 | |||
966c3d875d | |||
fe9383bed5 | |||
2c31770030 | |||
8d3e160166 | |||
408cfe5b97 | |||
279fee3595 | |||
28dddfcff2 | |||
9e2580ec6a | |||
aba5a9dc0e | |||
d27a0b3855 | |||
a575aa25ac | |||
9999828505 | |||
b04301c211 | |||
97157efcfa | |||
3756476809 | |||
b3d798eeee | |||
b499622eb8 | |||
04b8d0bac8 | |||
8ea4cbd942 | |||
7889ae1126 | |||
897d49ad87 | |||
1960d35d8b | |||
0b7d0dedf6 | |||
948e2cc66c | |||
396b816a1f | |||
0a58cbfd59 | |||
bbe4b7e565 | |||
2ecca2cbe3 | |||
f5d80fa888 | |||
f2bbe4eb12 | |||
60e51b7077 | |||
01d1ac9ab9 | |||
9b2396f5ff | |||
da15980b8a | |||
452678867f | |||
ff2d27f8e5 | |||
546cdaad92 | |||
5d38497f07 | |||
808a3af3cf | |||
889712761a | |||
46fab972a0 | |||
535c046b62 | |||
55dfab8394 | |||
18bf4d84b9 | |||
ef1d73df15 | |||
684107da5c | |||
2a97a9a9c5 | |||
ca12343b16 | |||
a974fbf31c | |||
6eaf33ec6c | |||
33fef7faee | |||
b083bf4297 | |||
4957e4d80c | |||
605f8a8249 | |||
7490a7e7f3 | |||
f32527feb4 | |||
ebc7ed538a | |||
a60868bfe8 | |||
4a2866d05f | |||
1a05f352c7 | |||
ba27de0669 | |||
e21e7054ce | |||
bc323e905c | |||
afa1fe9d5f | |||
2444efc834 | |||
7b6257ff20 | |||
13973125fb | |||
ddf9104349 | |||
47b53fffb4 | |||
9ba3753f80 | |||
a353194fe1 | |||
c205c47509 | |||
9c867edfbc | |||
9919d80782 | |||
a971850d26 | |||
8464ffe6ad | |||
6537225c61 | |||
dcbf59bd89 | |||
21d478d396 | |||
054c243416 | |||
d57e453541 | |||
3f45ef4908 | |||
1fd742464d | |||
382397407f | |||
82809235b9 | |||
7d5c507f16 | |||
bc139f16a7 | |||
f6f3284dc5 | |||
f4d2f1856f | |||
871e19ed92 | |||
9c599884a8 | |||
cbef45841c | |||
96d9f302c2 | |||
afcdf17a72 | |||
1bcdbe4c00 | |||
45c0ded88c | |||
ea30ad9494 | |||
7608615702 | |||
cc5f37392f | |||
413fcbcb8c | |||
c49d0a99e4 | |||
5a0cb9289c | |||
72998c4c41 | |||
da48be495b | |||
c843ed3460 | |||
4232aa9ea4 | |||
f6612fa78c | |||
bce1d983cb | |||
047fc43f8f | |||
96ae866baa | |||
0cbfc9252c | |||
d02e57bdb3 | |||
a1d5bccac2 | |||
a9bb8afca4 | |||
28cd92b708 | |||
a44c7ecc3a | |||
663c1fb627 | |||
a7969b8e9e | |||
4ca3f030cc | |||
5214c5d55d | |||
3e6ed7b69b | |||
7d2a8d343a | |||
03aacf483f | |||
02bc8c8445 | |||
7965b82cca | |||
bae04a492a | |||
73a1391f2f | |||
660ff0434d | |||
94208dce76 | |||
efe54fbba4 | |||
10561d6c83 | |||
72404e77e5 | |||
0ef87f93a6 | |||
c7310c21fb | |||
ed9e27b64f | |||
801ed63be5 | |||
2aa9b267ab | |||
90338d9600 | |||
5cecf16d4f | |||
a3ed309342 | |||
08b4c09b89 | |||
a509a5e1bc | |||
bec10e7836 | |||
f9a184800b | |||
f428c3ff8b | |||
97eb40a8e3 | |||
8211631950 | |||
a17cbb0e8b | |||
21a30f1b6f | |||
613c45ede9 | |||
8743c6a08d | |||
8098be6f93 | |||
eba0831538 | |||
1c73c9fbfe | |||
1ad103c29f | |||
ca37b4c9ac | |||
d3bbf09d12 | |||
f608317eec | |||
c8b9c9bcaa | |||
d638cdcdd3 | |||
46f82a4af3 | |||
978606edb4 | |||
42a2642d40 | |||
37840c3844 | |||
28335ef3d8 | |||
78ec5b3322 | |||
ac7e1ab299 | |||
ea60fd0f45 | |||
c291e8c5d9 | |||
f0ac542a7f | |||
a412085785 | |||
435b5c74bf | |||
15fd7041ab | |||
1ced6b4d67 |
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
|
40
.eslintrc.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"extends": ["airbnb-base", "prettier"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"globals": { "Plyr": false, "jQuery": false },
|
||||
"rules": {
|
||||
"no-const-assign": 1,
|
||||
"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"
|
||||
}
|
||||
}
|
17
.github/issue_template.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
<!---
|
||||
Please use this issue template as it makes replicating and fixing the issue easier!
|
||||
--->
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
### Actual behaviour
|
||||
|
||||
### Environment
|
||||
|
||||
- Browser:
|
||||
- Version:
|
||||
- Operating System:
|
||||
- Version:
|
||||
|
||||
### Steps to reproduce
|
||||
-
|
8
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
### Link to related issue (if applicable)
|
||||
|
||||
### Sumary of proposed changes
|
||||
|
||||
### Task list
|
||||
|
||||
- [ ] Tested on [supported browsers](https://github.com/sampotts/plyr#browser-support)
|
||||
- [ ] Gulp build completed
|
14
.gitignore
vendored
@ -1,11 +1,11 @@
|
||||
node_modules
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.DS_Store
|
||||
aws.json
|
||||
docs/index.dev.html
|
||||
*.mp4
|
||||
index-dev.html
|
||||
notes.txt
|
||||
*.vtt
|
||||
docs/index.dev.php
|
||||
!dist/blank.mp4
|
||||
index-*.html
|
||||
npm-debug.log
|
||||
*.webm
|
||||
/package-lock.json
|
||||
.idea/
|
||||
|
||||
|
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
|
||||
}
|
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-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"
|
||||
]
|
||||
}
|
1
.vscode/settings.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{}
|
@ -6,10 +6,10 @@
|
||||
"Audio",
|
||||
"Video",
|
||||
"HTML5 Audio",
|
||||
"HTml5 Video"
|
||||
"HTML5 Video"
|
||||
],
|
||||
"authors": [
|
||||
"Sam Potts <me@sampotts.me>"
|
||||
"Sam Potts <sam@potts.es>"
|
||||
],
|
||||
"dependencies": {},
|
||||
"main": [
|
||||
@ -27,7 +27,7 @@
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/selz/plyr.git"
|
||||
"url": "git://github.com/sampotts/plyr.git"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
19
bundles.json
@ -1,24 +1,19 @@
|
||||
{
|
||||
"plyr": {
|
||||
"less": {
|
||||
"plyr.css": ["src/less/plyr.less"]
|
||||
},
|
||||
"scss": {
|
||||
"plyr.css": ["src/scss/plyr.scss"]
|
||||
"sass": {
|
||||
"plyr.css": "src/sass/plyr.scss"
|
||||
},
|
||||
"js": {
|
||||
"plyr.js": ["src/js/plyr.js"]
|
||||
"plyr.js": "src/js/plyr.js"
|
||||
}
|
||||
},
|
||||
"demo": {
|
||||
"less": {
|
||||
"demo.css": ["demo/src/less/demo.less"]
|
||||
"sass": {
|
||||
"demo.css": "demo/src/sass/bundles/demo.scss",
|
||||
"error.css": "demo/src/sass/bundles/error.csss"
|
||||
},
|
||||
"js": {
|
||||
"demo.js": [
|
||||
"demo/src/js/lib/classlist.js",
|
||||
"demo/src/js/main.js"
|
||||
]
|
||||
"demo.js": "demo/src/js/demo.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
746
changelog.md
@ -1,495 +1,743 @@
|
||||
# Changelog
|
||||
# v3.0.0
|
||||
|
||||
This is a massive release. A _mostly_ complete rewrite in ES6. What started out as a few changes quickly snowballed. There's many breaking changes so be careful upgrading.
|
||||
|
||||
### Big changes
|
||||
|
||||
* New settings menu complete with funky animations
|
||||
* Ability to adjust speed of playback
|
||||
* Ability to toggle caption language (HTML5 and Vimeo only)
|
||||
* Ability to set YouTube quality (HTML5 will follow)
|
||||
* Added support for Vimeo captions
|
||||
* Added Picture-in-Picture support (Safari only)
|
||||
* Added AirPlay support (again, Safari only)
|
||||
* Added `playsinline` support for iOS 10+
|
||||
* Soundcloud removed until I can work on a plugin framework
|
||||
* Embedded players are now progressively enhanced - no more empty `<div>`s!
|
||||
|
||||
### Other stuff
|
||||
|
||||
* Now using SASS exclusively. Sorry, LESS folk it just made sense to maintain one method as SASS is what the cool kids use
|
||||
* Moved to ES6. All the rage these days
|
||||
* Added basic looping support
|
||||
* Added an aspect ratio option for those that can't leave the 90s and want 4:3
|
||||
* `controlshidden` and `controlsshown` events added for when the controls show or hide
|
||||
* `qualityrequested` and `qualitychange` events for YouTube quality control (HTML5 will follow)
|
||||
* Volume is now `0` to `1` as per HTML5 spec
|
||||
* No longer bodging a `<progress>` behind the `<input type="range">` to make up for WebKit's lack of lower fill styling
|
||||
* Captions now render with line breaks as intended
|
||||
* Captions now render without AJAX using the native events etc
|
||||
* Added a fallback for getting YouTube video data incase `.getVideoData()` disappears when one of their developers randomly deletes it again
|
||||
* Setup and building of the UI should be way "snappier"
|
||||
* Click to toggle inverted time (e.g. 0:01 or -2:59 for a 3 minute video at 1 seconds) - new `toggleInvert` and `invertTime` options
|
||||
* Added `autopause` option for Vimeo
|
||||
* Added `muted` option for you guessed it, muted playback
|
||||
* Restored the `.off()` API method
|
||||
* `.play()` will now return a promise to prevent that pesky uncaught promise issue in Chrome etc
|
||||
* Pressing and hold the seek bar no longer freezes all other updates of the UI
|
||||
|
||||
...plus loads of bug fixes.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
You gotta break eggs to make an omelette. Sadly, there's quite a few breaking changes:
|
||||
|
||||
* Setup now uses proper constructor, accepts a single selector/element/node and returns a single instance - much simpler than before
|
||||
* Much of the API is now using getters and setters rather than methods (where it makes sense) to match the HTML5 API - see the docs for more info
|
||||
* `blankUrl` -> `blankVideo`
|
||||
* `volume` is now `0` to `1` as per HTML5 spec
|
||||
* `keyboardShorcuts` (typo) is now just `keyboard`
|
||||
* `loop` is now `loop.active` in preparation for loop enhancements later
|
||||
* `html` option for custom controls removed in favour of the `controls` option which now accepts an array (to use built in controls) or a string of HTML for custom controls.
|
||||
* `classes` -> `classNames`
|
||||
* `classes.videoWrapper` -> `classNames.video`
|
||||
* `classes.embedWrapper` -> `classNames.embed`
|
||||
* `classes.ready` removed
|
||||
* `classes.setup` removed
|
||||
* `classes.muted` removed
|
||||
* `classes.fullscreen.active` removed in favour of the `:fullscreen` selector
|
||||
* `selectors.html5` removed
|
||||
* `selectors.embed` removed
|
||||
* `selectors.buttons.seek` -> `selectors.inputs.seek`
|
||||
* `selectors.volume.input` -> `selectors.inputs.volume`
|
||||
* `selectors.volume.display` -> `selectors.display.volume`
|
||||
* `selectors.currentTime` -> `selectors.display.currentTime`
|
||||
* `selectors.duration` -> `selectors.display.duration`
|
||||
|
||||
### Polyfilling
|
||||
|
||||
Because we're using the fancy new ES6 syntax, you will need to polyfill for vintage browsers if you want to use Plyr and still support them. Luckily there's a decent service for this that makes it painless, [https://polyfill.io](polyfill.io).
|
||||
|
||||
## v2.0.18
|
||||
|
||||
* Fix for YouTube .getVideoData() issue (fixes #709)
|
||||
|
||||
## v2.0.17
|
||||
|
||||
* Vimeo controls fix (fixes #697)
|
||||
* SVG4everybody compatibility fix
|
||||
* Allow Plyr.setup event listeners to be set up as separate event listeners (https://github.com/sampotts/plyr/pull/703)
|
||||
* Added title to the layer html template (for custom controls) (https://github.com/sampotts/plyr/pull/649)
|
||||
* Target is null bug fix (https://github.com/sampotts/plyr/pull/617)
|
||||
* fix #684 memory leaks issues after destroy (https://github.com/sampotts/plyr/pull/700)
|
||||
|
||||
## v2.0.16
|
||||
|
||||
* Fullscreen bug fix (fixes #664)
|
||||
|
||||
## v2.0.15
|
||||
|
||||
* Demo fix
|
||||
|
||||
## v2.0.14
|
||||
|
||||
* CDN URL updates. Sorry, still working on V3 as hard as I can...
|
||||
|
||||
## v2.0.13
|
||||
|
||||
* Repo moved and Vimeo demo fix
|
||||
|
||||
## v2.0.12
|
||||
|
||||
* Ability to set custom `blankUrl` for source changes (https://github.com/sampotts/plyr/pull/504)
|
||||
* Ability to set caption button listener (https://github.com/sampotts/plyr/pull/468)
|
||||
|
||||
## v2.0.11
|
||||
|
||||
* Fix for `cleanUp` being called twice (thanks to @sebastiancarlsson)
|
||||
* Fix for YouTube controls on iPad (fixes #391)
|
||||
|
||||
## v2.0.10
|
||||
|
||||
* Added seek event fixes for Vimeo and YouTube (fixes #409)
|
||||
* Added support for embed URLs rather than ID only (fixes #345)
|
||||
|
||||
## v2.0.9
|
||||
|
||||
* Temporary patch for the YouTube API issues with `getDuration()` (relates to #374)
|
||||
|
||||
## v2.0.8
|
||||
|
||||
* Added `isPaused()` API method (thanks to @darrena092)
|
||||
* Allowed `.on()` API method to be chainable (thanks to @gurupras) (fixes #357)
|
||||
* Improved the "awful" rendering of captions on small screens in fullscreen mode (fixes #390)
|
||||
* Fix for Firefox VTT compatibility (thanks to @magourex)
|
||||
* Fix for Firefox Developer Edition blank video due to `-webkit-mask-image` issue (fixes #392)
|
||||
* Added Issue and PR templates with the aim of reducing duplicate or duff issues
|
||||
|
||||
## v2.0.7
|
||||
|
||||
* Fixed `getCurrentTime()` method (fixes #351)
|
||||
* Added `getVolume()` , `isMuted()` and `getDuration()` API methods (fixes #346)
|
||||
|
||||
## v2.0.6
|
||||
|
||||
* Fixed merge issue with `Updated define to work with AMD imports #326` PR
|
||||
* Code formatting
|
||||
|
||||
## v2.0.5
|
||||
|
||||
* Fix for Vimeo in IE9 & IE10
|
||||
* Fix for HTML5 elements not firing `ready` event
|
||||
|
||||
## v2.0.4
|
||||
- Fix for Firefox full screen (fixes #343)
|
||||
|
||||
* Fix for Firefox full screen (fixes #343)
|
||||
|
||||
## v2.0.3
|
||||
- Set 'global' keyboard shortcut option to false as default, added `<textarea>` to editable elements to be ignored.
|
||||
|
||||
* Set 'global' keyboard shortcut option to false as default, added `<textarea>` to editable elements to be ignored
|
||||
|
||||
## v2.0.2
|
||||
- Added 'global' keyboard shortcut option
|
||||
|
||||
* Added 'global' keyboard shortcut option
|
||||
|
||||
## v2.0.1
|
||||
- Version bump for NPM
|
||||
|
||||
* Version bump for NPM (sorry for folks who upgraded to the now deleted v1.9.0 through NPM)
|
||||
|
||||
# v2.0.0
|
||||
This version contains several potential ***breaking changes***:
|
||||
|
||||
- `setup()` has been reverted to pre v1.8.0 behaviour; meaning it will return the *instance* rather than the *element*. This is because the reference to the instance is no longer added to the original element (see below).
|
||||
- The reference to the `plyr` instance is now added to the media element rather than original container. This is because if a container with multiple children was passed to `setup()` the references to all instances would have been added to the container, creating issues. I would recommend using the return value from `setup()` or the new `get()` method to access the instance.
|
||||
- Players will always be wrapped in their own div now - this makes `setup()` and `destroy()` cleaner. This *may* break any custom styling based on DOM position.
|
||||
- Players no longer seek to 0 on 'ended' - this is to fix a bug with Microsoft Edge as it triggers 'ended' on media change for whatever reason. They'll never change ;-)
|
||||
This version contains several potential **_breaking changes_**:
|
||||
|
||||
* `setup()` has been reverted to pre v1.8.0 behaviour; meaning it will return the _instance_ rather than the _element_. This is because the reference to the instance is no longer added to the original element (see below).
|
||||
* The reference to the `plyr` instance is now added to the media element rather than original container. This is because if a container with multiple children was passed to `setup()` the references to all instances would have been added to the container, creating issues. I would recommend using the return value from `setup()` or the new `get()` method to access the instance.
|
||||
* Players will always be wrapped in their own div now - this makes `setup()` and `destroy()` cleaner. This _may_ break any custom styling based on DOM position.
|
||||
* Players no longer seek to 0 on 'ended' - this is to fix a bug with Microsoft Edge as it triggers 'ended' on media change for whatever reason. They'll never change ;-)
|
||||
|
||||
And some other changes and bug fixes:
|
||||
|
||||
- New `get()` method on the global plyr object to get all instances inside a container
|
||||
- New API methods:
|
||||
- `getOriginal()` to get the original, *unmodified* element plyr was setup on (`<video>`, `<audio>` or empty `<div>` for YouTube and Vimeo)
|
||||
- `getContainer()` to get the players outer wrapper element
|
||||
- `getMedia()` to get the players media element (`<video>`, `<audio>` or empty `<div>` for YouTube and Vimeo)
|
||||
- `getEmbed()` to access the YouTube or Vimeo API directly
|
||||
- `getType()` to get the type of the player
|
||||
- `isReady()` to determine if an instance has completed setup and necessary APIs are loaded (for YouTube / Vimeo)
|
||||
- `on()` to provide an easy way to listen to events
|
||||
- `stop()` to, you guessed it, stop the player
|
||||
- `destroy()` now works correctly for YouTube and Vimeo (fixes #272)
|
||||
- New `destroyed` event when `destroy()` has completed (original element is passed as event.target)
|
||||
- Default volume is now 10 (max) rather than 5
|
||||
- Sprite is only loaded once (fixes #259)
|
||||
- Fixes for Vimeo post message bugs on source change or destroy (fixes #318)
|
||||
- Save caption state in storage (fixes #311)
|
||||
- Added keyboard shortcuts to the current focused player (with `keyboardShortcuts` boolean option to disable) (fixes #309)
|
||||
- Fix for captions bug (fixes #332)
|
||||
- Change to AMD (fixes #298)
|
||||
* New `get()` method on the global plyr object to get all instances inside a container
|
||||
* New API methods: - `getOriginal()` to get the original, _unmodified_ element plyr was setup on (`<video>`, `<audio>` or empty `<div>` for YouTube and Vimeo) - `getContainer()` to get the players outer wrapper element - `getMedia()` to get the players media element (`<video>`, `<audio>` or empty `<div>` for YouTube and Vimeo) - `getEmbed()` to access the YouTube or Vimeo API directly - `getType()` to get the type of the player - `isReady()` to determine if an instance has completed setup and necessary APIs are loaded (for YouTube / Vimeo) - `on()` to provide an easy way to listen to events - `stop()` to, you guessed it, stop the player
|
||||
* `destroy()` now works correctly for YouTube and Vimeo (fixes #272)
|
||||
* New `destroyed` event when `destroy()` has completed (original element is passed as event.target)
|
||||
* Default volume is now 10 (max) rather than 5
|
||||
* Sprite is only loaded once (fixes #259)
|
||||
* Fixes for Vimeo post message bugs on source change or destroy (fixes #318)
|
||||
* Save caption state in storage (fixes #311)
|
||||
* Added keyboard shortcuts to the current focused player (with `keyboardShortcuts` boolean option to disable) (fixes #309)
|
||||
* Fix for captions bug (fixes #332)
|
||||
* Change to AMD (fixes #298)
|
||||
|
||||
## v1.8.12
|
||||
- Vimeo keyboard focus fix (Fixes #317)
|
||||
- Fix for Vimeo on basic support devices
|
||||
|
||||
* Vimeo keyboard focus fix (Fixes #317)
|
||||
* Fix for Vimeo on basic support devices
|
||||
|
||||
## v1.8.11
|
||||
- Fix for keyboard navigation on Vimeo (Fixes #317)
|
||||
- Fix for bug introduced in v1.8.9 related to additional controls
|
||||
- Vimeo API upgrade
|
||||
- Fix for YouTube bug introduced in v1.8.9
|
||||
- Added support for passing array to .setup() (Fixes #319)
|
||||
|
||||
* Fix for keyboard navigation on Vimeo (Fixes #317)
|
||||
* Fix for bug introduced in v1.8.9 related to additional controls
|
||||
* Vimeo API upgrade
|
||||
* Fix for YouTube bug introduced in v1.8.9
|
||||
* Added support for passing array to .setup() (Fixes #319)
|
||||
|
||||
## v1.8.10
|
||||
- Fix for seek issues introduced in v1.8.9
|
||||
|
||||
* Fix for seek issues introduced in v1.8.9
|
||||
|
||||
## v1.8.9
|
||||
- Fix for fullscreen not being defined (Fixes #295)
|
||||
- Fix for multiline captions (Fixes #314)
|
||||
- Clean up of type checks and fix for `restart()` (Fixes #315)
|
||||
- Fix for `MEDIA_ERR_SRC_NOT_SUPPORTED` when calling `.source()` API method
|
||||
|
||||
* Fix for fullscreen not being defined (Fixes #295)
|
||||
* Fix for multiline captions (Fixes #314)
|
||||
* Clean up of type checks and fix for `restart()` (Fixes #315)
|
||||
* Fix for `MEDIA_ERR_SRC_NOT_SUPPORTED` when calling `.source()` API method
|
||||
|
||||
## v1.8.8
|
||||
- Added getCurrentTime API method (fixes #292)
|
||||
- Fix for !hideControls on touch devices (fixes #303)
|
||||
|
||||
* Added getCurrentTime API method (fixes #292)
|
||||
* Fix for !hideControls on touch devices (fixes #303)
|
||||
|
||||
## v1.8.7
|
||||
- Line height fix
|
||||
|
||||
* Line height fix
|
||||
|
||||
## v1.8.6
|
||||
- Reverted font size change
|
||||
|
||||
* Reverted font size change
|
||||
|
||||
## v1.8.5
|
||||
- Fixed overflow issues (fixes #286)
|
||||
|
||||
* Fixed overflow issues (fixes #286)
|
||||
|
||||
## v1.8.4
|
||||
- Fix for large play button on small videos
|
||||
|
||||
* Fix for large play button on small videos
|
||||
|
||||
## v1.8.3
|
||||
- Disabled iPad support for YouTube and Vimeo due to iOS limitations with iFrame playback
|
||||
- Fixed IE11 icon loading (fixes #269)
|
||||
- Updated screenshot (fixes #281)
|
||||
- Added WordPress plugin (fixes #239)
|
||||
- Added Neos plugin
|
||||
- Added HLS, Shaka and dash.js examples (see #235 for more)
|
||||
- Improvements for controls hiding and showing on touch devices
|
||||
|
||||
* Disabled iPad support for YouTube and Vimeo due to iOS limitations with iFrame playback
|
||||
* Fixed IE11 icon loading (fixes #269)
|
||||
* Updated screenshot (fixes #281)
|
||||
* Added WordPress plugin (fixes #239)
|
||||
* Added Neos plugin
|
||||
* Added HLS, Shaka and dash.js examples (see #235 for more)
|
||||
* Improvements for controls hiding and showing on touch devices
|
||||
|
||||
## v1.8.2
|
||||
- Fixed event bubbling
|
||||
|
||||
* Fixed event bubbling
|
||||
|
||||
## v1.8.1
|
||||
- Fixed inaccurate log message
|
||||
|
||||
* Fixed inaccurate log message
|
||||
|
||||
# v1.8.0
|
||||
- ***(Important)*** `setup()` now returns the element Plyr was setup on rather than the `plyr` object. This means `var player = plyr.setup()[0];` would now be `var player = plyr.setup()[0].plyr;`. This improves support for React and other virtual dom frameworks as mentioned in #254
|
||||
- Fixed using a relative URL for `iconUrl` in IE (fixes #269)
|
||||
|
||||
* **_(Important)_** `setup()` now returns the element Plyr was setup on rather than the `plyr` object. This means `var player = plyr.setup()[0];` would now be `var player = plyr.setup()[0].plyr;`. This improves support for React and other virtual dom frameworks as mentioned in #254
|
||||
* Fixed using a relative URL for `iconUrl` in IE (fixes #269)
|
||||
|
||||
# v1.7.0
|
||||
- SASS cleanup (fixes #265)
|
||||
- Docs tidy up to help quick start (fixes #253)
|
||||
- Fix for issues with data attribute options passing (fixes #257)
|
||||
- ***(Important)*** Removed the requirement for a wrapper div to setup Plyr and removed the dependency on the `plyr` classname as a JS hook. By default it will now look for `<video>`, `<audio>` and `[data-type]` elements. If you are just calling `setup()` with a `<div class="plyr">` you may want to give it a good test after upgrading. You can probably remove the wrapper div. The reason behind this is to make setup easier for newcomers and prevent the styling being used on unsupported players (because the plyr classname was used as a CSS and JS hook - which isn't ideal)
|
||||
- Renamed the 'docs' folder to `demo` to avoid confusion - the readme is the docs after all
|
||||
|
||||
* SASS cleanup (fixes #265)
|
||||
* Docs tidy up to help quick start (fixes #253)
|
||||
* Fix for issues with data attribute options passing (fixes #257)
|
||||
* **_(Important)_** Removed the requirement for a wrapper div to setup Plyr and removed the dependency on the `plyr` classname as a JS hook. By default it will now look for `<video>`, `<audio>` and `[data-type]` elements. If you are just calling `setup()` with a `<div class="plyr">` you may want to give it a good test after upgrading. You can probably remove the wrapper div. The reason behind this is to make setup easier for newcomers and prevent the styling being used on unsupported players (because the plyr classname was used as a CSS and JS hook - which isn't ideal)
|
||||
* Renamed the 'docs' folder to `demo` to avoid confusion - the readme is the docs after all
|
||||
|
||||
## v1.6.20
|
||||
- Fix for multiple sprites being requested (fixes #259)
|
||||
|
||||
* Fix for multiple sprites being requested (fixes #259)
|
||||
|
||||
## v1.6.19
|
||||
- Fix for scroll direction issues on volume control (fixes #258)
|
||||
|
||||
* Fix for scroll direction issues on volume control (fixes #258)
|
||||
|
||||
## v1.6.18
|
||||
- Reduced rounding of seek value from 1 decimal point to 4 (fixes #242)
|
||||
|
||||
* Reduced rounding of seek value from 1 decimal point to 4 (fixes #242)
|
||||
|
||||
## v1.6.17
|
||||
- Added `disableContextMenu` option to hide the right click context menu (fixes #248 and #225)
|
||||
|
||||
* Added `disableContextMenu` option to hide the right click context menu (fixes #248 and #225)
|
||||
|
||||
## v1.6.16
|
||||
- Always hide standard controls (fixes #225)
|
||||
- Fix for Tooltips overflowing (fixes #230)
|
||||
|
||||
* Always hide standard controls (fixes #225)
|
||||
* Fix for Tooltips overflowing (fixes #230)
|
||||
|
||||
## v1.6.15
|
||||
- Restore scroll position when exiting full screen (fixes #236)
|
||||
|
||||
* Restore scroll position when exiting full screen (fixes #236)
|
||||
|
||||
## v1.6.14
|
||||
- SVG sprite loading automatically for an easier setup
|
||||
- Touch devices now show controls on touch rather than pausing playback
|
||||
|
||||
* SVG sprite loading automatically for an easier setup
|
||||
* Touch devices now show controls on touch rather than pausing playback
|
||||
|
||||
## v1.6.13
|
||||
- Decreased sensitivity and inverted scroll on volume slider (scroll up to increase, down to decrease)
|
||||
|
||||
* Decreased sensitivity and inverted scroll on volume slider (scroll up to increase, down to decrease)
|
||||
|
||||
## v1.6.12
|
||||
- Fix for undefined buffer error
|
||||
- Add scroll listener on volume slider (PR #227 bty @igoradamenko)
|
||||
|
||||
* Fix for undefined buffer error
|
||||
* Add scroll listener on volume slider (PR #227 bty @igoradamenko)
|
||||
|
||||
## v1.6.11
|
||||
- Fix for Vimeo fullscreen (fixes #214)
|
||||
|
||||
* Fix for Vimeo fullscreen (fixes #214)
|
||||
|
||||
## v1.6.10
|
||||
- Changed default icon prefix from 'icon' to 'plyr' to avoid clashes
|
||||
|
||||
* Changed default icon prefix from 'icon' to 'plyr' to avoid clashes
|
||||
|
||||
## v1.6.9
|
||||
- Added 'latest' CDN option
|
||||
- Renamed `sprite.svg` to `plyr.svg` to be inline with the other package files
|
||||
|
||||
* Added 'latest' CDN option
|
||||
* Renamed `sprite.svg` to `plyr.svg` to be inline with the other package files
|
||||
|
||||
## v1.6.8
|
||||
- Fix for bug introduced in v1.6.7
|
||||
|
||||
* Fix for bug introduced in v1.6.7
|
||||
|
||||
## v1.6.7
|
||||
- Fixes for using `source` API method on iOS
|
||||
|
||||
* Fixes for using `source` API method on iOS
|
||||
|
||||
## v1.6.6
|
||||
- Icons cleaned up
|
||||
- IE11 button fix for tooltips (fixes #210)
|
||||
|
||||
* Icons cleaned up
|
||||
* IE11 button fix for tooltips (fixes #210)
|
||||
|
||||
## v1.6.5
|
||||
- IE UI bug fixes
|
||||
|
||||
* IE UI bug fixes
|
||||
|
||||
## v1.6.4
|
||||
- Bug fix for undefined progress bar
|
||||
|
||||
* Bug fix for undefined progress bar
|
||||
|
||||
## v1.6.3
|
||||
- Seek back to 0 for all media on ended
|
||||
- Check for HTML5 video on ended reload
|
||||
- Update to docs for `showPosterOnEnd` option
|
||||
|
||||
* Seek back to 0 for all media on ended
|
||||
* Check for HTML5 video on ended reload
|
||||
* Update to docs for `showPosterOnEnd` option
|
||||
|
||||
## v1.6.2
|
||||
- Fix for tooltip displaying when duration is not set (fixes #177)
|
||||
- `showPosterOnEnd` option to show poster when HTML5 video ended (fixes #59)
|
||||
- Error handler for YouTube (fixes #189)
|
||||
- Initial SoundCloud support (fixes #194)
|
||||
- Other minor bug fixes
|
||||
|
||||
* Fix for tooltip displaying when duration is not set (fixes #177)
|
||||
* `showPosterOnEnd` option to show poster when HTML5 video ended (fixes #59)
|
||||
* Error handler for YouTube (fixes #189)
|
||||
* Initial SoundCloud support (fixes #194)
|
||||
* Other minor bug fixes
|
||||
|
||||
## v1.6.1
|
||||
- Tooltip changes for accessibility
|
||||
|
||||
* Tooltip changes for accessibility
|
||||
|
||||
## v1.6.0
|
||||
- New, cleaner, UI:
|
||||
- Controls are now overlaid, maintaining the video's ratio and making sizing easier
|
||||
- A large play button can now be overlaid over videos
|
||||
- Default number of control buttons reduced
|
||||
- New play, pause, rewind and fast forward icons
|
||||
- Flexbox all the things!
|
||||
- Tidied up the LESS (and SCSS) as part of the above, variables and mixins in seprate files amking customization and upgrades easier
|
||||
- Toggle mute bug fix; if a player was muted previously and the user refreshed, unmuting would have meant volume was still zero (effectively muted), now the config default value is used. Not ideal but good for now
|
||||
- New `iconUrl` option allowing specifying a same origin SVG sprite location. Loading this way means you don't need the AJAX sprite loading JavaScript
|
||||
- `click` option renamed to `clickToPlay` to make it a bit more self explanatory. Unfortunately cross origin SVG sprites is not supported in any browser yet :-(
|
||||
- `hideControls` is now a global option, rather than being exclusive to fullscreen. Controls are now hidden after 2 seconds of no mouse movement. Controls are always shown when media is paused or stopped. This is defaulted to true.
|
||||
- `sass` folder in `src` renamed from to `scss`
|
||||
|
||||
* New, cleaner, UI: - Controls are now overlaid, maintaining the video's ratio and making sizing easier - A large play button can now be overlaid over videos - Default number of control buttons reduced - New play, pause, rewind and fast forward icons - Flexbox all the things!
|
||||
* Tidied up the LESS (and SCSS) as part of the above, variables and mixins in seprate files amking customization and upgrades easier
|
||||
* Toggle mute bug fix; if a player was muted previously and the user refreshed, unmuting would have meant volume was still zero (effectively muted), now the config default value is used. Not ideal but good for now
|
||||
* New `iconUrl` option allowing specifying a same origin SVG sprite location. Loading this way means you don't need the AJAX sprite loading JavaScript
|
||||
* `click` option renamed to `clickToPlay` to make it a bit more self explanatory. Unfortunately cross origin SVG sprites is not supported in any browser yet :-(
|
||||
* `hideControls` is now a global option, rather than being exclusive to fullscreen. Controls are now hidden after 2 seconds of no mouse movement. Controls are always shown when media is paused or stopped. This is defaulted to true.
|
||||
* `sass` folder in `src` renamed from to `scss`
|
||||
|
||||
## v1.5.21
|
||||
- Bug fix for embeds: `play` not being defined (fixes #185 and #186)
|
||||
|
||||
* Bug fix for embeds: `play` not being defined (fixes #185 and #186)
|
||||
|
||||
## v1.5.20
|
||||
- Bug fix for autoplay option
|
||||
|
||||
* Bug fix for autoplay option
|
||||
|
||||
## v1.5.19
|
||||
- Fix for accessing `embed` property after `ready` event fired
|
||||
|
||||
* Fix for accessing `embed` property after `ready` event fired
|
||||
|
||||
## v1.5.18
|
||||
- Added 'ready' event for initial setup complete or source change occurs
|
||||
- Fixed SASS stylesheet references to transparentize
|
||||
- Added default font stack to controls
|
||||
- Docs fixes inc controls HTML (fixes #180)
|
||||
|
||||
* Added 'ready' event for initial setup complete or source change occurs
|
||||
* Fixed SASS stylesheet references to transparentize
|
||||
* Added default font stack to controls
|
||||
* Docs fixes inc controls HTML (fixes #180)
|
||||
|
||||
## v1.5.17
|
||||
- Expose YouTube and Vimeo API (docs update required) (fixes #176)
|
||||
- Auto set title based on YouTube getVideoData() title property
|
||||
- Bug fix for Vimeo API change (Uncaught TypeError: Cannot read property 'value' of undefined) due to a change their end
|
||||
|
||||
* Expose YouTube and Vimeo API (docs update required) (fixes #176)
|
||||
* Auto set title based on YouTube getVideoData() title property
|
||||
* Bug fix for Vimeo API change (Uncaught TypeError: Cannot read property 'value' of undefined) due to a change their end
|
||||
|
||||
## v1.5.16
|
||||
- Cancel requests on source change (fixes #174)
|
||||
|
||||
* Cancel requests on source change (fixes #174)
|
||||
|
||||
## v1.5.15
|
||||
- Fix for CustomEvent polyfill and related bug (see #172)
|
||||
|
||||
* Fix for CustomEvent polyfill and related bug (see #172)
|
||||
|
||||
## v1.5.14
|
||||
- Volume storage fix (fixes #171)
|
||||
|
||||
* Volume storage fix (fixes #171)
|
||||
|
||||
## v1.5.13
|
||||
- Fix for manual caption rendering
|
||||
|
||||
* Fix for manual caption rendering
|
||||
|
||||
## v1.5.12
|
||||
- Added a duration option to pass the duration of the file
|
||||
- Added the ability to set options per element by setting a data-plyr attribute on the target elements (this might be useful for the duration option for example)
|
||||
- Fixes for Chrome and Safari caption rendering, they now use the default texttrack and cuechange events
|
||||
- Firefox bug fix for event not defined
|
||||
|
||||
* Added a duration option to pass the duration of the file
|
||||
* Added the ability to set options per element by setting a data-plyr attribute on the target elements (this might be useful for the duration option for example)
|
||||
* Fixes for Chrome and Safari caption rendering, they now use the default texttrack and cuechange events
|
||||
* Firefox bug fix for event not defined
|
||||
|
||||
## v1.5.11
|
||||
- iOS embed bug fixes (fixes #166)
|
||||
- Hide IE/Edge <input type='range'> tooltip (since we have a styled one) (fixes #160)
|
||||
- SASS bug fix for default values (fixes #158)
|
||||
|
||||
* iOS embed bug fixes (fixes #166)
|
||||
* Hide IE/Edge <input type='range'> tooltip (since we have a styled one) (fixes #160)
|
||||
* SASS bug fix for default values (fixes #158)
|
||||
|
||||
## v1.5.9 + v1.5.10
|
||||
- NPM bug fixes
|
||||
|
||||
* NPM bug fixes
|
||||
|
||||
## v1.5.8
|
||||
- Fix for touch device seek tooltip
|
||||
- Seek improvements
|
||||
|
||||
* Fix for touch device seek tooltip
|
||||
* Seek improvements
|
||||
|
||||
## v1.5.7
|
||||
- Fix for control tooltips always showing
|
||||
|
||||
* Fix for control tooltips always showing
|
||||
|
||||
## v1.5.6
|
||||
- Seek tooltip (option for tooltips changed, please check docs)
|
||||
- SASS compile error fixes (fixes #148)
|
||||
- Fullscreen fixes for controls not always hiding/showing (fixes #149)
|
||||
- Screen reader icon fixes (title was being read twice due to the tooltip/hidden label)
|
||||
|
||||
* Seek tooltip (option for tooltips changed, please check docs)
|
||||
* SASS compile error fixes (fixes #148)
|
||||
* Fullscreen fixes for controls not always hiding/showing (fixes #149)
|
||||
* Screen reader icon fixes (title was being read twice due to the tooltip/hidden label)
|
||||
|
||||
## v1.5.5
|
||||
- Fixed controls.md example
|
||||
- Bug fix for docs error page
|
||||
- Bug fix for controls tooltips
|
||||
|
||||
* Fixed controls.md example
|
||||
* Bug fix for docs error page
|
||||
* Bug fix for controls tooltips
|
||||
|
||||
## v1.5.4
|
||||
- Minor bug fix for clicking video to play/pause after source change
|
||||
|
||||
* Minor bug fix for clicking video to play/pause after source change
|
||||
|
||||
## v1.5.3
|
||||
- Minor bug fix for occasional display of 0:00 as the media duration
|
||||
|
||||
* Minor bug fix for occasional display of 0:00 as the media duration
|
||||
|
||||
## v1.5.2
|
||||
- `handlers` option renamed to `listeners`
|
||||
- Added event listeners for all types to the plyr container (playback, fullscreen, captions etc - see docs)
|
||||
- Removed onSetup config option (use the 'setup' event instead, plyr element is event.plyr)
|
||||
- Style bug fixes
|
||||
- Vimeo seek bug fix (requires whole seconds when seeking)
|
||||
- Fix for fullscreen player (using class hook, not browser fullscreen)
|
||||
|
||||
* `handlers` option renamed to `listeners`
|
||||
* Added event listeners for all types to the plyr container (playback, fullscreen, captions etc - see docs)
|
||||
* Removed onSetup config option (use the 'setup' event instead, plyr element is event.plyr)
|
||||
* Style bug fixes
|
||||
* Vimeo seek bug fix (requires whole seconds when seeking)
|
||||
* Fix for fullscreen player (using class hook, not browser fullscreen)
|
||||
|
||||
## v1.5.1
|
||||
- Fix for event listeners being duplicated on source change
|
||||
|
||||
* Fix for event listeners being duplicated on source change
|
||||
|
||||
# v1.5.0
|
||||
- Vimeo support (fixes #8)
|
||||
- New options for initialization (you can now pass a selector, HTMLElement or NodeList) (fixes #118)
|
||||
- Switched to BEM methodology (you will need to change CSS and probably HTML)
|
||||
- Decoupled CSS and JS hooks (fixes #129)
|
||||
- Custom controls container (fixes #98)
|
||||
- Fix for private/incognito mode local storage bug (fixes #131)
|
||||
- UMD module setup (fixes #121)
|
||||
- Specify iframe title for Vimeo and YouTube (fixes #124)
|
||||
- Better handling of mission controls (fixes #132)
|
||||
- Retain classname on source change (fixes #120)
|
||||
- Increased thumb size on seek (partially fixes #130)
|
||||
- Passing no argument to `source` api method, now returns current source (by @gurupras)
|
||||
- Ability to add custom handlers to controls prior to Plyr bindings (by @gurupras)
|
||||
- Keyboard navigation improvements (focus on seek, focus trap in fullscreen) (fixes #135)
|
||||
|
||||
* Vimeo support (fixes #8)
|
||||
* New options for initialization (you can now pass a selector, HTMLElement or NodeList) (fixes #118)
|
||||
* Switched to BEM methodology (you will need to change CSS and probably HTML)
|
||||
* Decoupled CSS and JS hooks (fixes #129)
|
||||
* Custom controls container (fixes #98)
|
||||
* Fix for private/incognito mode local storage bug (fixes #131)
|
||||
* UMD module setup (fixes #121)
|
||||
* Specify iframe title for Vimeo and YouTube (fixes #124)
|
||||
* Better handling of mission controls (fixes #132)
|
||||
* Retain classname on source change (fixes #120)
|
||||
* Increased thumb size on seek (partially fixes #130)
|
||||
* Passing no argument to `source` api method, now returns current source (by @gurupras)
|
||||
* Ability to add custom handlers to controls prior to Plyr bindings (by @gurupras)
|
||||
* Keyboard navigation improvements (focus on seek, focus trap in fullscreen) (fixes #135)
|
||||
|
||||
## v1.3.5
|
||||
- Fixed bug with API use on basic supported browsers
|
||||
|
||||
* Fixed bug with API use on basic supported browsers
|
||||
|
||||
## v1.3.4
|
||||
- Code cleanup by @calvintam236
|
||||
|
||||
* Code cleanup by @calvintam236
|
||||
|
||||
## v1.3.3
|
||||
- Removed captions being read by screen readers
|
||||
|
||||
* Removed captions being read by screen readers
|
||||
|
||||
## v1.3.2
|
||||
- Voiceover fix for captions
|
||||
|
||||
* Voiceover fix for captions
|
||||
|
||||
## v1.3.1
|
||||
- ARIA improvements for captions being read
|
||||
|
||||
* ARIA improvements for captions being read
|
||||
|
||||
## v1.3.0
|
||||
- Internationalization support (i18n) using default controls (required markup changes to controls)
|
||||
- ARIA enhancements for controls (required markup changes to controls)
|
||||
- Captions legibility improvements
|
||||
- YouTube bug fixes
|
||||
|
||||
* Internationalization support (i18n) using default controls (required markup changes to controls)
|
||||
* ARIA enhancements for controls (required markup changes to controls)
|
||||
* Captions legibility improvements
|
||||
* YouTube bug fixes
|
||||
|
||||
## v1.2.6
|
||||
- SASS updates and fixes (cheers @ChristianPV)
|
||||
|
||||
* SASS updates and fixes (cheers @ChristianPV)
|
||||
|
||||
## v1.2.5
|
||||
- Fix for YouTube quality (let them decide quality)
|
||||
|
||||
* Fix for YouTube quality (let them decide quality)
|
||||
|
||||
## v1.2.4
|
||||
- Fix for omitted kind attribute on <track> (fixes #88)
|
||||
|
||||
* Fix for omitted kind attribute on <track> (fixes #88)
|
||||
|
||||
## v1.2.3
|
||||
- Fix for YouTube on iPhone or unsupported browsers (fallback to YouTube native)
|
||||
- Docs tidy up
|
||||
- Fullscreen for Safari fix (fixes #96)
|
||||
|
||||
* Fix for YouTube on iPhone or unsupported browsers (fallback to YouTube native)
|
||||
* Docs tidy up
|
||||
* Fullscreen for Safari fix (fixes #96)
|
||||
|
||||
## v1.2.2
|
||||
- Fix for :focus keyboard vs mouse (fixes #61)
|
||||
- Fix for caption positioning in full screen (fixes #92)
|
||||
|
||||
* Fix for :focus keyboard vs mouse (fixes #61)
|
||||
* Fix for caption positioning in full screen (fixes #92)
|
||||
|
||||
## v1.2.1
|
||||
- Tooltip bug fix
|
||||
|
||||
* Tooltip bug fix
|
||||
|
||||
# v1.2.0
|
||||
- Added YouTube support
|
||||
|
||||
* Added YouTube support
|
||||
|
||||
## v1.1.13
|
||||
- Added icon prefix option for when using default controls
|
||||
|
||||
* Added icon prefix option for when using default controls
|
||||
|
||||
## v1.1.13
|
||||
- Logic tweaks for hiding controls in fullscreen
|
||||
|
||||
* Logic tweaks for hiding controls in fullscreen
|
||||
|
||||
## v1.1.12
|
||||
- Bug fix for Chrome Canary
|
||||
|
||||
* Bug fix for Chrome Canary
|
||||
|
||||
## v1.1.11
|
||||
- Bug fix
|
||||
|
||||
* Bug fix
|
||||
|
||||
## v1.1.10
|
||||
- Bug fix
|
||||
|
||||
* Bug fix
|
||||
|
||||
## v1.1.9
|
||||
- Bug fix for 1.1.8
|
||||
|
||||
* Bug fix for 1.1.8
|
||||
|
||||
## v1.1.8
|
||||
- setVolume API method improvements (fixes #83)
|
||||
|
||||
* setVolume API method improvements (fixes #83)
|
||||
|
||||
## v1.1.7
|
||||
- Restore classname on destroy()
|
||||
|
||||
* Restore classname on destroy()
|
||||
|
||||
## v1.1.6
|
||||
- New API methods (fixes #77), Fix for non strict mode (fixes #78)
|
||||
|
||||
* New API methods (fixes #77), Fix for non strict mode (fixes #78)
|
||||
|
||||
## v1.1.5
|
||||
- Fix for incorrect `isFullscreen()` return value in Mozilla (fixes #38)
|
||||
|
||||
* Fix for incorrect `isFullscreen()` return value in Mozilla (fixes #38)
|
||||
|
||||
## v1.1.4
|
||||
- Minor bug fixes
|
||||
|
||||
* Minor bug fixes
|
||||
|
||||
## v1.1.3
|
||||
- Fixes for random id used in controls with multiple instances and one call to setup
|
||||
- Audio player UI improvements
|
||||
|
||||
* Fixes for random id used in controls with multiple instances and one call to setup
|
||||
* Audio player UI improvements
|
||||
|
||||
## v1.1.2
|
||||
- Added an onSetup callback option
|
||||
- Added fullscreen API methods `toggleFullscreen()` (must be user iniated), and `isFullscreen()`
|
||||
|
||||
* Added an onSetup callback option
|
||||
* Added fullscreen API methods `toggleFullscreen()` (must be user iniated), and `isFullscreen()`
|
||||
|
||||
## v1.1.1
|
||||
- Fix for unsupported browser handling
|
||||
- Fix for config.controls having no effect
|
||||
|
||||
* Fix for unsupported browser handling
|
||||
* Fix for config.controls having no effect
|
||||
|
||||
## v1.1.0
|
||||
- Added config option to set which controls are shown (if using the default controls html) and better handling of missing controls
|
||||
|
||||
* Added config option to set which controls are shown (if using the default controls html) and better handling of missing controls
|
||||
|
||||
## v1.0.31
|
||||
- Display duration on `metadataloaded`
|
||||
|
||||
* Display duration on `metadataloaded`
|
||||
|
||||
## v1.0.30
|
||||
- Fixed bug with media longer than 60 minutes (fixes #69)
|
||||
|
||||
* Fixed bug with media longer than 60 minutes (fixes #69)
|
||||
|
||||
## v1.0.29
|
||||
- Added option to hide controls on fullscreen (default `true`) while palying, after 1s. Pause, mouse hover on progress, or focus on a child control re-shows the controls. On touch a tap of the video (which plays/pauses the video by default) is required. (fixes #47)
|
||||
- Fixed a bug with caption toggle in 1.0.28
|
||||
|
||||
* Added option to hide controls on fullscreen (default `true`) while palying, after 1s. Pause, mouse hover on progress, or focus on a child control re-shows the controls. On touch a tap of the video (which plays/pauses the video by default) is required. (fixes #47)
|
||||
* Fixed a bug with caption toggle in 1.0.28
|
||||
|
||||
## v1.0.28
|
||||
- Added API support for browsers that don't have full plyr support (pretty much <=IE9 and `<video>` on iPhone/iPod)
|
||||
|
||||
* Added API support for browsers that don't have full plyr support (pretty much <=IE9 and `<video>` on iPhone/iPod)
|
||||
|
||||
## v1.0.27
|
||||
- Keyboard accessibility improvements (fixes #66)
|
||||
|
||||
* Keyboard accessibility improvements (fixes #66)
|
||||
|
||||
## v1.0.26
|
||||
- Fixes for SASS (cheers @brunowego)
|
||||
- Indentation reset to 4 spaces
|
||||
|
||||
* Fixes for SASS (cheers @brunowego)
|
||||
* Indentation reset to 4 spaces
|
||||
|
||||
## v1.0.25
|
||||
- Fixes for iOS volume controls (hidden)
|
||||
- Classnames for left/right controls changed
|
||||
|
||||
* Fixes for iOS volume controls (hidden)
|
||||
* Classnames for left/right controls changed
|
||||
|
||||
## v1.0.24
|
||||
- Added tooltip option to display labels as tooltips (fixes #50)
|
||||
|
||||
* Added tooltip option to display labels as tooltips (fixes #50)
|
||||
|
||||
## v1.0.23
|
||||
- Handling loading states in the UI (fixes #36)
|
||||
|
||||
* Handling loading states in the UI (fixes #36)
|
||||
|
||||
## v1.0.22
|
||||
- Added support() API method for checking mimetype support
|
||||
- Added source() API method for setting media source(s) (fixes #44)
|
||||
- Added poster() API method for setting poster source
|
||||
- Refactored captions logic for manual captions
|
||||
|
||||
* Added support() API method for checking mimetype support
|
||||
* Added source() API method for setting media source(s) (fixes #44)
|
||||
* Added poster() API method for setting poster source
|
||||
* Refactored captions logic for manual captions
|
||||
|
||||
## v1.0.21
|
||||
- Added an <input type="range"> for seeking to improve experience (and support dragging) (fixes #40, #42)
|
||||
- Icons for restart and captions improved (and some IDs changed) (fixes #49)
|
||||
|
||||
* Added an <input type="range"> for seeking to improve experience (and support dragging) (fixes #40, #42)
|
||||
* Icons for restart and captions improved (and some IDs changed) (fixes #49)
|
||||
|
||||
## v1.0.20
|
||||
- Default controls included (Fixes #45)
|
||||
- Volume changes on `input` as well as `change` (fixes #43)
|
||||
- Fix for undefined Play text
|
||||
- License changed to MIT
|
||||
|
||||
* Default controls included (Fixes #45)
|
||||
* Volume changes on `input` as well as `change` (fixes #43)
|
||||
* Fix for undefined Play text
|
||||
* License changed to MIT
|
||||
|
||||
## v1.0.19
|
||||
- Fixed firefox fullscreen issue (fixes #38)
|
||||
|
||||
* Fixed firefox fullscreen issue (fixes #38)
|
||||
|
||||
## v1.0.18
|
||||
- Added CDN references
|
||||
|
||||
* Added CDN references
|
||||
|
||||
## v1.0.17
|
||||
- SASS support added (thanks to @brunowego)
|
||||
- Docs completely separated to avoid any confusion
|
||||
- New gulp tasks (will add more documentation for this)
|
||||
|
||||
* SASS support added (thanks to @brunowego)
|
||||
* Docs completely separated to avoid any confusion
|
||||
* New gulp tasks (will add more documentation for this)
|
||||
|
||||
## v1.0.16
|
||||
- Aria label is now dynamic
|
||||
|
||||
* Aria label is now dynamic
|
||||
|
||||
## v1.0.15
|
||||
- Fix for seek time display in controls
|
||||
- More documentation for controls html
|
||||
|
||||
* Fix for seek time display in controls
|
||||
* More documentation for controls html
|
||||
|
||||
## v1.0.14
|
||||
- Minor change for bootstrap compatibility
|
||||
|
||||
* Minor change for bootstrap compatibility
|
||||
|
||||
## v1.0.13
|
||||
- Minor tweaks
|
||||
|
||||
* Minor tweaks
|
||||
|
||||
## v1.0.12
|
||||
- Handle native events (issue #34)
|
||||
|
||||
* Handle native events (issue #34)
|
||||
|
||||
## v1.0.11
|
||||
- Bug fixes for fullscreen mode
|
||||
|
||||
* Bug fixes for fullscreen mode
|
||||
|
||||
## v1.0.10
|
||||
- Bower includes src files now
|
||||
- Folder re-arrangement
|
||||
|
||||
* Bower includes src files now
|
||||
* Folder re-arrangement
|
||||
|
||||
## v1.0.9
|
||||
- Added buffer progress bar
|
||||
- Fixed Safari 8 caption track (it needs to be removed from the DOM like in Safari 7)
|
||||
- Added validation (it works or it doesn't basically) of the `html` option passed
|
||||
|
||||
* Added buffer progress bar
|
||||
* Fixed Safari 8 caption track (it needs to be removed from the DOM like in Safari 7)
|
||||
* Added validation (it works or it doesn't basically) of the `html` option passed
|
||||
|
||||
## v1.0.8
|
||||
- Bug fix
|
||||
|
||||
* Bug fix
|
||||
|
||||
## v1.0.7
|
||||
- Storing user selected volume in local storage
|
||||
|
||||
* Storing user selected volume in local storage
|
||||
|
||||
## v1.0.6
|
||||
- Fullscreen fallback for older browsers to use "full window"
|
||||
|
||||
* Fullscreen fallback for older browsers to use "full window"
|
||||
|
||||
## v1.0.5
|
||||
- More minor bug fixes and improvements
|
||||
|
||||
* More minor bug fixes and improvements
|
||||
|
||||
## v1.0.4
|
||||
- Fixed caption legibility issues
|
||||
|
||||
* Fixed caption legibility issues
|
||||
|
||||
## v1.0.3
|
||||
- Minor bug fixes
|
||||
|
||||
* Minor bug fixes
|
||||
|
||||
## v1.0.2
|
||||
- Added OGG to <audio> example for Firefox
|
||||
- Fixed IE11 fullscreen issues
|
||||
|
||||
* Added OGG to <audio> example for Firefox
|
||||
* Fixed IE11 fullscreen issues
|
||||
|
||||
## v1.0.1
|
||||
- Bug fixes for IE (as per usual)
|
||||
- Added CSS hooks for media type
|
||||
- Return instances of Plyr to the element
|
||||
|
||||
* Bug fixes for IE (as per usual)
|
||||
* Added CSS hooks for media type
|
||||
* Return instances of Plyr to the element
|
||||
|
||||
## v1.0.0
|
||||
- Initial release
|
||||
|
||||
* Initial release
|
||||
|
@ -37,6 +37,7 @@ You need to add several placeholders to your html template that are replaced whe
|
||||
|
||||
- `{id}` - the dynamically generated ID for the player (for form controls)
|
||||
- `{seektime}` - the seek time specified in options for fast forward and rewind
|
||||
- `{title}` - the title of your media, if specified
|
||||
|
||||
You can include only the controls you need when specifying custom html.
|
||||
|
||||
|
2
demo/dist/demo.css
vendored
4
demo/dist/demo.js
vendored
@ -1 +1,3 @@
|
||||
"document"in self&&("classList"in document.createElement("_")?!function(){"use strict";var e=document.createElement("_");if(e.classList.add("c1","c2"),!e.classList.contains("c2")){var t=function(e){var t=DOMTokenList.prototype[e];DOMTokenList.prototype[e]=function(e){var i,s=arguments.length;for(i=0;s>i;i++)e=arguments[i],t.call(this,e)}};t("add"),t("remove")}if(e.classList.toggle("c3",!1),e.classList.contains("c3")){var i=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(e,t){return 1 in arguments&&!this.contains(e)==!t?t:i.call(this,e)}}e=null}():!function(e){"use strict";if("Element"in e){var t="classList",i="prototype",s=e.Element[i],o=Object,n=String[i].trim||function(){return this.replace(/^\s+|\s+$/g,"")},r=Array[i].indexOf||function(e){for(var t=0,i=this.length;i>t;t++)if(t in this&&this[t]===e)return t;return-1},a=function(e,t){this.name=e,this.code=DOMException[e],this.message=t},c=function(e,t){if(""===t)throw new a("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(t))throw new a("INVALID_CHARACTER_ERR","String contains an invalid character");return r.call(e,t)},l=function(e){for(var t=n.call(e.getAttribute("class")||""),i=t?t.split(/\s+/):[],s=0,o=i.length;o>s;s++)this.push(i[s]);this._updateClassName=function(){e.setAttribute("class",this.toString())}},u=l[i]=[],d=function(){return new l(this)};if(a[i]=Error[i],u.item=function(e){return this[e]||null},u.contains=function(e){return e+="",-1!==c(this,e)},u.add=function(){var e,t=arguments,i=0,s=t.length,o=!1;do e=t[i]+"",-1===c(this,e)&&(this.push(e),o=!0);while(++i<s);o&&this._updateClassName()},u.remove=function(){var e,t,i=arguments,s=0,o=i.length,n=!1;do for(e=i[s]+"",t=c(this,e);-1!==t;)this.splice(t,1),n=!0,t=c(this,e);while(++s<o);n&&this._updateClassName()},u.toggle=function(e,t){e+="";var i=this.contains(e),s=i?t!==!0&&"remove":t!==!1&&"add";return s&&this[s](e),t===!0||t===!1?t:!i},u.toString=function(){return this.join(" ")},o.defineProperty){var p={get:d,enumerable:!0,configurable:!0};try{o.defineProperty(s,t,p)}catch(h){-2146823252===h.number&&(p.enumerable=!1,o.defineProperty(s,t,p))}}else o[i].__defineGetter__&&s.__defineGetter__(t,d)}}(self)),function(){function e(e,t,i){if(e)if(e.classList)e.classList[i?"add":"remove"](t);else{var s=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=s+(i?" "+t:"")}}function t(t,i){if(t in n&&(i||t!=r)&&(r.length||t!=n.video)){switch(t){case n.video:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"},{src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.webm",type:"video/webm"}],poster:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.en.vtt","default":!0}]});break;case n.audio:s.source({type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]});break;case n.youtube:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"bTqVqk7FSmY",type:"youtube"}]});break;case n.vimeo:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"143418951",type:"vimeo"}]})}r=t;for(var a=o.length-1;a>=0;a--)e(o[a].parentElement,"active",!1);e(document.querySelector('[data-source="'+t+'"]').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 s=i[0],o=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=o.length-1;c>=0;c--)o[c].addEventListener("click",function(){var e=this.getAttribute("data-source");t(e),a&&history.pushState({type:e},"","#"+e)});if(window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),a){var l=!r.length;l&&(r=n.video),r in n&&history.replaceState({type:r},"",l?"":"#"+r),r!==n.video&&t(r,!0)}}(),document.domain.indexOf("plyr.io")>-1&&(!function(e,t,i,s,o,n,r){e.GoogleAnalyticsObject=o,e[o]=e[o]||function(){(e[o].q=e[o].q||[]).push(arguments)},e[o].l=1*new Date,n=t.createElement(i),r=t.getElementsByTagName(i)[0],n.async=1,n.src=s,r.parentNode.insertBefore(n,r)}(window,document,"script","//www.google-analytics.com/analytics.js","ga"),ga("create","UA-40881672-11","auto"),ga("send","pageview"));
|
||||
!function(){"use strict";var e,t,o,i,a,r;document.addEventListener("DOMContentLoaded",function(){window.shr&&window.shr.setup({count:{classname:"button__count"}});document.addEventListener("focusout",function(e){e.target.classList.remove("tab-focus")}),document.addEventListener("keydown",function(e){9===e.keyCode&&window.setTimeout(function(){document.activeElement.classList.add("tab-focus")},0)});var e=new window.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:{tag:"https://go.aniview.com/api/adserver6/vast/?AV_PUBLISHERID=58c25bb0073ef448b1087ad6&AV_CHANNELID=5a0458dc28a06145e4519d21&AV_URL=127.0.0.1:3000&cb=1&AV_WIDTH=640&AV_HEIGHT=480"}});window.player=e;var t=document.querySelectorAll("[data-source]"),o={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},i=window.location.hash.replace("#",""),a=window.history&&window.history.pushState;function r(e,t,o){e&&e.classList[o?"add":"remove"](t)}function n(a,n){if(a in o&&(n||a!==i)&&(i.length||a!==o.video)){switch(a){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=a,Array.from(t).forEach(function(e){return r(e.parentElement,"active",!1)}),r(document.querySelector('[data-source="'+a+'"]'),"active",!0),Array.from(document.querySelectorAll(".plyr__cite")).forEach(function(e){e.setAttribute("hidden","")}),document.querySelector(".plyr__cite--"+a).removeAttribute("hidden")}}if(Array.from(t).forEach(function(e){e.addEventListener("click",function(){var t=e.getAttribute("data-source");n(t),a&&window.history.pushState({type:t},"","#"+t)})}),window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&n(e.state.type)}),a){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,a=t.createElement(o),r=t.getElementsByTagName(o)[0],a.async=1,a.src="//www.google-analytics.com/analytics.js",r.parentNode.insertBefore(a,r),window.ga("create","UA-40881672-11","auto"),window.ga("send","pageview"))}();
|
||||
|
||||
//# sourceMappingURL=demo.js.map
|
||||
|
1
demo/dist/demo.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 |
@ -1,18 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="error">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Doh. Looks like something went wrong.</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Docs styles -->
|
||||
<link rel="stylesheet" href="dist/docs.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Doh.</h1>
|
||||
<p>Looks like something went wrong.</p>
|
||||
<a href="http://plyr.io" class="btn btn--primary">Back to plyr.io</a>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Doh. Looks like something went wrong.</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Docs styles -->
|
||||
<link rel="stylesheet" href="dist/error.css">
|
||||
|
||||
<!-- Preload -->
|
||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
|
||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>Doh.</h1>
|
||||
<p>Looks like something went wrong.</p>
|
||||
<a href="javascript:history.back()" class="button">Go back</a>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
240
demo/index.html
@ -1,88 +1,184 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Plyr - A simple HTML5 media player</title>
|
||||
<meta name="description" content="A simple HTML5 media player with custom controls and WebVTT captions.">
|
||||
<meta name="author" content="Sam Potts">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" href="../dist/plyr.css">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title>
|
||||
<meta name="description" property="og:description" content="A simple HTML5 media player with custom controls and WebVTT captions.">
|
||||
<meta name="author" content="Sam Potts">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Docs styles -->
|
||||
<link rel="stylesheet" href="dist/demo.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Icons -->
|
||||
<link rel="icon" href="https://cdn.plyr.io/static/icons/favicon.ico">
|
||||
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png">
|
||||
|
||||
<!-- Opengraph -->
|
||||
<meta property="og:title" content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player">
|
||||
<meta property="og:site_name" content="Plyr">
|
||||
<meta property="og:url" content="https://plyr.io">
|
||||
<meta property="og:image" content="https://cdn.plyr.io/static/icons/1200x630.png">
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:site" content="@sam_potts">
|
||||
<meta name="twitter:creator" content="@sam_potts">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
|
||||
<!-- Docs styles -->
|
||||
<link rel="stylesheet" href="dist/demo.css">
|
||||
|
||||
<!-- Preload -->
|
||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
|
||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="grid">
|
||||
<header>
|
||||
<h1>Plyr</h1>
|
||||
<p>A simple, accessible HTML5 media player by <a href="https://twitter.com/sam_potts" target="_blank">@sam_potts</a> from <a href="https://twitter.com/selz" target="_blank">@selz</a></p>
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/selz/plyr" target="_blank" class="btn btn--large btn--primary" data-shr-network="github">
|
||||
<svg class="icon"><use xlink:href="#icon-github"/></svg>Download on GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://twitter.com/intent/tweet?text=A+simple+HTML5+media+player+with+custom+controls+and+WebVTT+captions.&url=http%3A%2F%2Fplyr.io&via=Sam_Potts" target="_blank" class="btn btn--large btn--twitter" data-shr-network="twitter">
|
||||
<svg class="icon"><use xlink:href="#icon-twitter"/></svg>Tweet
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<p>A simple, accessible and customisable media player for
|
||||
<button type="button" class="faux-link" data-source="video">
|
||||
<svg class="icon">
|
||||
<title>HTML5</title>
|
||||
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
||||
</svg>Video</button>,
|
||||
<button type="button" class="faux-link" data-source="audio">
|
||||
<svg class="icon">
|
||||
<title>HTML5</title>
|
||||
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
||||
</svg>Audio</button>,
|
||||
<button type="button" class="faux-link" data-source="youtube">
|
||||
<svg class="icon" role="presentation">
|
||||
<title>YouTube</title>
|
||||
<path d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
||||
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
||||
M6,11V5l5,3L6,11z"></path>
|
||||
</svg>YouTube</button> and
|
||||
<button type="button" class="faux-link" data-source="vimeo">
|
||||
<svg class="icon" role="presentation">
|
||||
<title>Vimeo</title>
|
||||
<path d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
||||
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
||||
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"></path>
|
||||
</svg>Vimeo</button>
|
||||
</p>
|
||||
|
||||
<p>Advertisement service provided by
|
||||
<a href="https://vi.ai" target="_blank">vi.ai</a>
|
||||
</p>
|
||||
|
||||
<div class="call-to-action">
|
||||
<span class="button--with-count">
|
||||
<a href="https://github.com/sampotts/plyr" target="_blank" class="button" data-shr-network="github">
|
||||
<svg class="icon" role="presentation">
|
||||
<title>GitHub</title>
|
||||
<path d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
|
||||
C5.9,15.9,6,15.6,6,15.4c0-0.2,0-0.7,0-1.4C3.8,14.5,3.3,13,3.3,13c-0.4-0.9-0.9-1.2-0.9-1.2c-0.7-0.5,0.1-0.5,0.1-0.5
|
||||
c0.8,0.1,1.2,0.8,1.2,0.8C4.4,13.4,5.6,13,6,12.8c0.1-0.5,0.3-0.9,0.5-1.1c-1.8-0.2-3.6-0.9-3.6-4c0-0.9,0.3-1.6,0.8-2.1
|
||||
c-0.1-0.2-0.4-1,0.1-2.1c0,0,0.7-0.2,2.2,0.8c0.6-0.2,1.3-0.3,2-0.3c0.7,0,1.4,0.1,2,0.3c1.5-1,2.2-0.8,2.2-0.8
|
||||
c0.4,1.1,0.2,1.9,0.1,2.1c0.5,0.6,0.8,1.3,0.8,2.1c0,3.1-1.9,3.7-3.7,3.9C9.7,12,10,12.5,10,13.2c0,1.1,0,1.9,0,2.2
|
||||
c0,0.2,0.1,0.5,0.6,0.4c3.2-1.1,5.5-4.1,5.5-7.6C16,3.8,12.4,0.2,8,0.2z"></path>
|
||||
</svg>
|
||||
Download on GitHub
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main role="main" id="main">
|
||||
<nav class="btn__bar">
|
||||
<ul>
|
||||
<li class="active">
|
||||
<button type="button" class="btn" data-source="video">Video</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="btn" data-source="audio">Audio</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="btn btn--youtube" data-source="youtube"><svg class="icon"><use xlink:href="#icon-youtube"/></svg>YouTube</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="btn btn--vimeo" data-source="vimeo"><svg class="icon"><use xlink:href="#icon-vimeo"/></svg>Vimeo</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<section>
|
||||
<video poster="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg" controls crossorigin>
|
||||
<!-- Video files -->
|
||||
<source src="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4" type="video/mp4">
|
||||
<source src="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.webm" type="video/webm">
|
||||
<main>
|
||||
<video controls crossorigin playsinline poster="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
|
||||
<!-- Video files -->
|
||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" type="video/mp4">
|
||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.webm" type="video/webm">
|
||||
|
||||
<!-- Text track file -->
|
||||
<track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.en.vtt" default>
|
||||
<!-- Text track file -->
|
||||
<track kind="captions" label="English" srclang="en" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
|
||||
default>
|
||||
<track kind="captions" label="Français" srclang="fr" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
|
||||
|
||||
<!-- Fallback for browsers that don't support the <video> element -->
|
||||
<a href="https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4" download>Download</a>
|
||||
</video>
|
||||
<!-- Fallback for browsers that don't support the <video> element -->
|
||||
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4" download>Download</a>
|
||||
</video>
|
||||
|
||||
<ul>
|
||||
<li class="plyr__cite plyr__cite--video"><small><a href="http://viewfromabluemoon.com/" target="_blank">View From A Blue Moon</a> © Brainfarm</small></li>
|
||||
<li class="plyr__cite plyr__cite--audio"><small><a href="http://www.kishibashi.com/" target="_blank">Kishi Bashi – “It All Began With A Burst”</a> © Kishi Bashi</small></li>
|
||||
<li class="plyr__cite plyr__cite--youtube"><small><a href="https://www.youtube.com/watch?v=bTqVqk7FSmY" target="_blank">View From A Blue Moon</a> on <span class="color--youtube"><svg class="icon"><use xlink:href="#icon-youtube"/></svg>YouTube</span></small>
|
||||
<li 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>
|
||||
</ul>
|
||||
</section>
|
||||
<ul>
|
||||
<li class="plyr__cite plyr__cite--video" hidden>
|
||||
<small>
|
||||
<svg class="icon">
|
||||
<title>HTML5</title>
|
||||
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
||||
</svg>
|
||||
<a href="http://viewfromabluemoon.com/" target="_blank">View From A Blue Moon</a> © 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>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Plyr core script -->
|
||||
<script src="../dist/plyr.js"></script>
|
||||
<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>
|
||||
|
||||
<!-- Docs script -->
|
||||
<script src="dist/demo.js"></script>
|
||||
<!-- Polyfills -->
|
||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Array.prototype.includes,CustomEvent,fetch"></script>
|
||||
|
||||
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
|
||||
<script src="https://cdn.rangetouch.com/0.0.9/rangetouch.js" async></script>
|
||||
<!-- Plyr core script -->
|
||||
<script src="../dist/plyr.js"></script>
|
||||
|
||||
<!-- Sharing libary (https://shr.one) -->
|
||||
<script src="https://cdn.shr.one/0.1.9/shr.js"></script>
|
||||
<script>if(window.shr) { window.shr.setup({ count: { classname: 'btn__count' } }); }</script>
|
||||
</body>
|
||||
</html>
|
||||
<!-- Sharing libary (https://shr.one) -->
|
||||
<script src="https://cdn.shr.one/1.0.1/shr.js"></script>
|
||||
|
||||
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
|
||||
<script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async></script>
|
||||
|
||||
<!-- Docs script -->
|
||||
<script src="dist/demo.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
183
demo/index.local.html
Normal file
@ -0,0 +1,183 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player</title>
|
||||
<meta name="description" property="og:description" content="A simple HTML5 media player with custom controls and WebVTT captions.">
|
||||
<meta name="author" content="Sam Potts">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" href="https://cdn.plyr.io/static/icons/favicon.ico">
|
||||
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="https://cdn.plyr.io/static/icons/16x16.png" sizes="16x16">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.plyr.io/static/icons/180x180.png">
|
||||
|
||||
<!-- Opengraph -->
|
||||
<meta property="og:title" content="Plyr - A simple, customizable HTML5 Video, Audio, YouTube and Vimeo player">
|
||||
<meta property="og:site_name" content="Plyr">
|
||||
<meta property="og:url" content="https://plyr.io">
|
||||
<meta property="og:image" content="https://cdn.plyr.io/static/icons/1200x630.png">
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:site" content="@sam_potts">
|
||||
<meta name="twitter:creator" content="@sam_potts">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
|
||||
<!-- Docs styles -->
|
||||
<link rel="stylesheet" href="dist/demo.css">
|
||||
|
||||
<!-- Preload -->
|
||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-medium.woff2">
|
||||
<link rel="preload" as="font" crossorigin type="font/woff2" href="https://cdn.plyr.io/static/fonts/gordita-bold.woff2">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="grid">
|
||||
<header>
|
||||
<h1>Plyr</h1>
|
||||
<p>A simple, accessible and customisable media player for
|
||||
<button type="button" class="faux-link" data-source="video">
|
||||
<svg class="icon">
|
||||
<title>HTML5</title>
|
||||
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
||||
</svg>Video</button>,
|
||||
<button type="button" class="faux-link" data-source="audio">
|
||||
<svg class="icon">
|
||||
<title>HTML5</title>
|
||||
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
||||
</svg>Audio</button>,
|
||||
<button type="button" class="faux-link" data-source="youtube">
|
||||
<svg class="icon" role="presentation">
|
||||
<title>YouTube</title>
|
||||
<path d="M15.8,4.8c-0.2-1.3-0.8-2.2-2.2-2.4C11.4,2,8,2,8,2S4.6,2,2.4,2.4C1,2.6,0.3,3.5,0.2,4.8C0,6.1,0,8,0,8
|
||||
s0,1.9,0.2,3.2c0.2,1.3,0.8,2.2,2.2,2.4C4.6,14,8,14,8,14s3.4,0,5.6-0.4c1.4-0.3,2-1.1,2.2-2.4C16,9.9,16,8,16,8S16,6.1,15.8,4.8z
|
||||
M6,11V5l5,3L6,11z"></path>
|
||||
</svg>YouTube</button> and
|
||||
<button type="button" class="faux-link" data-source="vimeo">
|
||||
<svg class="icon" role="presentation">
|
||||
<title>Vimeo</title>
|
||||
<path d="M16,4.3c-0.1,1.6-1.2,3.7-3.3,6.4c-2.2,2.8-4,4.2-5.5,4.2c-0.9,0-1.7-0.9-2.4-2.6C4,9.9,3.4,5,2,5
|
||||
C1.9,5,1.5,5.3,0.8,5.8L0,4.8c0.8-0.7,3.5-3.4,4.7-3.5C5.9,1.2,6.7,2,7,3.8c0.3,2,0.8,6.1,1.8,6.1c0.9,0,2.5-3.4,2.6-4
|
||||
c0.1-0.9-0.3-1.9-2.3-1.1c0.8-2.6,2.3-3.8,4.5-3.8C15.3,1.1,16.1,2.2,16,4.3z"></path>
|
||||
</svg>Vimeo</button>
|
||||
</p>
|
||||
|
||||
<!--<p>Monetization options provided by
|
||||
<a href="https://vi.ai" target="_blank">vi.ai</a>
|
||||
</p>-->
|
||||
|
||||
<div class="call-to-action">
|
||||
<span class="button--with-count">
|
||||
<a href="https://github.com/sampotts/plyr" target="_blank" class="button" data-shr-network="github">
|
||||
<svg class="icon" role="presentation">
|
||||
<title>GitHub</title>
|
||||
<path d="M8,0.2c-4.4,0-8,3.6-8,8c0,3.5,2.3,6.5,5.5,7.6
|
||||
C5.9,15.9,6,15.6,6,15.4c0-0.2,0-0.7,0-1.4C3.8,14.5,3.3,13,3.3,13c-0.4-0.9-0.9-1.2-0.9-1.2c-0.7-0.5,0.1-0.5,0.1-0.5
|
||||
c0.8,0.1,1.2,0.8,1.2,0.8C4.4,13.4,5.6,13,6,12.8c0.1-0.5,0.3-0.9,0.5-1.1c-1.8-0.2-3.6-0.9-3.6-4c0-0.9,0.3-1.6,0.8-2.1
|
||||
c-0.1-0.2-0.4-1,0.1-2.1c0,0,0.7-0.2,2.2,0.8c0.6-0.2,1.3-0.3,2-0.3c0.7,0,1.4,0.1,2,0.3c1.5-1,2.2-0.8,2.2-0.8
|
||||
c0.4,1.1,0.2,1.9,0.1,2.1c0.5,0.6,0.8,1.3,0.8,2.1c0,3.1-1.9,3.7-3.7,3.9C9.7,12,10,12.5,10,13.2c0,1.1,0,1.9,0,2.2
|
||||
c0,0.2,0.1,0.5,0.6,0.4c3.2-1.1,5.5-4.1,5.5-7.6C16,3.8,12.4,0.2,8,0.2z"></path>
|
||||
</svg>
|
||||
Download on GitHub
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<video controls crossorigin playsinline poster="media/View_From_A_Blue_Moon_Trailer-HD.jpg" id="player">
|
||||
<!-- Video files -->
|
||||
<source src="media/View_From_A_Blue_Moon_Trailer-HD.mp4" type="video/mp4">
|
||||
<!--<source src="media/View_From_A_Blue_Moon_Trailer-UHD.mp4" type="video/mp4">-->
|
||||
|
||||
<!-- Text track file -->
|
||||
<track kind="captions" label="English" srclang="en" src="media/View_From_A_Blue_Moon_Trailer-HD.en.vtt" default>
|
||||
<track kind="captions" label="Français" srclang="fr" src="media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
|
||||
|
||||
<!-- Fallback for browsers that don't support the <video> element -->
|
||||
<a href="media/View_From_A_Blue_Moon_Trailer-HD.mp4" download>Download</a>
|
||||
</video>
|
||||
|
||||
<ul>
|
||||
<li class="plyr__cite plyr__cite--video" hidden>
|
||||
<small>
|
||||
<svg class="icon">
|
||||
<title>HTML5</title>
|
||||
<path d="M14.738.326C14.548.118 14.28 0 14 0H2c-.28 0-.55.118-.738.326S.98.81 1.004 1.09l1 11c.03.317.208.603.48.767l5 3c.16.095.338.143.516.143s.356-.048.515-.143l5-3c.273-.164.452-.45.48-.767l1-11c.026-.28-.067-.557-.257-.764zM12 4H6v2h6v5.72l-4 1.334-4-1.333V9h2v1.28l2 .666 2-.667V8H4V2h8v2z"></path>
|
||||
</svg>
|
||||
<a href="http://viewfromabluemoon.com/" target="_blank">View From A Blue Moon</a> © 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>
|
||||
</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,fetch"></script>
|
||||
|
||||
<!-- Plyr core script -->
|
||||
<script src="../dist/plyr.js"></script>
|
||||
|
||||
<!-- Sharing libary (https://shr.one) -->
|
||||
<script src="https://cdn.shr.one/1.0.1/shr.js"></script>
|
||||
|
||||
<!-- Rangetouch to fix <input type="range"> on touch devices (see https://rangetouch.com) -->
|
||||
<script src="https://cdn.rangetouch.com/1.0.1/rangetouch.js" async></script>
|
||||
|
||||
<!-- Docs script -->
|
||||
<script src="dist/demo.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
29
demo/media/View_From_A_Blue_Moon_Trailer-HD.en.vtt
Normal file
@ -0,0 +1,29 @@
|
||||
WEBVTT FILE
|
||||
|
||||
1
|
||||
00:00:09.500 --> 00:00:12.000
|
||||
The ocean floor rises 5 miles to the shores
|
||||
|
||||
2
|
||||
00:00:12.001 --> 00:00:16.500
|
||||
of what people call, the seven mile miracle
|
||||
|
||||
3
|
||||
00:00:25.500 --> 00:00:28.000
|
||||
What would it be like to be born on this island?
|
||||
|
||||
4
|
||||
00:00:32.500 --> 00:00:34.500
|
||||
To grow up on these shores
|
||||
|
||||
5
|
||||
00:00:37.500 --> 00:00:40.000
|
||||
To witness this water, every day
|
||||
|
||||
6
|
||||
00:00:43.500 --> 00:00:46.000
|
||||
You're about to meet someone, who did
|
||||
|
||||
7
|
||||
00:02:45.500 --> 00:02:49.000
|
||||
This is a film about John John Florence
|
29
demo/media/View_From_A_Blue_Moon_Trailer-HD.fr.vtt
Normal file
@ -0,0 +1,29 @@
|
||||
WEBVTT FILE
|
||||
|
||||
1
|
||||
00:00:09.500 --> 00:00:12.000
|
||||
Le fond de l'océan monte 5 miles des rives
|
||||
|
||||
2
|
||||
00:00:12.001 --> 00:00:16.500
|
||||
de ce que les gens appellent le miracle de sept mile
|
||||
|
||||
3
|
||||
00:00:25.500 --> 00:00:28.000
|
||||
Que serait-il d'être né sur cette île?
|
||||
|
||||
4
|
||||
00:00:32.500 --> 00:00:34.500
|
||||
Pour grandir sur ces rivages
|
||||
|
||||
5
|
||||
00:00:37.500 --> 00:00:40.000
|
||||
Pour assister à cette eau, tous les jours
|
||||
|
||||
6
|
||||
00:00:43.500 --> 00:00:46.000
|
||||
Vous êtes sur le point de rencontrer quelqu'un, qui ne
|
||||
|
||||
7
|
||||
00:02:45.500 --> 00:02:49.000
|
||||
Ceci est un film sur John John Florence
|
BIN
demo/media/View_From_A_Blue_Moon_Trailer-HD.jpg
Normal file
After Width: | Height: | Size: 154 KiB |
249
demo/src/js/demo.js
Normal file
@ -0,0 +1,249 @@
|
||||
// ==========================================================================
|
||||
// Plyr.io demo
|
||||
// This code is purely for the https://plyr.io website
|
||||
// Please see readme.md in the root or github.com/sampotts/plyr
|
||||
// ==========================================================================
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (window.shr) {
|
||||
window.shr.setup({
|
||||
count: {
|
||||
classname: 'button__count',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Setup tab focus
|
||||
const tabClassName = 'tab-focus';
|
||||
|
||||
// Remove class on blur
|
||||
document.addEventListener('focusout', event => {
|
||||
event.target.classList.remove(tabClassName);
|
||||
});
|
||||
|
||||
// Add classname to tabbed elements
|
||||
document.addEventListener('keydown', event => {
|
||||
if (event.keyCode !== 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delay the adding of classname until the focus has changed
|
||||
// This event fires before the focusin event
|
||||
window.setTimeout(() => {
|
||||
document.activeElement.classList.add(tabClassName);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// Setup the player
|
||||
const player = new window.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: {
|
||||
tag:
|
||||
'https://go.aniview.com/api/adserver6/vast/?AV_PUBLISHERID=58c25bb0073ef448b1087ad6&AV_CHANNELID=5a0458dc28a06145e4519d21&AV_URL=127.0.0.1:3000&cb=1&AV_WIDTH=640&AV_HEIGHT=480',
|
||||
// Test tags
|
||||
// tag: 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=',
|
||||
// tag: 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator=',
|
||||
},
|
||||
});
|
||||
|
||||
// Expose for testing
|
||||
window.player = player;
|
||||
|
||||
// Setup type toggle
|
||||
const buttons = document.querySelectorAll('[data-source]');
|
||||
const types = {
|
||||
video: 'video',
|
||||
audio: 'audio',
|
||||
youtube: 'youtube',
|
||||
vimeo: 'vimeo',
|
||||
};
|
||||
let currentType = window.location.hash.replace('#', '');
|
||||
const historySupport = window.history && window.history.pushState;
|
||||
|
||||
// Toggle class on an element
|
||||
function toggleClass(element, className, state) {
|
||||
if (element) {
|
||||
element.classList[state ? 'add' : 'remove'](className);
|
||||
}
|
||||
}
|
||||
|
||||
// Set a new source
|
||||
function newSource(type, init) {
|
||||
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
|
||||
if (!(type in types) || (!init && type === currentType) || (!currentType.length && type === types.video)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case types.video:
|
||||
player.source = {
|
||||
type: 'video',
|
||||
title: 'View From A Blue Moon',
|
||||
sources: [{
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4',
|
||||
type: 'video/mp4',
|
||||
}],
|
||||
poster: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
||||
tracks: [
|
||||
{
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
srclang: 'en',
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
kind: 'captions',
|
||||
label: 'French',
|
||||
srclang: 'fr',
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case types.audio:
|
||||
player.source = {
|
||||
type: 'audio',
|
||||
title: 'Kishi Bashi – “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', '//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,185 +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.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4',
|
||||
type: 'video/mp4'
|
||||
},
|
||||
{
|
||||
src: 'https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.webm',
|
||||
type: 'video/webm'
|
||||
}],
|
||||
poster: 'https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg',
|
||||
tracks: [{
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
srclang:'en',
|
||||
src: 'https://cdn.selz.com/plyr/1.5/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.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3',
|
||||
type: 'audio/mp3'
|
||||
},
|
||||
{
|
||||
src: 'https://cdn.selz.com/plyr/1.5/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: '143418951',
|
||||
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("//cdn.plyr.io/fonts/avenir-medium.woff2") format("woff2"),
|
||||
url("//cdn.plyr.io/fonts/avenir-medium.woff") format("woff");
|
||||
font-style: normal;
|
||||
font-weight: @font-weight-base;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Avenir";
|
||||
src: url("//cdn.plyr.io/fonts/avenir-bold.woff2") format("woff2"),
|
||||
url("//cdn.plyr.io/fonts/avenir-bold.woff") format("woff");
|
||||
font-style: normal;
|
||||
font-weight: @font-weight-bold;
|
||||
}
|
@ -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;
|
41
demo/src/sass/bundles/demo.scss
Normal file
@ -0,0 +1,41 @@
|
||||
// ==========================================================================
|
||||
// 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/navigation';
|
||||
@import '../components/players';
|
||||
|
||||
// Plyr
|
||||
@import '../../../../src/sass/plyr';
|
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: #fff;
|
||||
border: 0;
|
||||
border-radius: $border-radius-base;
|
||||
box-shadow: 0 1px 1px rgba(#000, 0.1);
|
||||
color: $gray;
|
||||
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: #fff;
|
||||
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;
|
||||
}
|
||||
}
|
@ -4,23 +4,20 @@
|
||||
|
||||
// Base size icon styles
|
||||
.icon {
|
||||
fill: currentColor;
|
||||
width: @icon-size;
|
||||
height: @icon-size;
|
||||
vertical-align: -3px;
|
||||
fill: currentColor;
|
||||
height: $icon-size;
|
||||
vertical-align: -3px;
|
||||
width: $icon-size;
|
||||
}
|
||||
|
||||
// Within elements
|
||||
a svg,
|
||||
button svg,
|
||||
label svg {
|
||||
pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
a .icon,
|
||||
.btn .icon {
|
||||
margin-right: (@padding-base / 2);
|
||||
}
|
||||
.btn:not(.btn-large) .icon {
|
||||
width: (@icon-size - 2);
|
||||
height: (@icon-size - 2);
|
||||
margin-right: floor($spacing-base / 3);
|
||||
}
|
45
demo/src/sass/components/links.scss
Normal file
@ -0,0 +1,45 @@
|
||||
// ==========================================================================
|
||||
// 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();
|
||||
}
|
||||
}
|
11
demo/src/sass/components/lists.scss
Normal file
@ -0,0 +1,11 @@
|
||||
// ==========================================================================
|
||||
// Lists
|
||||
// ==========================================================================
|
||||
|
||||
// Lists
|
||||
ul,
|
||||
li {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
9
demo/src/sass/components/navigation.scss
Normal file
@ -0,0 +1,9 @@
|
||||
// ==========================================================================
|
||||
// Navigation
|
||||
// ==========================================================================
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: $spacing-base;
|
||||
}
|
@ -10,31 +10,34 @@ video {
|
||||
|
||||
// Example players
|
||||
.plyr {
|
||||
margin: 0 auto;
|
||||
border-radius: @border-radius-large;
|
||||
}
|
||||
.plyr--audio {
|
||||
max-width: @example-width-audio;
|
||||
border-radius: $border-radius-base;
|
||||
box-shadow: 0 2px 5px rgba(#000, 0.2);
|
||||
margin: $spacing-base auto;
|
||||
|
||||
&.plyr--audio {
|
||||
max-width: 480px;
|
||||
}
|
||||
}
|
||||
|
||||
.plyr__video-wrapper::after {
|
||||
content: "";
|
||||
border: 1px solid rgba(#000, 0.15);
|
||||
border-radius: inherit;
|
||||
bottom: 0;
|
||||
content: '';
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 1px solid fade(#000, 15%);
|
||||
border-radius: inherit;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
// Style full supported player
|
||||
.plyr__cite {
|
||||
display: none;
|
||||
margin-top: @padding-base;
|
||||
margin-top: $spacing-base;
|
||||
|
||||
.icon {
|
||||
margin-right: (@padding-base / 4);
|
||||
margin-right: ceil($spacing-base / 6);
|
||||
}
|
||||
}
|
||||
|
63
demo/src/sass/layout/core.scss
Normal file
@ -0,0 +1,63 @@
|
||||
// ==========================================================================
|
||||
// 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;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
aside {
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
color: $gray;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
padding: ($spacing-base * 0.75);
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-shadow: none;
|
||||
width: 100%;
|
||||
|
||||
.icon {
|
||||
fill: $color-twitter;
|
||||
margin-right: ($spacing-base / 2);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $color-twitter;
|
||||
|
||||
&.tab-focus {
|
||||
@include tab-focus($color-twitter);
|
||||
}
|
||||
}
|
||||
}
|
@ -7,13 +7,24 @@ html.error,
|
||||
.error body {
|
||||
height: 100%;
|
||||
}
|
||||
.error body {
|
||||
width: 100%;
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
|
||||
html.error {
|
||||
background: $page-background;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
.error main {
|
||||
display: table-cell;
|
||||
|
||||
.error body {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.error main {
|
||||
padding: $spacing-base;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
||||
p {
|
||||
@include font-size($font-size-large);
|
||||
}
|
||||
}
|
19
demo/src/sass/layout/grid.scss
Normal file
@ -0,0 +1,19 @@
|
||||
// ==========================================================================
|
||||
// Super basic grid
|
||||
// ==========================================================================
|
||||
|
||||
.grid {
|
||||
margin: 0 auto;
|
||||
padding: $spacing-base;
|
||||
|
||||
@media only screen and (min-width: $screen-md) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
max-width: $container-max-width;
|
||||
width: 100%;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,11 @@
|
||||
// ==========================================================================
|
||||
|
||||
// Fade
|
||||
@keyframes fade-in {
|
||||
0% { opacity: 0 }
|
||||
100% { opacity: 1 }
|
||||
}
|
||||
@keyframes fadein {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
45
demo/src/sass/lib/fontface.scss
Normal file
@ -0,0 +1,45 @@
|
||||
// ==========================================================================
|
||||
// Fonts
|
||||
// ==========================================================================
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'Gordita';
|
||||
font-style: normal;
|
||||
font-weight: $font-weight-light;
|
||||
src: url('https://cdn.plyr.io/static/fonts/gordita-light.woff2') format('woff2'), url('https://cdn.plyr.io/static/fonts/gordita-light.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'Gordita';
|
||||
font-style: normal;
|
||||
font-weight: $font-weight-regular;
|
||||
src: url('https://cdn.plyr.io/static/fonts/gordita-regular.woff2') format('woff2'),
|
||||
url('https://cdn.plyr.io/static/fonts/gordita-regular.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'Gordita';
|
||||
font-style: normal;
|
||||
font-weight: $font-weight-medium;
|
||||
src: url('https://cdn.plyr.io/static/fonts/gordita-medium.woff2') format('woff2'),
|
||||
url('https://cdn.plyr.io/static/fonts/gordita-medium.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'Gordita';
|
||||
font-style: normal;
|
||||
font-weight: $font-weight-bold;
|
||||
src: url('https://cdn.plyr.io/static/fonts/gordita-bold.woff2') format('woff2'), url('https://cdn.plyr.io/static/fonts/gordita-bold.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: 'Gordita';
|
||||
font-style: normal;
|
||||
font-weight: $font-weight-black;
|
||||
src: url('https://cdn.plyr.io/static/fonts/gordita-black.woff2') format('woff2'), url('https://cdn.plyr.io/static/fonts/gordita-black.woff') format('woff');
|
||||
}
|
54
demo/src/sass/lib/mixins.scss
Normal file
@ -0,0 +1,54 @@
|
||||
// ==========================================================================
|
||||
// Mixins
|
||||
// ==========================================================================
|
||||
|
||||
// Convert a <button> into an <a>
|
||||
// ---------------------------------------
|
||||
@mixin cancel-button-styles() {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
line-height: $line-height-base;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
text-align: inherit;
|
||||
text-shadow: inherit;
|
||||
-moz-user-select: text; // stylelint-disable-line
|
||||
vertical-align: baseline;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
// Nicer focus styles
|
||||
// ---------------------------------------
|
||||
@mixin tab-focus($color: $tab-focus-default-color) {
|
||||
box-shadow: 0 0 0 3px rgba($color, 0.35);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
// Use rems for font sizing
|
||||
// Leave <body> at 100%/16px
|
||||
// ---------------------------------------
|
||||
@function calculate-rem($size) {
|
||||
$rem: $size / 16;
|
||||
@return #{$rem}rem;
|
||||
}
|
||||
|
||||
@mixin font-size($size: 16) {
|
||||
font-size: $size * 1px; // Fallback in px
|
||||
font-size: calculate-rem($size);
|
||||
}
|
||||
|
||||
// Font smoothing
|
||||
// ---------------------------------------
|
||||
@mixin font-smoothing($enabled: true) {
|
||||
@if $enabled {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
} @else {
|
||||
-moz-osx-font-smoothing: auto;
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
}
|
||||
}
|
450
demo/src/sass/lib/normalize.scss
vendored
Normal file
@ -0,0 +1,450 @@
|
||||
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in
|
||||
* IE on Windows Phone and in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
footer,
|
||||
header,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
* 1. Add the correct display in IE.
|
||||
*/
|
||||
|
||||
figcaption,
|
||||
figure,
|
||||
main {
|
||||
/* 1 */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct margin in IE 8.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Remove the gray background on active links in IE 10.
|
||||
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent; /* 1 */
|
||||
-webkit-text-decoration-skip: objects; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57- and Firefox 39-.
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font style in Android 4.3-.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct background and color in IE 9-.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background-color: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
audio,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in iOS 4-7.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10-.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the overflow in IE.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers (opinionated).
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
/* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
|
||||
* controls in Android 4.
|
||||
* 2. Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
html [type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type='button']::-moz-focus-inner,
|
||||
[type='reset']::-moz-focus-inner,
|
||||
[type='submit']::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type='button']:-moz-focusring,
|
||||
[type='reset']:-moz-focusring,
|
||||
[type='submit']:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct display in IE 9-.
|
||||
* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10-.
|
||||
* 2. Remove the padding in IE 10-.
|
||||
*/
|
||||
|
||||
[type='checkbox'],
|
||||
[type='radio'] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type='number']::-webkit-inner-spin-button,
|
||||
[type='number']::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type='search']::-webkit-search-cancel-button,
|
||||
[type='search']::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in IE 9-.
|
||||
* 1. Add the correct display in Edge, IE, and Firefox.
|
||||
*/
|
||||
|
||||
details,
|
||||
menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Scripting
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
canvas {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hidden
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10-.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
11
demo/src/sass/lib/reset.scss
Normal file
@ -0,0 +1,11 @@
|
||||
// ==========================================================================
|
||||
// Resets
|
||||
// ==========================================================================
|
||||
|
||||
// BORDER-BOX ALL THE THINGS!
|
||||
// http://paulirish.com/2012/box-sizing-border-box-ftw/
|
||||
*,
|
||||
*::after,
|
||||
*::before {
|
||||
box-sizing: border-box;
|
||||
}
|
6
demo/src/sass/settings/breakpoints.scss
Normal file
@ -0,0 +1,6 @@
|
||||
// ==========================================================================
|
||||
// Breakpoints
|
||||
// ==========================================================================
|
||||
|
||||
$screen-sm: 480px;
|
||||
$screen-md: 768px;
|
28
demo/src/sass/settings/colors.scss
Normal file
@ -0,0 +1,28 @@
|
||||
// ==========================================================================
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
}
|
11
demo/src/sass/utilities/color.scss
Normal file
@ -0,0 +1,11 @@
|
||||
// ==========================================================================
|
||||
// Color
|
||||
// ==========================================================================
|
||||
|
||||
.color--vimeo {
|
||||
color: $color-vimeo;
|
||||
}
|
||||
|
||||
.color--youtube {
|
||||
color: $color-youtube;
|
||||
}
|
7
demo/src/sass/utilities/hidden.scss
Normal file
@ -0,0 +1,7 @@
|
||||
// ==========================================================================
|
||||
// Hidden
|
||||
// ==========================================================================
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
@ -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
5
dist/plyr.js
vendored
1
dist/plyr.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 |
607
gulpfile.js
@ -1,337 +1,390 @@
|
||||
// ==========================================================================
|
||||
// Gulp build script
|
||||
// ==========================================================================
|
||||
/*global require, __dirname,Buffer*/
|
||||
/*jshint -W079 */
|
||||
/* global require, __dirname */
|
||||
/* eslint no-console: "off" */
|
||||
|
||||
var fs = require("fs"),
|
||||
path = require("path"),
|
||||
gulp = require("gulp"),
|
||||
gutil = require("gulp-util"),
|
||||
concat = require("gulp-concat"),
|
||||
uglify = require("gulp-uglify"),
|
||||
less = require("gulp-less"),
|
||||
sass = require("gulp-sass"),
|
||||
cleanCSS = require("gulp-clean-css"),
|
||||
run = require("run-sequence"),
|
||||
prefix = require("gulp-autoprefixer"),
|
||||
svgstore = require("gulp-svgstore"),
|
||||
svgmin = require("gulp-svgmin"),
|
||||
rename = require("gulp-rename"),
|
||||
s3 = require("gulp-s3"),
|
||||
replace = require("gulp-replace"),
|
||||
open = require("gulp-open"),
|
||||
size = require("gulp-size"),
|
||||
through = require("through2");
|
||||
const del = require('del');
|
||||
const path = require('path');
|
||||
const gulp = require('gulp');
|
||||
const gutil = require('gulp-util');
|
||||
const concat = require('gulp-concat');
|
||||
const sass = require('gulp-sass');
|
||||
const cleancss = require('gulp-clean-css');
|
||||
const run = require('run-sequence');
|
||||
const prefix = require('gulp-autoprefixer');
|
||||
const gitbranch = require('git-branch');
|
||||
const svgstore = require('gulp-svgstore');
|
||||
const svgmin = require('gulp-svgmin');
|
||||
const rename = require('gulp-rename');
|
||||
const s3 = require('gulp-s3');
|
||||
const replace = require('gulp-replace');
|
||||
const open = require('gulp-open');
|
||||
const size = require('gulp-size');
|
||||
const rollup = require('gulp-better-rollup');
|
||||
const babel = require('rollup-plugin-babel');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const uglify = require('rollup-plugin-uglify');
|
||||
const { minify } = require('uglify-es');
|
||||
const commonjs = require('rollup-plugin-commonjs');
|
||||
const resolve = require('rollup-plugin-node-resolve');
|
||||
|
||||
var root = __dirname,
|
||||
paths = {
|
||||
const bundles = require('./bundles.json');
|
||||
const pkg = require('./package.json');
|
||||
|
||||
// Get AWS config
|
||||
let aws = {};
|
||||
try {
|
||||
aws = require('./aws.json'); //eslint-disable-line
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
// Paths
|
||||
const root = __dirname;
|
||||
const paths = {
|
||||
plyr: {
|
||||
// Source paths
|
||||
src: {
|
||||
less: path.join(root, "src/less/**/*"),
|
||||
scss: path.join(root, "src/scss/**/*"),
|
||||
js: path.join(root, "src/js/**/*"),
|
||||
sprite: path.join(root, "src/sprite/*.svg")
|
||||
sass: path.join(root, 'src/sass/**/*.scss'),
|
||||
js: path.join(root, 'src/js/**/*'),
|
||||
sprite: path.join(root, 'src/sprite/*.svg'),
|
||||
},
|
||||
|
||||
// Output paths
|
||||
output: path.join(root, "dist/")
|
||||
output: path.join(root, 'dist/'),
|
||||
},
|
||||
demo: {
|
||||
// Source paths
|
||||
src: {
|
||||
less: path.join(root, "demo/src/less/**/*"),
|
||||
js: path.join(root, "demo/src/js/**/*"),
|
||||
sprite: path.join(root, "demo/src/sprite/**/*")
|
||||
sass: path.join(root, 'demo/src/sass/**/*.scss'),
|
||||
js: path.join(root, 'demo/src/js/**/*'),
|
||||
},
|
||||
|
||||
// Output paths
|
||||
output: path.join(root, "demo/dist/"),
|
||||
output: path.join(root, 'demo/dist/'),
|
||||
|
||||
// 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/**'),
|
||||
path.join(root, 'demo/dist/**'),
|
||||
],
|
||||
};
|
||||
|
||||
// Task arrays
|
||||
tasks = {
|
||||
less: [],
|
||||
scss: [],
|
||||
js: [],
|
||||
sprite: []
|
||||
},
|
||||
const tasks = {
|
||||
sass: [],
|
||||
js: [],
|
||||
sprite: [],
|
||||
clean: ['clean'],
|
||||
};
|
||||
|
||||
// Fetch bundles from JSON
|
||||
bundles = loadJSON(path.join(root, "bundles.json")),
|
||||
package = loadJSON(path.join(root, "package.json"));
|
||||
// Size plugin
|
||||
const sizeOptions = { showFiles: true, gzip: true };
|
||||
|
||||
// Load json
|
||||
function loadJSON(path) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(path));
|
||||
}
|
||||
catch(err) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
// Browserlist
|
||||
const browsers = ['> 1%'];
|
||||
|
||||
// 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
|
||||
}
|
||||
// Babel config
|
||||
const babelrc = {
|
||||
presets: [[
|
||||
'env',
|
||||
{
|
||||
targets: {
|
||||
browsers,
|
||||
},
|
||||
useBuiltIns: true,
|
||||
modules: false,
|
||||
},
|
||||
]],
|
||||
plugins: ['external-helpers'],
|
||||
babelrc: false,
|
||||
exclude: 'node_modules/**',
|
||||
};
|
||||
|
||||
var build = {
|
||||
js: function (files, bundle) {
|
||||
for (var key in files) {
|
||||
(function(key) {
|
||||
var name = "js-" + key;
|
||||
tasks.js.push(name);
|
||||
// Clean out /dist
|
||||
gulp.task('clean', () => {
|
||||
const dirs = [
|
||||
paths.plyr.output,
|
||||
paths.demo.output,
|
||||
].map(dir => path.join(dir, '**/*'));
|
||||
|
||||
gulp.task(name, function () {
|
||||
return gulp
|
||||
.src(bundles[bundle].js[key])
|
||||
.pipe(concat(key))
|
||||
.pipe(uglify())
|
||||
.pipe(gulp.dest(paths[bundle].output));
|
||||
});
|
||||
})(key);
|
||||
}
|
||||
// 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);
|
||||
|
||||
gulp.task(name, () =>
|
||||
gulp
|
||||
.src(bundles[bundle].js[key])
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat(key))
|
||||
.pipe(
|
||||
rollup(
|
||||
{
|
||||
plugins: [
|
||||
resolve(),
|
||||
commonjs(),
|
||||
babel(babelrc),
|
||||
uglify({}, minify),
|
||||
],
|
||||
},
|
||||
options,
|
||||
),
|
||||
)
|
||||
.pipe(size(sizeOptions))
|
||||
.pipe(sourcemaps.write(''))
|
||||
.pipe(gulp.dest(paths[bundle].output)),
|
||||
);
|
||||
});
|
||||
},
|
||||
less: function(files, bundle) {
|
||||
for (var key in files) {
|
||||
(function (key) {
|
||||
var name = "less-" + key;
|
||||
tasks.less.push(name);
|
||||
sass(files, bundle) {
|
||||
Object.keys(files).forEach(key => {
|
||||
const name = `sass:${key}`;
|
||||
tasks.sass.push(name);
|
||||
|
||||
gulp.task(name, function () {
|
||||
return gulp
|
||||
.src(bundles[bundle].less[key])
|
||||
.pipe(less())
|
||||
.on("error", gutil.log)
|
||||
.pipe(concat(key))
|
||||
.pipe(prefix(["last 2 versions"], { cascade: true }))
|
||||
.pipe(cleanCSS())
|
||||
.pipe(gulp.dest(paths[bundle].output));
|
||||
});
|
||||
})(key);
|
||||
}
|
||||
gulp.task(name, () =>
|
||||
gulp
|
||||
.src(bundles[bundle].sass[key])
|
||||
.pipe(sass())
|
||||
.on('error', gutil.log)
|
||||
.pipe(concat(key))
|
||||
.pipe(prefix(browsers, { cascade: false }))
|
||||
.pipe(cleancss())
|
||||
.pipe(size(sizeOptions))
|
||||
.pipe(gulp.dest(paths[bundle].output)),
|
||||
);
|
||||
});
|
||||
},
|
||||
scss: function(files, bundle) {
|
||||
for (var key in files) {
|
||||
(function (key) {
|
||||
var name = "scss-" + key;
|
||||
tasks.scss.push(name);
|
||||
|
||||
gulp.task(name, function () {
|
||||
return gulp
|
||||
.src(bundles[bundle].scss[key])
|
||||
.pipe(sass())
|
||||
.on("error", gutil.log)
|
||||
.pipe(concat(key))
|
||||
.pipe(prefix(["last 2 versions"], { cascade: true }))
|
||||
.pipe(cleanCSS())
|
||||
.pipe(gulp.dest(paths[bundle].output));
|
||||
});
|
||||
})(key);
|
||||
}
|
||||
},
|
||||
sprite: function(bundle) {
|
||||
var name = "sprite-" + bundle;
|
||||
sprite(bundle) {
|
||||
const name = `svg:sprite:${bundle}`;
|
||||
tasks.sprite.push(name);
|
||||
|
||||
// Process Icons
|
||||
gulp.task(name, function () {
|
||||
return gulp
|
||||
gulp.task(name, () =>
|
||||
gulp
|
||||
.src(paths[bundle].src.sprite)
|
||||
.pipe(svgmin({
|
||||
plugins: [{
|
||||
removeDesc: true
|
||||
}]
|
||||
}))
|
||||
.pipe(
|
||||
svgmin({
|
||||
plugins: [{
|
||||
removeDesc: true,
|
||||
}],
|
||||
}),
|
||||
)
|
||||
.pipe(svgstore())
|
||||
.pipe(rename({ basename: bundle }))
|
||||
.pipe(gulp.dest(paths[bundle].output));
|
||||
});
|
||||
}
|
||||
.pipe(size(sizeOptions))
|
||||
.pipe(gulp.dest(paths[bundle].output)),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
// Plyr core files
|
||||
build.js(bundles.plyr.js, "plyr");
|
||||
build.less(bundles.plyr.less, "plyr");
|
||||
build.scss(bundles.plyr.scss, "plyr");
|
||||
build.sprite("plyr");
|
||||
build.js(bundles.plyr.js, 'plyr', { name: 'Plyr', format: 'umd' });
|
||||
build.sass(bundles.plyr.sass, 'plyr');
|
||||
build.sprite('plyr');
|
||||
|
||||
// Demo files
|
||||
build.less(bundles.demo.less, "demo");
|
||||
build.js(bundles.demo.js, "demo");
|
||||
build.sprite("demo");
|
||||
build.sass(bundles.demo.sass, 'demo');
|
||||
build.js(bundles.demo.js, 'demo', { format: 'iife' });
|
||||
|
||||
// Build all JS
|
||||
gulp.task("js", function(){
|
||||
gulp.task('js', () => {
|
||||
run(tasks.js);
|
||||
});
|
||||
|
||||
// Build SCSS (for testing, default is LESS)
|
||||
gulp.task("scss", function(){
|
||||
run(tasks.scss);
|
||||
});
|
||||
|
||||
// Watch for file changes
|
||||
gulp.task("watch", function () {
|
||||
gulp.task('watch', () => {
|
||||
// Plyr core
|
||||
gulp.watch(paths.plyr.src.js, tasks.js);
|
||||
gulp.watch(paths.plyr.src.less, tasks.less);
|
||||
gulp.watch(paths.plyr.src.sass, tasks.sass);
|
||||
gulp.watch(paths.plyr.src.sprite, tasks.sprite);
|
||||
|
||||
// Demo
|
||||
gulp.watch(paths.demo.src.js, tasks.js);
|
||||
gulp.watch(paths.demo.src.less, tasks.less);
|
||||
gulp.watch(paths.demo.src.sprite, tasks.sprite);
|
||||
gulp.watch(paths.demo.src.sass, tasks.sass);
|
||||
});
|
||||
|
||||
// Default gulp task
|
||||
gulp.task("default", function(){
|
||||
run(tasks.js, tasks.less, tasks.sprite, "watch");
|
||||
gulp.task('default', () => {
|
||||
run(tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'watch');
|
||||
});
|
||||
|
||||
// Publish a version to CDN and demo
|
||||
// --------------------------------------------
|
||||
|
||||
// Some options
|
||||
var aws = loadJSON(path.join(root, "aws.json")),
|
||||
version = package.version,
|
||||
maxAge = 31536000, // seconds 1 year
|
||||
options = {
|
||||
cdn: {
|
||||
headers: {
|
||||
"Cache-Control": "max-age=" + maxAge,
|
||||
"Vary": "Accept-Encoding"
|
||||
}
|
||||
},
|
||||
demo: {
|
||||
headers: {
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate, max-age=0",
|
||||
"Vary": "Accept-Encoding"
|
||||
}
|
||||
},
|
||||
symlinks: function(version, filename) {
|
||||
return {
|
||||
headers: {
|
||||
// http://stackoverflow.com/questions/2272835/amazon-s3-object-redirect
|
||||
"x-amz-website-redirect-location": "/" + version + "/" + filename,
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate, max-age=0"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If aws is setup
|
||||
if("cdn" in aws) {
|
||||
var regex = "(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-[\\da-z\\-]+(?:\.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:\.[\\da-z\\-]+)*)?",
|
||||
cdnpath = new RegExp(aws.cdn.bucket + "\/" + regex, "gi"),
|
||||
semver = new RegExp("v" + regex, "gi"),
|
||||
localPath = new RegExp("(\.\.\/)?dist", "gi"),
|
||||
versionPath = "https://" + aws.cdn.bucket + "/" + version;
|
||||
if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
||||
const { version } = pkg;
|
||||
|
||||
// Get branch info
|
||||
const branch = {
|
||||
current: gitbranch.sync(),
|
||||
master: 'master',
|
||||
beta: 'beta',
|
||||
};
|
||||
const allowed = [
|
||||
branch.master,
|
||||
branch.beta,
|
||||
];
|
||||
|
||||
const maxAge = 31536000; // 1 year
|
||||
const options = {
|
||||
cdn: {
|
||||
headers: {
|
||||
'Cache-Control': `max-age=${maxAge}`,
|
||||
Vary: 'Accept-Encoding',
|
||||
},
|
||||
},
|
||||
demo: {
|
||||
uploadPath: branch.current === branch.beta ? 'beta/' : null,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
|
||||
Vary: 'Accept-Encoding',
|
||||
},
|
||||
},
|
||||
symlinks(ver, filename) {
|
||||
return {
|
||||
headers: {
|
||||
// http://stackoverflow.com/questions/2272835/amazon-s3-object-redirect
|
||||
'x-amz-website-redirect-location': `/${ver}/${filename}`,
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const regex = '(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*).(?:0|[1-9][0-9]*)(?:-[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?';
|
||||
const semver = new RegExp(`v${regex}`, 'gi');
|
||||
const localPath = new RegExp('(../)?dist', 'gi');
|
||||
const versionPath = `https://${aws.cdn.domain}/${version}`;
|
||||
|
||||
// Publish version to CDN bucket
|
||||
gulp.task('cdn', () => {
|
||||
if (!allowed.includes(branch.current)) {
|
||||
console.error(`Must be on ${allowed.join(', ')} to publish! (current: ${branch.current})`);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`Uploading '${version}' to ${aws.cdn.domain}...`);
|
||||
|
||||
// Upload to CDN
|
||||
return gulp
|
||||
.src(paths.upload)
|
||||
.pipe(
|
||||
size({
|
||||
showFiles: true,
|
||||
gzip: true,
|
||||
}),
|
||||
)
|
||||
.pipe(
|
||||
rename(p => {
|
||||
// eslint-disable-next-line
|
||||
p.dirname = p.dirname.replace('.', version);
|
||||
}),
|
||||
)
|
||||
.pipe(replace(localPath, versionPath))
|
||||
.pipe(s3(aws.cdn, options.cdn));
|
||||
});
|
||||
|
||||
// Publish to demo bucket
|
||||
gulp.task('demo', () => {
|
||||
if (!allowed.includes(branch.current)) {
|
||||
console.error(`Must be on ${allowed.join(', ')} to publish! (current: ${branch.current})`);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`Uploading '${version}' demo to ${aws.demo.domain}...`);
|
||||
|
||||
const cdnpath = new RegExp(`${aws.cdn.domain}/${regex}/`, 'gi');
|
||||
|
||||
// Replace versioned files in readme.md
|
||||
gulp
|
||||
.src([`${root}/readme.md`])
|
||||
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
||||
.pipe(gulp.dest(root));
|
||||
|
||||
// Replace versioned files in plyr.js
|
||||
gulp
|
||||
.src(path.join(root, 'src/js/plyr.js'))
|
||||
.pipe(replace(semver, `v${version}`))
|
||||
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
||||
.pipe(gulp.dest(path.join(root, 'src/js/')));
|
||||
|
||||
// Replace local file paths with remote paths in demo HTML
|
||||
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
|
||||
const index = `${paths.demo.root}index.html`;
|
||||
const error = `${paths.demo.root}error.html`;
|
||||
const pages = [index];
|
||||
|
||||
if (branch.current === branch.master) {
|
||||
pages.push(error);
|
||||
}
|
||||
|
||||
gulp
|
||||
.src(pages)
|
||||
.pipe(replace(localPath, versionPath))
|
||||
.pipe(s3(aws.demo, options.demo));
|
||||
|
||||
// Only update CDN for master (prod)
|
||||
if (branch.current !== branch.master) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Upload error.html to cdn (as well as demo site)
|
||||
return gulp
|
||||
.src([error])
|
||||
.pipe(replace(localPath, versionPath))
|
||||
.pipe(s3(aws.cdn, options.demo));
|
||||
});
|
||||
|
||||
// Update symlinks for latest
|
||||
/* gulp.task("symlinks", function () {
|
||||
console.log("Updating symlinks...");
|
||||
|
||||
return gulp.src(paths.upload)
|
||||
.pipe(through.obj(function (chunk, enc, callback) {
|
||||
if (chunk.stat.isFile()) {
|
||||
// Get the filename
|
||||
var filename = chunk.path.split("/").reverse()[0];
|
||||
|
||||
// Create the 0 byte redirect files to upload
|
||||
createFile(filename, "")
|
||||
.pipe(rename(function (path) {
|
||||
path.dirname = path.dirname.replace(".", "latest");
|
||||
}))
|
||||
// Upload to S3 with correct headers
|
||||
.pipe(s3(aws.cdn, options.symlinks(version, filename)));
|
||||
}
|
||||
|
||||
callback(null, chunk);
|
||||
}));
|
||||
}); */
|
||||
|
||||
// Open the demo site to check it's sweet
|
||||
gulp.task('open', () => {
|
||||
console.log(`Opening ${aws.demo.domain}...`);
|
||||
|
||||
// A file must be specified or gulp will skip the task
|
||||
// Doesn't matter which file since we set the URL above
|
||||
// Weird, I know...
|
||||
return gulp.src([`${paths.demo.root}index.html`]).pipe(
|
||||
open('', {
|
||||
url: `http://${aws.demo.domain}`,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// Do everything
|
||||
gulp.task('publish', () => {
|
||||
run(tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'cdn', 'demo');
|
||||
});
|
||||
}
|
||||
|
||||
// Publish version to CDN bucket
|
||||
gulp.task("cdn", function () {
|
||||
console.log("Uploading " + version + " to " + aws.cdn.bucket + "...");
|
||||
|
||||
// Upload to CDN
|
||||
return gulp.src(paths.upload)
|
||||
.pipe(size({
|
||||
showFiles: true,
|
||||
gzip: true
|
||||
}))
|
||||
.pipe(rename(function (path) {
|
||||
path.dirname = path.dirname.replace(".", version);
|
||||
}))
|
||||
.pipe(replace(localPath, versionPath))
|
||||
.pipe(s3(aws.cdn, options.cdn));
|
||||
});
|
||||
|
||||
// Publish to demo bucket
|
||||
gulp.task("demo", function () {
|
||||
console.log("Uploading " + version + " demo to " + aws.demo.bucket + "...");
|
||||
|
||||
// Replace versioned files in readme.md
|
||||
gulp.src([root + "/readme.md"])
|
||||
.pipe(replace(cdnpath, aws.cdn.bucket + "/" + version))
|
||||
.pipe(gulp.dest(root));
|
||||
|
||||
// Replace versioned files in plyr.js
|
||||
gulp.src(path.join(root, "src/js/plyr.js"))
|
||||
.pipe(replace(semver, "v" + version))
|
||||
.pipe(replace(cdnpath, aws.cdn.bucket + "/" + version))
|
||||
.pipe(gulp.dest(path.join(root, "src/js/")));
|
||||
|
||||
// Replace local file paths with remote paths in demo HTML
|
||||
// e.g. "../dist/plyr.js" to "https://cdn.plyr.io/x.x.x/plyr.js"
|
||||
gulp.src([paths.demo.root + "*.html"])
|
||||
.pipe(replace(localPath, versionPath))
|
||||
.pipe(s3(aws.demo, options.demo));
|
||||
|
||||
// Upload error.html to cdn (as well as demo site)
|
||||
return gulp.src([paths.demo.root + "error.html"])
|
||||
.pipe(replace(localPath, versionPath))
|
||||
.pipe(s3(aws.cdn, options.demo));
|
||||
});
|
||||
|
||||
// Open the demo site to check it's sweet
|
||||
gulp.task("symlinks", function () {
|
||||
console.log("Updating symlinks...");
|
||||
|
||||
return gulp.src(paths.upload)
|
||||
.pipe(through.obj(function (chunk, enc, callback) {
|
||||
if (chunk.stat.isFile()) {
|
||||
// Get the filename
|
||||
var filename = chunk.path.split("/").reverse()[0];
|
||||
|
||||
// Create the 0 byte redirect files to upload
|
||||
createFile(filename, "")
|
||||
.pipe(rename(function (path) {
|
||||
path.dirname = path.dirname.replace(".", "latest");
|
||||
}))
|
||||
// Upload to S3 with correct headers
|
||||
.pipe(s3(aws.cdn, options.symlinks(version, filename)));
|
||||
}
|
||||
|
||||
callback(null, chunk);
|
||||
}));
|
||||
});
|
||||
|
||||
// Open the demo site to check it's sweet
|
||||
gulp.task("open", function () {
|
||||
console.log("Opening " + aws.demo.bucket + "...");
|
||||
|
||||
// A file must be specified or gulp will skip the task
|
||||
// Doesn't matter which file since we set the URL above
|
||||
// Weird, I know...
|
||||
return gulp.src([paths.demo.root + "index.html"])
|
||||
.pipe(open("", {
|
||||
url: "http://" + aws.demo.bucket
|
||||
}));
|
||||
});
|
||||
|
||||
// Do everything
|
||||
gulp.task("publish", function () {
|
||||
run(tasks.js, tasks.less, tasks.sprite, "cdn", "demo", "symlinks");
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Selz.com
|
||||
Copyright (c) 2017 Sam Potts
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
113
package.json
@ -1,51 +1,66 @@
|
||||
{
|
||||
"name": "plyr",
|
||||
"version": "2.0.4",
|
||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||
"homepage": "http://plyr.io",
|
||||
"main": "src/js/plyr.js",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-autoprefixer": "^3.1.0",
|
||||
"gulp-clean-css": "^2.0.6",
|
||||
"gulp-concat": "^2.3.3",
|
||||
"gulp-less": "^3.0.5",
|
||||
"gulp-open": "^2.0.0",
|
||||
"gulp-rename": "^1.2.0",
|
||||
"gulp-replace": "^0.5.3",
|
||||
"gulp-s3": "^0.3.0",
|
||||
"gulp-sass": "^2.3.1",
|
||||
"gulp-size": "^2.1.0",
|
||||
"gulp-svgmin": "^1.2.2",
|
||||
"gulp-svgstore": "^5.0.5",
|
||||
"gulp-uglify": "^1.5.3",
|
||||
"gulp-util": "^3.0.7",
|
||||
"run-sequence": "^1.1.5",
|
||||
"through2": "^2.0.1"
|
||||
},
|
||||
"keywords": [
|
||||
"HTML5 Video",
|
||||
"HTML5 Audio",
|
||||
"Media Player",
|
||||
"DASH",
|
||||
"Shaka",
|
||||
"WordPress",
|
||||
"HLS"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/selz/plyr.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/selz/plyr/issues"
|
||||
},
|
||||
"directories": {
|
||||
"doc": "readme.md"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Sam Potts <sam@selz.com>"
|
||||
"name": "plyr",
|
||||
"version": "3.0.0-beta.7",
|
||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||
"homepage": "https://plyr.io",
|
||||
"main": "./dist",
|
||||
"sass": "./src/sass/plyr.scss",
|
||||
"style": "./dist/plyr.css",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^8.2.1",
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"del": "^3.0.0",
|
||||
"eslint": "^4.16.0",
|
||||
"eslint-config-airbnb-base": "^12.1.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"git-branch": "^1.0.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-autoprefixer": "^4.1.0",
|
||||
"gulp-better-rollup": "^3.0.0",
|
||||
"gulp-clean-css": "^3.9.2",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-open": "^2.1.0",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-replace": "^0.6.1",
|
||||
"gulp-s3": "^0.11.0",
|
||||
"gulp-sass": "^3.1.0",
|
||||
"gulp-size": "^3.0.0",
|
||||
"gulp-sourcemaps": "^1.12.1",
|
||||
"gulp-svgmin": "^1.2.4",
|
||||
"gulp-svgstore": "^6.1.1",
|
||||
"gulp-util": "^3.0.8",
|
||||
"rollup-plugin-babel": "^3.0.3",
|
||||
"rollup-plugin-commonjs": "^8.2.6",
|
||||
"rollup-plugin-node-resolve": "^3.0.2",
|
||||
"rollup-plugin-uglify": "^3.0.0",
|
||||
"run-sequence": "^2.2.1",
|
||||
"stylelint": "^8.4.0",
|
||||
"stylelint-config-prettier": "^2.0.0",
|
||||
"stylelint-config-sass-guidelines": "^4.1.0",
|
||||
"stylelint-config-standard": "^18.0.0",
|
||||
"stylelint-order": "^0.8.0",
|
||||
"stylelint-scss": "^2.2.0",
|
||||
"stylelint-selector-bem-pattern": "^2.0.0",
|
||||
"uglify-es": "^3.3.8"
|
||||
},
|
||||
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/sampotts/plyr.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/sampotts/plyr/issues"
|
||||
},
|
||||
"directories": {
|
||||
"doc": "readme.md"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Sam Potts <sam@potts.es>",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
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
|
||||
}
|
||||
}
|
199
src/js/captions.js
Normal file
@ -0,0 +1,199 @@
|
||||
// ==========================================================================
|
||||
// Plyr Captions
|
||||
// ==========================================================================
|
||||
|
||||
import support from './support';
|
||||
import utils from './utils';
|
||||
import controls from './controls';
|
||||
|
||||
const captions = {
|
||||
// Setup captions
|
||||
setup() {
|
||||
// Requires UI support
|
||||
if (!this.supported.ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set default language if not set
|
||||
const stored = this.storage.get('language');
|
||||
|
||||
if (!utils.is.empty(stored)) {
|
||||
this.captions.language = stored;
|
||||
}
|
||||
|
||||
if (utils.is.empty(this.captions.language)) {
|
||||
this.captions.language = this.config.captions.language.toLowerCase();
|
||||
}
|
||||
|
||||
// Set captions enabled state if not set
|
||||
if (!utils.is.boolean(this.captions.active)) {
|
||||
const active = this.storage.get('captions');
|
||||
|
||||
if (utils.is.boolean(active)) {
|
||||
this.captions.active = active;
|
||||
} else {
|
||||
this.captions.active = this.config.captions.active;
|
||||
}
|
||||
}
|
||||
|
||||
// Only Vimeo and HTML5 video supported at this point
|
||||
if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
|
||||
// Clear menu and hide
|
||||
if (this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
|
||||
controls.setCaptionsMenu.call(this);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the container
|
||||
if (!utils.is.element(this.elements.captions)) {
|
||||
this.elements.captions = utils.createElement('div', utils.getAttributesFromSelector(this.config.selectors.captions));
|
||||
|
||||
utils.insertAfter(this.elements.captions, this.elements.wrapper);
|
||||
}
|
||||
|
||||
// Set the class hook
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.captions.enabled, !utils.is.empty(captions.getTracks.call(this)));
|
||||
|
||||
// If no caption file exists, hide container for caption text
|
||||
if (utils.is.empty(captions.getTracks.call(this))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set language
|
||||
captions.setLanguage.call(this);
|
||||
|
||||
// Enable UI
|
||||
captions.show.call(this);
|
||||
|
||||
// Set available languages in list
|
||||
if (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 => {
|
||||
// Remove previous bindings
|
||||
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 active = track.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;
|
||||
}
|
||||
}
|
1227
src/js/controls.js
vendored
Normal file
372
src/js/defaults.js
Normal file
@ -0,0 +1,372 @@
|
||||
// ==========================================================================
|
||||
// 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/2.0.10/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
|
||||
},
|
||||
|
||||
// Local storage
|
||||
storage: {
|
||||
enabled: true,
|
||||
key: 'plyr',
|
||||
},
|
||||
|
||||
// Default controls
|
||||
controls: [
|
||||
'play-large',
|
||||
'play',
|
||||
'progress',
|
||||
'current-time',
|
||||
'mute',
|
||||
'volume',
|
||||
'captions',
|
||||
'settings',
|
||||
'pip',
|
||||
'airplay',
|
||||
'fullscreen',
|
||||
],
|
||||
settings: [
|
||||
'captions',
|
||||
'quality',
|
||||
'speed',
|
||||
],
|
||||
|
||||
// Localisation
|
||||
i18n: {
|
||||
restart: 'Restart',
|
||||
rewind: 'Rewind {seektime} secs',
|
||||
play: 'Play',
|
||||
pause: 'Pause',
|
||||
forward: 'Forward {seektime} secs',
|
||||
seek: 'Seek',
|
||||
played: 'Played',
|
||||
buffered: 'Buffered',
|
||||
currentTime: 'Current time',
|
||||
duration: 'Duration',
|
||||
volume: 'Volume',
|
||||
mute: 'Mute',
|
||||
unmute: 'Unmute',
|
||||
enableCaptions: 'Enable captions',
|
||||
disableCaptions: 'Disable captions',
|
||||
enterFullscreen: 'Enter fullscreen',
|
||||
exitFullscreen: 'Exit fullscreen',
|
||||
frameTitle: 'Player for {title}',
|
||||
captions: 'Captions',
|
||||
settings: 'Settings',
|
||||
speed: 'Speed',
|
||||
quality: 'Quality',
|
||||
loop: 'Loop',
|
||||
start: 'Start',
|
||||
end: 'End',
|
||||
all: 'All',
|
||||
reset: 'Reset',
|
||||
none: 'None',
|
||||
disabled: 'Disabled',
|
||||
advertisment: 'Ad',
|
||||
},
|
||||
|
||||
// URLs
|
||||
urls: {
|
||||
vimeo: {
|
||||
api: 'https://player.vimeo.com/api/player.js',
|
||||
},
|
||||
youtube: {
|
||||
api: 'https://www.youtube.com/iframe_api',
|
||||
},
|
||||
googleIMA: {
|
||||
api: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
|
||||
},
|
||||
},
|
||||
|
||||
// Custom control listeners
|
||||
listeners: {
|
||||
seek: null,
|
||||
play: null,
|
||||
pause: null,
|
||||
restart: null,
|
||||
rewind: null,
|
||||
forward: null,
|
||||
mute: null,
|
||||
volume: null,
|
||||
captions: null,
|
||||
fullscreen: null,
|
||||
pip: null,
|
||||
airplay: null,
|
||||
speed: null,
|
||||
quality: null,
|
||||
loop: null,
|
||||
language: null,
|
||||
},
|
||||
|
||||
// Events to watch and bubble
|
||||
events: [
|
||||
// Events to watch on HTML5 media elements and bubble
|
||||
// https://developer.mozilla.org/en/docs/Web/Guide/Events/Media_events
|
||||
'ended',
|
||||
'progress',
|
||||
'stalled',
|
||||
'playing',
|
||||
'waiting',
|
||||
'canplay',
|
||||
'canplaythrough',
|
||||
'loadstart',
|
||||
'loadeddata',
|
||||
'loadedmetadata',
|
||||
'timeupdate',
|
||||
'volumechange',
|
||||
'play',
|
||||
'pause',
|
||||
'error',
|
||||
'seeking',
|
||||
'seeked',
|
||||
'emptied',
|
||||
'ratechange',
|
||||
'cuechange',
|
||||
|
||||
// Custom events
|
||||
'enterfullscreen',
|
||||
'exitfullscreen',
|
||||
'captionsenabled',
|
||||
'captionsdisabled',
|
||||
'languagechange',
|
||||
'controlshidden',
|
||||
'controlsshown',
|
||||
'ready',
|
||||
|
||||
// YouTube
|
||||
'statechange',
|
||||
'qualitychange',
|
||||
'qualityrequested',
|
||||
|
||||
// Ads
|
||||
'adsloaded',
|
||||
'adscontentpause',
|
||||
'adsconentresume',
|
||||
'adstarted',
|
||||
'adsmidpoint',
|
||||
'adscomplete',
|
||||
'adsallcomplete',
|
||||
'adsimpression',
|
||||
'adsclick',
|
||||
],
|
||||
|
||||
// Selectors
|
||||
// Change these to match your template if using custom HTML
|
||||
selectors: {
|
||||
editable: 'input, textarea, select, [contenteditable]',
|
||||
container: '.plyr',
|
||||
controls: {
|
||||
container: null,
|
||||
wrapper: '.plyr__controls',
|
||||
},
|
||||
labels: '[data-plyr]',
|
||||
buttons: {
|
||||
play: '[data-plyr="play"]',
|
||||
pause: '[data-plyr="pause"]',
|
||||
restart: '[data-plyr="restart"]',
|
||||
rewind: '[data-plyr="rewind"]',
|
||||
forward: '[data-plyr="fast-forward"]',
|
||||
mute: '[data-plyr="mute"]',
|
||||
captions: '[data-plyr="captions"]',
|
||||
fullscreen: '[data-plyr="fullscreen"]',
|
||||
pip: '[data-plyr="pip"]',
|
||||
airplay: '[data-plyr="airplay"]',
|
||||
settings: '[data-plyr="settings"]',
|
||||
loop: '[data-plyr="loop"]',
|
||||
},
|
||||
inputs: {
|
||||
seek: '[data-plyr="seek"]',
|
||||
volume: '[data-plyr="volume"]',
|
||||
speed: '[data-plyr="speed"]',
|
||||
language: '[data-plyr="language"]',
|
||||
quality: '[data-plyr="quality"]',
|
||||
},
|
||||
display: {
|
||||
currentTime: '.plyr__time--current',
|
||||
duration: '.plyr__time--duration',
|
||||
buffer: '.plyr__progress--buffer',
|
||||
played: '.plyr__progress--played',
|
||||
loop: '.plyr__progress--loop',
|
||||
volume: '.plyr__volume--display',
|
||||
},
|
||||
progress: '.plyr__progress',
|
||||
captions: '.plyr__captions',
|
||||
menu: {
|
||||
quality: '.js-plyr__menu__list--quality',
|
||||
},
|
||||
},
|
||||
|
||||
// Class hooks added to the player in different states
|
||||
classNames: {
|
||||
video: 'plyr__video-wrapper',
|
||||
embed: 'plyr__video-embed',
|
||||
ads: 'plyr__ads',
|
||||
control: 'plyr__control',
|
||||
type: 'plyr--{0}',
|
||||
provider: 'plyr--{0}',
|
||||
stopped: 'plyr--stopped',
|
||||
playing: 'plyr--playing',
|
||||
loading: 'plyr--loading',
|
||||
error: 'plyr--has-error',
|
||||
hover: 'plyr--hover',
|
||||
tooltip: 'plyr__tooltip',
|
||||
cues: 'plyr__cues',
|
||||
hidden: 'plyr__sr-only',
|
||||
hideControls: 'plyr--hide-controls',
|
||||
isIos: 'plyr--is-ios',
|
||||
isTouch: 'plyr--is-touch',
|
||||
uiSupported: 'plyr--full-ui',
|
||||
noTransition: 'plyr--no-transition',
|
||||
menu: {
|
||||
value: 'plyr__menu__value',
|
||||
badge: 'plyr__badge',
|
||||
open: 'plyr--menu-open',
|
||||
},
|
||||
captions: {
|
||||
enabled: 'plyr--captions-enabled',
|
||||
active: 'plyr--captions-active',
|
||||
},
|
||||
fullscreen: {
|
||||
enabled: 'plyr--fullscreen-enabled',
|
||||
fallback: 'plyr--fullscreen-fallback',
|
||||
},
|
||||
pip: {
|
||||
supported: 'plyr--pip-supported',
|
||||
active: 'plyr--pip-active',
|
||||
},
|
||||
airplay: {
|
||||
supported: 'plyr--airplay-supported',
|
||||
active: 'plyr--airplay-active',
|
||||
},
|
||||
tabFocus: 'plyr__tab-focus',
|
||||
},
|
||||
|
||||
// API keys
|
||||
keys: {
|
||||
google: null,
|
||||
},
|
||||
|
||||
// Ads
|
||||
ads: {
|
||||
tag: null,
|
||||
},
|
||||
};
|
||||
|
||||
export default defaults;
|
127
src/js/fullscreen.js
Normal file
@ -0,0 +1,127 @@
|
||||
// ==========================================================================
|
||||
// Plyr fullscreen API
|
||||
// ==========================================================================
|
||||
|
||||
import utils from './utils';
|
||||
|
||||
// Determine the prefix
|
||||
const prefix = (() => {
|
||||
let value = false;
|
||||
|
||||
if (utils.is.function(document.cancelFullScreen)) {
|
||||
value = '';
|
||||
} else {
|
||||
// Check for fullscreen support by vendor prefix
|
||||
[
|
||||
'webkit',
|
||||
'o',
|
||||
'moz',
|
||||
'ms',
|
||||
'khtml',
|
||||
].some(pre => {
|
||||
if (utils.is.function(document[`${pre}CancelFullScreen`])) {
|
||||
value = pre;
|
||||
return true;
|
||||
} else if (utils.is.function(document.msExitFullscreen) && document.msFullscreenEnabled) {
|
||||
// Special case for MS (when isn't it?)
|
||||
value = 'ms';
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
return value;
|
||||
})();
|
||||
|
||||
// Fullscreen API
|
||||
const fullscreen = {
|
||||
// Get the prefix
|
||||
prefix,
|
||||
|
||||
// Check if we can use it
|
||||
enabled: document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled,
|
||||
|
||||
// Yet again Microsoft awesomeness,
|
||||
// Sometimes the prefix is 'ms', sometimes 'MS' to keep you on your toes
|
||||
eventType: prefix === 'ms' ? 'MSFullscreenChange' : `${prefix}fullscreenchange`,
|
||||
|
||||
// Is an element fullscreen
|
||||
isFullScreen(element) {
|
||||
if (!fullscreen.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = utils.is.nullOrUndefined(element) ? document.body : element;
|
||||
|
||||
switch (prefix) {
|
||||
case '':
|
||||
return document.fullscreenElement === target;
|
||||
|
||||
case 'moz':
|
||||
return document.mozFullScreenElement === target;
|
||||
|
||||
default:
|
||||
return document[`${prefix}FullscreenElement`] === target;
|
||||
}
|
||||
},
|
||||
|
||||
// Make an element fullscreen
|
||||
requestFullScreen(element) {
|
||||
if (!fullscreen.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = utils.is.nullOrUndefined(element) ? document.body : element;
|
||||
|
||||
return !prefix.length ? target.requestFullScreen() : target[prefix + (prefix === 'ms' ? 'RequestFullscreen' : 'RequestFullScreen')]();
|
||||
},
|
||||
|
||||
// Bail from fullscreen
|
||||
cancelFullScreen() {
|
||||
if (!fullscreen.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !prefix.length ? document.cancelFullScreen() : document[prefix + (prefix === 'ms' ? 'ExitFullscreen' : 'CancelFullScreen')]();
|
||||
},
|
||||
|
||||
// Get the current element
|
||||
element() {
|
||||
if (!fullscreen.enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return !prefix.length ? document.fullscreenElement : document[`${prefix}FullscreenElement`];
|
||||
},
|
||||
|
||||
// Setup fullscreen
|
||||
setup() {
|
||||
if (!this.supported.ui || this.isAudio || !this.config.fullscreen.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for native support
|
||||
const nativeSupport = fullscreen.enabled;
|
||||
|
||||
if (nativeSupport || (this.config.fullscreen.fallback && !utils.inFrame())) {
|
||||
this.debug.log(`${nativeSupport ? 'Native' : 'Fallback'} fullscreen enabled`);
|
||||
|
||||
// Add styling hook to show button
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.fullscreen.enabled, true);
|
||||
} else {
|
||||
this.debug.log('Fullscreen not supported and fallback disabled');
|
||||
}
|
||||
|
||||
// Toggle state
|
||||
if (this.elements.buttons && this.elements.buttons.fullscreen) {
|
||||
utils.toggleState(this.elements.buttons.fullscreen, false);
|
||||
}
|
||||
|
||||
// Trap focus in container
|
||||
utils.trapFocus.call(this);
|
||||
},
|
||||
};
|
||||
|
||||
export default fullscreen;
|
591
src/js/listeners.js
Normal file
@ -0,0 +1,591 @@
|
||||
// ==========================================================================
|
||||
// Plyr Event Listeners
|
||||
// ==========================================================================
|
||||
|
||||
import support from './support';
|
||||
import utils from './utils';
|
||||
import controls from './controls';
|
||||
import fullscreen from './fullscreen';
|
||||
import ui from './ui';
|
||||
|
||||
// Sniff out the browser
|
||||
const browser = utils.getBrowser();
|
||||
|
||||
const listeners = {
|
||||
// Global listeners
|
||||
global() {
|
||||
let last = null;
|
||||
|
||||
// Get the key code for an event
|
||||
const getKeyCode = event => (event.keyCode ? event.keyCode : event.which);
|
||||
|
||||
// Handle key press
|
||||
const handleKey = event => {
|
||||
const code = getKeyCode(event);
|
||||
const pressed = event.type === 'keydown';
|
||||
const repeat = pressed && code === last;
|
||||
|
||||
// Bail if a modifier key is set
|
||||
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the event is bubbled from the media element
|
||||
// Firefox doesn't get the keycode for whatever reason
|
||||
if (!utils.is.number(code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Seek by the number keys
|
||||
const seekByKey = () => {
|
||||
// Divide the max duration into 10th's and times by the number value
|
||||
this.currentTime = this.duration / 10 * (code - 48);
|
||||
};
|
||||
|
||||
// Handle the key on keydown
|
||||
// Reset on keyup
|
||||
if (pressed) {
|
||||
// Which keycodes should we prevent default
|
||||
const preventDefault = [
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
51,
|
||||
52,
|
||||
53,
|
||||
54,
|
||||
56,
|
||||
57,
|
||||
32,
|
||||
75,
|
||||
38,
|
||||
40,
|
||||
77,
|
||||
39,
|
||||
37,
|
||||
70,
|
||||
67,
|
||||
73,
|
||||
76,
|
||||
79,
|
||||
];
|
||||
|
||||
// Check focused element
|
||||
// and if the focused element is not editable (e.g. text input)
|
||||
// and any that accept key input http://webaim.org/techniques/keyboard/
|
||||
const focused = utils.getFocusElement();
|
||||
if (utils.is.element(focused) && utils.matches(focused, this.config.selectors.editable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the code is found prevent default (e.g. prevent scrolling for arrows)
|
||||
if (preventDefault.includes(code)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case 48:
|
||||
case 49:
|
||||
case 50:
|
||||
case 51:
|
||||
case 52:
|
||||
case 53:
|
||||
case 54:
|
||||
case 55:
|
||||
case 56:
|
||||
case 57:
|
||||
// 0-9
|
||||
if (!repeat) {
|
||||
seekByKey();
|
||||
}
|
||||
break;
|
||||
|
||||
case 32:
|
||||
case 75:
|
||||
// Space and K key
|
||||
if (!repeat) {
|
||||
this.togglePlay();
|
||||
}
|
||||
break;
|
||||
|
||||
case 38:
|
||||
// Arrow up
|
||||
this.increaseVolume(0.1);
|
||||
break;
|
||||
|
||||
case 40:
|
||||
// Arrow down
|
||||
this.decreaseVolume(0.1);
|
||||
break;
|
||||
|
||||
case 77:
|
||||
// M key
|
||||
if (!repeat) {
|
||||
this.muted = !this.muted;
|
||||
}
|
||||
break;
|
||||
|
||||
case 39:
|
||||
// Arrow forward
|
||||
this.forward();
|
||||
break;
|
||||
|
||||
case 37:
|
||||
// Arrow back
|
||||
this.rewind();
|
||||
break;
|
||||
|
||||
case 70:
|
||||
// F key
|
||||
this.toggleFullscreen();
|
||||
break;
|
||||
|
||||
case 67:
|
||||
// C key
|
||||
if (!repeat) {
|
||||
this.toggleCaptions();
|
||||
}
|
||||
break;
|
||||
|
||||
case 76:
|
||||
// L key
|
||||
this.loop = !this.loop;
|
||||
break;
|
||||
|
||||
/* case 73:
|
||||
this.setLoop('start');
|
||||
break;
|
||||
|
||||
case 76:
|
||||
this.setLoop();
|
||||
break;
|
||||
|
||||
case 79:
|
||||
this.setLoop('end');
|
||||
break; */
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Escape is handle natively when in full screen
|
||||
// So we only need to worry about non native
|
||||
if (!fullscreen.enabled && this.fullscreen.active && code === 27) {
|
||||
this.toggleFullscreen();
|
||||
}
|
||||
|
||||
// Store last code for next cycle
|
||||
last = code;
|
||||
} else {
|
||||
last = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Keyboard shortcuts
|
||||
if (this.config.keyboard.global) {
|
||||
utils.on(window, 'keydown keyup', handleKey, false);
|
||||
} else if (this.config.keyboard.focused) {
|
||||
utils.on(this.elements.container, 'keydown keyup', handleKey, false);
|
||||
}
|
||||
|
||||
// Detect tab focus
|
||||
// Remove class on blur/focusout
|
||||
utils.on(this.elements.container, 'focusout', event => {
|
||||
utils.toggleClass(event.target, this.config.classNames.tabFocus, false);
|
||||
});
|
||||
|
||||
// Add classname to tabbed elements
|
||||
utils.on(this.elements.container, 'keydown', event => {
|
||||
if (event.keyCode !== 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delay the adding of classname until the focus has changed
|
||||
// This event fires before the focusin event
|
||||
window.setTimeout(() => {
|
||||
utils.toggleClass(utils.getFocusElement(), this.config.classNames.tabFocus, true);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// Toggle controls visibility based on mouse movement
|
||||
if (this.config.hideControls) {
|
||||
// Toggle controls on mouse events and entering fullscreen
|
||||
utils.on(this.elements.container, 'mouseenter mouseleave mousemove touchstart touchend touchmove enterfullscreen exitfullscreen', event => {
|
||||
this.toggleControls(event);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle user exiting fullscreen by escaping etc
|
||||
if (fullscreen.enabled) {
|
||||
utils.on(document, fullscreen.eventType, event => {
|
||||
this.toggleFullscreen(event);
|
||||
});
|
||||
|
||||
// Fullscreen toggle on double click
|
||||
utils.on(this.elements.container, 'dblclick', event => {
|
||||
this.toggleFullscreen(event);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Listen for media events
|
||||
media() {
|
||||
// Time change on media
|
||||
utils.on(this.media, 'timeupdate seeking', event => ui.timeUpdate.call(this, event));
|
||||
|
||||
// Display duration
|
||||
utils.on(this.media, 'durationchange loadedmetadata', event => ui.durationUpdate.call(this, event));
|
||||
|
||||
// Check for audio tracks on load
|
||||
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
||||
utils.on(this.media, 'loadeddata', () => {
|
||||
utils.toggleHidden(this.elements.volume, !this.hasAudio);
|
||||
utils.toggleHidden(this.elements.buttons.mute, !this.hasAudio);
|
||||
});
|
||||
|
||||
// Handle the media finishing
|
||||
utils.on(this.media, 'ended', () => {
|
||||
// Show poster on end
|
||||
if (this.isHTML5 && this.isVideo && this.config.showPosterOnEnd) {
|
||||
// Restart
|
||||
this.restart();
|
||||
|
||||
// Re-load media
|
||||
this.media.load();
|
||||
}
|
||||
});
|
||||
|
||||
// Check for buffer progress
|
||||
utils.on(this.media, 'progress playing', event => ui.updateProgress.call(this, event));
|
||||
|
||||
// Handle native mute
|
||||
utils.on(this.media, 'volumechange', event => ui.updateVolume.call(this, event));
|
||||
|
||||
// Handle native play/pause
|
||||
utils.on(this.media, 'playing play pause ended', event => ui.checkPlaying.call(this, event));
|
||||
|
||||
// Loading
|
||||
utils.on(this.media, 'stalled waiting canplay seeked playing', event => ui.checkLoading.call(this, event));
|
||||
|
||||
// Check if media failed to load
|
||||
// utils.on(this.media, 'play', event => ui.checkFailed.call(this, event));
|
||||
|
||||
// Click video
|
||||
if (this.supported.ui && this.config.clickToPlay && !this.isAudio) {
|
||||
// Re-fetch the wrapper
|
||||
const wrapper = utils.getElement.call(this, `.${this.config.classNames.video}`);
|
||||
|
||||
// Bail if there's no wrapper (this should never happen)
|
||||
if (!utils.is.element(wrapper)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// On click play, pause ore restart
|
||||
utils.on(wrapper, 'click', () => {
|
||||
// Touch devices will just show controls (if we're hiding controls)
|
||||
if (this.config.hideControls && support.touch && !this.paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.paused) {
|
||||
this.play();
|
||||
} else if (this.ended) {
|
||||
this.restart();
|
||||
this.play();
|
||||
} else {
|
||||
this.pause();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Disable right click
|
||||
if (this.supported.ui && this.config.disableContextMenu) {
|
||||
utils.on(
|
||||
this.media,
|
||||
'contextmenu',
|
||||
event => {
|
||||
event.preventDefault();
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// Volume change
|
||||
utils.on(this.media, 'volumechange', () => {
|
||||
// Save to storage
|
||||
this.storage.set({ volume: this.volume, muted: this.muted });
|
||||
});
|
||||
|
||||
// Speed change
|
||||
utils.on(this.media, 'ratechange', () => {
|
||||
// Update UI
|
||||
controls.updateSetting.call(this, 'speed');
|
||||
|
||||
// Save to storage
|
||||
this.storage.set({ speed: this.speed });
|
||||
});
|
||||
|
||||
// Quality change
|
||||
utils.on(this.media, 'qualitychange', () => {
|
||||
// Update UI
|
||||
controls.updateSetting.call(this, 'quality');
|
||||
|
||||
// Save to storage
|
||||
this.storage.set({ quality: this.quality });
|
||||
});
|
||||
|
||||
// Caption language change
|
||||
utils.on(this.media, 'languagechange', () => {
|
||||
// Update UI
|
||||
controls.updateSetting.call(this, 'captions');
|
||||
|
||||
// Save to storage
|
||||
this.storage.set({ language: this.language });
|
||||
});
|
||||
|
||||
// Captions toggle
|
||||
utils.on(this.media, 'captionsenabled captionsdisabled', () => {
|
||||
// Update UI
|
||||
controls.updateSetting.call(this, 'captions');
|
||||
|
||||
// Save to storage
|
||||
this.storage.set({ captions: this.captions.active });
|
||||
});
|
||||
|
||||
// Proxy events to container
|
||||
// Bubble up key events for Edge
|
||||
utils.on(this.media, this.config.events.concat([
|
||||
'keyup',
|
||||
'keydown',
|
||||
]).join(' '), event => {
|
||||
let detail = {};
|
||||
|
||||
// Get error details from media
|
||||
if (event.type === 'error') {
|
||||
detail = this.media.error;
|
||||
}
|
||||
|
||||
utils.dispatchEvent.call(this, this.elements.container, event.type, true, detail);
|
||||
});
|
||||
},
|
||||
|
||||
// Listen for control events
|
||||
controls() {
|
||||
// IE doesn't support input event, so we fallback to change
|
||||
const inputEvent = browser.isIE ? 'change' : 'input';
|
||||
|
||||
// Trigger custom and default handlers
|
||||
const proxy = (event, handlerKey, defaultHandler) => {
|
||||
const customHandler = this.config.listeners[handlerKey];
|
||||
|
||||
// Execute custom handler
|
||||
if (utils.is.function(customHandler)) {
|
||||
customHandler.call(this, event);
|
||||
}
|
||||
|
||||
// Only call default handler if not prevented in custom handler
|
||||
if (!event.defaultPrevented && utils.is.function(defaultHandler)) {
|
||||
defaultHandler.call(this, event);
|
||||
}
|
||||
};
|
||||
|
||||
// Play/pause toggle
|
||||
utils.on(this.elements.buttons.play, 'click', event =>
|
||||
proxy(event, 'play', () => {
|
||||
this.togglePlay();
|
||||
})
|
||||
);
|
||||
|
||||
// Pause
|
||||
utils.on(this.elements.buttons.restart, 'click', event =>
|
||||
proxy(event, 'restart', () => {
|
||||
this.restart();
|
||||
})
|
||||
);
|
||||
|
||||
// Rewind
|
||||
utils.on(this.elements.buttons.rewind, 'click', event =>
|
||||
proxy(event, 'rewind', () => {
|
||||
this.rewind();
|
||||
})
|
||||
);
|
||||
|
||||
// Rewind
|
||||
utils.on(this.elements.buttons.forward, 'click', event =>
|
||||
proxy(event, 'forward', () => {
|
||||
this.forward();
|
||||
})
|
||||
);
|
||||
|
||||
// Mute toggle
|
||||
utils.on(this.elements.buttons.mute, 'click', event =>
|
||||
proxy(event, 'mute', () => {
|
||||
this.muted = !this.muted;
|
||||
})
|
||||
);
|
||||
|
||||
// Captions toggle
|
||||
utils.on(this.elements.buttons.captions, 'click', event =>
|
||||
proxy(event, 'captions', () => {
|
||||
this.toggleCaptions();
|
||||
})
|
||||
);
|
||||
|
||||
// Fullscreen toggle
|
||||
utils.on(this.elements.buttons.fullscreen, 'click', event =>
|
||||
proxy(event, 'fullscreen', () => {
|
||||
this.toggleFullscreen();
|
||||
})
|
||||
);
|
||||
|
||||
// Picture-in-Picture
|
||||
utils.on(this.elements.buttons.pip, 'click', event =>
|
||||
proxy(event, 'pip', () => {
|
||||
this.pip = 'toggle';
|
||||
})
|
||||
);
|
||||
|
||||
// Airplay
|
||||
utils.on(this.elements.buttons.airplay, 'click', event =>
|
||||
proxy(event, 'airplay', () => {
|
||||
this.airplay();
|
||||
})
|
||||
);
|
||||
|
||||
// Settings menu
|
||||
utils.on(this.elements.buttons.settings, 'click', event => {
|
||||
controls.toggleMenu.call(this, event);
|
||||
});
|
||||
|
||||
// Click anywhere closes menu
|
||||
utils.on(document.documentElement, 'click', event => {
|
||||
controls.toggleMenu.call(this, event);
|
||||
});
|
||||
|
||||
// Settings menu
|
||||
utils.on(this.elements.settings.form, 'click', event => {
|
||||
event.stopPropagation();
|
||||
|
||||
// Settings menu items - use event delegation as items are added/removed
|
||||
if (utils.matches(event.target, this.config.selectors.inputs.language)) {
|
||||
proxy(event, 'language', () => {
|
||||
this.language = event.target.value;
|
||||
});
|
||||
} else if (utils.matches(event.target, this.config.selectors.inputs.quality)) {
|
||||
proxy(event, 'quality', () => {
|
||||
this.quality = event.target.value;
|
||||
});
|
||||
} else if (utils.matches(event.target, this.config.selectors.inputs.speed)) {
|
||||
proxy(event, 'speed', () => {
|
||||
this.speed = parseFloat(event.target.value);
|
||||
});
|
||||
} else {
|
||||
controls.showTab.call(this, event);
|
||||
}
|
||||
});
|
||||
|
||||
// Seek
|
||||
utils.on(this.elements.inputs.seek, inputEvent, event =>
|
||||
proxy(event, 'seek', () => {
|
||||
this.currentTime = event.target.value / event.target.max * this.duration;
|
||||
})
|
||||
);
|
||||
|
||||
// Current time invert
|
||||
// Only if one time element is used for both currentTime and duration
|
||||
if (this.config.toggleInvert && !utils.is.element(this.elements.display.duration)) {
|
||||
utils.on(this.elements.display.currentTime, 'click', () => {
|
||||
// Do nothing if we're at the start
|
||||
if (this.currentTime === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.config.invertTime = !this.config.invertTime;
|
||||
ui.timeUpdate.call(this);
|
||||
});
|
||||
}
|
||||
|
||||
// Volume
|
||||
utils.on(this.elements.inputs.volume, inputEvent, event =>
|
||||
proxy(event, 'volume', () => {
|
||||
this.volume = event.target.value;
|
||||
})
|
||||
);
|
||||
|
||||
// Polyfill for lower fill in <input type="range"> for webkit
|
||||
if (browser.isWebkit) {
|
||||
utils.on(utils.getElements.call(this, 'input[type="range"]'), 'input', event => {
|
||||
controls.updateRangeFill.call(this, event.target);
|
||||
});
|
||||
}
|
||||
|
||||
// Seek tooltip
|
||||
utils.on(this.elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(this, event));
|
||||
|
||||
// Toggle controls visibility based on mouse movement
|
||||
if (this.config.hideControls) {
|
||||
// Watch for cursor over controls so they don't hide when trying to interact
|
||||
utils.on(this.elements.controls, 'mouseenter mouseleave', event => {
|
||||
this.elements.controls.hover = event.type === 'mouseenter';
|
||||
});
|
||||
|
||||
// Watch for cursor over controls so they don't hide when trying to interact
|
||||
utils.on(this.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
|
||||
this.elements.controls.pressed = [
|
||||
'mousedown',
|
||||
'touchstart',
|
||||
].includes(event.type);
|
||||
});
|
||||
|
||||
// Focus in/out on controls
|
||||
utils.on(this.elements.controls, 'focusin focusout', event => {
|
||||
this.toggleControls(event);
|
||||
});
|
||||
}
|
||||
|
||||
// Mouse wheel for volume
|
||||
utils.on(
|
||||
this.elements.inputs.volume,
|
||||
'wheel',
|
||||
event =>
|
||||
proxy(event, 'volume', () => {
|
||||
// Detect "natural" scroll - suppored on OS X Safari only
|
||||
// Other browsers on OS X will be inverted until support improves
|
||||
const inverted = event.webkitDirectionInvertedFromDevice;
|
||||
const step = 1 / 50;
|
||||
let direction = 0;
|
||||
|
||||
// Scroll down (or up on natural) to decrease
|
||||
if (event.deltaY < 0 || event.deltaX > 0) {
|
||||
if (inverted) {
|
||||
this.decreaseVolume(step);
|
||||
direction = -1;
|
||||
} else {
|
||||
this.increaseVolume(step);
|
||||
direction = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll up (or down on natural) to increase
|
||||
if (event.deltaY > 0 || event.deltaX < 0) {
|
||||
if (inverted) {
|
||||
this.increaseVolume(step);
|
||||
direction = 1;
|
||||
} else {
|
||||
this.decreaseVolume(step);
|
||||
direction = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't break page scrolling at max and min
|
||||
if ((direction === 1 && this.media.volume < 1) || (direction === -1 && this.media.volume > 0)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}),
|
||||
false
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default listeners;
|
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
|
||||
Array.from(this.media.querySelectorAll('source')).forEach(utils.removeElement);
|
||||
|
||||
// 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;
|
550
src/js/plugins/ads.js
Normal file
@ -0,0 +1,550 @@
|
||||
// ==========================================================================
|
||||
// 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.playing = false;
|
||||
this.initialized = false;
|
||||
this.enabled = utils.is.url(player.config.ads.tag);
|
||||
|
||||
// Check if a tag URL is provided.
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the Google IMA3 SDK is loaded
|
||||
if (!utils.is.object(window.google)) {
|
||||
utils.loadScript(player.config.urls.googleIMA.api, () => {
|
||||
this.ready();
|
||||
});
|
||||
} else {
|
||||
this.ready();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ads instance ready.
|
||||
*/
|
||||
ready() {
|
||||
this.elements = {
|
||||
container: null,
|
||||
displayContainer: null,
|
||||
};
|
||||
this.manager = null;
|
||||
this.loader = null;
|
||||
this.cuePoints = null;
|
||||
this.events = {};
|
||||
this.safetyTimer = null;
|
||||
this.countdownTimer = null;
|
||||
|
||||
// Set listeners on the Plyr instance
|
||||
this.listeners();
|
||||
|
||||
// Start ticking our safety timer. If the whole advertisement
|
||||
// thing doesn't resolve within our set time; we bail
|
||||
this.startSafetyTimer(12000, 'ready()');
|
||||
|
||||
// Setup a simple promise to resolve if the IMA loader is ready
|
||||
this.loaderPromise = new Promise(resolve => {
|
||||
this.on('ADS_LOADER_LOADED', () => resolve());
|
||||
});
|
||||
|
||||
// Setup a promise to resolve if the IMA manager is ready
|
||||
this.managerPromise = new Promise(resolve => {
|
||||
this.on('ADS_MANAGER_LOADED', () => resolve());
|
||||
});
|
||||
|
||||
// Clear the safety timer
|
||||
this.managerPromise.then(() => {
|
||||
this.clearSafetyTimer('onAdsManagerLoaded()');
|
||||
});
|
||||
|
||||
// Setup the IMA SDK
|
||||
this.setupIMA();
|
||||
}
|
||||
|
||||
/**
|
||||
* In order for the SDK to display ads for our video, we need to tell it where to put them,
|
||||
* so here we define our ad container. This div is set up to render on top of the video player.
|
||||
* Using the code below, we tell the SDK to render ads within that div. We also provide a
|
||||
* handle to the content video player - the SDK will poll the current time of our player to
|
||||
* properly place mid-rolls. After we create the ad display container, we initialize it. On
|
||||
* mobile devices, this initialization is done as the result of a user action.
|
||||
*/
|
||||
setupIMA() {
|
||||
// Create the container for our advertisements
|
||||
this.elements.container = utils.createElement('div', {
|
||||
class: this.player.config.classNames.ads,
|
||||
hidden: '',
|
||||
});
|
||||
this.player.elements.container.appendChild(this.elements.container);
|
||||
|
||||
// So we can run VPAID2
|
||||
google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED);
|
||||
|
||||
// Set language
|
||||
google.ima.settings.setLocale(this.player.config.ads.language);
|
||||
|
||||
// We assume the adContainer is the video container of the plyr element
|
||||
// that will house the ads
|
||||
this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container);
|
||||
|
||||
// Request video ads to be pre-loaded
|
||||
this.requestAds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request advertisements
|
||||
*/
|
||||
requestAds() {
|
||||
const { container } = this.player.elements;
|
||||
|
||||
try {
|
||||
// Create ads loader
|
||||
this.loader = new google.ima.AdsLoader(this.elements.displayContainer);
|
||||
|
||||
// Listen and respond to ads loaded and error events
|
||||
this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, event => this.onAdsManagerLoaded(event), false);
|
||||
this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error), false);
|
||||
|
||||
// Request video ads
|
||||
const request = new google.ima.AdsRequest();
|
||||
request.adTagUrl = this.player.config.ads.tag;
|
||||
|
||||
// Specify the linear and nonlinear slot sizes. This helps the SDK
|
||||
// to select the correct creative if multiple are returned
|
||||
request.linearAdSlotWidth = container.offsetWidth;
|
||||
request.linearAdSlotHeight = container.offsetHeight;
|
||||
request.nonLinearAdSlotWidth = container.offsetWidth;
|
||||
request.nonLinearAdSlotHeight = container.offsetHeight;
|
||||
|
||||
// We only overlay ads as we only support video.
|
||||
request.forceNonLinearFullSlot = false;
|
||||
|
||||
this.loader.requestAds(request);
|
||||
|
||||
this.handleEventListeners('ADS_LOADER_LOADED');
|
||||
} catch (e) {
|
||||
this.onAdError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ad countdown
|
||||
* @param {boolean} start
|
||||
*/
|
||||
pollCountdown(start = false) {
|
||||
if (!start) {
|
||||
window.clearInterval(this.countdownTimer);
|
||||
this.elements.container.removeAttribute('data-badge-text');
|
||||
return;
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
const time = utils.formatTime(this.manager.getRemainingTime());
|
||||
const label = `${this.player.config.i18n.advertisment} - ${time}`;
|
||||
this.elements.container.setAttribute('data-badge-text', label);
|
||||
};
|
||||
|
||||
this.countdownTimer = window.setInterval(update, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called whenever the ads are ready inside the AdDisplayContainer
|
||||
* @param {Event} adsManagerLoadedEvent
|
||||
*/
|
||||
onAdsManagerLoaded(adsManagerLoadedEvent) {
|
||||
// Get the ads manager
|
||||
const settings = new google.ima.AdsRenderingSettings();
|
||||
|
||||
// Tell the SDK to save and restore content video state on our behalf
|
||||
settings.restoreCustomPlaybackStateOnAdBreakComplete = true;
|
||||
settings.enablePreloading = true;
|
||||
|
||||
// The SDK is polling currentTime on the contentPlayback. And needs a duration
|
||||
// so it can determine when to start the mid- and post-roll
|
||||
this.manager = adsManagerLoadedEvent.getAdsManager(this.player, settings);
|
||||
|
||||
// Get the cue points for any mid-rolls by filtering out the pre- and post-roll
|
||||
this.cuePoints = this.manager.getCuePoints();
|
||||
|
||||
// Add advertisement cue's within the time line if available
|
||||
this.cuePoints.forEach(cuePoint => {
|
||||
if (cuePoint !== 0 && cuePoint !== -1) {
|
||||
const seekElement = this.player.elements.progress;
|
||||
|
||||
if (seekElement) {
|
||||
const cuePercentage = 100 / this.player.duration * cuePoint;
|
||||
const cue = utils.createElement('span', {
|
||||
class: this.player.config.classNames.cues,
|
||||
});
|
||||
|
||||
cue.style.left = `${cuePercentage.toString()}%`;
|
||||
seekElement.appendChild(cue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Get skippable state
|
||||
// TODO: Skip button
|
||||
// this.manager.getAdSkippableState();
|
||||
|
||||
// Set volume to match player
|
||||
this.manager.setVolume(this.player.volume);
|
||||
|
||||
// Add listeners to the required events
|
||||
// Advertisement error events
|
||||
this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, error => this.onAdError(error));
|
||||
|
||||
// Advertisement regular events
|
||||
Object.keys(google.ima.AdEvent.Type).forEach(type => {
|
||||
this.manager.addEventListener(google.ima.AdEvent.Type[type], event => this.onAdEvent(event));
|
||||
});
|
||||
|
||||
// Resolve our adsManager
|
||||
this.handleEventListeners('ADS_MANAGER_LOADED');
|
||||
}
|
||||
|
||||
/**
|
||||
* This is where all the event handling takes place. Retrieve the ad from the event. Some
|
||||
* events (e.g. ALL_ADS_COMPLETED) don't have the ad object associated
|
||||
* https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdEvent.Type
|
||||
* @param {Event} event
|
||||
*/
|
||||
onAdEvent(event) {
|
||||
const { container } = this.player.elements;
|
||||
|
||||
// Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED)
|
||||
// don't have ad object associated
|
||||
const ad = event.getAd();
|
||||
|
||||
// Proxy event
|
||||
const dispatchEvent = type => {
|
||||
utils.dispatchEvent.call(this.player, this.player.media, `ads${type}`);
|
||||
};
|
||||
|
||||
switch (event.type) {
|
||||
case google.ima.AdEvent.Type.LOADED:
|
||||
// This is the first event sent for an ad - it is possible to determine whether the
|
||||
// ad is a video ad or an overlay
|
||||
this.handleEventListeners('LOADED');
|
||||
|
||||
// Bubble event
|
||||
dispatchEvent('loaded');
|
||||
|
||||
// Start countdown
|
||||
this.pollCountdown(true);
|
||||
|
||||
if (!ad.isLinear()) {
|
||||
// Position AdDisplayContainer correctly for overlay
|
||||
ad.width = container.offsetWidth;
|
||||
ad.height = container.offsetHeight;
|
||||
}
|
||||
|
||||
// console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex());
|
||||
// console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset());
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
|
||||
// All ads for the current videos are done. We can now request new advertisements
|
||||
// in case the video is re-played
|
||||
this.handleEventListeners('ALL_ADS_COMPLETED');
|
||||
|
||||
// Fire event
|
||||
dispatchEvent('allcomplete');
|
||||
|
||||
// TODO: Example for what happens when a next video in a playlist would be loaded.
|
||||
// So here we load a new video when all ads are done.
|
||||
// Then we load new ads within a new adsManager. When the video
|
||||
// Is started - after - the ads are loaded, then we get ads.
|
||||
// You can also easily test cancelling and reloading by running
|
||||
// player.ads.cancel() and player.ads.play from the console I guess.
|
||||
// this.player.source = {
|
||||
// type: 'video',
|
||||
// title: 'View From A Blue Moon',
|
||||
// sources: [{
|
||||
// src:
|
||||
// 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4', type:
|
||||
// 'video/mp4', }], poster:
|
||||
// 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg', tracks:
|
||||
// [ { kind: 'captions', label: 'English', srclang: 'en', src:
|
||||
// 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
|
||||
// default: true, }, { kind: 'captions', label: 'French', srclang: 'fr', src:
|
||||
// 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt', }, ],
|
||||
// };
|
||||
|
||||
// TODO: So there is still this thing where a video should only be allowed to start
|
||||
// playing when the IMA SDK is ready or has failed
|
||||
|
||||
this.loadAds();
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
|
||||
// This event indicates the ad has started - the video player can adjust the UI,
|
||||
// for example display a pause button and remaining time. Fired when content should
|
||||
// be paused. This usually happens right before an ad is about to cover the content
|
||||
this.handleEventListeners('CONTENT_PAUSE_REQUESTED');
|
||||
|
||||
dispatchEvent('contentpause');
|
||||
|
||||
this.pauseContent();
|
||||
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED:
|
||||
// This event indicates the ad has finished - the video player can perform
|
||||
// appropriate UI actions, such as removing the timer for remaining time detection.
|
||||
// Fired when content should be resumed. This usually happens when an ad finishes
|
||||
// or collapses
|
||||
this.handleEventListeners('CONTENT_RESUME_REQUESTED');
|
||||
|
||||
dispatchEvent('contentresume');
|
||||
|
||||
this.pollCountdown();
|
||||
|
||||
this.resumeContent();
|
||||
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.STARTED:
|
||||
dispatchEvent('started');
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.MIDPOINT:
|
||||
dispatchEvent('midpoint');
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.COMPLETE:
|
||||
dispatchEvent('complete');
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.IMPRESSION:
|
||||
dispatchEvent('impression');
|
||||
break;
|
||||
|
||||
case google.ima.AdEvent.Type.CLICK:
|
||||
dispatchEvent('click');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Any ad error handling comes through here
|
||||
* @param {Event} event
|
||||
*/
|
||||
onAdError(event) {
|
||||
this.cancel();
|
||||
this.player.debug.log('Ads error', event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup hooks for Plyr and window events. This ensures
|
||||
* the mid- and post-roll launch at the correct time. And
|
||||
* resize the advertisement when the player resizes
|
||||
*/
|
||||
listeners() {
|
||||
const { container } = this.player.elements;
|
||||
let time;
|
||||
|
||||
// Add listeners to the required events
|
||||
this.player.on('ended', () => {
|
||||
this.loader.contentComplete();
|
||||
});
|
||||
|
||||
this.player.on('seeking', () => {
|
||||
time = this.player.currentTime;
|
||||
return time;
|
||||
});
|
||||
|
||||
this.player.on('seeked', () => {
|
||||
const seekedTime = this.player.currentTime;
|
||||
|
||||
this.cuePoints.forEach((cuePoint, index) => {
|
||||
if (time < cuePoint && cuePoint < seekedTime) {
|
||||
this.manager.discardAdBreak();
|
||||
this.cuePoints.splice(index, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Listen to the resizing of the window. And resize ad accordingly
|
||||
// TODO: eventually implement ResizeObserver
|
||||
window.addEventListener('resize', () => {
|
||||
this.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the adsManager and start playing advertisements
|
||||
*/
|
||||
play() {
|
||||
const { container } = this.player.elements;
|
||||
|
||||
if (!this.managerPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Play the requested advertisement whenever the adsManager is ready
|
||||
this.managerPromise.then(() => {
|
||||
// Initialize the container. Must be done via a user action on mobile devices
|
||||
this.elements.displayContainer.initialize();
|
||||
|
||||
try {
|
||||
if (!this.initialized) {
|
||||
// Initialize the ads manager. Ad rules playlist will start at this time
|
||||
this.manager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
|
||||
|
||||
// Call play to start showing the ad. Single video and overlay ads will
|
||||
// start at this time; the call will be ignored for ad rules
|
||||
this.manager.start();
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
} catch (adError) {
|
||||
// An error may be thrown if there was a problem with the
|
||||
// VAST response
|
||||
this.onAdError(adError);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume our video.
|
||||
*/
|
||||
resumeContent() {
|
||||
// Hide our ad container
|
||||
utils.toggleHidden(this.elements.container, true);
|
||||
|
||||
// Ad is stopped
|
||||
this.playing = false;
|
||||
|
||||
// Play our video
|
||||
if (this.player.currentTime < this.player.duration) {
|
||||
this.player.play();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause our video
|
||||
*/
|
||||
pauseContent() {
|
||||
// Show our ad container.
|
||||
utils.toggleHidden(this.elements.container, false);
|
||||
|
||||
// Ad is playing.
|
||||
this.playing = true;
|
||||
|
||||
// Pause our video.
|
||||
this.player.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the adsManager so we can grab new ads after this. If we don't then we're not
|
||||
* allowed to call new ads based on google policies, as they interpret this as an accidental
|
||||
* video requests. https://developers.google.com/interactive-
|
||||
* media-ads/docs/sdks/android/faq#8
|
||||
*/
|
||||
cancel() {
|
||||
// Pause our video
|
||||
if (this.initialized) {
|
||||
this.resumeContent();
|
||||
}
|
||||
|
||||
// Tell our instance that we're done for now
|
||||
this.handleEventListeners('ERROR');
|
||||
|
||||
// Re-create our adsManager
|
||||
this.loadAds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-create our adsManager
|
||||
*/
|
||||
loadAds() {
|
||||
// Tell our adsManager to go bye bye
|
||||
this.managerPromise.then(() => {
|
||||
// Destroy our adsManager
|
||||
if (this.manager) {
|
||||
this.manager.destroy();
|
||||
}
|
||||
|
||||
// Re-set our adsManager promises
|
||||
this.managerPromise = new Promise(resolve => {
|
||||
this.on('ADS_MANAGER_LOADED', () => resolve());
|
||||
this.player.debug.log(this.manager);
|
||||
});
|
||||
|
||||
// Now request some new advertisements
|
||||
this.requestAds();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles callbacks after an ad event was invoked
|
||||
* @param {string} event - Event type
|
||||
*/
|
||||
handleEventListeners(event) {
|
||||
if (utils.is.function(this.events[event])) {
|
||||
this.events[event].call(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listeners
|
||||
* @param {string} event - Event type
|
||||
* @param {function} callback - Callback for when event occurs
|
||||
* @return {Ads}
|
||||
*/
|
||||
on(event, callback) {
|
||||
this.events[event] = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup a safety timer for when the ad network doesn't respond for whatever reason.
|
||||
* The advertisement has 12 seconds to get its things together. We stop this timer when the
|
||||
* advertisement is playing, or when a user action is required to start, then we clear the
|
||||
* timer on ad ready
|
||||
* @param {number} time
|
||||
* @param {string} from
|
||||
*/
|
||||
startSafetyTimer(time, from) {
|
||||
this.player.debug.log(`Safety timer invoked from: ${from}`);
|
||||
|
||||
this.safetyTimer = window.setTimeout(() => {
|
||||
this.cancel();
|
||||
this.clearSafetyTimer('startSafetyTimer()');
|
||||
}, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear our safety timer(s)
|
||||
* @param {string} from
|
||||
*/
|
||||
clearSafetyTimer(from) {
|
||||
if (!utils.is.nullOrUndefined(this.safetyTimer)) {
|
||||
this.player.debug.log(`Safety timer cleared from: ${from}`);
|
||||
|
||||
clearTimeout(this.safetyTimer);
|
||||
this.safetyTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Ads;
|
309
src/js/plugins/vimeo.js
Normal file
@ -0,0 +1,309 @@
|
||||
// ==========================================================================
|
||||
// Vimeo plugin
|
||||
// ==========================================================================
|
||||
|
||||
import utils from './../utils';
|
||||
import captions from './../captions';
|
||||
import ui from './../ui';
|
||||
|
||||
const vimeo = {
|
||||
setup() {
|
||||
// Add embed class for responsive
|
||||
utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
|
||||
|
||||
// Set intial ratio
|
||||
vimeo.setAspectRatio.call(this);
|
||||
|
||||
// Load the API if not already
|
||||
if (!utils.is.object(window.Vimeo)) {
|
||||
utils.loadScript(this.config.urls.vimeo.api, () => {
|
||||
vimeo.ready.call(this);
|
||||
});
|
||||
} else {
|
||||
vimeo.ready.call(this);
|
||||
}
|
||||
},
|
||||
|
||||
// Set aspect ratio
|
||||
// For Vimeo we have an extra 300% height <div> to hide the standard controls and UI
|
||||
setAspectRatio(input) {
|
||||
const ratio = utils.is.string(input) ? input.split(':') : this.config.ratio.split(':');
|
||||
const padding = 100 / ratio[0] * ratio[1];
|
||||
const height = 200;
|
||||
const offset = (height - padding) / (height / 50);
|
||||
this.elements.wrapper.style.paddingBottom = `${padding}%`;
|
||||
this.media.style.transform = `translateY(-${offset}%)`;
|
||||
},
|
||||
|
||||
// API Ready
|
||||
ready() {
|
||||
const player = this;
|
||||
|
||||
// Get Vimeo params for the iframe
|
||||
const options = {
|
||||
loop: player.config.loop.active,
|
||||
autoplay: player.autoplay,
|
||||
byline: false,
|
||||
portrait: false,
|
||||
title: false,
|
||||
speed: true,
|
||||
transparent: 0,
|
||||
gesture: 'media',
|
||||
};
|
||||
const params = utils.buildUrlParams(options);
|
||||
const id = utils.parseVimeoId(player.media.getAttribute('src'));
|
||||
|
||||
// Build an iframe
|
||||
const iframe = utils.createElement('iframe');
|
||||
const src = `https://player.vimeo.com/video/${id}?${params}`;
|
||||
iframe.setAttribute('src', src);
|
||||
iframe.setAttribute('allowfullscreen', '');
|
||||
iframe.setAttribute('allowtransparency', '');
|
||||
iframe.setAttribute('allow', 'autoplay');
|
||||
|
||||
// Inject the package
|
||||
const wrapper = utils.createElement('div');
|
||||
wrapper.appendChild(iframe);
|
||||
player.media = utils.replaceElement(wrapper, player.media);
|
||||
|
||||
// Setup instance
|
||||
// https://github.com/vimeo/player.js
|
||||
player.embed = new window.Vimeo.Player(iframe);
|
||||
|
||||
player.media.paused = true;
|
||||
player.media.currentTime = 0;
|
||||
|
||||
// Create a faux HTML5 API using the Vimeo API
|
||||
player.media.play = () => {
|
||||
player.embed.play().then(() => {
|
||||
player.media.paused = false;
|
||||
});
|
||||
};
|
||||
|
||||
player.media.pause = () => {
|
||||
player.embed.pause().then(() => {
|
||||
player.media.paused = true;
|
||||
});
|
||||
};
|
||||
|
||||
player.media.stop = () => {
|
||||
player.embed.stop().then(() => {
|
||||
player.media.paused = true;
|
||||
player.currentTime = 0;
|
||||
});
|
||||
};
|
||||
|
||||
// Seeking
|
||||
let { currentTime } = player.media;
|
||||
Object.defineProperty(player.media, 'currentTime', {
|
||||
get() {
|
||||
return currentTime;
|
||||
},
|
||||
set(time) {
|
||||
// Get current paused state
|
||||
// Vimeo will automatically play on seek
|
||||
const { paused } = player.media;
|
||||
|
||||
// Set seeking flag
|
||||
player.media.seeking = true;
|
||||
|
||||
// Trigger seeking
|
||||
utils.dispatchEvent.call(player, player.media, 'seeking');
|
||||
|
||||
// Seek after events
|
||||
player.embed.setCurrentTime(time);
|
||||
|
||||
// Restore pause state
|
||||
if (paused) {
|
||||
player.pause();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Playback speed
|
||||
let speed = player.config.speed.selected;
|
||||
Object.defineProperty(player.media, 'playbackRate', {
|
||||
get() {
|
||||
return speed;
|
||||
},
|
||||
set(input) {
|
||||
player.embed.setPlaybackRate(input).then(() => {
|
||||
speed = input;
|
||||
utils.dispatchEvent.call(player, player.media, 'ratechange');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Volume
|
||||
let { volume } = player.config;
|
||||
Object.defineProperty(player.media, 'volume', {
|
||||
get() {
|
||||
return volume;
|
||||
},
|
||||
set(input) {
|
||||
player.embed.setVolume(input).then(() => {
|
||||
volume = input;
|
||||
utils.dispatchEvent.call(player, player.media, 'volumechange');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Muted
|
||||
let { muted } = player.config;
|
||||
Object.defineProperty(player.media, 'muted', {
|
||||
get() {
|
||||
return muted;
|
||||
},
|
||||
set(input) {
|
||||
const toggle = utils.is.boolean(input) ? input : false;
|
||||
|
||||
player.embed.setVolume(toggle ? 0 : player.config.volume).then(() => {
|
||||
muted = toggle;
|
||||
utils.dispatchEvent.call(player, player.media, 'volumechange');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Loop
|
||||
let { loop } = player.config;
|
||||
Object.defineProperty(player.media, 'loop', {
|
||||
get() {
|
||||
return loop;
|
||||
},
|
||||
set(input) {
|
||||
const toggle = utils.is.boolean(input) ? input : player.config.loop.active;
|
||||
|
||||
player.embed.setLoop(toggle).then(() => {
|
||||
loop = toggle;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Source
|
||||
let currentSrc;
|
||||
player.embed.getVideoUrl().then(value => {
|
||||
currentSrc = value;
|
||||
});
|
||||
Object.defineProperty(player.media, 'currentSrc', {
|
||||
get() {
|
||||
return currentSrc;
|
||||
},
|
||||
});
|
||||
|
||||
// Ended
|
||||
Object.defineProperty(player.media, 'ended', {
|
||||
get() {
|
||||
return player.currentTime === player.duration;
|
||||
},
|
||||
});
|
||||
|
||||
// Set aspect ratio based on video size
|
||||
Promise.all([
|
||||
player.embed.getVideoWidth(),
|
||||
player.embed.getVideoHeight(),
|
||||
]).then(dimensions => {
|
||||
const ratio = utils.getAspectRatio(dimensions[0], dimensions[1]);
|
||||
vimeo.setAspectRatio.call(this, ratio);
|
||||
});
|
||||
|
||||
// Set autopause
|
||||
player.embed.setAutopause(player.config.autopause).then(state => {
|
||||
player.config.autopause = state;
|
||||
});
|
||||
|
||||
// Get title
|
||||
player.embed.getVideoTitle().then(title => {
|
||||
player.config.title = title;
|
||||
ui.setTitle.call(this);
|
||||
});
|
||||
|
||||
// Get current time
|
||||
player.embed.getCurrentTime().then(value => {
|
||||
currentTime = value;
|
||||
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
||||
});
|
||||
|
||||
// Get duration
|
||||
player.embed.getDuration().then(value => {
|
||||
player.media.duration = value;
|
||||
utils.dispatchEvent.call(player, player.media, 'durationchange');
|
||||
});
|
||||
|
||||
// Get captions
|
||||
player.embed.getTextTracks().then(tracks => {
|
||||
player.media.textTracks = tracks;
|
||||
captions.setup.call(player);
|
||||
});
|
||||
|
||||
player.embed.on('cuechange', data => {
|
||||
let cue = null;
|
||||
|
||||
if (data.cues.length) {
|
||||
cue = utils.stripHTML(data.cues[0].text);
|
||||
}
|
||||
|
||||
captions.setText.call(player, cue);
|
||||
});
|
||||
|
||||
player.embed.on('loaded', () => {
|
||||
if (utils.is.element(player.embed.element) && player.supported.ui) {
|
||||
const frame = player.embed.element;
|
||||
|
||||
// Fix keyboard focus issues
|
||||
// https://github.com/sampotts/plyr/issues/317
|
||||
frame.setAttribute('tabindex', -1);
|
||||
}
|
||||
});
|
||||
|
||||
player.embed.on('play', () => {
|
||||
// Only fire play if paused before
|
||||
if (player.media.paused) {
|
||||
utils.dispatchEvent.call(player, player.media, 'play');
|
||||
}
|
||||
player.media.paused = false;
|
||||
utils.dispatchEvent.call(player, player.media, 'playing');
|
||||
});
|
||||
|
||||
player.embed.on('pause', () => {
|
||||
player.media.paused = true;
|
||||
utils.dispatchEvent.call(player, player.media, 'pause');
|
||||
});
|
||||
|
||||
player.embed.on('timeupdate', data => {
|
||||
player.media.seeking = false;
|
||||
currentTime = data.seconds;
|
||||
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
||||
});
|
||||
|
||||
player.embed.on('progress', data => {
|
||||
player.media.buffered = data.percent;
|
||||
utils.dispatchEvent.call(player, player.media, 'progress');
|
||||
|
||||
// Check all loaded
|
||||
if (parseInt(data.percent, 10) === 1) {
|
||||
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
|
||||
}
|
||||
});
|
||||
|
||||
player.embed.on('seeked', () => {
|
||||
player.media.seeking = false;
|
||||
utils.dispatchEvent.call(player, player.media, 'seeked');
|
||||
utils.dispatchEvent.call(player, player.media, 'play');
|
||||
});
|
||||
|
||||
player.embed.on('ended', () => {
|
||||
player.media.paused = true;
|
||||
utils.dispatchEvent.call(player, player.media, 'ended');
|
||||
});
|
||||
|
||||
player.embed.on('error', detail => {
|
||||
player.media.error = detail;
|
||||
utils.dispatchEvent.call(player, player.media, 'error');
|
||||
});
|
||||
|
||||
// Rebuild UI
|
||||
window.setTimeout(() => ui.build.call(player), 0);
|
||||
},
|
||||
};
|
||||
|
||||
export default vimeo;
|
410
src/js/plugins/youtube.js
Normal file
@ -0,0 +1,410 @@
|
||||
// ==========================================================================
|
||||
// YouTube plugin
|
||||
// ==========================================================================
|
||||
|
||||
import utils from './../utils';
|
||||
import controls from './../controls';
|
||||
import ui from './../ui';
|
||||
|
||||
const youtube = {
|
||||
setup() {
|
||||
// Add embed class for responsive
|
||||
utils.toggleClass(this.elements.wrapper, this.config.classNames.embed, true);
|
||||
|
||||
// Set aspect ratio
|
||||
youtube.setAspectRatio.call(this);
|
||||
|
||||
// Setup API
|
||||
if (utils.is.object(window.YT) && utils.is.function(window.YT.Player)) {
|
||||
youtube.ready.call(this);
|
||||
} else {
|
||||
// Load the API
|
||||
utils.loadScript(this.config.urls.youtube.api);
|
||||
|
||||
// Setup callback for the API
|
||||
// YouTube has it's own system of course...
|
||||
window.onYouTubeReadyCallbacks = window.onYouTubeReadyCallbacks || [];
|
||||
|
||||
// Add to queue
|
||||
window.onYouTubeReadyCallbacks.push(() => {
|
||||
youtube.ready.call(this);
|
||||
});
|
||||
|
||||
// Set callback to process queue
|
||||
window.onYouTubeIframeAPIReady = () => {
|
||||
window.onYouTubeReadyCallbacks.forEach(callback => {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// Get the media title
|
||||
getTitle(videoId) {
|
||||
// Try via undocumented API method first
|
||||
// This method disappears now and then though...
|
||||
// https://github.com/sampotts/plyr/issues/709
|
||||
if (utils.is.function(this.embed.getVideoData)) {
|
||||
const { title } = this.embed.getVideoData();
|
||||
|
||||
if (utils.is.empty(title)) {
|
||||
this.config.title = title;
|
||||
ui.setTitle.call(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Or via Google API
|
||||
const key = this.config.keys.google;
|
||||
if (utils.is.string(key) && !utils.is.empty(key)) {
|
||||
const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${key}&fields=items(snippet(title))&part=snippet`;
|
||||
|
||||
fetch(url)
|
||||
.then(response => (response.ok ? response.json() : null))
|
||||
.then(result => {
|
||||
if (result !== null && 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;
|
||||
}
|
||||
|
||||
// Replace the <iframe> with a <div> due to YouTube API issues
|
||||
const videoId = utils.parseYouTubeId(player.media.getAttribute('src'));
|
||||
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.hostname,
|
||||
widget_referrer: window && window.location.href,
|
||||
|
||||
// Captions are flaky on YouTube
|
||||
cc_load_policy: player.captions.active ? 1 : 0,
|
||||
cc_lang_pref: player.config.captions.language,
|
||||
},
|
||||
events: {
|
||||
onError(event) {
|
||||
// If we've already fired an error, don't do it again
|
||||
// YouTube fires onError twice
|
||||
if (utils.is.object(player.media.error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const detail = {
|
||||
code: event.data,
|
||||
};
|
||||
|
||||
// Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
|
||||
switch (event.data) {
|
||||
case 2:
|
||||
detail.message =
|
||||
'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.';
|
||||
break;
|
||||
|
||||
case 5:
|
||||
detail.message =
|
||||
'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.';
|
||||
break;
|
||||
|
||||
case 100:
|
||||
detail.message =
|
||||
'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.';
|
||||
break;
|
||||
|
||||
case 101:
|
||||
case 150:
|
||||
detail.message = 'The owner of the requested video does not allow it to be played in embedded players.';
|
||||
break;
|
||||
|
||||
default:
|
||||
detail.message = 'An unknown error occured';
|
||||
break;
|
||||
}
|
||||
|
||||
player.media.error = detail;
|
||||
|
||||
utils.dispatchEvent.call(player, player.media, 'error');
|
||||
},
|
||||
onPlaybackQualityChange(event) {
|
||||
// Get the instance
|
||||
const instance = event.target;
|
||||
|
||||
// Get current quality
|
||||
player.media.quality = instance.getPlaybackQuality();
|
||||
|
||||
utils.dispatchEvent.call(player, player.media, 'qualitychange');
|
||||
},
|
||||
onPlaybackRateChange(event) {
|
||||
// Get the instance
|
||||
const instance = event.target;
|
||||
|
||||
// Get current speed
|
||||
player.media.playbackRate = instance.getPlaybackRate();
|
||||
|
||||
utils.dispatchEvent.call(player, player.media, 'ratechange');
|
||||
},
|
||||
onReady(event) {
|
||||
// Get the instance
|
||||
const instance = event.target;
|
||||
|
||||
// Get the title
|
||||
youtube.getTitle.call(player, videoId);
|
||||
|
||||
// Create a faux HTML5 API using the YouTube API
|
||||
player.media.play = () => {
|
||||
instance.playVideo();
|
||||
player.media.paused = false;
|
||||
};
|
||||
|
||||
player.media.pause = () => {
|
||||
instance.pauseVideo();
|
||||
player.media.paused = true;
|
||||
};
|
||||
|
||||
player.media.stop = () => {
|
||||
instance.stopVideo();
|
||||
player.media.paused = true;
|
||||
};
|
||||
|
||||
player.media.duration = instance.getDuration();
|
||||
player.media.paused = true;
|
||||
|
||||
// Seeking
|
||||
player.media.currentTime = 0;
|
||||
Object.defineProperty(player.media, 'currentTime', {
|
||||
get() {
|
||||
return Number(instance.getCurrentTime());
|
||||
},
|
||||
set(time) {
|
||||
// Set seeking flag
|
||||
player.media.seeking = true;
|
||||
|
||||
// Trigger seeking
|
||||
utils.dispatchEvent.call(player, player.media, 'seeking');
|
||||
|
||||
// Seek after events sent
|
||||
instance.seekTo(time);
|
||||
},
|
||||
});
|
||||
|
||||
// Playback speed
|
||||
Object.defineProperty(player.media, 'playbackRate', {
|
||||
get() {
|
||||
return instance.getPlaybackRate();
|
||||
},
|
||||
set(input) {
|
||||
instance.setPlaybackRate(input);
|
||||
},
|
||||
});
|
||||
|
||||
// Quality
|
||||
Object.defineProperty(player.media, 'quality', {
|
||||
get() {
|
||||
return instance.getPlaybackQuality();
|
||||
},
|
||||
set(input) {
|
||||
// Trigger request event
|
||||
utils.dispatchEvent.call(player, player.media, 'qualityrequested', false, {
|
||||
quality: input,
|
||||
});
|
||||
|
||||
instance.setPlaybackQuality(input);
|
||||
},
|
||||
});
|
||||
|
||||
// Volume
|
||||
let { volume } = player.config;
|
||||
Object.defineProperty(player.media, 'volume', {
|
||||
get() {
|
||||
return volume;
|
||||
},
|
||||
set(input) {
|
||||
volume = input;
|
||||
instance.setVolume(volume * 100);
|
||||
utils.dispatchEvent.call(player, player.media, 'volumechange');
|
||||
},
|
||||
});
|
||||
|
||||
// Muted
|
||||
let { muted } = player.config;
|
||||
Object.defineProperty(player.media, 'muted', {
|
||||
get() {
|
||||
return muted;
|
||||
},
|
||||
set(input) {
|
||||
const toggle = utils.is.boolean(input) ? input : muted;
|
||||
muted = toggle;
|
||||
instance[toggle ? 'mute' : 'unMute']();
|
||||
utils.dispatchEvent.call(player, player.media, 'volumechange');
|
||||
},
|
||||
});
|
||||
|
||||
// Source
|
||||
Object.defineProperty(player.media, 'currentSrc', {
|
||||
get() {
|
||||
return instance.getVideoUrl();
|
||||
},
|
||||
});
|
||||
|
||||
// Ended
|
||||
Object.defineProperty(player.media, 'ended', {
|
||||
get() {
|
||||
return player.currentTime === player.duration;
|
||||
},
|
||||
});
|
||||
|
||||
// Get available speeds
|
||||
player.options.speed = instance.getAvailablePlaybackRates();
|
||||
|
||||
// Set the tabindex to avoid focus entering iframe
|
||||
if (player.supported.ui) {
|
||||
player.media.setAttribute('tabindex', -1);
|
||||
}
|
||||
|
||||
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
||||
utils.dispatchEvent.call(player, player.media, 'durationchange');
|
||||
|
||||
// Reset timer
|
||||
window.clearInterval(player.timers.buffering);
|
||||
|
||||
// Setup buffering
|
||||
player.timers.buffering = window.setInterval(() => {
|
||||
// Get loaded % from YouTube
|
||||
player.media.buffered = instance.getVideoLoadedFraction();
|
||||
|
||||
// Trigger progress only when we actually buffer something
|
||||
if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
|
||||
utils.dispatchEvent.call(player, player.media, 'progress');
|
||||
}
|
||||
|
||||
// Set last buffer point
|
||||
player.media.lastBuffered = player.media.buffered;
|
||||
|
||||
// Bail if we're at 100%
|
||||
if (player.media.buffered === 1) {
|
||||
window.clearInterval(player.timers.buffering);
|
||||
|
||||
// Trigger event
|
||||
utils.dispatchEvent.call(player, player.media, 'canplaythrough');
|
||||
}
|
||||
}, 200);
|
||||
|
||||
// Rebuild UI
|
||||
window.setTimeout(() => ui.build.call(player), 50);
|
||||
},
|
||||
onStateChange(event) {
|
||||
// Get the instance
|
||||
const instance = event.target;
|
||||
|
||||
// Reset timer
|
||||
window.clearInterval(player.timers.playing);
|
||||
|
||||
// Handle events
|
||||
// -1 Unstarted
|
||||
// 0 Ended
|
||||
// 1 Playing
|
||||
// 2 Paused
|
||||
// 3 Buffering
|
||||
// 5 Video cued
|
||||
switch (event.data) {
|
||||
case 0:
|
||||
player.media.paused = true;
|
||||
|
||||
// YouTube doesn't support loop for a single video, so mimick it.
|
||||
if (player.media.loop) {
|
||||
// YouTube needs a call to `stopVideo` before playing again
|
||||
instance.stopVideo();
|
||||
instance.playVideo();
|
||||
} else {
|
||||
utils.dispatchEvent.call(player, player.media, 'ended');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// If we were seeking, fire seeked event
|
||||
if (player.media.seeking) {
|
||||
utils.dispatchEvent.call(player, player.media, 'seeked');
|
||||
}
|
||||
player.media.seeking = false;
|
||||
|
||||
// Only fire play if paused before
|
||||
if (player.media.paused) {
|
||||
utils.dispatchEvent.call(player, player.media, 'play');
|
||||
}
|
||||
player.media.paused = false;
|
||||
|
||||
utils.dispatchEvent.call(player, player.media, 'playing');
|
||||
|
||||
// Poll to get playback progress
|
||||
player.timers.playing = window.setInterval(() => {
|
||||
utils.dispatchEvent.call(player, player.media, 'timeupdate');
|
||||
}, 50);
|
||||
|
||||
// Check duration again due to YouTube bug
|
||||
// https://github.com/sampotts/plyr/issues/374
|
||||
// https://code.google.com/p/gdata-issues/issues/detail?id=8690
|
||||
if (player.media.duration !== instance.getDuration()) {
|
||||
player.media.duration = instance.getDuration();
|
||||
utils.dispatchEvent.call(player, player.media, 'durationchange');
|
||||
}
|
||||
|
||||
// Get quality
|
||||
controls.setQualityMenu.call(player, instance.getAvailableQualityLevels());
|
||||
|
||||
break;
|
||||
|
||||
case 2:
|
||||
player.media.paused = true;
|
||||
|
||||
utils.dispatchEvent.call(player, player.media, 'pause');
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
utils.dispatchEvent.call(player, player.elements.container, 'statechange', false, {
|
||||
code: event.data,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default youtube;
|
4846
src/js/plyr.js
145
src/js/source.js
Normal file
@ -0,0 +1,145 @@
|
||||
// ==========================================================================
|
||||
// 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);
|
||||
}
|
||||
},
|
||||
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;
|
161
src/js/support.js
Normal file
@ -0,0 +1,161 @@
|
||||
// ==========================================================================
|
||||
// Plyr support checks
|
||||
// ==========================================================================
|
||||
|
||||
import utils from './utils';
|
||||
|
||||
// Check for feature support
|
||||
const support = {
|
||||
// Basic support
|
||||
audio: 'canPlayType' in document.createElement('audio'),
|
||||
video: 'canPlayType' in document.createElement('video'),
|
||||
|
||||
// Check for support
|
||||
// Basic functionality vs full UI
|
||||
check(type, provider, inline) {
|
||||
let api = false;
|
||||
let ui = false;
|
||||
const browser = utils.getBrowser();
|
||||
const playsInline = browser.isIPhone && inline && support.inline;
|
||||
|
||||
switch (`${provider}:${type}`) {
|
||||
case 'html5:video':
|
||||
api = support.video;
|
||||
ui = api && support.rangeInput && (!browser.isIPhone || playsInline);
|
||||
break;
|
||||
|
||||
case 'html5:audio':
|
||||
api = support.audio;
|
||||
ui = api && support.rangeInput;
|
||||
break;
|
||||
|
||||
case 'youtube:video':
|
||||
api = true;
|
||||
ui = support.rangeInput && (!browser.isIPhone || playsInline);
|
||||
break;
|
||||
|
||||
case 'vimeo:video':
|
||||
api = true;
|
||||
ui = support.rangeInput && !browser.isIPhone;
|
||||
break;
|
||||
|
||||
default:
|
||||
api = support.audio && support.video;
|
||||
ui = api && support.rangeInput;
|
||||
}
|
||||
|
||||
return {
|
||||
api,
|
||||
ui,
|
||||
};
|
||||
},
|
||||
|
||||
// Picture-in-picture support
|
||||
// Safari only currently
|
||||
pip: (() => {
|
||||
const browser = utils.getBrowser();
|
||||
return !browser.isIPhone && utils.is.function(utils.createElement('video').webkitSetPresentationMode);
|
||||
})(),
|
||||
|
||||
// Airplay support
|
||||
// Safari only currently
|
||||
airplay: utils.is.function(window.WebKitPlaybackTargetAvailabilityEvent),
|
||||
|
||||
// Inline playback support
|
||||
// https://webkit.org/blog/6784/new-video-policies-for-ios/
|
||||
inline: 'playsInline' in document.createElement('video'),
|
||||
|
||||
// Check for mime type support against a player instance
|
||||
// Credits: http://diveintohtml5.info/everything.html
|
||||
// Related: http://www.leanbackplayer.com/test/h5mt.html
|
||||
mime(type) {
|
||||
const { media } = this;
|
||||
|
||||
try {
|
||||
// Bail if no checking function
|
||||
if (!this.isHTML5 || !utils.is.function(media.canPlayType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Type specific checks
|
||||
if (this.isVideo) {
|
||||
switch (type) {
|
||||
case 'video/webm':
|
||||
return media.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/, '');
|
||||
|
||||
case 'video/mp4':
|
||||
return media.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, '');
|
||||
|
||||
case 'video/ogg':
|
||||
return media.canPlayType('video/ogg; codecs="theora"').replace(/no/, '');
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else if (this.isAudio) {
|
||||
switch (type) {
|
||||
case 'audio/mpeg':
|
||||
return media.canPlayType('audio/mpeg;').replace(/no/, '');
|
||||
|
||||
case 'audio/ogg':
|
||||
return media.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, '');
|
||||
|
||||
case 'audio/wav':
|
||||
return media.canPlayType('audio/wav; codecs="1"').replace(/no/, '');
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we got this far, we're stuffed
|
||||
return false;
|
||||
},
|
||||
|
||||
// Check for textTracks support
|
||||
textTracks: 'textTracks' in document.createElement('video'),
|
||||
|
||||
// Check for passive event listener support
|
||||
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
|
||||
// https://www.youtube.com/watch?v=NPM6172J22g
|
||||
passiveListeners: (() => {
|
||||
// Test via a getter in the options object to see if the passive property is accessed
|
||||
let supported = false;
|
||||
try {
|
||||
const options = Object.defineProperty({}, 'passive', {
|
||||
get() {
|
||||
supported = true;
|
||||
return null;
|
||||
},
|
||||
});
|
||||
window.addEventListener('test', null, options);
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return supported;
|
||||
})(),
|
||||
|
||||
// <input type="range"> Sliders
|
||||
rangeInput: (() => {
|
||||
const range = document.createElement('input');
|
||||
range.type = 'range';
|
||||
return range.type === 'range';
|
||||
})(),
|
||||
|
||||
// Touch
|
||||
// Remember a device can be moust + touch enabled
|
||||
touch: 'ontouchstart' in document.documentElement,
|
||||
|
||||
// Detect transitions support
|
||||
transitions: utils.transitionEndEvent !== false,
|
||||
|
||||
// Reduced motion iOS & MacOS setting
|
||||
// https://webkit.org/blog/7551/responsive-design-for-motion/
|
||||
reducedMotion: 'matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
};
|
||||
|
||||
export default support;
|
16
src/js/types.js
Normal file
@ -0,0 +1,16 @@
|
||||
// ==========================================================================
|
||||
// Plyr supported types and providers
|
||||
// ==========================================================================
|
||||
|
||||
export const providers = {
|
||||
html5: 'html5',
|
||||
youtube: 'youtube',
|
||||
vimeo: 'vimeo',
|
||||
};
|
||||
|
||||
export const types = {
|
||||
audio: 'audio',
|
||||
video: 'video',
|
||||
};
|
||||
|
||||
export default { providers, types };
|
348
src/js/ui.js
Normal file
@ -0,0 +1,348 @@
|
||||
// ==========================================================================
|
||||
// Plyr UI
|
||||
// ==========================================================================
|
||||
|
||||
import utils from './utils';
|
||||
import captions from './captions';
|
||||
import controls from './controls';
|
||||
import fullscreen from './fullscreen';
|
||||
import listeners from './listeners';
|
||||
|
||||
const ui = {
|
||||
addStyleHook() {
|
||||
utils.toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui);
|
||||
},
|
||||
|
||||
// Toggle native HTML5 media controls
|
||||
toggleNativeControls(toggle = false) {
|
||||
if (toggle && this.isHTML5) {
|
||||
this.media.setAttribute('controls', '');
|
||||
} else {
|
||||
this.media.removeAttribute('controls');
|
||||
}
|
||||
},
|
||||
|
||||
// Setup the UI
|
||||
build() {
|
||||
// Re-attach media element listeners
|
||||
// TODO: Use event bubbling
|
||||
listeners.media.call(this);
|
||||
|
||||
// Don't setup interface if no support
|
||||
if (!this.supported.ui) {
|
||||
this.debug.warn(`Basic support only for ${this.provider} ${this.type}`);
|
||||
|
||||
// Remove controls
|
||||
utils.removeElement.call(this, 'controls');
|
||||
|
||||
// Remove large play
|
||||
utils.removeElement.call(this, 'buttons.play');
|
||||
|
||||
// Restore native controls
|
||||
ui.toggleNativeControls.call(this, true);
|
||||
|
||||
// Bail
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject custom controls if not present
|
||||
if (!utils.is.element(this.elements.controls)) {
|
||||
// Inject custom controls
|
||||
controls.inject.call(this);
|
||||
|
||||
// Re-attach control listeners
|
||||
listeners.controls.call(this);
|
||||
}
|
||||
|
||||
// If there's no controls, bail
|
||||
if (!utils.is.element(this.elements.controls)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove native controls
|
||||
ui.toggleNativeControls.call(this);
|
||||
|
||||
// Setup fullscreen
|
||||
fullscreen.setup.call(this);
|
||||
|
||||
// Captions
|
||||
captions.setup.call(this);
|
||||
|
||||
// Reset volume
|
||||
this.volume = null;
|
||||
|
||||
// Reset mute state
|
||||
this.muted = null;
|
||||
|
||||
// Reset speed
|
||||
this.speed = null;
|
||||
|
||||
// Reset loop state
|
||||
this.loop = null;
|
||||
|
||||
// Reset quality options
|
||||
this.options.quality = [];
|
||||
|
||||
// Reset time display
|
||||
ui.timeUpdate.call(this);
|
||||
|
||||
// Update the UI
|
||||
ui.checkPlaying.call(this);
|
||||
|
||||
// Ready for API calls
|
||||
this.ready = true;
|
||||
|
||||
// Ready event at end of execution stack
|
||||
utils.dispatchEvent.call(this, this.media, 'ready');
|
||||
|
||||
// Set the title
|
||||
ui.setTitle.call(this);
|
||||
},
|
||||
|
||||
// Setup aria attribute for play and iframe title
|
||||
setTitle() {
|
||||
// Find the current text
|
||||
let label = this.config.i18n.play;
|
||||
|
||||
// If there's a media title set, use that for the label
|
||||
if (utils.is.string(this.config.title) && !utils.is.empty(this.config.title)) {
|
||||
label += `, ${this.config.title}`;
|
||||
|
||||
// Set container label
|
||||
this.elements.container.setAttribute('aria-label', this.config.title);
|
||||
}
|
||||
|
||||
// If there's a play button, set label
|
||||
if (utils.is.nodeList(this.elements.buttons.play)) {
|
||||
Array.from(this.elements.buttons.play).forEach(button => {
|
||||
button.setAttribute('aria-label', label);
|
||||
});
|
||||
}
|
||||
|
||||
// Set iframe title
|
||||
// https://github.com/sampotts/plyr/issues/124
|
||||
if (this.isEmbed) {
|
||||
const iframe = utils.getElement.call(this, 'iframe');
|
||||
|
||||
if (!utils.is.element(iframe)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Default to media type
|
||||
const title = !utils.is.empty(this.config.title) ? this.config.title : 'video';
|
||||
|
||||
iframe.setAttribute('title', this.config.i18n.frameTitle.replace('{title}', title));
|
||||
}
|
||||
},
|
||||
|
||||
// Check playing state
|
||||
checkPlaying() {
|
||||
// Class hooks
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.stopped, this.paused);
|
||||
|
||||
// Set aria state
|
||||
if (utils.is.nodeList(this.elements.buttons.play)) {
|
||||
Array.from(this.elements.buttons.play).forEach(button => utils.toggleState(button, this.playing));
|
||||
}
|
||||
|
||||
// Toggle controls
|
||||
this.toggleControls(!this.playing);
|
||||
},
|
||||
|
||||
// Check if media is loading
|
||||
checkLoading(event) {
|
||||
this.loading = [
|
||||
'stalled',
|
||||
'waiting',
|
||||
].includes(event.type);
|
||||
|
||||
// Clear timer
|
||||
clearTimeout(this.timers.loading);
|
||||
|
||||
// Timer to prevent flicker when seeking
|
||||
this.timers.loading = setTimeout(() => {
|
||||
// Toggle container class hook
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
|
||||
|
||||
// Show controls if loading, hide if done
|
||||
this.toggleControls(this.loading);
|
||||
}, this.loading ? 250 : 0);
|
||||
},
|
||||
|
||||
// Check if media failed to load
|
||||
checkFailed() {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/networkState
|
||||
this.failed = this.media.networkState === 3;
|
||||
|
||||
if (this.failed) {
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.loading, false);
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.error, true);
|
||||
}
|
||||
|
||||
// Clear timer
|
||||
clearTimeout(this.timers.failed);
|
||||
|
||||
// Timer to prevent flicker when seeking
|
||||
this.timers.loading = setTimeout(() => {
|
||||
// Toggle container class hook
|
||||
utils.toggleClass(this.elements.container, this.config.classNames.loading, this.loading);
|
||||
|
||||
// Show controls if loading, hide if done
|
||||
this.toggleControls(this.loading);
|
||||
}, this.loading ? 250 : 0);
|
||||
},
|
||||
|
||||
// Update volume UI and storage
|
||||
updateVolume() {
|
||||
if (!this.supported.ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update range
|
||||
if (utils.is.element(this.elements.inputs.volume)) {
|
||||
ui.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume);
|
||||
}
|
||||
|
||||
// Update mute state
|
||||
if (utils.is.element(this.elements.buttons.mute)) {
|
||||
utils.toggleState(this.elements.buttons.mute, this.muted || this.volume === 0);
|
||||
}
|
||||
},
|
||||
|
||||
// Update seek value and lower fill
|
||||
setRange(target, value = 0) {
|
||||
if (!utils.is.element(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
target.value = value;
|
||||
|
||||
// Webkit range fill
|
||||
controls.updateRangeFill.call(this, target);
|
||||
},
|
||||
|
||||
// Set <progress> value
|
||||
setProgress(target, input) {
|
||||
const value = utils.is.number(input) ? input : 0;
|
||||
const progress = utils.is.element(target) ? target : this.elements.display.buffer;
|
||||
|
||||
// Update value and label
|
||||
if (utils.is.element(progress)) {
|
||||
progress.value = value;
|
||||
|
||||
// Update text label inside
|
||||
const label = progress.getElementsByTagName('span')[0];
|
||||
if (utils.is.element(label)) {
|
||||
label.childNodes[0].nodeValue = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Update <progress> elements
|
||||
updateProgress(event) {
|
||||
if (!this.supported.ui || !utils.is.event(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let value = 0;
|
||||
|
||||
if (event) {
|
||||
switch (event.type) {
|
||||
// Video playing
|
||||
case 'timeupdate':
|
||||
case 'seeking':
|
||||
value = utils.getPercentage(this.currentTime, this.duration);
|
||||
|
||||
// Set seek range value only if it's a 'natural' time event
|
||||
if (event.type === 'timeupdate') {
|
||||
ui.setRange.call(this, this.elements.inputs.seek, value);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// Check buffer status
|
||||
case 'playing':
|
||||
case 'progress':
|
||||
value = (() => {
|
||||
const { buffered } = this.media;
|
||||
|
||||
if (buffered && buffered.length) {
|
||||
// HTML5
|
||||
return utils.getPercentage(buffered.end(0), this.duration);
|
||||
} else if (utils.is.number(buffered)) {
|
||||
// YouTube returns between 0 and 1
|
||||
return buffered * 100;
|
||||
}
|
||||
|
||||
return 0;
|
||||
})();
|
||||
|
||||
ui.setProgress.call(this, this.elements.display.buffer, value);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Update the displayed time
|
||||
updateTimeDisplay(target = null, time = 0, inverted = false) {
|
||||
// Bail if there's no element to display or the value isn't a number
|
||||
if (!utils.is.element(target) || !utils.is.number(time)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always display hours if duration is over an hour
|
||||
const displayHours = utils.getHours(this.duration) > 0;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
target.textContent = utils.formatTime(time, displayHours, inverted);
|
||||
},
|
||||
|
||||
// Handle time change event
|
||||
timeUpdate(event) {
|
||||
// Only invert if only one time element is displayed and used for both duration and currentTime
|
||||
const invert = !utils.is.element(this.elements.display.duration) && this.config.invertTime;
|
||||
|
||||
// Duration
|
||||
ui.updateTimeDisplay.call(this, this.elements.display.currentTime, invert ? this.duration - this.currentTime : this.currentTime, invert);
|
||||
|
||||
// Ignore updates while seeking
|
||||
if (event && event.type === 'timeupdate' && this.media.seeking) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Playing progress
|
||||
ui.updateProgress.call(this, event);
|
||||
},
|
||||
|
||||
// Show the duration on metadataloaded
|
||||
durationUpdate() {
|
||||
if (!this.supported.ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's a spot to display duration
|
||||
const hasDuration = utils.is.element(this.elements.display.duration);
|
||||
|
||||
// If there's only one time display, display duration there
|
||||
if (!hasDuration && this.config.displayDuration && this.paused) {
|
||||
ui.updateTimeDisplay.call(this, this.elements.display.currentTime, this.duration);
|
||||
}
|
||||
|
||||
// If there's a duration element, update content
|
||||
if (hasDuration) {
|
||||
ui.updateTimeDisplay.call(this, this.elements.display.duration, this.duration);
|
||||
}
|
||||
|
||||
// Update the tooltip (if visible)
|
||||
controls.updateSeekTooltip.call(this);
|
||||
},
|
||||
};
|
||||
|
||||
export default ui;
|
790
src/js/utils.js
Normal file
@ -0,0 +1,790 @@
|
||||
// ==========================================================================
|
||||
// Plyr utils
|
||||
// ==========================================================================
|
||||
|
||||
import support from './support';
|
||||
import { providers } from './types';
|
||||
|
||||
const utils = {
|
||||
// Check variable types
|
||||
is: {
|
||||
plyr(input) {
|
||||
return this.instanceof(input, Plyr);
|
||||
},
|
||||
object(input) {
|
||||
return this.getConstructor(input) === Object;
|
||||
},
|
||||
number(input) {
|
||||
return this.getConstructor(input) === Number && !Number.isNaN(input);
|
||||
},
|
||||
string(input) {
|
||||
return this.getConstructor(input) === String;
|
||||
},
|
||||
boolean(input) {
|
||||
return this.getConstructor(input) === Boolean;
|
||||
},
|
||||
function(input) {
|
||||
return this.getConstructor(input) === Function;
|
||||
},
|
||||
array(input) {
|
||||
return !this.nullOrUndefined(input) && Array.isArray(input);
|
||||
},
|
||||
weakMap(input) {
|
||||
return this.instanceof(input, window.WeakMap);
|
||||
},
|
||||
nodeList(input) {
|
||||
return this.instanceof(input, window.NodeList);
|
||||
},
|
||||
element(input) {
|
||||
return this.instanceof(input, window.Element);
|
||||
},
|
||||
textNode(input) {
|
||||
return this.getConstructor(input) === Text;
|
||||
},
|
||||
event(input) {
|
||||
return this.instanceof(input, window.Event);
|
||||
},
|
||||
cue(input) {
|
||||
return this.instanceof(input, window.TextTrackCue) || this.instanceof(input, window.VTTCue);
|
||||
},
|
||||
track(input) {
|
||||
return this.instanceof(input, TextTrack) || (!this.nullOrUndefined(input) && this.string(input.kind));
|
||||
},
|
||||
url(input) {
|
||||
return !this.nullOrUndefined(input) && /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/.test(input);
|
||||
},
|
||||
nullOrUndefined(input) {
|
||||
return input === null || typeof input === 'undefined';
|
||||
},
|
||||
empty(input) {
|
||||
return (
|
||||
this.nullOrUndefined(input) ||
|
||||
((this.string(input) || this.array(input) || this.nodeList(input)) && !input.length) ||
|
||||
(this.object(input) && !Object.keys(input).length)
|
||||
);
|
||||
},
|
||||
instanceof(input, constructor) {
|
||||
return Boolean(input && constructor && input instanceof constructor);
|
||||
},
|
||||
getConstructor(input) {
|
||||
return !this.nullOrUndefined(input) ? input.constructor : null;
|
||||
},
|
||||
},
|
||||
|
||||
// Unfortunately, due to mixed support, UA sniffing is required
|
||||
getBrowser() {
|
||||
return {
|
||||
isIE: /* @cc_on!@ */ false || !!document.documentMode,
|
||||
isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
|
||||
isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
|
||||
isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform),
|
||||
};
|
||||
},
|
||||
|
||||
// Load an external script
|
||||
loadScript(url, callback) {
|
||||
const current = document.querySelector(`script[src="${url}"]`);
|
||||
|
||||
// Check script is not already referenced, if so wait for load
|
||||
if (current !== null) {
|
||||
current.callbacks = current.callbacks || [];
|
||||
current.callbacks.push(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the element
|
||||
const element = document.createElement('script');
|
||||
|
||||
// Callback queue
|
||||
element.callbacks = element.callbacks || [];
|
||||
element.callbacks.push(callback);
|
||||
|
||||
// Bind callback
|
||||
if (utils.is.function(callback)) {
|
||||
element.addEventListener(
|
||||
'load',
|
||||
event => {
|
||||
element.callbacks.forEach(cb => cb.call(null, event));
|
||||
element.callbacks = null;
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
// Set the URL after binding callback
|
||||
element.src = url;
|
||||
|
||||
// Inject
|
||||
const first = document.getElementsByTagName('script')[0];
|
||||
first.parentNode.insertBefore(element, first);
|
||||
},
|
||||
|
||||
// Load an external SVG sprite
|
||||
loadSprite(url, id) {
|
||||
if (!utils.is.string(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prefix = 'cache-';
|
||||
const hasId = utils.is.string(id);
|
||||
let isCached = false;
|
||||
|
||||
function updateSprite(data) {
|
||||
// Inject content
|
||||
this.innerHTML = data;
|
||||
|
||||
// Inject the SVG to the body
|
||||
document.body.insertBefore(this, document.body.childNodes[0]);
|
||||
}
|
||||
|
||||
// Only load once
|
||||
if (!hasId || !document.querySelectorAll(`#${id}`).length) {
|
||||
// Create container
|
||||
const container = document.createElement('div');
|
||||
utils.toggleHidden(container, true);
|
||||
|
||||
if (hasId) {
|
||||
container.setAttribute('id', id);
|
||||
}
|
||||
|
||||
// Check in cache
|
||||
if (support.storage) {
|
||||
const cached = window.localStorage.getItem(prefix + id);
|
||||
isCached = cached !== null;
|
||||
|
||||
if (isCached) {
|
||||
const data = JSON.parse(cached);
|
||||
updateSprite.call(container, data.content);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the sprite
|
||||
fetch(url)
|
||||
.then(response => (response.ok ? response.text() : null))
|
||||
.then(text => {
|
||||
if (text === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (support.storage) {
|
||||
window.localStorage.setItem(
|
||||
prefix + id,
|
||||
JSON.stringify({
|
||||
content: text,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
updateSprite.call(container, text);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
|
||||
// Generate a random ID
|
||||
generateId(prefix) {
|
||||
return `${prefix}-${Math.floor(Math.random() * 10000)}`;
|
||||
},
|
||||
|
||||
// Determine if we're in an iframe
|
||||
inFrame() {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
// Wrap an element
|
||||
wrap(elements, wrapper) {
|
||||
// Convert `elements` to an array, if necessary.
|
||||
const targets = elements.length ? elements : [elements];
|
||||
|
||||
// Loops backwards to prevent having to clone the wrapper on the
|
||||
// first element (see `child` below).
|
||||
Array.from(targets)
|
||||
.reverse()
|
||||
.forEach((element, index) => {
|
||||
const child = index > 0 ? wrapper.cloneNode(true) : wrapper;
|
||||
|
||||
// Cache the current parent and sibling.
|
||||
const parent = element.parentNode;
|
||||
const sibling = element.nextSibling;
|
||||
|
||||
// Wrap the element (is automatically removed from its current
|
||||
// parent).
|
||||
child.appendChild(element);
|
||||
|
||||
// If the element had a sibling, insert the wrapper before
|
||||
// the sibling to maintain the HTML structure; otherwise, just
|
||||
// append it to the parent.
|
||||
if (sibling) {
|
||||
parent.insertBefore(child, sibling);
|
||||
} else {
|
||||
parent.appendChild(child);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Create a DocumentFragment
|
||||
createElement(type, attributes, text) {
|
||||
// Create a new <element>
|
||||
const element = document.createElement(type);
|
||||
|
||||
// Set all passed attributes
|
||||
if (utils.is.object(attributes)) {
|
||||
utils.setAttributes(element, attributes);
|
||||
}
|
||||
|
||||
// Add text node
|
||||
if (utils.is.string(text)) {
|
||||
element.textContent = text;
|
||||
}
|
||||
|
||||
// Return built element
|
||||
return element;
|
||||
},
|
||||
|
||||
// Inaert an element after another
|
||||
insertAfter(element, target) {
|
||||
target.parentNode.insertBefore(element, target.nextSibling);
|
||||
},
|
||||
|
||||
// Insert a DocumentFragment
|
||||
insertElement(type, parent, attributes, text) {
|
||||
// Inject the new <element>
|
||||
parent.appendChild(utils.createElement(type, attributes, text));
|
||||
},
|
||||
|
||||
// Remove an element
|
||||
removeElement(element) {
|
||||
if (!utils.is.element(element) || !utils.is.element(element.parentNode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
element.parentNode.removeChild(element);
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
// Remove all child elements
|
||||
emptyElement(element) {
|
||||
let { length } = element.childNodes;
|
||||
|
||||
while (length > 0) {
|
||||
element.removeChild(element.lastChild);
|
||||
length -= 1;
|
||||
}
|
||||
},
|
||||
|
||||
// Replace element
|
||||
replaceElement(newChild, oldChild) {
|
||||
if (!utils.is.element(oldChild) || !utils.is.element(oldChild.parentNode) || !utils.is.element(newChild)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
oldChild.parentNode.replaceChild(newChild, oldChild);
|
||||
|
||||
return newChild;
|
||||
},
|
||||
|
||||
// Set attributes
|
||||
setAttributes(element, attributes) {
|
||||
if (!utils.is.element(element) || utils.is.empty(attributes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(attributes).forEach(key => {
|
||||
element.setAttribute(key, attributes[key]);
|
||||
});
|
||||
},
|
||||
|
||||
// Get an attribute object from a string selector
|
||||
getAttributesFromSelector(sel, existingAttributes) {
|
||||
// For example:
|
||||
// '.test' to { class: 'test' }
|
||||
// '#test' to { id: 'test' }
|
||||
// '[data-test="test"]' to { 'data-test': 'test' }
|
||||
|
||||
if (!utils.is.string(sel) || utils.is.empty(sel)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const attributes = {};
|
||||
const existing = existingAttributes;
|
||||
|
||||
sel.split(',').forEach(s => {
|
||||
// Remove whitespace
|
||||
const selector = s.trim();
|
||||
const className = selector.replace('.', '');
|
||||
const stripped = selector.replace(/[[\]]/g, '');
|
||||
|
||||
// Get the parts and value
|
||||
const parts = stripped.split('=');
|
||||
const key = parts[0];
|
||||
const value = parts.length > 1 ? parts[1].replace(/["']/g, '') : '';
|
||||
|
||||
// Get the first character
|
||||
const start = selector.charAt(0);
|
||||
|
||||
switch (start) {
|
||||
case '.':
|
||||
// Add to existing classname
|
||||
if (utils.is.object(existing) && utils.is.string(existing.class)) {
|
||||
existing.class += ` ${className}`;
|
||||
}
|
||||
|
||||
attributes.class = className;
|
||||
break;
|
||||
|
||||
case '#':
|
||||
// ID selector
|
||||
attributes.id = selector.replace('#', '');
|
||||
break;
|
||||
|
||||
case '[':
|
||||
// Attribute selector
|
||||
attributes[key] = value;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return attributes;
|
||||
},
|
||||
|
||||
// Toggle class on an element
|
||||
toggleClass(element, className, toggle) {
|
||||
if (utils.is.element(element)) {
|
||||
const contains = element.classList.contains(className);
|
||||
|
||||
element.classList[toggle ? 'add' : 'remove'](className);
|
||||
|
||||
return (toggle && !contains) || (!toggle && contains);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
// Has class name
|
||||
hasClass(element, className) {
|
||||
return utils.is.element(element) && element.classList.contains(className);
|
||||
},
|
||||
|
||||
// Toggle hidden attribute on an element
|
||||
toggleHidden(element, toggle) {
|
||||
if (!utils.is.element(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (toggle) {
|
||||
element.setAttribute('hidden', '');
|
||||
} else {
|
||||
element.removeAttribute('hidden');
|
||||
}
|
||||
},
|
||||
|
||||
// Element matches selector
|
||||
matches(element, selector) {
|
||||
const prototype = { Element };
|
||||
|
||||
function match() {
|
||||
return Array.from(document.querySelectorAll(selector)).includes(this);
|
||||
}
|
||||
|
||||
const matches = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;
|
||||
|
||||
return matches.call(element, selector);
|
||||
},
|
||||
|
||||
// Find all elements
|
||||
getElements(selector) {
|
||||
return this.elements.container.querySelectorAll(selector);
|
||||
},
|
||||
|
||||
// Find a single element
|
||||
getElement(selector) {
|
||||
return this.elements.container.querySelector(selector);
|
||||
},
|
||||
|
||||
// Find the UI controls and store references in custom controls
|
||||
// TODO: Allow settings menus with custom controls
|
||||
findElements() {
|
||||
try {
|
||||
this.elements.controls = utils.getElement.call(this, this.config.selectors.controls.wrapper);
|
||||
|
||||
// Buttons
|
||||
this.elements.buttons = {
|
||||
play: utils.getElements.call(this, this.config.selectors.buttons.play),
|
||||
pause: utils.getElement.call(this, this.config.selectors.buttons.pause),
|
||||
restart: utils.getElement.call(this, this.config.selectors.buttons.restart),
|
||||
rewind: utils.getElement.call(this, this.config.selectors.buttons.rewind),
|
||||
forward: utils.getElement.call(this, this.config.selectors.buttons.forward),
|
||||
mute: utils.getElement.call(this, this.config.selectors.buttons.mute),
|
||||
pip: utils.getElement.call(this, this.config.selectors.buttons.pip),
|
||||
airplay: utils.getElement.call(this, this.config.selectors.buttons.airplay),
|
||||
settings: utils.getElement.call(this, this.config.selectors.buttons.settings),
|
||||
captions: utils.getElement.call(this, this.config.selectors.buttons.captions),
|
||||
fullscreen: utils.getElement.call(this, this.config.selectors.buttons.fullscreen),
|
||||
};
|
||||
|
||||
// Progress
|
||||
this.elements.progress = utils.getElement.call(this, this.config.selectors.progress);
|
||||
|
||||
// Inputs
|
||||
this.elements.inputs = {
|
||||
seek: utils.getElement.call(this, this.config.selectors.inputs.seek),
|
||||
volume: utils.getElement.call(this, this.config.selectors.inputs.volume),
|
||||
};
|
||||
|
||||
// Display
|
||||
this.elements.display = {
|
||||
buffer: utils.getElement.call(this, this.config.selectors.display.buffer),
|
||||
duration: utils.getElement.call(this, this.config.selectors.display.duration),
|
||||
currentTime: utils.getElement.call(this, this.config.selectors.display.currentTime),
|
||||
};
|
||||
|
||||
// Seek tooltip
|
||||
if (utils.is.element(this.elements.progress)) {
|
||||
this.elements.display.seekTooltip = this.elements.progress.querySelector(`.${this.config.classNames.tooltip}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
// Log it
|
||||
this.debug.warn('It looks like there is a problem with your custom controls HTML', error);
|
||||
|
||||
// Restore native video controls
|
||||
this.toggleNativeControls(true);
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Get the focused element
|
||||
getFocusElement() {
|
||||
let focused = document.activeElement;
|
||||
|
||||
if (!focused || focused === document.body) {
|
||||
focused = null;
|
||||
} else {
|
||||
focused = document.querySelector(':focus');
|
||||
}
|
||||
|
||||
return focused;
|
||||
},
|
||||
|
||||
// Trap focus inside container
|
||||
trapFocus() {
|
||||
const focusable = utils.getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
|
||||
const first = focusable[0];
|
||||
const last = focusable[focusable.length - 1];
|
||||
|
||||
utils.on(
|
||||
this.elements.container,
|
||||
'keydown',
|
||||
event => {
|
||||
// Bail if not tab key or not fullscreen
|
||||
if (event.key !== 'Tab' || event.keyCode !== 9 || !this.fullscreen.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current focused element
|
||||
const focused = utils.getFocusElement();
|
||||
|
||||
if (focused === last && !event.shiftKey) {
|
||||
// Move focus to first element that can be tabbed if Shift isn't used
|
||||
first.focus();
|
||||
event.preventDefault();
|
||||
} else if (focused === first && event.shiftKey) {
|
||||
// Move focus to last element that can be tabbed if Shift is used
|
||||
last.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
|
||||
// Toggle event listener
|
||||
toggleListener(elements, event, callback, toggle, passive, capture) {
|
||||
// Bail if no elements
|
||||
if (utils.is.nullOrUndefined(elements)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If a nodelist is passed, call itself on each node
|
||||
if (utils.is.nodeList(elements)) {
|
||||
// Create listener for each node
|
||||
Array.from(elements).forEach(element => {
|
||||
if (element instanceof Node) {
|
||||
utils.toggleListener.call(null, element, event, callback, toggle, passive, capture);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow multiple events
|
||||
const events = event.split(' ');
|
||||
|
||||
// Build options
|
||||
// Default to just capture boolean
|
||||
let options = utils.is.boolean(capture) ? capture : false;
|
||||
|
||||
// If passive events listeners are supported
|
||||
if (support.passiveListeners) {
|
||||
options = {
|
||||
// Whether the listener can be passive (i.e. default never prevented)
|
||||
passive: utils.is.boolean(passive) ? passive : true,
|
||||
// Whether the listener is a capturing listener or not
|
||||
capture: utils.is.boolean(capture) ? capture : false,
|
||||
};
|
||||
}
|
||||
|
||||
// If a single node is passed, bind the event listener
|
||||
events.forEach(type => {
|
||||
elements[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options);
|
||||
});
|
||||
},
|
||||
|
||||
// Bind event handler
|
||||
on(element, events, callback, passive, capture) {
|
||||
utils.toggleListener(element, events, callback, true, passive, capture);
|
||||
},
|
||||
|
||||
// Unbind event handler
|
||||
off(element, events, callback, passive, capture) {
|
||||
utils.toggleListener(element, events, callback, false, passive, capture);
|
||||
},
|
||||
|
||||
// Trigger event
|
||||
dispatchEvent(element, type, bubbles, detail) {
|
||||
// Bail if no element
|
||||
if (!element || !type) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create and dispatch the event
|
||||
const event = new CustomEvent(type, {
|
||||
bubbles: utils.is.boolean(bubbles) ? bubbles : false,
|
||||
detail: Object.assign({}, detail, {
|
||||
plyr: this instanceof Plyr ? this : null,
|
||||
}),
|
||||
});
|
||||
|
||||
// Dispatch the event
|
||||
element.dispatchEvent(event);
|
||||
},
|
||||
|
||||
// Toggle aria-pressed state on a toggle button
|
||||
// http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles
|
||||
toggleState(element, input) {
|
||||
// Bail if no target
|
||||
if (!utils.is.element(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get state
|
||||
const pressed = element.getAttribute('aria-pressed') === 'true';
|
||||
const state = utils.is.boolean(input) ? input : !pressed;
|
||||
|
||||
// Set the attribute on target
|
||||
element.setAttribute('aria-pressed', state);
|
||||
},
|
||||
|
||||
// Get percentage
|
||||
getPercentage(current, max) {
|
||||
if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
|
||||
return 0;
|
||||
}
|
||||
return (current / max * 100).toFixed(2);
|
||||
},
|
||||
|
||||
// Time helpers
|
||||
getHours(value) { return parseInt((value / 60 / 60) % 60, 10); },
|
||||
getMinutes(value) { return parseInt((value / 60) % 60, 10); },
|
||||
getSeconds(value) { return parseInt(value % 60, 10); },
|
||||
|
||||
// Format time to UI friendly string
|
||||
formatTime(time = 0, displayHours = false, inverted = false) {
|
||||
// Bail if the value isn't a number
|
||||
if (!utils.is.number(time)) {
|
||||
return this.formatTime(null, displayHours, inverted);
|
||||
}
|
||||
|
||||
// Format time component to add leading zero
|
||||
const format = value => `0${value}`.slice(-2);
|
||||
|
||||
// Breakdown to hours, mins, secs
|
||||
let hours = this.getHours(time);
|
||||
const mins = this.getMinutes(time);
|
||||
const secs = this.getSeconds(time);
|
||||
|
||||
// Do we need to display hours?
|
||||
if (displayHours || hours > 0) {
|
||||
hours = `${hours}:`;
|
||||
} else {
|
||||
hours = '';
|
||||
}
|
||||
|
||||
// Render
|
||||
return `${inverted ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;
|
||||
},
|
||||
|
||||
// Deep extend destination object with N more objects
|
||||
extend(target = {}, ...sources) {
|
||||
if (!sources.length) {
|
||||
return target;
|
||||
}
|
||||
|
||||
const source = sources.shift();
|
||||
|
||||
if (!utils.is.object(source)) {
|
||||
return target;
|
||||
}
|
||||
|
||||
Object.keys(source).forEach(key => {
|
||||
if (utils.is.object(source[key])) {
|
||||
if (!Object.keys(target).includes(key)) {
|
||||
Object.assign(target, { [key]: {} });
|
||||
}
|
||||
|
||||
utils.extend(target[key], source[key]);
|
||||
} else {
|
||||
Object.assign(target, { [key]: source[key] });
|
||||
}
|
||||
});
|
||||
|
||||
return utils.extend(target, ...sources);
|
||||
},
|
||||
|
||||
// Get the provider for a given URL
|
||||
getProviderByUrl(url) {
|
||||
// YouTube
|
||||
if (/^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+$/.test(url)) {
|
||||
return providers.youtube;
|
||||
}
|
||||
|
||||
// Vimeo
|
||||
if (/^https?:\/\/player.vimeo.com\/video\/\d{8,}(?=\b|\/)/.test(url)) {
|
||||
return providers.vimeo;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
// Parse YouTube ID from URL
|
||||
parseYouTubeId(url) {
|
||||
if (utils.is.empty(url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
|
||||
return url.match(regex) ? RegExp.$2 : url;
|
||||
},
|
||||
|
||||
// Parse Vimeo ID from URL
|
||||
parseVimeoId(url) {
|
||||
if (utils.is.empty(url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (utils.is.number(Number(url))) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const regex = /^.*(vimeo.com\/|video\/)(\d+).*/;
|
||||
return url.match(regex) ? RegExp.$2 : url;
|
||||
},
|
||||
|
||||
// Convert a URL to a location object
|
||||
parseUrl(url) {
|
||||
const parser = document.createElement('a');
|
||||
parser.href = url;
|
||||
return parser;
|
||||
},
|
||||
|
||||
// Get URL query parameters
|
||||
getUrlParams(input) {
|
||||
let search = input;
|
||||
|
||||
// Parse URL if needed
|
||||
if (input.startsWith('http://') || input.startsWith('https://')) {
|
||||
({ search } = this.parseUrl(input));
|
||||
}
|
||||
|
||||
if (this.is.empty(search)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hashes = search.slice(search.indexOf('?') + 1).split('&');
|
||||
|
||||
return hashes.reduce((params, hash) => {
|
||||
const [
|
||||
key,
|
||||
val,
|
||||
] = hash.split('=');
|
||||
|
||||
return Object.assign(params, { [key]: decodeURIComponent(val) });
|
||||
}, {});
|
||||
},
|
||||
|
||||
// Convert object to URL parameters
|
||||
buildUrlParams(input) {
|
||||
if (!utils.is.object(input)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return Object.keys(input)
|
||||
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(input[key])}`)
|
||||
.join('&');
|
||||
},
|
||||
|
||||
// Remove HTML from a string
|
||||
stripHTML(source) {
|
||||
const fragment = document.createDocumentFragment();
|
||||
const element = document.createElement('div');
|
||||
fragment.appendChild(element);
|
||||
element.innerHTML = source;
|
||||
return fragment.firstChild.innerText;
|
||||
},
|
||||
|
||||
// Get aspect ratio for dimensions
|
||||
getAspectRatio(width, height) {
|
||||
const getRatio = (w, h) => (h === 0 ? w : getRatio(h, w % h));
|
||||
const ratio = getRatio(width, height);
|
||||
return `${width / ratio}:${height / ratio}`;
|
||||
},
|
||||
|
||||
// Get the transition end event
|
||||
get transitionEndEvent() {
|
||||
const element = document.createElement('span');
|
||||
|
||||
const events = {
|
||||
WebkitTransition: 'webkitTransitionEnd',
|
||||
MozTransition: 'transitionend',
|
||||
OTransition: 'oTransitionEnd otransitionend',
|
||||
transition: 'transitionend',
|
||||
};
|
||||
|
||||
const type = Object.keys(events).find(event => element.style[event] !== undefined);
|
||||
|
||||
return utils.is.string(type) ? events[type] : false;
|
||||
},
|
||||
|
||||
// Force repaint of element
|
||||
repaint(element) {
|
||||
window.setTimeout(() => {
|
||||
utils.toggleHidden(element, true);
|
||||
element.offsetHeight; // eslint-disable-line
|
||||
utils.toggleHidden(element, false);
|
||||
}, 0);
|
||||
},
|
||||
};
|
||||
|
||||
export default utils;
|
@ -1,29 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Plyr mixins
|
||||
// https://github.com/selz/plyr
|
||||
// ==========================================================================
|
||||
|
||||
// <input type="range"> styling
|
||||
.plyr-range-track() {
|
||||
height: @plyr-range-track-height;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: (@plyr-range-track-height / 2);
|
||||
user-select: none;
|
||||
}
|
||||
.plyr-range-thumb() {
|
||||
position: relative;
|
||||
height: @plyr-range-thumb-height;
|
||||
width: @plyr-range-thumb-width;
|
||||
background: @plyr-range-thumb-bg;
|
||||
border: @plyr-range-thumb-border;
|
||||
border-radius: 100%;
|
||||
transition: background .2s ease, border .2s ease, transform .2s ease;
|
||||
box-shadow: @plyr-range-thumb-shadow;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.plyr-range-thumb-active() {
|
||||
background: @plyr-range-thumb-active-bg;
|
||||
border-color: @plyr-range-thumb-active-border-color;
|
||||
transform: scale(@plyr-range-thumb-active-scale);
|
||||
}
|
@ -1,752 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Plyr styles
|
||||
// https://github.com/selz/plyr
|
||||
// ==========================================================================
|
||||
|
||||
@import "variables";
|
||||
@import "mixins";
|
||||
|
||||
// Animation
|
||||
// ---------------------------------------
|
||||
@keyframes plyr-progress {
|
||||
to { background-position: @plyr-progress-loading-size 0; }
|
||||
}
|
||||
|
||||
// Styles
|
||||
// -------------------------------
|
||||
// Base
|
||||
.plyr {
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
min-width: 200px;
|
||||
font-family: @plyr-font-family;
|
||||
direction: ltr;
|
||||
|
||||
& when (@plyr-border-box = true) {
|
||||
// border-box everything
|
||||
// http://paulirish.com/2012/box-sizing-border-box-ftw/
|
||||
&,
|
||||
*,
|
||||
*::after,
|
||||
*::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
& when (@plyr-touch-action = true) {
|
||||
// Fix 300ms delay
|
||||
a, button, input, label {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
}
|
||||
|
||||
// Focus
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
// Media elements
|
||||
video,
|
||||
audio {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
// Range inputs
|
||||
// Specificity is for bootstrap compatibility
|
||||
input[type='range'] {
|
||||
display: block;
|
||||
height: (@plyr-range-thumb-height * @plyr-range-thumb-active-scale);
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: transparent;
|
||||
|
||||
// WebKit
|
||||
&::-webkit-slider-runnable-track {
|
||||
.plyr-range-track();
|
||||
}
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
margin-top: -((@plyr-range-thumb-height - @plyr-range-track-height) / 2);
|
||||
.plyr-range-thumb();
|
||||
}
|
||||
|
||||
// Mozilla
|
||||
&::-moz-range-track {
|
||||
.plyr-range-track();
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
.plyr-range-thumb();
|
||||
}
|
||||
|
||||
// Microsoft
|
||||
&::-ms-track {
|
||||
height: @plyr-range-track-height;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: transparent;
|
||||
}
|
||||
&::-ms-fill-upper {
|
||||
.plyr-range-track();
|
||||
}
|
||||
&::-ms-fill-lower {
|
||||
.plyr-range-track();
|
||||
background: @plyr-range-selected-bg;
|
||||
}
|
||||
&::-ms-thumb {
|
||||
.plyr-range-thumb();
|
||||
// For some reason, Edge uses the -webkit margin above
|
||||
margin-top: 0;
|
||||
}
|
||||
&::-ms-tooltip {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Focus styles
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
&::-moz-focus-outer {
|
||||
border: 0;
|
||||
}
|
||||
&.tab-focus:focus {
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
// Pressed styles
|
||||
&:active {
|
||||
&::-webkit-slider-thumb {
|
||||
.plyr-range-thumb-active();
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
.plyr-range-thumb-active();
|
||||
}
|
||||
&::-ms-thumb {
|
||||
.plyr-range-thumb-active();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Video range inputs
|
||||
.plyr--video input[type='range'].tab-focus:focus {
|
||||
outline: 1px dotted fade(@plyr-video-control-color, 50%);
|
||||
}
|
||||
|
||||
// Audio range inputs
|
||||
.plyr--audio input[type='range'].tab-focus:focus {
|
||||
outline: 1px dotted fade(@plyr-audio-control-color, 50%);
|
||||
}
|
||||
|
||||
// Screen reader only elements
|
||||
.plyr__sr-only {
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
overflow: hidden;
|
||||
|
||||
// !important is not always needed
|
||||
& when (@plyr-sr-only-important = true) {
|
||||
position: absolute !important;
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
height: 1px !important;
|
||||
width: 1px !important;
|
||||
}
|
||||
& when (@plyr-sr-only-important = false) {
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
// Video
|
||||
.plyr__video-wrapper {
|
||||
position: relative;
|
||||
background: #000;
|
||||
border-radius: inherit;
|
||||
|
||||
// Cleaner radius, also forces iframe radius
|
||||
-webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC);
|
||||
}
|
||||
|
||||
// Container for embeds
|
||||
.plyr__video-embed {
|
||||
padding-bottom: 56.25%; /* 16:9 */
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// Vimeo hack
|
||||
> div {
|
||||
position: relative;
|
||||
padding-bottom: 200%;
|
||||
transform: translateY(-35.95%);
|
||||
}
|
||||
}
|
||||
// To allow mouse events to be captured if full support
|
||||
.plyr .plyr__video-embed iframe {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Captions
|
||||
// --------------------------------------------------------------
|
||||
// Hide default captions
|
||||
.plyr video::-webkit-media-text-track-container {
|
||||
display: none;
|
||||
}
|
||||
.plyr__captions {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: (@plyr-control-spacing * 2);
|
||||
transform: translateY(-(@plyr-control-spacing * 4));
|
||||
transition: transform .3s ease;
|
||||
color: @plyr-captions-color;
|
||||
font-size: @plyr-font-size-captions-base;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
|
||||
span {
|
||||
border-radius: 2px;
|
||||
padding: floor(@plyr-control-spacing / 3) @plyr-control-spacing;
|
||||
background: @plyr-captions-bg;
|
||||
box-decoration-break: clone;
|
||||
line-height: 150%;
|
||||
}
|
||||
span:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: @plyr-bp-screen-md) {
|
||||
font-size: @plyr-font-size-captions-medium;
|
||||
}
|
||||
}
|
||||
.plyr--captions-active .plyr__captions {
|
||||
display: block;
|
||||
}
|
||||
.plyr--fullscreen-active .plyr__captions {
|
||||
font-size: @plyr-font-size-captions-large;
|
||||
}
|
||||
.plyr--hide-controls .plyr__captions {
|
||||
transform: translateY(-(@plyr-control-spacing * 1.5));
|
||||
}
|
||||
|
||||
// Controls
|
||||
// --------------------------------------------------------------
|
||||
// Hide native controls
|
||||
.plyr ::-webkit-media-controls {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Playback controls
|
||||
.plyr__controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
|
||||
// Spacing
|
||||
> button,
|
||||
.plyr__progress,
|
||||
.plyr__time {
|
||||
margin-left: (@plyr-control-spacing / 2);
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
.plyr__volume {
|
||||
margin-left: (@plyr-control-spacing / 2);
|
||||
}
|
||||
[data-plyr="pause"] {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
// Buttons
|
||||
button {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
overflow: visible; // IE11
|
||||
vertical-align: middle;
|
||||
padding: @plyr-control-padding;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: background .3s ease, color .3s ease, opacity .3s ease;
|
||||
color: inherit;
|
||||
|
||||
svg {
|
||||
width: @plyr-control-icon-size;
|
||||
height: @plyr-control-icon-size;
|
||||
display: block;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
// Default focus
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide toggle icons by default
|
||||
.icon--exit-fullscreen,
|
||||
.icon--muted,
|
||||
.icon--captions-on {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: @plyr-bp-screen-sm) {
|
||||
> button,
|
||||
.plyr__progress,
|
||||
.plyr__time {
|
||||
margin-left: @plyr-control-spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Hide controls
|
||||
.plyr--hide-controls .plyr__controls {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Video controls
|
||||
.plyr--video .plyr__controls {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: (@plyr-control-spacing * 5) @plyr-control-spacing @plyr-control-spacing;
|
||||
background: linear-gradient(fade(@plyr-video-controls-bg, 0%), fade(@plyr-video-controls-bg, 50%));
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
color: @plyr-video-control-color;
|
||||
transition: opacity .3s ease;
|
||||
|
||||
button {
|
||||
// Hover and tab focus
|
||||
&.tab-focus:focus,
|
||||
&:hover {
|
||||
background: @plyr-video-control-bg-hover;
|
||||
color: @plyr-video-control-color-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Audio controls
|
||||
.plyr--audio .plyr__controls {
|
||||
padding: @plyr-control-spacing;
|
||||
border-radius: inherit;
|
||||
background: @plyr-audio-controls-bg;
|
||||
border: @plyr-audio-controls-border;
|
||||
color: @plyr-audio-control-color;
|
||||
|
||||
button {
|
||||
// Hover and tab focus
|
||||
&.tab-focus:focus,
|
||||
&:hover {
|
||||
background: @plyr-audio-control-bg-hover;
|
||||
color: @plyr-audio-control-color-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Large play button (video only)
|
||||
.plyr__play-large {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: @plyr-control-spacing;
|
||||
background: @plyr-video-control-bg-hover;
|
||||
border: 4px solid currentColor;
|
||||
border-radius: 100%;
|
||||
box-shadow: 0 1px 1px fade(#000, 15%);
|
||||
color: @plyr-video-control-color;
|
||||
transition: all .3s ease;
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
left: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 1px dotted fade(@plyr-video-control-color, 50%);
|
||||
}
|
||||
}
|
||||
.plyr .plyr__play-large {
|
||||
display: inline-block;
|
||||
}
|
||||
.plyr--audio .plyr__play-large {
|
||||
display: none;
|
||||
}
|
||||
.plyr--playing .plyr__play-large {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
// States
|
||||
.plyr__controls [data-plyr='pause'],
|
||||
.plyr--playing .plyr__controls [data-plyr='play'] {
|
||||
display: none;
|
||||
}
|
||||
.plyr--playing .plyr__controls [data-plyr='pause'] {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// Change icons on state change
|
||||
.plyr--fullscreen-active .icon--exit-fullscreen,
|
||||
.plyr--muted .plyr__controls .icon--muted,
|
||||
.plyr--captions-active .plyr__controls .icon--captions-on {
|
||||
display: block;
|
||||
|
||||
& + svg {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Some options are hidden by default
|
||||
.plyr [data-plyr='captions'],
|
||||
.plyr [data-plyr='fullscreen'] {
|
||||
display: none;
|
||||
}
|
||||
.plyr--captions-enabled [data-plyr='captions'],
|
||||
.plyr--fullscreen-enabled [data-plyr='fullscreen'] {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// Tooltips
|
||||
// --------------------------------------------------------------
|
||||
.plyr__tooltip {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
bottom: 100%;
|
||||
margin-bottom: (@plyr-tooltip-padding * 2);
|
||||
padding: @plyr-tooltip-padding (@plyr-tooltip-padding * 1.5);
|
||||
pointer-events: none;
|
||||
|
||||
opacity: 0;
|
||||
background: @plyr-tooltip-bg;
|
||||
border-radius: @plyr-tooltip-radius;
|
||||
|
||||
color: @plyr-tooltip-color;
|
||||
font-size: @plyr-font-size-small;
|
||||
line-height: 1.3;
|
||||
|
||||
transform: translate(-50%, 10px) scale(.8);
|
||||
transform-origin: 50% 100%;
|
||||
transition: transform .2s .1s ease, opacity .2s .1s ease;
|
||||
|
||||
&::before {
|
||||
// Arrows
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
// The background triangle
|
||||
bottom: -@plyr-tooltip-arrow-size;
|
||||
border-right: @plyr-tooltip-arrow-size solid transparent;
|
||||
border-top: @plyr-tooltip-arrow-size solid @plyr-tooltip-bg;
|
||||
border-left: @plyr-tooltip-arrow-size solid transparent;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
.plyr button:hover .plyr__tooltip,
|
||||
.plyr button.tab-focus:focus .plyr__tooltip,
|
||||
.plyr__tooltip--visible {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0) scale(1);
|
||||
}
|
||||
.plyr button:hover .plyr__tooltip {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
// First tooltip
|
||||
.plyr__controls button:first-child .plyr__tooltip {
|
||||
left: 0;
|
||||
transform: translate(0, 10px) scale(.8);
|
||||
transform-origin: 0 100%;
|
||||
|
||||
&::before {
|
||||
left: (@plyr-control-icon-size / 2) + @plyr-control-padding;
|
||||
}
|
||||
}
|
||||
|
||||
// Last tooltip
|
||||
.plyr__controls button:last-child .plyr__tooltip {
|
||||
right: 0;
|
||||
transform: translate(0, 10px) scale(.8);
|
||||
transform-origin: 100% 100%;
|
||||
|
||||
&::before {
|
||||
left: auto;
|
||||
right: (@plyr-control-icon-size / 2) + @plyr-control-padding;
|
||||
transform: translateX(50%);
|
||||
}
|
||||
}
|
||||
|
||||
.plyr__controls button:first-child,
|
||||
.plyr__controls button:last-child {
|
||||
&:hover .plyr__tooltip,
|
||||
&.tab-focus:focus .plyr__tooltip,
|
||||
.plyr__tooltip--visible {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Playback progress
|
||||
// --------------------------------------------------------------
|
||||
// <progress> element
|
||||
.plyr__progress {
|
||||
position: relative;
|
||||
display: none;
|
||||
flex: 1;
|
||||
|
||||
input[type="range"] {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
background: transparent;
|
||||
}
|
||||
&::-moz-range-track {
|
||||
background: transparent;
|
||||
}
|
||||
&::-ms-fill-upper {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// Seek tooltip to show time
|
||||
.plyr__tooltip {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
.plyr .plyr__progress {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.plyr__progress--buffer,
|
||||
.plyr__progress--played,
|
||||
.plyr__volume--display {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
height: @plyr-range-track-height;
|
||||
margin: -(@plyr-range-track-height / 2) 0 0;
|
||||
padding: 0;
|
||||
vertical-align: top;
|
||||
appearance: none;
|
||||
border: none;
|
||||
border-radius: 100px;
|
||||
|
||||
&::-webkit-progress-bar {
|
||||
background: transparent;
|
||||
}
|
||||
&::-webkit-progress-value {
|
||||
background: currentColor;
|
||||
border-radius: 100px;
|
||||
min-width: @plyr-range-track-height;
|
||||
}
|
||||
&::-moz-progress-bar {
|
||||
background: currentColor;
|
||||
border-radius: 100px;
|
||||
min-width: @plyr-range-track-height;
|
||||
}
|
||||
&::-ms-fill {
|
||||
border-radius: 100px;
|
||||
}
|
||||
}
|
||||
.plyr__progress--played,
|
||||
.plyr__volume--display {
|
||||
z-index: 1;
|
||||
color: @plyr-range-selected-bg;
|
||||
background: transparent;
|
||||
transition: none;
|
||||
|
||||
&::-webkit-progress-value {
|
||||
min-width: @plyr-range-track-height;
|
||||
max-width: 99%;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
transition: none;
|
||||
}
|
||||
&::-moz-progress-bar {
|
||||
min-width: @plyr-range-track-height;
|
||||
max-width: 99%;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
transition: none;
|
||||
}
|
||||
&::-ms-fill {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.plyr__progress--buffer {
|
||||
&::-webkit-progress-value {
|
||||
transition: width .2s ease;
|
||||
}
|
||||
&::-moz-progress-bar {
|
||||
transition: width .2s ease;
|
||||
}
|
||||
&::-ms-fill {
|
||||
transition: width .2s ease;
|
||||
}
|
||||
}
|
||||
.plyr--video .plyr__progress--buffer,
|
||||
.plyr--video .plyr__volume--display {
|
||||
background: @plyr-video-range-track-bg;
|
||||
}
|
||||
.plyr--video .plyr__progress--buffer {
|
||||
color: @plyr-video-progress-buffered-bg;
|
||||
}
|
||||
.plyr--audio .plyr__progress--buffer,
|
||||
.plyr--audio .plyr__volume--display {
|
||||
background: @plyr-audio-range-track-bg;
|
||||
}
|
||||
.plyr--audio .plyr__progress--buffer {
|
||||
color: @plyr-audio-progress-buffered-bg;
|
||||
}
|
||||
|
||||
// Loading state
|
||||
.plyr--loading .plyr__progress--buffer {
|
||||
animation: plyr-progress 1s linear infinite;
|
||||
background-size: @plyr-progress-loading-size @plyr-progress-loading-size;
|
||||
background-repeat: repeat-x;
|
||||
background-image: linear-gradient(
|
||||
-45deg,
|
||||
@plyr-progress-loading-bg 25%,
|
||||
transparent 25%,
|
||||
transparent 50%,
|
||||
@plyr-progress-loading-bg 50%,
|
||||
@plyr-progress-loading-bg 75%,
|
||||
transparent 75%,
|
||||
transparent);
|
||||
color: transparent;
|
||||
}
|
||||
.plyr--video.plyr--loading .plyr__progress--buffer {
|
||||
background-color: @plyr-video-progress-buffered-bg;
|
||||
}
|
||||
.plyr--audio.plyr--loading .plyr__progress--buffer {
|
||||
background-color: @plyr-audio-progress-buffered-bg;
|
||||
}
|
||||
|
||||
// Time
|
||||
// --------------------------------------------------------------
|
||||
.plyr__time {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
font-size: @plyr-font-size-small;
|
||||
}
|
||||
// Media duration hidden on small screens
|
||||
.plyr__time + .plyr__time {
|
||||
display: none;
|
||||
|
||||
@media (min-width: @plyr-bp-screen-md) {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// Add a slash in before
|
||||
&::before {
|
||||
content: '\2044';
|
||||
margin-right: @plyr-control-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
// Volume
|
||||
// --------------------------------------------------------------
|
||||
.plyr__volume {
|
||||
display: none;
|
||||
}
|
||||
.plyr .plyr__volume {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
input[type="range"] {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
@media (min-width: @plyr-bp-screen-sm) {
|
||||
display: block;
|
||||
max-width: 60px;
|
||||
}
|
||||
@media (min-width: @plyr-bp-screen-md) {
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide sound controls on iOS
|
||||
// It's not supported to change volume using JavaScript:
|
||||
// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html
|
||||
.plyr--is-ios .plyr__volume,
|
||||
.plyr--is-ios [data-plyr='mute'] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Fullscreen
|
||||
// --------------------------------------------------------------
|
||||
.plyr--fullscreen-active {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 10000000;
|
||||
background: #000;
|
||||
border-radius: 0 !important;
|
||||
|
||||
video {
|
||||
height: 100%;
|
||||
}
|
||||
.plyr__video-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.plyr__controls {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
// Vimeo requires some different styling
|
||||
&.plyr--vimeo .plyr__video-wrapper {
|
||||
height: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
// ==========================================================================
|
||||
// Plyr variables
|
||||
// https://github.com/selz/plyr
|
||||
// ==========================================================================
|
||||
|
||||
// Settings
|
||||
@plyr-border-box: true;
|
||||
@plyr-touch-action: true;
|
||||
@plyr-sr-only-important: true;
|
||||
|
||||
// Colors
|
||||
@plyr-color-main: #3498db;
|
||||
|
||||
// Font
|
||||
@plyr-font-family: Avenir, 'Avenir Next', 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif;
|
||||
@plyr-font-size-small: 14px;
|
||||
@plyr-font-size-base: 16px;
|
||||
|
||||
// Captions
|
||||
@plyr-captions-bg: fade(#000, 70%);
|
||||
@plyr-captions-color: #fff;
|
||||
@plyr-font-size-captions-base: @plyr-font-size-base;
|
||||
@plyr-font-size-captions-medium: ceil(@plyr-font-size-base * 1.5);
|
||||
@plyr-font-size-captions-large: (@plyr-font-size-base * 2);
|
||||
|
||||
// Controls
|
||||
@plyr-control-icon-size: 18px;
|
||||
@plyr-control-spacing: 10px;
|
||||
@plyr-control-padding: (@plyr-control-spacing * .7);
|
||||
@plyr-video-controls-bg: #000;
|
||||
@plyr-video-control-color: #fff;
|
||||
@plyr-video-control-color-hover: #fff;
|
||||
@plyr-video-control-bg-hover: @plyr-color-main;
|
||||
@plyr-audio-controls-bg: #fff;
|
||||
@plyr-audio-controls-border: 1px solid #dbe3e8;
|
||||
@plyr-audio-control-color: #565D64;
|
||||
@plyr-audio-control-color-hover: #fff;
|
||||
@plyr-audio-control-bg-hover: @plyr-color-main;
|
||||
|
||||
// Tooltips
|
||||
@plyr-tooltip-bg: fade(#000, 70%);
|
||||
@plyr-tooltip-color: #fff;
|
||||
@plyr-tooltip-padding: (@plyr-control-spacing / 2);
|
||||
@plyr-tooltip-arrow-size: 4px;
|
||||
@plyr-tooltip-radius: 3px;
|
||||
|
||||
// Progress
|
||||
@plyr-progress-loading-size: 25px;
|
||||
@plyr-progress-loading-bg: fade(#000, 15%);
|
||||
@plyr-video-progress-bg: fade(#fff, 25%);
|
||||
@plyr-video-progress-buffered-bg: @plyr-video-progress-bg;
|
||||
@plyr-audio-progress-bg: fade(#C6D6DB, 66%);
|
||||
@plyr-audio-progress-buffered-bg: @plyr-audio-progress-bg;
|
||||
|
||||
// Range sliders
|
||||
@plyr-range-track-height: 8px;
|
||||
@plyr-range-thumb-height: floor(@plyr-range-track-height * 2);
|
||||
@plyr-range-thumb-width: floor(@plyr-range-track-height * 2);
|
||||
@plyr-range-thumb-bg: #fff;
|
||||
@plyr-range-thumb-border: 2px solid transparent;
|
||||
@plyr-range-thumb-shadow: 0 1px 1px fade(@plyr-video-controls-bg, 15%), 0 0 0 1px fade(#000, 15%);
|
||||
@plyr-range-thumb-active-border-color: #fff;
|
||||
@plyr-range-thumb-active-bg: @plyr-video-control-bg-hover;
|
||||
@plyr-range-thumb-active-scale: 1.25;
|
||||
@plyr-video-range-track-bg: @plyr-video-progress-buffered-bg;
|
||||
@plyr-audio-range-track-bg: @plyr-audio-progress-buffered-bg;
|
||||
@plyr-range-selected-bg: @plyr-color-main;
|
||||
|
||||
// Breakpoints
|
||||
@plyr-bp-screen-sm: 480px;
|
||||
@plyr-bp-screen-md: 768px;
|
58
src/sass/base.scss
Normal file
@ -0,0 +1,58 @@
|
||||
// --------------------------------------------------------------
|
||||
// Base styling
|
||||
// --------------------------------------------------------------
|
||||
|
||||
// Base
|
||||
.plyr {
|
||||
@include plyr-font-smoothing($plyr-font-smoothing);
|
||||
|
||||
direction: ltr;
|
||||
font-family: $plyr-font-family;
|
||||
font-weight: $plyr-font-weight-regular;
|
||||
line-height: $plyr-line-height;
|
||||
max-width: 100%;
|
||||
min-width: 200px;
|
||||
position: relative;
|
||||
text-shadow: none;
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
// Media elements
|
||||
video,
|
||||
audio {
|
||||
border-radius: inherit;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Ignore focus
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// border-box everything
|
||||
// http://paulirish.com/2012/box-sizing-border-box-ftw/
|
||||
@if $plyr-border-box {
|
||||
.plyr--full-ui {
|
||||
box-sizing: border-box;
|
||||
|
||||
*,
|
||||
*::after,
|
||||
*::before {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix 300ms delay
|
||||
@if $plyr-touch-action {
|
||||
.plyr--full-ui {
|
||||
a,
|
||||
button,
|
||||
input,
|
||||
label {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
}
|
||||
}
|