Compare commits
206 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c8a337f26 | |||
| c24e52d97d | |||
| 574f40949c | |||
| cf3848fbd5 | |||
| a19ad69038 | |||
| d6f20e2756 | |||
| e2fc20ca76 | |||
| 37c3f7109d | |||
| 99d5211a33 | |||
| b97f143195 | |||
| e8da4326b6 | |||
| 67f908aa8d | |||
| 65eb5c1b8b | |||
| 7d484c6e09 | |||
| 8252e13eb9 | |||
| 1a9b860e68 | |||
| cede7d0f35 | |||
| fe26d383f1 | |||
| df4bc268dc | |||
| e49da6c13f | |||
| 67b7262764 | |||
| 88528ef979 | |||
| b6175b1ca9 | |||
| aa20ebaa9c | |||
| 779e45c11b | |||
| 5d5a6eabaa | |||
| 03c9b53232 | |||
| ebaded66b4 | |||
| c232eb2478 | |||
| 7559cc6897 | |||
| 69d0d6d7ee | |||
| 3e9336b15d | |||
| 5c78ecc15d | |||
| 06db3f702d | |||
| a2a82a96a6 | |||
| a86bbae851 | |||
| fac134dd95 | |||
| 515ae32160 | |||
| df8f040795 | |||
| 64a23992f0 | |||
| f5baff6e6b | |||
| 8bdd90a2a8 | |||
| 5536e97482 | |||
| de47071256 | |||
| 87072cb690 | |||
| d9565e9250 | |||
| f80b568e67 | |||
| 7fed689f9a | |||
| 3f48df8f10 | |||
| cc55092ca6 | |||
| 3331d9d01d | |||
| 62d80e6b76 | |||
| 7dc4d9cd22 | |||
| 8fb8ae1260 | |||
| 90304369f4 | |||
| 922456c46c | |||
| eaeccd66ae | |||
| 7a43649c13 | |||
| 525bbf313e | |||
| cfaebe9bf2 | |||
| b57b7b2153 | |||
| 48bf368316 | |||
| 8f94ce86a0 | |||
| 10a9cf08f1 | |||
| 286d0d1794 | |||
| 95f6fa2731 | |||
| 1aeef81288 | |||
| 211ad6c8f5 | |||
| 468b20d227 | |||
| f6bc42c2bc | |||
| 2c01b8ba76 | |||
| 4e1df8677f | |||
| 6953a12e2a | |||
| 1d0db89194 | |||
| 297f297d18 | |||
| 059205c378 | |||
| f94e53ffb1 | |||
| a4f1fdec5d | |||
| 75374eb154 | |||
| 3ad118c026 | |||
| 0bc6b1f1b3 | |||
| 4ea458e1a3 | |||
| aacb172017 | |||
| dbf768b1bd | |||
| b96fcfc8ac | |||
| 18b4d26bee | |||
| 7f4b74e2d4 | |||
| a8f8486cf4 | |||
| a343e58e53 | |||
| 0892d69ba2 | |||
| ba511b51c7 | |||
| e090581913 | |||
| aaa56caa9c | |||
| c8db1e55dd | |||
| 58079393e6 | |||
| 0b44f2d897 | |||
| 2371619486 | |||
| 13a54b5dbe | |||
| fa0861ff2e | |||
| 748aa5179f | |||
| 56a485bac6 | |||
| 9488de30e5 | |||
| e3dfd16096 | |||
| c230ccce86 | |||
| db22a8e9c4 | |||
| 3a3358e2b4 | |||
| 248005e8e0 | |||
| dae272ef66 | |||
| 2679c5898e | |||
| efb7401e6d | |||
| 60a0f0c979 | |||
| 5d168d0e14 | |||
| f964e34d8c | |||
| 021ba0b8e9 | |||
| 96d371546c | |||
| e1780a4df0 | |||
| e5e169a1e2 | |||
| 5eda498516 | |||
| 599b33e55f | |||
| 3a8332bdb3 | |||
| 44b5d9f6b9 | |||
| 24deff0e2d | |||
| 0933b48c2a | |||
| 71578e07ec | |||
| 671325dd17 | |||
| 53a3d06103 | |||
| ad1989e45e | |||
| f9ac98bc6d | |||
| 544ab0086b | |||
| 13bf80d372 | |||
| e2fb922d73 | |||
| a6cc85c437 | |||
| d061be5d2b | |||
| dc2feedd79 | |||
| f8e4ba36e5 | |||
| f3d5389587 | |||
| d9ffb10b93 | |||
| e63ad7c74b | |||
| 1186377b25 | |||
| 8616895e57 | |||
| 2cf5a22c85 | |||
| 763eb2df80 | |||
| 8bbf66a0fb | |||
| 676b46e4a7 | |||
| 82a119c67f | |||
| 6fd4389887 | |||
| 1e1a548459 | |||
| 8db9b53a8f | |||
| ba33fd8277 | |||
| 8071feda18 | |||
| a49b73cd01 | |||
| 8226493a9e | |||
| 38a8a0e8a1 | |||
| ead6601394 | |||
| e61ebd8d05 | |||
| 6eeca8b5d1 | |||
| bf51ce4414 | |||
| 5ad614e251 | |||
| 93c890603d | |||
| 29fb4dfc2b | |||
| 7de9fd1d65 | |||
| 566c059832 | |||
| 0c9572f0a1 | |||
| c99607c85a | |||
| e2010bcd1a | |||
| 3bf1c59bd6 | |||
| e9f1b55f51 | |||
| 4f5152f526 | |||
| de9b53045a | |||
| e59fe1aacf | |||
| df458c5e7a | |||
| e206554146 | |||
| e422806c44 | |||
| b6ddf144f4 | |||
| 86406ee59a | |||
| 81c5477f1d | |||
| ac64350a5f | |||
| 333619f1e3 | |||
| 17dcb63c26 | |||
| e04b90c9c0 | |||
| 1f1d74ba50 | |||
| f62e1da01a | |||
| 2fe949629f | |||
| 20f2ddc11d | |||
| 004528a65c | |||
| 39c7bd40c2 | |||
| 43879e08f4 | |||
| bb546fe43f | |||
| 5ed7aa6620 | |||
| 47750b6aad | |||
| de7832eb8b | |||
| 52ea5bd0ab | |||
| 457d112df7 | |||
| 22cdec9d38 | |||
| d72e502107 | |||
| 94055f0772 | |||
| ede9323524 | |||
| c45f428f61 | |||
| b61ba02f3d | |||
| 9e1218547b | |||
| 715b88c09b | |||
| ea4d91d2a0 | |||
| 7b9ef7d757 | |||
| d64ed4ba5a | |||
| 22d524ac9d | |||
| ffd864ed39 |
@@ -5,8 +5,12 @@
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"globals": { "Plyr": false, "jQuery": false },
|
||||
"globals": {
|
||||
"Plyr": false,
|
||||
"jQuery": false
|
||||
},
|
||||
"rules": {
|
||||
"import/no-cycle": 1,
|
||||
"no-const-assign": 1,
|
||||
"no-shadow": 0,
|
||||
"no-this-before-super": 1,
|
||||
@@ -21,17 +25,8 @@
|
||||
"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."
|
||||
}
|
||||
],
|
||||
"spaced-comment": [2, "always"],
|
||||
"no-restricted-globals": 2,
|
||||
"no-param-reassign": [2, { "props": false }]
|
||||
},
|
||||
"parserOptions": {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report an issue or unexpected behaviour with Plyr
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
Before creating the issue, please make sure that...
|
||||
|
||||
* You aren't getting any errors in your own code, causing the problem.
|
||||
* You are using the latest version of Plyr.
|
||||
* There isn't already an open issue for your problem.
|
||||
* You are following the documentation correctly (https://github.com/sampotts/plyr/)
|
||||
* Your problem doesn't happen if you remove Plyr and use native HTML5 media (when applicable).
|
||||
|
||||
For problems with autoplay, see our FAQ (https://github.com/sampotts/plyr/wiki/FAQ)
|
||||
|
||||
If you have multiple unrelated problems, create separate issues rather than combining them into one.
|
||||
|
||||
Note that leaving sections blank or being vague will make it difficult for us to troubleshoot and we may close the issue.
|
||||
-->
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
### Actual behaviour
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
### Environment
|
||||
|
||||
- Browser:
|
||||
- Version:
|
||||
- Operating System:
|
||||
- Version:
|
||||
|
||||
### Console errors (if any)
|
||||
|
||||
### Link to where the bug is happening
|
||||
|
||||
<!--
|
||||
This link can be either to our demo at https://plyr.io/ if the problem can be observed there, or to a code playground with a **minimal** test case that demonstrates the problem.
|
||||
|
||||
You can use one of our prepared templates to get started creating the test case:
|
||||
|
||||
* HTML5 video: https://codepen.io/pen?template=bKeqpr
|
||||
* HTML5 audio: https://codepen.io/pen?template=rKLywR
|
||||
* YouTube: https://codepen.io/pen?template=GGqbbJ
|
||||
* Vimeo: https://codepen.io/pen?template=bKeXNq
|
||||
* Dash.js integration: https://codepen.io/pen?template=zaBgBy
|
||||
* Hls.js integration: https://codepen.io/pen?template=oyLKQb
|
||||
* Shaka Player integration: https://codepen.io/pen?template=ZRpzZO
|
||||
|
||||
It's important that you keep the issue description and replication demo **minimal**. If your replication includes frameworks, libraries or customizations, this makes it much harder to understand the problem and find the bug. For more help on how to create the demo, see https://github.com/sampotts/plyr/wiki/Writing-helpful-issue-descriptions
|
||||
|
||||
-->
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: New feature
|
||||
about: Request new functionality
|
||||
---
|
||||
|
||||
<!--
|
||||
Please describe the behaviour that you want to add, and why. Be as clear as possible to avoid confusion.
|
||||
|
||||
If you want to request multiple features that aren't directly related, then create one issue per feature.
|
||||
-->
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Improvement
|
||||
about: Request a change that isn't a bug or new feature
|
||||
---
|
||||
|
||||
<!--
|
||||
Please describe the behaviour that you want to change, and why. Be as clear as possible to avoid confusion.
|
||||
|
||||
If you want to request multiple changes that aren't directly related, then create one issue per change.
|
||||
-->
|
||||
@@ -1,17 +1,3 @@
|
||||
<!---
|
||||
Please use this issue template as it makes replicating and fixing the issue easier!
|
||||
--->
|
||||
PLEASE USE OUR SPECIFIC ISSUE TEMPLATES for bug reports, features and improvement suggestions.
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
### Actual behaviour
|
||||
|
||||
### Environment
|
||||
|
||||
- Browser:
|
||||
- Version:
|
||||
- Operating System:
|
||||
- Version:
|
||||
|
||||
### Steps to reproduce
|
||||
-
|
||||
Our issue tracker is not for support questions. If you need help, follow our support instructions: https://github.com/sampotts/plyr/blob/master/contributing.md#support
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
|
||||
### Checklist
|
||||
- [ ] Use `develop` as the base branch
|
||||
- [ ] Exclude the gulp build from the PR
|
||||
- [ ] Exclude the gulp build (`/dist` changes) from the PR
|
||||
- [ ] Test on [supported browsers](https://github.com/sampotts/plyr#browser-support)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
aws.json
|
||||
credentials.json
|
||||
*.mp4
|
||||
!dist/blank.mp4
|
||||
index-*.html
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
package-lock.json
|
||||
*.webm
|
||||
/package-lock.json
|
||||
.idea/
|
||||
|
||||
|
||||
@@ -2,3 +2,10 @@ demo
|
||||
.github
|
||||
.vscode
|
||||
*.code-workspace
|
||||
credentials.json
|
||||
bundles.json
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
*.mp4
|
||||
*.webm
|
||||
!dist/blank.mp4
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"useTabs": false,
|
||||
"tabWidth": 4,
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
linters:
|
||||
eslint:
|
||||
files:
|
||||
ignore:
|
||||
- 'node_modules/*'
|
||||
@@ -1,7 +1,8 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 'lts/*'
|
||||
node_js: lts/*
|
||||
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run build
|
||||
- bash .travis/prevent-base-master.sh
|
||||
- bash .travis/omit-dist.sh
|
||||
- npm run lint
|
||||
- npm run build
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
if [ $TRAVIS_BRANCH == "develop" ] && $(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qE "^(demo/)?dist/"); then
|
||||
echo 'Build output ("dist" and "demo/dist") not permitted in develop' >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
if [ "$TRAVIS_PULL_REQUEST" != "false" ] && [ $TRAVIS_BRANCH == "master" ] && $(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -q "^src/"); then
|
||||
echo 'The base branch for pull requests must be "develop"' >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,61 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
We welcome bug reports, feature requests and pull requests. If you want to help us out, please follow these guidelines, in order to avoid redundant work.
|
||||
|
||||
## Commenting
|
||||
When commenting, keep a civil tone and stay on topic. Don't ask for support (use [Stack Overflow](https://stackoverflow.com/) or [our Slack](https://bit.ly/plyr-chat) for that), or post "+1" or "I agree" type of comments. Use the emojis instead.
|
||||
|
||||
Asking for the status on issues is discouraged. Unless someone has explicitly said in an issue that it's work in progress, most likely that means no one is working on it. We have a lot to do, and it may not be a top priority for us.
|
||||
|
||||
We *may* moderate discussions. We do this to avoid threads being "hijacked", to avoid confusion in case the content is misleading or outdated, and to avoid bothering people with github notifications.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
Our GitHub issue tracker is for bug reports and feature requests. Don't ask for support here. Use [Stack Overflow](https://stackoverflow.com/) or [our Slack](https://bit.ly/plyr-chat) for that.
|
||||
|
||||
Please verify that your issue hasn't already been answered by our [FAQ](https://github.com/sampotts/plyr/wiki/FAQ), and that there isn't already an open issue for it.
|
||||
|
||||
When applicable, check that your problem doesn't happen without Plyr (see [FAQ#1](https://github.com/sampotts/plyr/wiki/FAQ#1-does-plyr-work-with--)).
|
||||
|
||||
Verify that you are following the documentation, are using the latest version of Plyr, and aren't getting any errors in your own code, causing the issues.
|
||||
|
||||
Create one issue per problem or request (i.e. don't combine multiple problems to one git issue). Describe the issue as detailed as possible (see [Replication](#replication))
|
||||
|
||||
## Replication
|
||||
|
||||
In order to solve a problem, we first need to understand it. Please answer these questions when reporting issues or asking for help in [our Slack](https://bit.ly/plyr-chat).
|
||||
|
||||
* Does it happen only with specific options and/or specific browsers?
|
||||
* Does is happen only with HTML5 video, audio, YouTube, Vimeo or a specific library?
|
||||
* Does the issue happen on [our demo](https://plyr.io/)? If not, please recreate it with a **minimal** example online. You can use our Codepen templates to get started:
|
||||
* [HTML5 video](https://codepen.io/pen?template=bKeqpr)
|
||||
* [HTML5 audio](https://codepen.io/pen?template=rKLywR)
|
||||
* [YouTube](https://codepen.io/pen?template=GGqbbJ)
|
||||
* [Vimeo](https://codepen.io/pen?template=bKeXNq)
|
||||
* [Dash.js integration](https://codepen.io/pen?template=zaBgBy)
|
||||
* [Hls.js integration](https://codepen.io/pen?template=oyLKQb)
|
||||
* [Shaka Player integration](https://codepen.io/pen?template=ZRpzZO)
|
||||
|
||||
It's important that you keep the issue description and replication demo **minimal**. If your replication includes frameworks, libraries or customizations, this makes it harder to debug and understand the issue. While it may be relevant to bring this up (ex: "I need Plyr to trigger the event sooner or it breaks Framework X"), please keep these out of your replication demo if they aren't strictly needed to reproduce the issue. If the issue is caused by something a library does that Plyr doesn't handle, it's more helpful for us if you find out what it is, and replicate the same problem without the library. Otherwise any developer who is willing to help out with the issue has to understand the frameworks, libraries and customizations of *your* choice, or no one will try to fix your issue because it's too much work.
|
||||
|
||||
## Requesting features and improvements
|
||||
|
||||
If you are missing something in Plyr, you can create a GitHub issue for this as well. Since we prioritize fixing bugs first, and may have a lot of other suggestions and architectural changes to work on as well, these may not be at the top of our list.
|
||||
|
||||
If your suggestion is important or urgent to you, you may want to first ensure it's something we want to have in Plyr, and then contribute it as a pull request. [Our Slack](https://bit.ly/plyr-chat) is the best place for questions like this.
|
||||
|
||||
## Contributing features and documentation
|
||||
|
||||
* Fork Plyr, and create a new branch in your fork, based on the **develop** branch
|
||||
|
||||
* To test locally, you can use the demo. First make sure you have installed the dependencies with `npm install` or `yarn`. Run `gulp` to build while you are working, and run a local server from the repository root directory. If you have Python installed, this command should work: `python -m SimpleHTTPServer 8080`. Then go to `http://localhost:8080/demo/`
|
||||
|
||||
* Develop and test your modifications.
|
||||
|
||||
* Preferably commit your changes as independent logical chunks, with meaningful messages. Make sure you do not commit unnecessary files or changes, such as the build output, or logging and breakpoints you added for testing.
|
||||
|
||||
* If your modifications changes the documented behavior or add new features, document these changes in readme.md.
|
||||
|
||||
* When finished, push the changes to your GitHub repository and send a pull request to **develop**. Describe what your PR does.
|
||||
|
||||
* If the Travis build fails, or if you get a code review with change requests, you can fix these by pushing new or rebased commits to the branch.
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "plyr",
|
||||
"description": "A simple HTML5 media player using custom controls",
|
||||
"homepage": "http://plyr.io",
|
||||
"keywords": [
|
||||
"Audio",
|
||||
"Video",
|
||||
"HTML5 Audio",
|
||||
"HTML5 Video"
|
||||
],
|
||||
"authors": [
|
||||
"Sam Potts <sam@potts.es>"
|
||||
],
|
||||
"dependencies": {},
|
||||
"main": [
|
||||
"dist/plyr.css",
|
||||
"dist/plyr.js",
|
||||
"dist/plyr.svg",
|
||||
"src/less/plyr.less",
|
||||
"src/scss/plyr.scss",
|
||||
"src/js/plyr.js"
|
||||
],
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
".gitignore"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/sampotts/plyr.git"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Contributing
|
||||
|
||||
We welcome bug reports, feature requests and pull requests. If you want to help us out, please follow these guidelines, in order to avoid redundant work.
|
||||
|
||||
## Support
|
||||
|
||||
Before asking questions, read our [documentation](https://github.com/sampotts/plyr) and [FAQ](https://github.com/sampotts/plyr/wiki/FAQ).
|
||||
|
||||
If these doesn't answer your question
|
||||
* Use [Stack Overflow](https://stackoverflow.com/) for questions that doesn't directly involve Plyr. This includes for example how to use Javascript, CSS or HTML5 media in general, and how to use other frameworks, libraries and technology.
|
||||
* Use [our Slack](https://bit.ly/plyr-chat) if you need help using Plyr or have questions about Plyr.
|
||||
|
||||
## Commenting
|
||||
When commenting, keep a civil tone and stay on topic. Don't ask for [support](#support), or post "+1" or "I agree" type of comments. Use the emojis instead.
|
||||
|
||||
Asking for the status on issues is discouraged. Unless someone has explicitly said in an issue that it's work in progress, most likely that means no one is working on it. We have a lot to do, and it may not be a top priority for us.
|
||||
|
||||
We *may* moderate discussions. We do this to avoid threads being "hijacked", to avoid confusion in case the content is misleading or outdated, and to avoid bothering people with github notifications.
|
||||
|
||||
## Creating issues
|
||||
|
||||
Please follow the instructions in our issue templates. Don't use github issues to ask for [support](#support).
|
||||
|
||||
## Contributing features and documentation
|
||||
|
||||
* If you want to add a feature or make critical changes, you may want to ensure that this is something we also want (so you don't waste your time). Ask us about this in the corresponding issue if there is one, or on [our Slack](https://bit.ly/plyr-chat) otherwise.
|
||||
|
||||
* Fork Plyr, and create a new branch in your fork, based on the **develop** branch
|
||||
|
||||
* To test locally, you can use the demo. First make sure you have installed the dependencies with `npm install` or `yarn`. Run `gulp` to build while you are working, and run a local server from the repository root directory. If you have Python installed, this command should work: `python -m SimpleHTTPServer 8080`. Then go to `http://localhost:8080/demo/`
|
||||
|
||||
* Develop and test your modifications.
|
||||
|
||||
* Preferably commit your changes as independent logical chunks, with meaningful messages. Make sure you do not commit unnecessary files or changes, such as the build output, or logging and breakpoints you added for testing.
|
||||
|
||||
* If your modifications changes the documented behavior or add new features, document these changes in readme.md.
|
||||
|
||||
* When finished, push the changes to your GitHub repository and send a pull request to **develop**. Describe what your PR does.
|
||||
|
||||
* If the Travis build fails, or if you get a code review with change requests, you can fix these by pushing new or rebased commits to the branch.
|
||||
@@ -3,7 +3,9 @@
|
||||
This is the markup that is rendered for the Plyr controls. You can use the default controls or provide a customized version of markup based on your needs. You can pass the following to the `controls` option:
|
||||
|
||||
- `Array` of options (this builds the default controls based on your choices)
|
||||
- `Element` with the controls
|
||||
- `String` containing the desired HTML
|
||||
- `false` (or empty string or array) to disable all controls
|
||||
- `Function` that will be executed and should return one of the above
|
||||
|
||||
## Using default controls
|
||||
@@ -26,6 +28,7 @@ controls: [
|
||||
'settings', // Settings menu
|
||||
'pip', // Picture-in-picture (currently Safari only)
|
||||
'airplay', // Airplay (currently Safari only)
|
||||
'download', // Show a download button with a link to either the current source or a custom URL you specify in your options
|
||||
'fullscreen', // Toggle fullscreen
|
||||
];
|
||||
```
|
||||
|
||||
@@ -1833,7 +1833,6 @@ typeof navigator === "object" && (function () {
|
||||
};
|
||||
this._fetchDefaults = {
|
||||
method: 'POST',
|
||||
keepalive: true,
|
||||
// Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default
|
||||
// https://caniuse.com/#feat=referrer-policy
|
||||
// It doesn't. And it throw exception instead of ignoring this parameter...
|
||||
@@ -1874,7 +1873,7 @@ typeof navigator === "object" && (function () {
|
||||
// webpack (using a build step causes webpack #1617). Grunt verifies that
|
||||
// this value matches package.json during build.
|
||||
// See: https://github.com/getsentry/raven-js/issues/465
|
||||
VERSION: '3.26.2',
|
||||
VERSION: '3.27.0',
|
||||
|
||||
debug: false,
|
||||
|
||||
@@ -2351,7 +2350,9 @@ typeof navigator === "object" && (function () {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._globalOptions.stacktrace || (options && options.stacktrace)) {
|
||||
// Always attempt to get stacktrace if message is empty.
|
||||
// It's the only way to provide any helpful information to the user.
|
||||
if (this._globalOptions.stacktrace || options.stacktrace || data.message === '') {
|
||||
// fingerprint on msg, not stack trace (legacy behavior, could be revisited)
|
||||
data.fingerprint = data.fingerprint == null ? msg : data.fingerprint;
|
||||
|
||||
@@ -2610,34 +2611,40 @@ typeof navigator === "object" && (function () {
|
||||
)
|
||||
return;
|
||||
|
||||
options = options || {};
|
||||
options = objectMerge$1(
|
||||
{
|
||||
eventId: this.lastEventId(),
|
||||
dsn: this._dsn,
|
||||
user: this._globalContext.user || {}
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
var lastEventId = options.eventId || this.lastEventId();
|
||||
if (!lastEventId) {
|
||||
if (!options.eventId) {
|
||||
throw new configError('Missing eventId');
|
||||
}
|
||||
|
||||
var dsn = options.dsn || this._dsn;
|
||||
if (!dsn) {
|
||||
if (!options.dsn) {
|
||||
throw new configError('Missing DSN');
|
||||
}
|
||||
|
||||
var encode = encodeURIComponent;
|
||||
var qs = '';
|
||||
qs += '?eventId=' + encode(lastEventId);
|
||||
qs += '&dsn=' + encode(dsn);
|
||||
var encodedOptions = [];
|
||||
|
||||
var user = options.user || this._globalContext.user;
|
||||
if (user) {
|
||||
if (user.name) qs += '&name=' + encode(user.name);
|
||||
if (user.email) qs += '&email=' + encode(user.email);
|
||||
for (var key in options) {
|
||||
if (key === 'user') {
|
||||
var user = options.user;
|
||||
if (user.name) encodedOptions.push('name=' + encode(user.name));
|
||||
if (user.email) encodedOptions.push('email=' + encode(user.email));
|
||||
} else {
|
||||
encodedOptions.push(encode(key) + '=' + encode(options[key]));
|
||||
}
|
||||
}
|
||||
|
||||
var globalServer = this._getGlobalServer(this._parseDSN(dsn));
|
||||
var globalServer = this._getGlobalServer(this._parseDSN(options.dsn));
|
||||
|
||||
var script = _document.createElement('script');
|
||||
script.async = true;
|
||||
script.src = globalServer + '/api/embed/error-page/' + qs;
|
||||
script.src = globalServer + '/api/embed/error-page/?' + encodedOptions.join('&');
|
||||
(_document.head || _document.body).appendChild(script);
|
||||
},
|
||||
|
||||
@@ -3508,6 +3515,11 @@ typeof navigator === "object" && (function () {
|
||||
options
|
||||
);
|
||||
|
||||
var ex = data.exception.values[0];
|
||||
if (ex.type == null && ex.value === '') {
|
||||
ex.value = 'Unrecoverable error caught';
|
||||
}
|
||||
|
||||
// Move mechanism from options to exception interface
|
||||
// We do this, as requiring user to pass `{exception:{mechanism:{ ... }}}` would be
|
||||
// too much
|
||||
@@ -4080,308 +4092,263 @@ typeof navigator === "object" && (function () {
|
||||
// ==========================================================================
|
||||
|
||||
(function () {
|
||||
var isLive = window.location.host === 'plyr.io';
|
||||
var host = window.location.host;
|
||||
var env = {
|
||||
prod: host === 'plyr.io',
|
||||
dev: host === 'dev.plyr.io'
|
||||
};
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
singleton.context(function () {
|
||||
var selector = '#player';
|
||||
var container = document.getElementById('container');
|
||||
|
||||
// Raven / Sentry
|
||||
// For demo site (https://plyr.io) only
|
||||
if (isLive) {
|
||||
singleton.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
singleton.context(function () {
|
||||
if (window.shr) {
|
||||
window.shr.setup({
|
||||
count: {
|
||||
classname: 'button__count'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Setup tab focus
|
||||
var tabClassName = 'tab-focus';
|
||||
|
||||
// Remove class on blur
|
||||
document.addEventListener('focusout', function (event) {
|
||||
event.target.classList.remove(tabClassName);
|
||||
});
|
||||
|
||||
// Add classname to tabbed elements
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.keyCode !== 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delay the adding of classname until the focus has changed
|
||||
// This event fires before the focusin event
|
||||
setTimeout(function () {
|
||||
document.activeElement.classList.add(tabClassName);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// Setup the player
|
||||
var player = new Plyr('#player', {
|
||||
debug: true,
|
||||
title: 'View From A Blue Moon',
|
||||
iconUrl: '../dist/plyr.svg',
|
||||
keyboard: {
|
||||
global: true
|
||||
},
|
||||
tooltips: {
|
||||
controls: true
|
||||
},
|
||||
/* controls: [
|
||||
'play-large',
|
||||
'restart',
|
||||
'rewind',
|
||||
'play',
|
||||
'fast-forward',
|
||||
'progress',
|
||||
'current-time',
|
||||
'duration',
|
||||
'mute',
|
||||
'volume',
|
||||
'captions',
|
||||
'settings',
|
||||
'pip',
|
||||
'airplay',
|
||||
'fullscreen',
|
||||
], */
|
||||
/* i18n: {
|
||||
restart: '重新開始',
|
||||
rewind: '快退{seektime}秒',
|
||||
play: '播放',
|
||||
pause: '暫停',
|
||||
fastForward: '快進{seektime}秒',
|
||||
seek: '尋求',
|
||||
played: '發揮',
|
||||
buffered: '緩衝的',
|
||||
currentTime: '當前時間戳',
|
||||
duration: '長短',
|
||||
volume: '音量',
|
||||
mute: '靜音',
|
||||
unmute: '取消靜音',
|
||||
enableCaptions: '開啟字幕',
|
||||
disableCaptions: '關閉字幕',
|
||||
enterFullscreen: '進入全螢幕',
|
||||
exitFullscreen: '退出全螢幕',
|
||||
frameTitle: '球員為{title}',
|
||||
captions: '字幕',
|
||||
settings: '設定',
|
||||
speed: '速度',
|
||||
normal: '正常',
|
||||
quality: '質量',
|
||||
loop: '循環',
|
||||
start: 'Start',
|
||||
end: 'End',
|
||||
all: 'All',
|
||||
reset: '重啟',
|
||||
disabled: '殘',
|
||||
enabled: '啟用',
|
||||
advertisement: '廣告',
|
||||
}, */
|
||||
captions: {
|
||||
active: true
|
||||
},
|
||||
keys: {
|
||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c'
|
||||
},
|
||||
ads: {
|
||||
enabled: true,
|
||||
publisherId: '918848828995742'
|
||||
}
|
||||
});
|
||||
|
||||
// Expose for tinkering in the console
|
||||
window.player = player;
|
||||
|
||||
// Setup type toggle
|
||||
var buttons = document.querySelectorAll('[data-source]');
|
||||
var types = {
|
||||
video: 'video',
|
||||
audio: 'audio',
|
||||
youtube: 'youtube',
|
||||
vimeo: 'vimeo'
|
||||
};
|
||||
var currentType = window.location.hash.replace('#', '');
|
||||
var historySupport = window.history && window.history.pushState;
|
||||
|
||||
// Toggle class on an element
|
||||
function toggleClass(element, className, state) {
|
||||
if (element) {
|
||||
element.classList[state ? 'add' : 'remove'](className);
|
||||
}
|
||||
}
|
||||
|
||||
// Set a new source
|
||||
function newSource(type, init) {
|
||||
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
|
||||
if (!(type in types) || !init && type === currentType || !currentType.length && type === types.video) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case types.video:
|
||||
player.source = {
|
||||
type: 'video',
|
||||
title: 'View From A Blue Moon',
|
||||
sources: [{
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
|
||||
type: 'video/mp4',
|
||||
size: 576
|
||||
}, {
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
|
||||
type: 'video/mp4',
|
||||
size: 720
|
||||
}, {
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
|
||||
type: 'video/mp4',
|
||||
size: 1080
|
||||
}, {
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
|
||||
type: 'video/mp4',
|
||||
size: 1440
|
||||
}],
|
||||
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',
|
||||
sources: [{
|
||||
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
||||
provider: 'youtube'
|
||||
}]
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case types.vimeo:
|
||||
player.source = {
|
||||
type: 'video',
|
||||
sources: [{
|
||||
src: 'https://vimeo.com/76979871',
|
||||
provider: 'vimeo'
|
||||
}]
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the current type for next time
|
||||
currentType = type;
|
||||
|
||||
// Remove active classes
|
||||
Array.from(buttons).forEach(function (button) {
|
||||
return toggleClass(button.parentElement, 'active', false);
|
||||
});
|
||||
|
||||
// Set active on parent
|
||||
toggleClass(document.querySelector('[data-source="' + type + '"]'), 'active', true);
|
||||
|
||||
// Show cite
|
||||
Array.from(document.querySelectorAll('.plyr__cite')).forEach(function (cite) {
|
||||
cite.setAttribute('hidden', '');
|
||||
});
|
||||
document.querySelector('.plyr__cite--' + type).removeAttribute('hidden');
|
||||
}
|
||||
|
||||
// Bind to each button
|
||||
Array.from(buttons).forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
var type = button.getAttribute('data-source');
|
||||
|
||||
newSource(type);
|
||||
|
||||
if (historySupport) {
|
||||
window.history.pushState({ type: type }, '', '#' + type);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// List for backwards/forwards
|
||||
window.addEventListener('popstate', function (event) {
|
||||
if (event.state && 'type' in event.state) {
|
||||
newSource(event.state.type);
|
||||
}
|
||||
});
|
||||
|
||||
// On load
|
||||
if (historySupport) {
|
||||
var video = !currentType.length;
|
||||
|
||||
// If there's no current type set, assume video
|
||||
if (video) {
|
||||
currentType = types.video;
|
||||
}
|
||||
|
||||
// Replace current history state
|
||||
if (currentType in types) {
|
||||
window.history.replaceState({
|
||||
type: currentType
|
||||
}, '', video ? '' : '#' + currentType);
|
||||
}
|
||||
|
||||
// If it's not video, load the source
|
||||
if (currentType !== types.video) {
|
||||
newSource(currentType, true);
|
||||
}
|
||||
}
|
||||
if (window.shr) {
|
||||
window.shr.setup({
|
||||
count: {
|
||||
classname: 'button__count'
|
||||
}
|
||||
});
|
||||
});
|
||||
} // Setup tab focus
|
||||
|
||||
// Google analytics
|
||||
// For demo site (https://plyr.io) only
|
||||
/* eslint-disable */
|
||||
if (isLive) {
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i.GoogleAnalyticsObject = r;
|
||||
i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments);
|
||||
|
||||
var tabClassName = 'tab-focus'; // Remove class on blur
|
||||
|
||||
document.addEventListener('focusout', function (event) {
|
||||
if (!event.target.classList || container.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.target.classList.remove(tabClassName);
|
||||
}); // Add classname to tabbed elements
|
||||
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.keyCode !== 9) {
|
||||
return;
|
||||
} // Delay the adding of classname until the focus has changed
|
||||
// This event fires before the focusin event
|
||||
|
||||
|
||||
setTimeout(function () {
|
||||
var focused = document.activeElement;
|
||||
|
||||
if (!focused || !focused.classList || container.contains(focused)) {
|
||||
return;
|
||||
}
|
||||
|
||||
focused.classList.add(tabClassName);
|
||||
}, 10);
|
||||
}); // Setup the player
|
||||
|
||||
var player = new Plyr(selector, {
|
||||
debug: true,
|
||||
title: 'View From A Blue Moon',
|
||||
iconUrl: '../dist/plyr.svg',
|
||||
keyboard: {
|
||||
global: true
|
||||
},
|
||||
tooltips: {
|
||||
controls: true
|
||||
},
|
||||
captions: {
|
||||
active: true
|
||||
},
|
||||
keys: {
|
||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c'
|
||||
},
|
||||
ads: {
|
||||
enabled: env.prod || env.dev,
|
||||
publisherId: '918848828995742'
|
||||
}
|
||||
}); // Expose for tinkering in the console
|
||||
|
||||
window.player = player; // Setup type toggle
|
||||
|
||||
var buttons = document.querySelectorAll('[data-source]');
|
||||
var types = {
|
||||
video: 'video',
|
||||
audio: 'audio',
|
||||
youtube: 'youtube',
|
||||
vimeo: 'vimeo'
|
||||
};
|
||||
var currentType = window.location.hash.replace('#', '');
|
||||
var historySupport = window.history && window.history.pushState; // Toggle class on an element
|
||||
|
||||
function toggleClass(element, className, state) {
|
||||
if (element) {
|
||||
element.classList[state ? 'add' : 'remove'](className);
|
||||
}
|
||||
} // Set a new source
|
||||
|
||||
|
||||
function newSource(type, init) {
|
||||
// Bail if new type isn't known, it's the current type, or current type is empty (video is default) and new type is video
|
||||
if (!(type in types) || !init && type === currentType || !currentType.length && type === types.video) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case types.video:
|
||||
player.source = {
|
||||
type: 'video',
|
||||
title: 'View From A Blue Moon',
|
||||
sources: [{
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4',
|
||||
type: 'video/mp4',
|
||||
size: 576
|
||||
}, {
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4',
|
||||
type: 'video/mp4',
|
||||
size: 720
|
||||
}, {
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4',
|
||||
type: 'video/mp4',
|
||||
size: 1080
|
||||
}, {
|
||||
src: 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4',
|
||||
type: 'video/mp4',
|
||||
size: 1440
|
||||
}],
|
||||
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'
|
||||
}]
|
||||
};
|
||||
i[r].l = 1 * new Date();
|
||||
a = s.createElement(o);
|
||||
m = s.getElementsByTagName(o)[0];
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m);
|
||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||
window.ga('create', 'UA-40881672-11', 'auto');
|
||||
window.ga('send', 'pageview');
|
||||
}
|
||||
/* eslint-enable */
|
||||
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',
|
||||
sources: [{
|
||||
src: 'https://youtube.com/watch?v=bTqVqk7FSmY',
|
||||
provider: 'youtube'
|
||||
}]
|
||||
};
|
||||
break;
|
||||
|
||||
case types.vimeo:
|
||||
player.source = {
|
||||
type: 'video',
|
||||
sources: [{
|
||||
src: 'https://vimeo.com/76979871',
|
||||
provider: 'vimeo'
|
||||
}]
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
} // Set the current type for next time
|
||||
|
||||
|
||||
currentType = type; // Remove active classes
|
||||
|
||||
Array.from(buttons).forEach(function (button) {
|
||||
return toggleClass(button.parentElement, 'active', false);
|
||||
}); // Set active on parent
|
||||
|
||||
toggleClass(document.querySelector("[data-source=\"".concat(type, "\"]")), 'active', true); // Show cite
|
||||
|
||||
Array.from(document.querySelectorAll('.plyr__cite')).forEach(function (cite) {
|
||||
cite.setAttribute('hidden', '');
|
||||
});
|
||||
document.querySelector(".plyr__cite--".concat(type)).removeAttribute('hidden');
|
||||
} // Bind to each button
|
||||
|
||||
|
||||
Array.from(buttons).forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
var type = button.getAttribute('data-source');
|
||||
newSource(type);
|
||||
|
||||
if (historySupport) {
|
||||
window.history.pushState({
|
||||
type: type
|
||||
}, '', "#".concat(type));
|
||||
}
|
||||
});
|
||||
}); // List for backwards/forwards
|
||||
|
||||
window.addEventListener('popstate', function (event) {
|
||||
if (event.state && 'type' in event.state) {
|
||||
newSource(event.state.type);
|
||||
}
|
||||
}); // On load
|
||||
|
||||
if (historySupport) {
|
||||
var video = !currentType.length; // If there's no current type set, assume video
|
||||
|
||||
if (video) {
|
||||
currentType = types.video;
|
||||
} // Replace current history state
|
||||
|
||||
|
||||
if (currentType in types) {
|
||||
window.history.replaceState({
|
||||
type: currentType
|
||||
}, '', video ? '' : "#".concat(currentType));
|
||||
} // If it's not video, load the source
|
||||
|
||||
|
||||
if (currentType !== types.video) {
|
||||
newSource(currentType, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}); // Raven / Sentry
|
||||
// For demo site (https://plyr.io) only
|
||||
|
||||
if (env.prod) {
|
||||
singleton.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
||||
} // Google analytics
|
||||
// For demo site (https://plyr.io) only
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
|
||||
if (env.prod) {
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i.GoogleAnalyticsObject = r;
|
||||
|
||||
i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments);
|
||||
};
|
||||
|
||||
i[r].l = 1 * new Date();
|
||||
a = s.createElement(o);
|
||||
m = s.getElementsByTagName(o)[0];
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m);
|
||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
window.ga('create', 'UA-40881672-11', 'auto');
|
||||
window.ga('send', 'pageview');
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
})();
|
||||
|
||||
}());
|
||||
|
||||
@@ -91,21 +91,22 @@
|
||||
</header>
|
||||
|
||||
<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-576p.mp4" type="video/mp4" size="576">
|
||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4" type="video/mp4" size="720">
|
||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4" type="video/mp4" size="1080">
|
||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1440p.mp4" type="video/mp4" size="1440">
|
||||
<div id="container">
|
||||
<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-576p.mp4" type="video/mp4" size="576">
|
||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-720p.mp4" type="video/mp4" size="720">
|
||||
<source src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-1080p.mp4" type="video/mp4" size="1080">
|
||||
|
||||
<!-- Caption files -->
|
||||
<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">
|
||||
<!-- Caption files -->
|
||||
<track kind="captions" label="English" srclang="en" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt"
|
||||
default>
|
||||
<track kind="captions" label="Français" srclang="fr" src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt">
|
||||
|
||||
<!-- Fallback for browsers that don't support the <video> element -->
|
||||
<a href="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.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-576p.mp4" download>Download</a>
|
||||
</video>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li class="plyr__cite plyr__cite--video" hidden>
|
||||
@@ -166,7 +167,7 @@
|
||||
</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>
|
||||
target="_blank" data-shr-network="twitter">tweet it</a> 👍
|
||||
</p>
|
||||
</aside>
|
||||
|
||||
|
||||
@@ -7,16 +7,17 @@
|
||||
import Raven from 'raven-js';
|
||||
|
||||
(() => {
|
||||
const isLive = window.location.host === 'plyr.io';
|
||||
|
||||
// Raven / Sentry
|
||||
// For demo site (https://plyr.io) only
|
||||
if (isLive) {
|
||||
Raven.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
||||
}
|
||||
const { host } = window.location;
|
||||
const env = {
|
||||
prod: host === 'plyr.io',
|
||||
dev: host === 'dev.plyr.io',
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
Raven.context(() => {
|
||||
const selector = '#player';
|
||||
const container = document.getElementById('container');
|
||||
|
||||
if (window.shr) {
|
||||
window.shr.setup({
|
||||
count: {
|
||||
@@ -30,6 +31,10 @@ import Raven from 'raven-js';
|
||||
|
||||
// Remove class on blur
|
||||
document.addEventListener('focusout', event => {
|
||||
if (!event.target.classList || container.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.target.classList.remove(tabClassName);
|
||||
});
|
||||
|
||||
@@ -42,12 +47,18 @@ import Raven from 'raven-js';
|
||||
// Delay the adding of classname until the focus has changed
|
||||
// This event fires before the focusin event
|
||||
setTimeout(() => {
|
||||
document.activeElement.classList.add(tabClassName);
|
||||
}, 0);
|
||||
const focused = document.activeElement;
|
||||
|
||||
if (!focused || !focused.classList || container.contains(focused)) {
|
||||
return;
|
||||
}
|
||||
|
||||
focused.classList.add(tabClassName);
|
||||
}, 10);
|
||||
});
|
||||
|
||||
// Setup the player
|
||||
const player = new Plyr('#player', {
|
||||
const player = new Plyr(selector, {
|
||||
debug: true,
|
||||
title: 'View From A Blue Moon',
|
||||
iconUrl: '../dist/plyr.svg',
|
||||
@@ -57,56 +68,6 @@ import Raven from 'raven-js';
|
||||
tooltips: {
|
||||
controls: true,
|
||||
},
|
||||
/* controls: [
|
||||
'play-large',
|
||||
'restart',
|
||||
'rewind',
|
||||
'play',
|
||||
'fast-forward',
|
||||
'progress',
|
||||
'current-time',
|
||||
'duration',
|
||||
'mute',
|
||||
'volume',
|
||||
'captions',
|
||||
'settings',
|
||||
'pip',
|
||||
'airplay',
|
||||
'fullscreen',
|
||||
], */
|
||||
/* i18n: {
|
||||
restart: '重新開始',
|
||||
rewind: '快退{seektime}秒',
|
||||
play: '播放',
|
||||
pause: '暫停',
|
||||
fastForward: '快進{seektime}秒',
|
||||
seek: '尋求',
|
||||
played: '發揮',
|
||||
buffered: '緩衝的',
|
||||
currentTime: '當前時間戳',
|
||||
duration: '長短',
|
||||
volume: '音量',
|
||||
mute: '靜音',
|
||||
unmute: '取消靜音',
|
||||
enableCaptions: '開啟字幕',
|
||||
disableCaptions: '關閉字幕',
|
||||
enterFullscreen: '進入全螢幕',
|
||||
exitFullscreen: '退出全螢幕',
|
||||
frameTitle: '球員為{title}',
|
||||
captions: '字幕',
|
||||
settings: '設定',
|
||||
speed: '速度',
|
||||
normal: '正常',
|
||||
quality: '質量',
|
||||
loop: '循環',
|
||||
start: 'Start',
|
||||
end: 'End',
|
||||
all: 'All',
|
||||
reset: '重啟',
|
||||
disabled: '殘',
|
||||
enabled: '啟用',
|
||||
advertisement: '廣告',
|
||||
}, */
|
||||
captions: {
|
||||
active: true,
|
||||
},
|
||||
@@ -114,7 +75,7 @@ import Raven from 'raven-js';
|
||||
google: 'AIzaSyDrNwtN3nLH_8rjCmu5Wq3ZCm4MNAVdc0c',
|
||||
},
|
||||
ads: {
|
||||
enabled: true,
|
||||
enabled: env.prod || env.dev,
|
||||
publisherId: '918848828995742',
|
||||
},
|
||||
});
|
||||
@@ -310,11 +271,17 @@ import Raven from 'raven-js';
|
||||
});
|
||||
});
|
||||
|
||||
// Raven / Sentry
|
||||
// For demo site (https://plyr.io) only
|
||||
if (env.prod) {
|
||||
Raven.config('https://d4ad9866ad834437a4754e23937071e4@sentry.io/305555').install();
|
||||
}
|
||||
|
||||
// Google analytics
|
||||
// For demo site (https://plyr.io) only
|
||||
/* eslint-disable */
|
||||
if (isLive) {
|
||||
(function(i, s, o, g, r, a, m) {
|
||||
if (env.prod) {
|
||||
((i, s, o, g, r, a, m) => {
|
||||
i.GoogleAnalyticsObject = r;
|
||||
i[r] =
|
||||
i[r] ||
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
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');
|
||||
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 {
|
||||
@@ -33,7 +34,8 @@
|
||||
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');
|
||||
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 {
|
||||
@@ -41,5 +43,6 @@
|
||||
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');
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ $plyr-font-size-small: 12px;
|
||||
$plyr-font-size-time: 11px;
|
||||
$plyr-font-size-badges: 9px;
|
||||
|
||||
// Other
|
||||
$plyr-font-smoothing: true;
|
||||
|
||||
// Captions
|
||||
$plyr-font-size-captions-base: $plyr-font-size-base;
|
||||
$plyr-font-size-captions-small: $plyr-font-size-small;
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// Typography
|
||||
// ==========================================================================
|
||||
|
||||
$font-sans-serif: 'Gordita', 'Avenir', 'Helvetica Neue', sans-serif;
|
||||
$font-sans-serif: 'Gordita', 'Avenir', 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol';
|
||||
|
||||
$font-size-base: 15;
|
||||
$font-size-small: 13;
|
||||
|
||||
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.3 KiB |
@@ -12,7 +12,6 @@ const concat = require('gulp-concat');
|
||||
const filter = require('gulp-filter');
|
||||
const sass = require('gulp-sass');
|
||||
const cleancss = require('gulp-clean-css');
|
||||
const run = require('run-sequence');
|
||||
const header = require('gulp-header');
|
||||
const prefix = require('gulp-autoprefixer');
|
||||
const gitbranch = require('git-branch');
|
||||
@@ -29,18 +28,12 @@ const sourcemaps = require('gulp-sourcemaps');
|
||||
const uglify = require('gulp-uglify-es').default;
|
||||
const commonjs = require('rollup-plugin-commonjs');
|
||||
const resolve = require('rollup-plugin-node-resolve');
|
||||
const FastlyPurge = require('fastly-purge');
|
||||
const through = require('through2');
|
||||
|
||||
const bundles = require('./bundles.json');
|
||||
const pkg = require('./package.json');
|
||||
|
||||
// Get AWS config
|
||||
let aws = {};
|
||||
try {
|
||||
aws = require('./aws.json'); //eslint-disable-line
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
const minSuffix = '.min';
|
||||
|
||||
// Paths
|
||||
@@ -50,7 +43,7 @@ const paths = {
|
||||
// Source paths
|
||||
src: {
|
||||
sass: path.join(root, 'src/sass/**/*.scss'),
|
||||
js: path.join(root, 'src/js/**/*'),
|
||||
js: path.join(root, 'src/js/**/*.js'),
|
||||
sprite: path.join(root, 'src/sprite/*.svg'),
|
||||
},
|
||||
|
||||
@@ -61,7 +54,7 @@ const paths = {
|
||||
// Source paths
|
||||
src: {
|
||||
sass: path.join(root, 'demo/src/sass/**/*.scss'),
|
||||
js: path.join(root, 'demo/src/js/**/*'),
|
||||
js: path.join(root, 'demo/src/js/**/*.js'),
|
||||
},
|
||||
|
||||
// Output paths
|
||||
@@ -94,33 +87,33 @@ const sizeOptions = { showFiles: true, gzip: true };
|
||||
const browsers = ['> 1%'];
|
||||
|
||||
// Babel config
|
||||
const babelrc = {
|
||||
presets: [[
|
||||
'env',
|
||||
{
|
||||
targets: {
|
||||
browsers,
|
||||
const babelrc = (polyfill = false) => ({
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: {
|
||||
browsers,
|
||||
},
|
||||
useBuiltIns: polyfill ? 'usage' : false,
|
||||
modules: false,
|
||||
},
|
||||
useBuiltIns: true,
|
||||
modules: false,
|
||||
},
|
||||
]],
|
||||
plugins: ['external-helpers'],
|
||||
],
|
||||
],
|
||||
babelrc: false,
|
||||
exclude: 'node_modules/**',
|
||||
};
|
||||
});
|
||||
|
||||
// Clean out /dist
|
||||
gulp.task('clean', () => {
|
||||
const dirs = [
|
||||
paths.plyr.output,
|
||||
paths.demo.output,
|
||||
].map(dir => path.join(dir, '**/*'));
|
||||
gulp.task('clean', done => {
|
||||
const dirs = [paths.plyr.output, paths.demo.output].map(dir => path.join(dir, '**/*'));
|
||||
|
||||
// Don't delete the mp4
|
||||
dirs.push(`!${path.join(paths.plyr.output, '**/*.mp4')}`);
|
||||
|
||||
del(dirs);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
const build = {
|
||||
@@ -129,6 +122,7 @@ const build = {
|
||||
const name = `js:${key}`;
|
||||
tasks.js.push(name);
|
||||
const { output } = paths[bundle];
|
||||
const polyfill = name.includes('polyfilled');
|
||||
|
||||
return gulp.task(name, () =>
|
||||
gulp
|
||||
@@ -138,11 +132,7 @@ const build = {
|
||||
.pipe(
|
||||
rollup(
|
||||
{
|
||||
plugins: [
|
||||
resolve(),
|
||||
commonjs(),
|
||||
babel(babelrc),
|
||||
],
|
||||
plugins: [resolve(), commonjs(), babel(babelrc(polyfill))],
|
||||
},
|
||||
options,
|
||||
),
|
||||
@@ -187,9 +177,11 @@ const build = {
|
||||
.src(paths[bundle].src.sprite)
|
||||
.pipe(
|
||||
svgmin({
|
||||
plugins: [{
|
||||
removeDesc: true,
|
||||
}],
|
||||
plugins: [
|
||||
{
|
||||
removeDesc: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
)
|
||||
.pipe(svgstore())
|
||||
@@ -210,37 +202,40 @@ build.sass(bundles.demo.sass, 'demo');
|
||||
build.js(bundles.demo.js, 'demo', { format: 'iife' });
|
||||
|
||||
// Build all JS
|
||||
gulp.task('js', () => {
|
||||
run(tasks.js);
|
||||
});
|
||||
gulp.task('js', () => gulp.parallel(tasks.js));
|
||||
|
||||
// Watch for file changes
|
||||
gulp.task('watch', () => {
|
||||
// Plyr core
|
||||
gulp.watch(paths.plyr.src.js, tasks.js);
|
||||
gulp.watch(paths.plyr.src.sass, tasks.sass);
|
||||
gulp.watch(paths.plyr.src.sprite, tasks.sprite);
|
||||
gulp.watch(paths.plyr.src.js, gulp.parallel(tasks.js));
|
||||
gulp.watch(paths.plyr.src.sass, gulp.parallel(tasks.sass));
|
||||
gulp.watch(paths.plyr.src.sprite, gulp.parallel(tasks.sprite));
|
||||
|
||||
// Demo
|
||||
gulp.watch(paths.demo.src.js, tasks.js);
|
||||
gulp.watch(paths.demo.src.sass, tasks.sass);
|
||||
gulp.watch(paths.demo.src.js, gulp.parallel(tasks.js));
|
||||
gulp.watch(paths.demo.src.sass, gulp.parallel(tasks.sass));
|
||||
});
|
||||
|
||||
// Build distribution
|
||||
gulp.task('build', () => {
|
||||
run(tasks.clean, tasks.js, tasks.sass, tasks.sprite);
|
||||
});
|
||||
gulp.task('build', gulp.series(tasks.clean, gulp.parallel(tasks.js, tasks.sass, tasks.sprite)));
|
||||
|
||||
// Default gulp task
|
||||
gulp.task('default', () => {
|
||||
run('build', 'watch');
|
||||
});
|
||||
gulp.task('default', gulp.series('build', 'watch'));
|
||||
|
||||
// Publish a version to CDN and demo
|
||||
// --------------------------------------------
|
||||
// If aws is setup
|
||||
if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
||||
// Get deployment config
|
||||
let credentials = {};
|
||||
try {
|
||||
credentials = require('./credentials.json'); //eslint-disable-line
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
// If deployment is setup
|
||||
if (Object.keys(credentials).includes('aws') && Object.keys(credentials).includes('fastly')) {
|
||||
const { version } = pkg;
|
||||
const { aws, fastly } = credentials;
|
||||
|
||||
// Get branch info
|
||||
const branch = {
|
||||
@@ -248,10 +243,6 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
||||
master: 'master',
|
||||
develop: 'develop',
|
||||
};
|
||||
const allowed = [
|
||||
branch.master,
|
||||
branch.develop,
|
||||
];
|
||||
|
||||
const maxAge = 31536000; // 1 year
|
||||
const options = {
|
||||
@@ -279,33 +270,51 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
||||
},
|
||||
};
|
||||
|
||||
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 regex =
|
||||
'(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*).(?:0|[1-9][0-9]*)(?:-[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:.[\\da-z\\-]+)*)?';
|
||||
const semver = new RegExp(`v${regex}`, 'gi');
|
||||
const localPath = new RegExp('(../)?dist', 'gi');
|
||||
const versionPath = `https://${aws.cdn.domain}/${version}`;
|
||||
const cdnpath = new RegExp(`${aws.cdn.domain}/${regex}/`, 'gi');
|
||||
|
||||
const renameFile = rename(p => {
|
||||
p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line
|
||||
p.dirname = p.dirname.replace('.', version); // eslint-disable-line
|
||||
});
|
||||
|
||||
// Check we're on the correct branch to deploy
|
||||
const canDeploy = () => {
|
||||
const allowed = [branch.master, branch.develop];
|
||||
|
||||
if (!allowed.includes(branch.current)) {
|
||||
console.error(`Must be on ${allowed.join(', ')} to publish! (current: ${branch.current})`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
gulp.task('version', () => {
|
||||
if (!canDeploy()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`Updating versions to '${version}'...`);
|
||||
|
||||
// Replace versioned URLs in source
|
||||
const files = [
|
||||
'plyr.js',
|
||||
'plyr.polyfilled.js',
|
||||
'defaults.js',
|
||||
];
|
||||
const files = ['plyr.js', 'plyr.polyfilled.js', 'config/defaults.js'];
|
||||
|
||||
return gulp
|
||||
.src(files.map(file => path.join(root, `src/js/${file}`)))
|
||||
.src(files.map(file => path.join(root, `src/js/${file}`)), { base: '.' })
|
||||
.pipe(replace(semver, `v${version}`))
|
||||
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
||||
.pipe(gulp.dest(path.join(root, 'src/js/')));
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
// 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})`);
|
||||
if (!canDeploy()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -315,14 +324,14 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
||||
return (
|
||||
gulp
|
||||
.src(paths.upload)
|
||||
.pipe(
|
||||
rename(p => {
|
||||
p.basename = p.basename.replace(minSuffix, ''); // eslint-disable-line
|
||||
p.dirname = p.dirname.replace('.', version); // eslint-disable-line
|
||||
}),
|
||||
)
|
||||
.pipe(renameFile)
|
||||
// Remove min suffix from source map URL
|
||||
.pipe(replace(/sourceMappingURL=([\w-?.]+)/, (match, p1) => `sourceMappingURL=${p1.replace(minSuffix, '')}`))
|
||||
.pipe(
|
||||
replace(
|
||||
/sourceMappingURL=([\w-?.]+)/,
|
||||
(match, p1) => `sourceMappingURL=${p1.replace(minSuffix, '')}`,
|
||||
),
|
||||
)
|
||||
.pipe(
|
||||
size({
|
||||
showFiles: true,
|
||||
@@ -334,18 +343,46 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
||||
);
|
||||
});
|
||||
|
||||
// Purge the fastly cache incase any 403/404 are cached
|
||||
gulp.task('purge', () => {
|
||||
const list = [];
|
||||
|
||||
return gulp
|
||||
.src(paths.upload)
|
||||
.pipe(
|
||||
through.obj((file, enc, cb) => {
|
||||
const filename = file.path.split('/').pop();
|
||||
list.push(`${versionPath}/${filename}`);
|
||||
cb(null);
|
||||
}),
|
||||
)
|
||||
.on('end', () => {
|
||||
const purge = new FastlyPurge(fastly.token);
|
||||
|
||||
list.forEach(url => {
|
||||
console.log(`Purging ${url}...`);
|
||||
|
||||
purge.url(url, (error, result) => {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
} else if (result) {
|
||||
console.log(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Publish to demo bucket
|
||||
gulp.task('demo', () => {
|
||||
if (!allowed.includes(branch.current)) {
|
||||
console.error(`Must be on ${allowed.join(', ')} to publish! (current: ${branch.current})`);
|
||||
if (!canDeploy()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`Uploading '${version}' demo to ${aws.demo.domain}...`);
|
||||
|
||||
// Replace versioned files in readme.md
|
||||
gulp
|
||||
.src([`${root}/readme.md`])
|
||||
gulp.src([`${root}/readme.md`])
|
||||
.pipe(replace(cdnpath, `${aws.cdn.domain}/${version}/`))
|
||||
.pipe(gulp.dest(root));
|
||||
|
||||
@@ -359,8 +396,7 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
||||
pages.push(error);
|
||||
}
|
||||
|
||||
gulp
|
||||
.src(pages)
|
||||
gulp.src(pages)
|
||||
.pipe(replace(localPath, versionPath))
|
||||
.pipe(s3(aws.demo, options.demo));
|
||||
|
||||
@@ -399,22 +435,28 @@ if (Object.keys(aws).includes('cdn') && Object.keys(aws).includes('demo')) {
|
||||
}));
|
||||
}); */
|
||||
|
||||
// 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}`,
|
||||
// Open the demo site to check it's ok
|
||||
gulp.task('open', callback => {
|
||||
gulp.src(__filename).pipe(
|
||||
open({
|
||||
uri: `https://${aws.demo.domain}`,
|
||||
}),
|
||||
);
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
// Do everything
|
||||
gulp.task('publish', callback => {
|
||||
run('version', tasks.clean, tasks.js, tasks.sass, tasks.sprite, 'cdn', 'demo', callback);
|
||||
});
|
||||
gulp.task(
|
||||
'deploy',
|
||||
gulp.series(
|
||||
'version',
|
||||
tasks.clean,
|
||||
gulp.parallel(tasks.js, tasks.sass, tasks.sprite),
|
||||
'cdn',
|
||||
'demo',
|
||||
'purge',
|
||||
'open',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,63 +1,27 @@
|
||||
{
|
||||
"name": "plyr",
|
||||
"version": "3.3.14",
|
||||
"version": "3.4.7",
|
||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||
"homepage": "https://plyr.io",
|
||||
"author": "Sam Potts <sam@potts.es>",
|
||||
"keywords": [
|
||||
"HTML5 Video",
|
||||
"HTML5 Audio",
|
||||
"Media Player",
|
||||
"DASH",
|
||||
"Shaka",
|
||||
"WordPress",
|
||||
"HLS"
|
||||
],
|
||||
"main": "./dist/plyr.js",
|
||||
"browser": "./dist/plyr.min.js",
|
||||
"sass": "./src/sass/plyr.scss",
|
||||
"style": "./dist/plyr.css",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"del": "^3.0.0",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-airbnb-base": "^12.1.0",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-import": "^2.12.0",
|
||||
"git-branch": "^2.0.1",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-autoprefixer": "^5.0.0",
|
||||
"gulp-better-rollup": "^3.2.1",
|
||||
"gulp-clean-css": "^3.9.4",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-filter": "^5.1.0",
|
||||
"gulp-header": "^2.0.5",
|
||||
"gulp-open": "^3.0.1",
|
||||
"gulp-postcss": "^7.0.1",
|
||||
"gulp-rename": "^1.3.0",
|
||||
"gulp-replace": "^1.0.0",
|
||||
"gulp-s3": "^0.11.0",
|
||||
"gulp-sass": "^4.0.1",
|
||||
"gulp-size": "^3.0.0",
|
||||
"gulp-sourcemaps": "^2.6.4",
|
||||
"gulp-svgmin": "^1.2.4",
|
||||
"gulp-svgstore": "^6.1.1",
|
||||
"gulp-uglify-es": "^1.0.4",
|
||||
"gulp-util": "^3.0.8",
|
||||
"postcss-custom-properties": "^7.0.0",
|
||||
"prettier-eslint": "^8.8.1",
|
||||
"prettier-stylelint": "^0.4.2",
|
||||
"rollup-plugin-babel": "^3.0.4",
|
||||
"rollup-plugin-commonjs": "^9.1.3",
|
||||
"rollup-plugin-node-resolve": "^3.3.0",
|
||||
"run-sequence": "^2.2.1",
|
||||
"stylelint": "^9.3.0",
|
||||
"stylelint-config-prettier": "^3.2.0",
|
||||
"stylelint-config-recommended": "^2.1.0",
|
||||
"stylelint-config-sass-guidelines": "^5.0.0",
|
||||
"stylelint-order": "^0.8.1",
|
||||
"stylelint-scss": "^3.1.2",
|
||||
"stylelint-selector-bem-pattern": "^2.0.0"
|
||||
},
|
||||
"keywords": ["HTML5 Video", "HTML5 Audio", "Media Player", "DASH", "Shaka", "WordPress", "HLS"],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/sampotts/plyr.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/sampotts/plyr/issues"
|
||||
},
|
||||
@@ -66,15 +30,62 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"lint": "eslint src/js",
|
||||
"lint": "eslint src/js && npm run-script remark",
|
||||
"remark": "remark -f --use 'validate-links=repository:\"sampotts/plyr\"' '{,!(node_modules),.?**/}*.md'",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Sam Potts <sam@potts.es>",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.5",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"@babel/preset-env": "^7.1.5",
|
||||
"del": "^3.0.0",
|
||||
"eslint": "^5.8.0",
|
||||
"eslint-config-airbnb-base": "^13.1.0",
|
||||
"eslint-config-prettier": "^3.1.0",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"fastly-purge": "^1.0.1",
|
||||
"git-branch": "^2.0.1",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-autoprefixer": "^6.0.0",
|
||||
"gulp-better-rollup": "^3.4.0",
|
||||
"gulp-clean-css": "^3.10.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-filter": "^5.1.0",
|
||||
"gulp-header": "^2.0.5",
|
||||
"gulp-open": "^3.0.1",
|
||||
"gulp-postcss": "^8.0.0",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"gulp-replace": "^1.0.0",
|
||||
"gulp-s3": "^0.11.0",
|
||||
"gulp-sass": "^4.0.2",
|
||||
"gulp-size": "^3.0.0",
|
||||
"gulp-sourcemaps": "^2.6.4",
|
||||
"gulp-svgmin": "^2.1.0",
|
||||
"gulp-svgstore": "^7.0.0",
|
||||
"gulp-uglify-es": "^1.0.4",
|
||||
"gulp-util": "^3.0.8",
|
||||
"postcss-custom-properties": "^8.0.9",
|
||||
"prettier-eslint": "^8.8.2",
|
||||
"prettier-stylelint": "^0.4.2",
|
||||
"remark-cli": "^6.0.0",
|
||||
"remark-validate-links": "^7.1.0",
|
||||
"rollup-plugin-babel": "^4.0.3",
|
||||
"rollup-plugin-commonjs": "^9.2.0",
|
||||
"rollup-plugin-node-resolve": "^3.4.0",
|
||||
"stylelint": "^9.7.1",
|
||||
"stylelint-config-prettier": "^4.0.0",
|
||||
"stylelint-config-recommended": "^2.1.0",
|
||||
"stylelint-config-sass-guidelines": "^5.2.0",
|
||||
"stylelint-order": "^1.0.0",
|
||||
"stylelint-scss": "^3.4.0",
|
||||
"stylelint-selector-bem-pattern": "^2.0.0",
|
||||
"through2": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"custom-event-polyfill": "^0.3.0",
|
||||
"core-js": "^2.5.7",
|
||||
"custom-event-polyfill": "^1.0.6",
|
||||
"loadjs": "^3.5.4",
|
||||
"raven-js": "^3.26.2",
|
||||
"url-polyfill": "^1.0.13"
|
||||
"raven-js": "^3.27.0",
|
||||
"url-polyfill": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,26 +8,26 @@ A simple, lightweight, accessible and customizable HTML5, YouTube and Vimeo medi
|
||||
|
||||
## Features
|
||||
|
||||
* **Accessible** - full support for VTT captions and screen readers
|
||||
* **[Customisable](#html)** - make the player look how you want with the markup you want
|
||||
* **Good HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
|
||||
- **Accessible** - full support for VTT captions and screen readers
|
||||
- **[Customisable](#html)** - make the player look how you want with the markup you want
|
||||
- **Good HTML** - uses the _right_ elements. `<input type="range">` for volume and `<progress>` for progress and well, `<button>`s for buttons. There's no
|
||||
`<span>` or `<a href="#">` button hacks
|
||||
* **Responsive** - works with any screen size
|
||||
* **HTML Video & Audio** - support for both formats
|
||||
* **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
|
||||
* **[Monetization](#ads)** - make money from your videos
|
||||
* **[Streaming](#streaming)** - support for hls.js, Shaka and dash.js streaming playback
|
||||
* **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
||||
* **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
|
||||
* **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes
|
||||
* **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
|
||||
* **Picture-in-Picture** - supports Safari's picture-in-picture mode
|
||||
* **Playsinline** - supports the `playsinline` attribute
|
||||
* **Speed controls** - adjust speed on the fly
|
||||
* **Multiple captions** - support for multiple caption tracks
|
||||
* **i18n support** - support for internationalization of controls
|
||||
* **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
|
||||
* **SASS** - to include in your build processes
|
||||
- **Responsive** - works with any screen size
|
||||
- **HTML Video & Audio** - support for both formats
|
||||
- **[Embedded Video](#embeds)** - support for YouTube and Vimeo video playback
|
||||
- **[Monetization](#ads)** - make money from your videos
|
||||
- **[Streaming](#try-plyr-online)** - support for hls.js, Shaka and dash.js streaming playback
|
||||
- **[API](#api)** - toggle playback, volume, seeking, and more through a standardized API
|
||||
- **[Events](#events)** - no messing around with Vimeo and YouTube APIs, all events are standardized across formats
|
||||
- **[Fullscreen](#fullscreen)** - supports native fullscreen with fallback to "full window" modes
|
||||
- **[Shortcuts](#shortcuts)** - supports keyboard shortcuts
|
||||
- **Picture-in-Picture** - supports Safari's picture-in-picture mode
|
||||
- **Playsinline** - supports the `playsinline` attribute
|
||||
- **Speed controls** - adjust speed on the fly
|
||||
- **Multiple captions** - support for multiple caption tracks
|
||||
- **i18n support** - support for internationalization of controls
|
||||
- **No dependencies** - written in "vanilla" ES6 JavaScript, no jQuery required
|
||||
- **SASS** - to include in your build processes
|
||||
|
||||
Oh and yes, it works with Bootstrap.
|
||||
|
||||
@@ -80,7 +80,7 @@ Plyr extends upon the standard [HTML5 media element](https://developer.mozilla.o
|
||||
</audio>
|
||||
```
|
||||
|
||||
For YouTube and Vimeo players, Plyr uses progressive enhancement to enhance the default `<iframe>` embeds. Below are some examples. The `plyr__video-embed` classname will make the embed responsive. You can add the `autoplay`, `loop` and `playsinline` (YouTube only) query parameters to the URL and they will be set as config options automatically. For YouTube, the `origin` should be updated to reflect the domain you're hosting the embed on, or you can opt to omit it.
|
||||
For YouTube and Vimeo players, Plyr uses progressive enhancement to enhance the default `<iframe>` embeds. Below are some examples. The `plyr__video-embed` classname will make the embed responsive. You can add the `autoplay`, `loop`, `hl` (YouTube only) and `playsinline` (YouTube only) query parameters to the URL and they will be set as config options automatically. For YouTube, the `origin` should be updated to reflect the domain you're hosting the embed on, or you can opt to omit it.
|
||||
|
||||
#### YouTube embed
|
||||
|
||||
@@ -132,13 +132,13 @@ See [initialising](#initialising) for more information on advanced setups.
|
||||
You can use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript. There's 2 versions; one with and one without [polyfills](#polyfills). My recommendation would be to manage polyfills seperately as part of your application but to make life easier you can use the polyfilled build.
|
||||
|
||||
```html
|
||||
<script src="https://cdn.plyr.io/3.3.14/plyr.js"></script>
|
||||
<script src="https://cdn.plyr.io/3.4.7/plyr.js"></script>
|
||||
```
|
||||
|
||||
...or...
|
||||
|
||||
```html
|
||||
<script src="https://cdn.plyr.io/3.3.14/plyr.polyfilled.js"></script>
|
||||
<script src="https://cdn.plyr.io/3.4.7/plyr.polyfilled.js"></script>
|
||||
```
|
||||
|
||||
### CSS
|
||||
@@ -152,21 +152,21 @@ Include the `plyr.css` stylsheet into your `<head>`
|
||||
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the default CSS, you can use the following:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.3.14/plyr.css">
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.4.7/plyr.css">
|
||||
```
|
||||
|
||||
### SVG Sprite
|
||||
|
||||
The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https://www.fastly.com/)). To change this, see the [options](#options) below. For
|
||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.3.14/plyr.svg`.
|
||||
reference, the CDN hosted SVG sprite can be found at `https://cdn.plyr.io/3.4.7/plyr.svg`.
|
||||
|
||||
## Ads
|
||||
|
||||
Plyr has partnered up with [vi.ai](http://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy:
|
||||
Plyr has partnered up with [vi.ai](https://vi.ai/publisher-video-monetization/?aid=plyrio) to offer monetization options for your videos. Getting setup is easy:
|
||||
|
||||
* [Sign up for a vi.ai account](http://vi.ai/publisher-video-monetization/?aid=plyrio)
|
||||
* Grab your publisher ID from the code snippet
|
||||
* Enable ads in the [config options](#options) and enter your publisher ID
|
||||
- [Sign up for a vi.ai account](https://vi.ai/publisher-video-monetization/?aid=plyrio)
|
||||
- Grab your publisher ID from the code snippet
|
||||
- Enable ads in the [config options](#options) and enter your publisher ID
|
||||
|
||||
Any questions regarding the ads can be sent straight to vi.ai and any issues with rendering raised through GitHub issues.
|
||||
|
||||
@@ -175,7 +175,7 @@ Any questions regarding the ads can be sent straight to vi.ai and any issues wit
|
||||
### SASS
|
||||
|
||||
You can use `bundle.scss` file included in `/src` as part of your build and change variables to suit your design. The SASS require you to
|
||||
use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you be should already!) as all declarations use the W3C definitions.
|
||||
use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you should be already!) as all declarations use the W3C definitions.
|
||||
|
||||
The HTML markup uses the BEM methodology with `plyr` as the block, e.g. `.plyr__controls`. You can change the class hooks in the options to match any custom CSS
|
||||
you write. Check out the JavaScript source for more on this.
|
||||
@@ -213,10 +213,10 @@ WebVTT captions are supported. To add a caption track, check the HTML example ab
|
||||
|
||||
You can specify a range of arguments for the constructor to use:
|
||||
|
||||
* A CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
|
||||
* A [`HTMLElement`](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
|
||||
* A [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList)
|
||||
* A [jQuery](https://jquery.com) object
|
||||
- A CSS string selector that's compatible with [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
|
||||
- A [`HTMLElement`](https://developer.mozilla.org/en/docs/Web/API/HTMLElement)
|
||||
- A [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList)
|
||||
- A [jQuery](https://jquery.com) object
|
||||
|
||||
_Note_: If a `NodeList`, `Array`, or jQuery object are passed, the first element will be used for setup. To setup multiple players, see [setting up multiple players](#setting-up-multiple-players) below.
|
||||
|
||||
@@ -286,11 +286,11 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
||||
| `debug` | Boolean | `false` | Display debugging information in the console |
|
||||
| `controls` | Array, Function or Element | `['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', 'fullscreen']` | If a function is passed, it is assumed your method will return either an element or HTML string for the controls. Three arguments will be passed to your function; `id` (the unique id for the player), `seektime` (the seektime step in seconds), and `title` (the media title). See [controls.md](controls.md) for more info on how the html needs to be structured. |
|
||||
| `settings` | Array | `['captions', 'quality', 'speed', 'loop']` | If you're using the default controls are used then you can specify which settings to show in the menu |
|
||||
| `i18n` | Object | See [defaults.js](/src/js/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
|
||||
| `i18n` | Object | See [defaults.js](/src/js/config/defaults.js) | Used for internationalization (i18n) of the text within the UI. |
|
||||
| `loadSprite` | Boolean | `true` | Load the SVG sprite specified as the `iconUrl` option (if a URL). If `false`, it is assumed you are handling sprite loading yourself. |
|
||||
| `iconUrl` | String | `null` | Specify a URL or path to the SVG sprite. See the [SVG section](#svg) for more info. |
|
||||
| `iconPrefix` | String | `plyr` | Specify the id prefix for the icons used in the default controls (e.g. "plyr-play" would be "plyr"). This is to prevent clashes if you're using your own SVG sprite but with the default controls. Most people can ignore this option. |
|
||||
| `blankUrl` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. |
|
||||
| `blankVideo` | String | `https://cdn.plyr.io/static/blank.mp4` | Specify a URL or path to a blank video file used to properly cancel network requests. |
|
||||
| `autoplay` | Boolean | `false` | Autoplay the media on load. This is generally advised against on UX grounds. It is also disabled by default in some browsers. If the `autoplay` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true. |
|
||||
| `autopause`¹ | Boolean | `true` | Only allow one player playing at once. |
|
||||
| `seekTime` | Number | `10` | The time, in seconds, to seek when a user hits fast forward or rewind. |
|
||||
@@ -307,7 +307,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
||||
| `invertTime` | Boolean | `true` | Display the current time as a countdown rather than an incremental counter. |
|
||||
| `toggleInvert` | Boolean | `true` | Allow users to click to toggle the above. |
|
||||
| `listeners` | Object | `null` | Allows binding of event listeners to the controls before the default handlers. See the `defaults.js` for available listeners. If your handler prevents default on the event (`event.preventDefault()`), the default handler will not fire. |
|
||||
| `captions` | Object | `{ active: false, language: 'auto', update: false }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). 'auto' uses the browser language. `update`: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in unselectable language options). |
|
||||
| `captions` | Object | `{ active: false, language: 'auto', update: false }` | `active`: Toggles if captions should be active by default. `language`: Sets the default language to load (if available). 'auto' uses the browser language. `update`: Listen to changes to tracks and update menu. This is needed for some streaming libraries, but can result in unselectable language options). |
|
||||
| `fullscreen` | Object | `{ enabled: true, fallback: true, iosNative: false }` | `enabled`: Toggles whether fullscreen should be enabled. `fallback`: Allow fallback to a full-window solution. `iosNative`: whether to use native iOS fullscreen when entering fullscreen (no custom controls) |
|
||||
| `ratio` | String | `16:9` | The aspect ratio you want to use for embedded players. |
|
||||
| `storage` | Object | `{ enabled: true, key: 'plyr' }` | `enabled`: Allow use of local storage to store user settings. `key`: The key name to use. |
|
||||
@@ -315,6 +315,7 @@ Note the single quotes encapsulating the JSON and double quotes on the object ke
|
||||
| `quality` | Object | `{ default: 'default', options: ['hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'default'] }` | Currently only supported by YouTube. `default` is the default quality level, determined by YouTube. `options` are the options to display. |
|
||||
| `loop` | Object | `{ active: false }` | `active`: Whether to loop the current video. If the `loop` attribute is present on a `<video>` or `<audio>` element, this will be automatically set to true This is an object to support future functionality. |
|
||||
| `ads` | Object | `{ enabled: false, publisherId: '' }` | `enabled`: Whether to enable vi.ai ads. `publisherId`: Your unique vi.ai publisher ID. |
|
||||
| `urls` | Object | See source. | If you wish to override any API URLs then you can do so here. You can also set a custom download URL for the download button. |
|
||||
|
||||
1. Vimeo only
|
||||
|
||||
@@ -365,9 +366,9 @@ player.fullscreen.enter(); // Enter fullscreen
|
||||
| `fullscreen.exit()` | - | Exit fullscreen. |
|
||||
| `fullscreen.toggle()` | - | Toggle fullscreen. |
|
||||
| `airplay()` | - | Trigger the airplay dialog on supported devices. |
|
||||
| `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. |
|
||||
| `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. |
|
||||
| `on(event, function)` | String, Function | Add an event listener for the specified event. |
|
||||
| `once(event, function)` | String, Function | Add an event listener for the specified event once. |
|
||||
| `once(event, function)` | String, Function | Add an event listener for the specified event once. |
|
||||
| `off(event, function)` | String, Function | Remove an event listener for the specified event. |
|
||||
| `supports(type)` | String | Check support for a mime type. |
|
||||
| `destroy()` | - | Destroy the instance and garbage collect any elements. |
|
||||
@@ -391,32 +392,32 @@ player.currentTime; // 10
|
||||
player.fullscreen.active; // false;
|
||||
```
|
||||
|
||||
| Property | Getter | Setter | Description |
|
||||
| -------------------- | ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `isHTML5` | ✓ | - | Returns a boolean indicating if the current player is HTML5. |
|
||||
| `isEmbed` | ✓ | - | Returns a boolean indicating if the current player is an embedded player. |
|
||||
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
|
||||
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
|
||||
| `stopped` | ✓ | - | Returns a boolean indicating if the current player is stopped. |
|
||||
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
|
||||
| `buffered` | ✓ | - | Returns a float between 0 and 1 indicating how much of the media is buffered |
|
||||
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
|
||||
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
|
||||
| `duration` | ✓ | - | Returns the duration for the current media. |
|
||||
| `volume` | ✓ | ✓ | Gets or sets the volume for the player. The setter accepts a float between 0 and 1. |
|
||||
| `muted` | ✓ | ✓ | Gets or sets the muted state of the player. The setter accepts a boolean. |
|
||||
| `hasAudio` | ✓ | - | Returns a boolean indicating if the current media has an audio track. |
|
||||
| `speed` | ✓ | ✓ | Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5. |
|
||||
| `quality`¹ | ✓ | ✓ | Gets or sets the quality for the player. The setter accepts a value from the options specified in your config. |
|
||||
| `loop` | ✓ | ✓ | Gets or sets the current loop state of the player. The setter accepts a boolean. |
|
||||
| `source` | ✓ | ✓ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#source-setter) below for examples. |
|
||||
| `poster` | ✓ | ✓ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image. |
|
||||
| `autoplay` | ✓ | ✓ | Gets or sets the autoplay state of the player. The setter accepts a boolean. |
|
||||
| `currentTrack` | ✓ | ✓ | Gets or sets the caption track by index. `-1` means the track is missing or captions is not active |
|
||||
| Property | Getter | Setter | Description |
|
||||
| -------------------- | ------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `isHTML5` | ✓ | - | Returns a boolean indicating if the current player is HTML5. |
|
||||
| `isEmbed` | ✓ | - | Returns a boolean indicating if the current player is an embedded player. |
|
||||
| `playing` | ✓ | - | Returns a boolean indicating if the current player is playing. |
|
||||
| `paused` | ✓ | - | Returns a boolean indicating if the current player is paused. |
|
||||
| `stopped` | ✓ | - | Returns a boolean indicating if the current player is stopped. |
|
||||
| `ended` | ✓ | - | Returns a boolean indicating if the current player has finished playback. |
|
||||
| `buffered` | ✓ | - | Returns a float between 0 and 1 indicating how much of the media is buffered |
|
||||
| `currentTime` | ✓ | ✓ | Gets or sets the currentTime for the player. The setter accepts a float in seconds. |
|
||||
| `seeking` | ✓ | - | Returns a boolean indicating if the current player is seeking. |
|
||||
| `duration` | ✓ | - | Returns the duration for the current media. |
|
||||
| `volume` | ✓ | ✓ | Gets or sets the volume for the player. The setter accepts a float between 0 and 1. |
|
||||
| `muted` | ✓ | ✓ | Gets or sets the muted state of the player. The setter accepts a boolean. |
|
||||
| `hasAudio` | ✓ | - | Returns a boolean indicating if the current media has an audio track. |
|
||||
| `speed` | ✓ | ✓ | Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5. |
|
||||
| `quality`¹ | ✓ | ✓ | Gets or sets the quality for the player. The setter accepts a value from the options specified in your config. |
|
||||
| `loop` | ✓ | ✓ | Gets or sets the current loop state of the player. The setter accepts a boolean. |
|
||||
| `source` | ✓ | ✓ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#the-source-setter) below for examples. |
|
||||
| `poster` | ✓ | ✓ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image. |
|
||||
| `autoplay` | ✓ | ✓ | Gets or sets the autoplay state of the player. The setter accepts a boolean. |
|
||||
| `currentTrack` | ✓ | ✓ | Gets or sets the caption track by index. `-1` means the track is missing or captions is not active |
|
||||
| `language` | ✓ | ✓ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include. If your captions don't have any language data, or if you have multiple tracks with the same language, you may want to use `currentTrack` instead. |
|
||||
| `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. |
|
||||
| `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. |
|
||||
| `pip` | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+. |
|
||||
| `fullscreen.active` | ✓ | - | Returns a boolean indicating if the current player is in fullscreen mode. |
|
||||
| `fullscreen.enabled` | ✓ | - | Returns a boolean indicating if the current player has fullscreen enabled. |
|
||||
| `pip`² | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ (on MacOS Sierra+ and iOS 10+) and Chrome 70+. |
|
||||
|
||||
1. YouTube only. HTML5 will follow.
|
||||
2. HTML5 only
|
||||
@@ -435,10 +436,12 @@ player.source = {
|
||||
{
|
||||
src: '/path/to/movie.mp4',
|
||||
type: 'video/mp4',
|
||||
size: 720,
|
||||
},
|
||||
{
|
||||
src: '/path/to/movie.webm',
|
||||
type: 'video/webm',
|
||||
size: 1080,
|
||||
},
|
||||
],
|
||||
poster: '/path/to/poster.jpg',
|
||||
@@ -563,6 +566,7 @@ player.on('ready', event => {
|
||||
| `loadstart` | Sent when loading of the media begins. |
|
||||
| `loadeddata` | The first frame of the media has finished loading. |
|
||||
| `loadedmetadata` | The media's metadata has finished loading; all attributes now contain as much useful information as they're going to. |
|
||||
| `qualitychange` | The quality of playback has changed. |
|
||||
| `canplay` | Sent when enough data is available that the media can be played, at least for a couple of frames. This corresponds to the `HAVE_ENOUGH_DATA` `readyState`. |
|
||||
| `canplaythrough` | Sent when the ready state changes to `CAN_PLAY_THROUGH`, indicating that the entire media can be played without interruption, assuming the download rate remains at least at the current level. _Note:_ Manually setting the `currentTime` will eventually fire a `canplaythrough` event in firefox. Other browsers might not fire this event. |
|
||||
| `stalled` | Sent when the user agent is trying to fetch media data, but data is unexpectedly not forthcoming. |
|
||||
@@ -573,11 +577,9 @@ player.on('ready', event => {
|
||||
|
||||
#### YouTube only
|
||||
|
||||
| Event Type | Description |
|
||||
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `statechange` | The state of the player has changed. The code can be accessed via `event.detail.code`. Possible values are `-1`: Unstarted, `0`: Ended, `1`: Playing, `2`: Paused, `3`: Buffering, `5`: Video cued. See the [YouTube Docs](https://developers.google.com/youtube/iframe_api_reference#onStateChange) for more information. |
|
||||
| `qualitychange` | The quality of playback has changed. |
|
||||
| `qualityrequested` | A change to playback quality has been requested. _Note:_ A change to quality can only be _requested_ via the API. There is no guarantee the quality will change to the level requested. You should listen to the `qualitychange` event for true changes. |
|
||||
| Event Type | Description |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `statechange` | The state of the player has changed. The code can be accessed via `event.detail.code`. Possible values are `-1`: Unstarted, `0`: Ended, `1`: Playing, `2`: Paused, `3`: Buffering, `5`: Video cued. See the [YouTube Docs](https://developers.google.com/youtube/iframe_api_reference#onStateChange) for more information. |
|
||||
|
||||
_Note:_ These events also bubble up the DOM. The event target will be the container element.
|
||||
|
||||
@@ -589,8 +591,8 @@ YouTube and Vimeo are currently supported and function much like a HTML5 video.
|
||||
to access the API's directly. You can do so via the `embed` property of your player object - e.g. `player.embed`. You can then use the relevant methods from the
|
||||
third party APIs. More info on the respective API's here:
|
||||
|
||||
* [YouTube iframe API Reference](https://developers.google.com/youtube/iframe_api_reference)
|
||||
* [Vimeo player.js Reference](https://github.com/vimeo/player.js)
|
||||
- [YouTube iframe API Reference](https://developers.google.com/youtube/iframe_api_reference)
|
||||
- [Vimeo player.js Reference](https://github.com/vimeo/player.js)
|
||||
|
||||
_Note_: Not all API methods may work 100%. Your mileage may vary. It's better to use the Plyr API where possible.
|
||||
|
||||
@@ -650,9 +652,9 @@ const supported = Plyr.supported('video', 'html5', true);
|
||||
|
||||
The arguments are:
|
||||
|
||||
* Media type (`audio` or `video`)
|
||||
* Provider (`html5`, `youtube` or `vimeo`)
|
||||
* Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
|
||||
- Media type (`audio` or `video`)
|
||||
- Provider (`html5`, `youtube` or `vimeo`)
|
||||
- Whether the player has the `playsinline` attribute (only applicable to iOS 10+)
|
||||
|
||||
### Disable support programatically
|
||||
|
||||
@@ -685,33 +687,34 @@ Plyr is developed by [@sam_potts](https://twitter.com/sam_potts) / [sampotts.me]
|
||||
|
||||
Plyr costs money to run, not only my time. I donate my time for free as I enjoy building Plyr but unfortunately have to pay for domains, hosting, and more. Any help with costs is appreciated...
|
||||
|
||||
* [Donate via Patron](https://www.patreon.com/plyr)
|
||||
* [Donate via PayPal](https://www.paypal.me/pottsy/20usd)
|
||||
- [Donate via Patreon](https://www.patreon.com/plyr)
|
||||
- [Donate via PayPal](https://www.paypal.me/pottsy/20usd)
|
||||
|
||||
## Mentions
|
||||
|
||||
* [ProductHunt](https://www.producthunt.com/tech/plyr)
|
||||
* [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
|
||||
* [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
||||
* [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
|
||||
* [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
|
||||
* [Hacker News](https://news.ycombinator.com/item?id=9136774)
|
||||
* [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
|
||||
* [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
|
||||
* [The Treehouse Show #131](https://teamtreehouse.com/library/episode-131-origami-react-responsive-hero-images)
|
||||
* [noupe.com](http://www.noupe.com/design/html5-plyr-is-a-responsive-and-accessible-video-player-94389.html)
|
||||
- [ProductHunt](https://www.producthunt.com/tech/plyr)
|
||||
- [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
|
||||
- [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
||||
- [Responsive Design #149](http://us4.campaign-archive2.com/?u=559bc631fe5294fc66f5f7f89&id=451a61490f)
|
||||
- [Web Design Weekly #174](https://web-design-weekly.com/2015/02/24/web-design-weekly-174/)
|
||||
- [Hacker News](https://news.ycombinator.com/item?id=9136774)
|
||||
- [Web Platform Daily](http://webplatformdaily.org/releases/2015-03-04)
|
||||
- [LayerVault Designer News](https://news.layervault.com/stories/45394-plyr--a-simple-html5-media-player)
|
||||
- [The Treehouse Show #131](https://teamtreehouse.com/library/episode-131-origami-react-responsive-hero-images)
|
||||
- [noupe.com](http://www.noupe.com/design/html5-plyr-is-a-responsive-and-accessible-video-player-94389.html)
|
||||
|
||||
## Used by
|
||||
|
||||
* [Selz.com](https://selz.com)
|
||||
* [Peugeot.fr](http://www.peugeot.fr/marque-et-technologie/technologies/peugeot-i-cockpit.html)
|
||||
* [Peugeot.de](http://www.peugeot.de/modelle/modellberater/208-3-turer/fotos-videos.html)
|
||||
* [TomTom.com](http://prioritydriving.tomtom.com/)
|
||||
* [DIGBMX](http://digbmx.com/)
|
||||
* [Grime Archive](https://grimearchive.com/)
|
||||
* [koel - A personal music streaming server that works.](http://koel.phanan.net/)
|
||||
* [Oscar Radio](http://oscar-radio.xyz/)
|
||||
* [Sparkk TV](https://www.sparkktv.com/)
|
||||
- [Selz.com](https://selz.com)
|
||||
- [Peugeot.fr](http://www.peugeot.fr/marque-et-technologie/technologies/peugeot-i-cockpit.html)
|
||||
- [Peugeot.de](http://www.peugeot.de/modelle/modellberater/208-3-turer/fotos-videos.html)
|
||||
- [TomTom.com](http://prioritydriving.tomtom.com/)
|
||||
- [DIGBMX](http://digbmx.com/)
|
||||
- [Grime Archive](https://grimearchive.com/)
|
||||
- [koel - A personal music streaming server that works.](http://koel.phanan.net/)
|
||||
- [Oscar Radio](http://oscar-radio.xyz/)
|
||||
- [Sparkk TV](https://www.sparkktv.com/)
|
||||
- [@halfhalftravel](https://www.halfhalftravel.com/)
|
||||
|
||||
Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the above list. It'd be awesome to see how you're using Plyr :-)
|
||||
|
||||
@@ -719,8 +722,8 @@ Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the abo
|
||||
|
||||
Credit to the PayPal HTML5 Video player from which Plyr's caption functionality was originally ported from:
|
||||
|
||||
* [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player)
|
||||
* [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
||||
- [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player)
|
||||
- [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
||||
|
||||
## Thanks
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// ==========================================================================
|
||||
|
||||
import controls from './controls';
|
||||
import i18n from './i18n';
|
||||
import support from './support';
|
||||
import { dedupe } from './utils/arrays';
|
||||
import browser from './utils/browser';
|
||||
@@ -18,6 +17,7 @@ import {
|
||||
} from './utils/elements';
|
||||
import { on, triggerEvent } from './utils/events';
|
||||
import fetch from './utils/fetch';
|
||||
import i18n from './utils/i18n';
|
||||
import is from './utils/is';
|
||||
import { getHTML } from './utils/strings';
|
||||
import { parseUrl } from './utils/urls';
|
||||
@@ -83,9 +83,8 @@ const captions = {
|
||||
// * active: The state preferred by user settings or config
|
||||
// * toggled: The real captions state
|
||||
|
||||
const languages = dedupe(
|
||||
Array.from(navigator.languages || navigator.userLanguage).map(language => language.split('-')[0]),
|
||||
);
|
||||
const browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];
|
||||
const languages = dedupe(browserLanguages.map(language => language.split('-')[0]));
|
||||
|
||||
let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();
|
||||
|
||||
@@ -191,8 +190,10 @@ const captions = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle state
|
||||
this.elements.buttons.captions.pressed = active;
|
||||
// Toggle button if it's enabled
|
||||
if (this.elements.buttons.captions) {
|
||||
this.elements.buttons.captions.pressed = active;
|
||||
}
|
||||
|
||||
// Add class hook
|
||||
toggleClass(this.elements.container, activeClass, active);
|
||||
|
||||
@@ -60,7 +60,7 @@ const defaults = {
|
||||
// Sprite (for icons)
|
||||
loadSprite: true,
|
||||
iconPrefix: 'plyr',
|
||||
iconUrl: 'https://cdn.plyr.io/3.3.12/plyr.svg',
|
||||
iconUrl: 'https://cdn.plyr.io/3.4.7/plyr.svg',
|
||||
|
||||
// Blank video (used to prevent errors on source change)
|
||||
blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
|
||||
@@ -68,19 +68,7 @@ const defaults = {
|
||||
// Quality default
|
||||
quality: {
|
||||
default: 576,
|
||||
options: [
|
||||
4320,
|
||||
2880,
|
||||
2160,
|
||||
1440,
|
||||
1080,
|
||||
720,
|
||||
576,
|
||||
480,
|
||||
360,
|
||||
240,
|
||||
'default', // YouTube's "auto"
|
||||
],
|
||||
options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],
|
||||
},
|
||||
|
||||
// Set loops
|
||||
@@ -145,6 +133,7 @@ const defaults = {
|
||||
'settings',
|
||||
'pip',
|
||||
'airplay',
|
||||
// 'download',
|
||||
'fullscreen',
|
||||
],
|
||||
settings: ['captions', 'quality', 'speed'],
|
||||
@@ -167,6 +156,7 @@ const defaults = {
|
||||
unmute: 'Unmute',
|
||||
enableCaptions: 'Enable captions',
|
||||
disableCaptions: 'Disable captions',
|
||||
download: 'Download',
|
||||
enterFullscreen: 'Enter fullscreen',
|
||||
exitFullscreen: 'Exit fullscreen',
|
||||
frameTitle: 'Player for {title}',
|
||||
@@ -196,6 +186,7 @@ const defaults = {
|
||||
|
||||
// URLs
|
||||
urls: {
|
||||
download: null,
|
||||
vimeo: {
|
||||
sdk: 'https://player.vimeo.com/api/player.js',
|
||||
iframe: 'https://player.vimeo.com/video/{0}?{1}',
|
||||
@@ -222,6 +213,7 @@ const defaults = {
|
||||
mute: null,
|
||||
volume: null,
|
||||
captions: null,
|
||||
download: null,
|
||||
fullscreen: null,
|
||||
pip: null,
|
||||
airplay: null,
|
||||
@@ -257,6 +249,7 @@ const defaults = {
|
||||
'cuechange',
|
||||
|
||||
// Custom events
|
||||
'download',
|
||||
'enterfullscreen',
|
||||
'exitfullscreen',
|
||||
'captionsenabled',
|
||||
@@ -268,8 +261,9 @@ const defaults = {
|
||||
|
||||
// YouTube
|
||||
'statechange',
|
||||
|
||||
// Quality
|
||||
'qualitychange',
|
||||
'qualityrequested',
|
||||
|
||||
// Ads
|
||||
'adsloaded',
|
||||
@@ -301,6 +295,7 @@ const defaults = {
|
||||
fastForward: '[data-plyr="fast-forward"]',
|
||||
mute: '[data-plyr="mute"]',
|
||||
captions: '[data-plyr="captions"]',
|
||||
download: '[data-plyr="download"]',
|
||||
fullscreen: '[data-plyr="fullscreen"]',
|
||||
pip: '[data-plyr="pip"]',
|
||||
airplay: '[data-plyr="airplay"]',
|
||||
@@ -354,6 +349,9 @@ const defaults = {
|
||||
isTouch: 'plyr--is-touch',
|
||||
uiSupported: 'plyr--full-ui',
|
||||
noTransition: 'plyr--no-transition',
|
||||
display: {
|
||||
time: 'plyr__time',
|
||||
},
|
||||
menu: {
|
||||
value: 'plyr__menu__value',
|
||||
badge: 'plyr__badge',
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// ==========================================================================
|
||||
// Plyr states
|
||||
// ==========================================================================
|
||||
|
||||
export const pip = {
|
||||
active: 'picture-in-picture',
|
||||
inactive: 'inline',
|
||||
};
|
||||
|
||||
export default { pip };
|
||||
@@ -15,7 +15,7 @@ export const types = {
|
||||
|
||||
/**
|
||||
* Get provider by URL
|
||||
* @param {string} url
|
||||
* @param {String} url
|
||||
*/
|
||||
export function getProviderByUrl(url) {
|
||||
// YouTube
|
||||
|
||||
@@ -17,10 +17,12 @@ export default class Console {
|
||||
// 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;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ==========================================================================
|
||||
// Fullscreen wrapper
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API#prefixing
|
||||
// https://webkit.org/blog/7929/designing-websites-for-iphone-x/
|
||||
// ==========================================================================
|
||||
|
||||
import { repaint } from './utils/animation';
|
||||
import browser from './utils/browser';
|
||||
import { hasClass, toggleClass, trapFocus } from './utils/elements';
|
||||
import { on, triggerEvent } from './utils/events';
|
||||
@@ -45,6 +47,37 @@ function toggleFallback(toggle = false) {
|
||||
// Toggle class hook
|
||||
toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);
|
||||
|
||||
// Force full viewport on iPhone X+
|
||||
if (browser.isIos) {
|
||||
let viewport = document.head.querySelector('meta[name="viewport"]');
|
||||
const property = 'viewport-fit=cover';
|
||||
|
||||
// Inject the viewport meta if required
|
||||
if (!viewport) {
|
||||
viewport = document.createElement('meta');
|
||||
viewport.setAttribute('name', 'viewport');
|
||||
}
|
||||
|
||||
// Check if the property already exists
|
||||
const hasProperty = is.string(viewport.content) && viewport.content.includes(property);
|
||||
|
||||
if (toggle) {
|
||||
this.cleanupViewport = !hasProperty;
|
||||
|
||||
if (!hasProperty) {
|
||||
viewport.content += `,${property}`;
|
||||
}
|
||||
} else if (this.cleanupViewport) {
|
||||
viewport.content = viewport.content
|
||||
.split(',')
|
||||
.filter(part => part.trim() !== property)
|
||||
.join(',');
|
||||
}
|
||||
|
||||
// Force a repaint as sometimes Safari doesn't want to fill the screen
|
||||
setTimeout(() => repaint(this.target), 100);
|
||||
}
|
||||
|
||||
// Toggle button and fire events
|
||||
onChange.call(this);
|
||||
}
|
||||
@@ -177,9 +210,7 @@ class Fullscreen {
|
||||
|
||||
// iOS native fullscreen doesn't need the request step
|
||||
if (browser.isIos && this.player.config.fullscreen.iosNative) {
|
||||
if (this.player.playing) {
|
||||
this.target.webkitEnterFullscreen();
|
||||
}
|
||||
this.target.webkitEnterFullscreen();
|
||||
} else if (!Fullscreen.native) {
|
||||
toggleFallback.call(this, true);
|
||||
} else if (!this.prefix) {
|
||||
|
||||
@@ -39,7 +39,7 @@ const html5 = {
|
||||
get() {
|
||||
// Get sources
|
||||
const sources = html5.getSources.call(player);
|
||||
const [source] = sources.filter(source => source.getAttribute('src') === player.source);
|
||||
const source = sources.find(source => source.getAttribute('src') === player.source);
|
||||
|
||||
// Return size, if match is found
|
||||
return source && Number(source.getAttribute('size'));
|
||||
@@ -57,23 +57,25 @@ const html5 = {
|
||||
}
|
||||
|
||||
// Get current state
|
||||
const { currentTime, playing } = player;
|
||||
const { currentTime, paused, preload, readyState } = player.media;
|
||||
|
||||
// Set new source
|
||||
player.media.src = source.getAttribute('src');
|
||||
|
||||
// Restore time
|
||||
const onLoadedMetaData = () => {
|
||||
player.currentTime = currentTime;
|
||||
};
|
||||
player.once('loadedmetadata', onLoadedMetaData);
|
||||
// Prevent loading if preload="none" and the current source isn't loaded (#1044)
|
||||
if (preload !== 'none' || readyState) {
|
||||
// Restore time
|
||||
player.once('loadedmetadata', () => {
|
||||
player.currentTime = currentTime;
|
||||
|
||||
// Load new source
|
||||
player.media.load();
|
||||
// Resume playing
|
||||
if (!paused) {
|
||||
player.play();
|
||||
}
|
||||
});
|
||||
|
||||
// Resume playing
|
||||
if (playing) {
|
||||
player.play();
|
||||
// Load new source
|
||||
player.media.load();
|
||||
}
|
||||
|
||||
// Trigger change event
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
import controls from './controls';
|
||||
import ui from './ui';
|
||||
import { repaint } from './utils/animation';
|
||||
import browser from './utils/browser';
|
||||
import { getElement, getElements, getFocusElement, matches, toggleClass, toggleHidden } from './utils/elements';
|
||||
import { getElement, getElements, matches, toggleClass, toggleHidden } from './utils/elements';
|
||||
import { on, once, toggleListener, triggerEvent } from './utils/events';
|
||||
import is from './utils/is';
|
||||
|
||||
@@ -13,14 +14,19 @@ class Listeners {
|
||||
constructor(player) {
|
||||
this.player = player;
|
||||
this.lastKey = null;
|
||||
this.focusTimer = null;
|
||||
this.lastKeyDown = null;
|
||||
|
||||
this.handleKey = this.handleKey.bind(this);
|
||||
this.toggleMenu = this.toggleMenu.bind(this);
|
||||
this.setTabFocus = this.setTabFocus.bind(this);
|
||||
this.firstTouch = this.firstTouch.bind(this);
|
||||
}
|
||||
|
||||
// Handle key presses
|
||||
handleKey(event) {
|
||||
const { player } = this;
|
||||
const { elements } = player;
|
||||
const code = event.keyCode ? event.keyCode : event.which;
|
||||
const pressed = event.type === 'keydown';
|
||||
const repeat = pressed && code === this.lastKey;
|
||||
@@ -39,27 +45,32 @@ class Listeners {
|
||||
// Seek by the number keys
|
||||
const seekByKey = () => {
|
||||
// Divide the max duration into 10th's and times by the number value
|
||||
this.player.currentTime = this.player.duration / 10 * (code - 48);
|
||||
player.currentTime = (player.duration / 10) * (code - 48);
|
||||
};
|
||||
|
||||
// Handle the key on keydown
|
||||
// Reset on keyup
|
||||
if (pressed) {
|
||||
// Which keycodes should we prevent default
|
||||
const preventDefault = [32, 37, 38, 39, 40, 48, 49, 50, 51, 52, 53, 54, 56, 57, 67, 70, 73, 75, 76, 77, 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 = getFocusElement();
|
||||
if (
|
||||
is.element(focused) &&
|
||||
(focused !== this.player.elements.inputs.seek &&
|
||||
matches(focused, this.player.config.selectors.editable))
|
||||
) {
|
||||
return;
|
||||
const focused = document.activeElement;
|
||||
if (is.element(focused)) {
|
||||
const { editable } = player.config.selectors;
|
||||
const { seek } = elements.inputs;
|
||||
|
||||
if (focused !== seek && matches(focused, editable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.which === 32 && matches(focused, 'button, [role^="menuitem"]')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Which keycodes should we prevent default
|
||||
const preventDefault = [32, 37, 38, 39, 40, 48, 49, 50, 51, 52, 53, 54, 56, 57, 67, 70, 73, 75, 76, 77, 79];
|
||||
|
||||
// If the code is found prevent default (e.g. prevent scrolling for arrows)
|
||||
if (preventDefault.includes(code)) {
|
||||
event.preventDefault();
|
||||
@@ -87,55 +98,55 @@ class Listeners {
|
||||
case 75:
|
||||
// Space and K key
|
||||
if (!repeat) {
|
||||
this.player.togglePlay();
|
||||
player.togglePlay();
|
||||
}
|
||||
break;
|
||||
|
||||
case 38:
|
||||
// Arrow up
|
||||
this.player.increaseVolume(0.1);
|
||||
player.increaseVolume(0.1);
|
||||
break;
|
||||
|
||||
case 40:
|
||||
// Arrow down
|
||||
this.player.decreaseVolume(0.1);
|
||||
player.decreaseVolume(0.1);
|
||||
break;
|
||||
|
||||
case 77:
|
||||
// M key
|
||||
if (!repeat) {
|
||||
this.player.muted = !this.player.muted;
|
||||
player.muted = !player.muted;
|
||||
}
|
||||
break;
|
||||
|
||||
case 39:
|
||||
// Arrow forward
|
||||
this.player.forward();
|
||||
player.forward();
|
||||
break;
|
||||
|
||||
case 37:
|
||||
// Arrow back
|
||||
this.player.rewind();
|
||||
player.rewind();
|
||||
break;
|
||||
|
||||
case 70:
|
||||
// F key
|
||||
this.player.fullscreen.toggle();
|
||||
player.fullscreen.toggle();
|
||||
break;
|
||||
|
||||
case 67:
|
||||
// C key
|
||||
if (!repeat) {
|
||||
this.player.toggleCaptions();
|
||||
player.toggleCaptions();
|
||||
}
|
||||
break;
|
||||
|
||||
case 76:
|
||||
// L key
|
||||
this.player.loop = !this.player.loop;
|
||||
player.loop = !player.loop;
|
||||
break;
|
||||
|
||||
/* case 73:
|
||||
/* case 73:
|
||||
this.setLoop('start');
|
||||
break;
|
||||
|
||||
@@ -153,8 +164,8 @@ class Listeners {
|
||||
|
||||
// Escape is handle natively when in full screen
|
||||
// So we only need to worry about non native
|
||||
if (!this.player.fullscreen.enabled && this.player.fullscreen.active && code === 27) {
|
||||
this.player.fullscreen.toggle();
|
||||
if (!player.fullscreen.enabled && player.fullscreen.active && code === 27) {
|
||||
player.fullscreen.toggle();
|
||||
}
|
||||
|
||||
// Store last code for next cycle
|
||||
@@ -171,61 +182,102 @@ class Listeners {
|
||||
|
||||
// Device is touch enabled
|
||||
firstTouch() {
|
||||
this.player.touch = true;
|
||||
const { player } = this;
|
||||
const { elements } = player;
|
||||
|
||||
player.touch = true;
|
||||
|
||||
// Add touch class
|
||||
toggleClass(this.player.elements.container, this.player.config.classNames.isTouch, true);
|
||||
toggleClass(elements.container, player.config.classNames.isTouch, true);
|
||||
}
|
||||
|
||||
setTabFocus(event) {
|
||||
const { player } = this;
|
||||
const { elements } = player;
|
||||
|
||||
clearTimeout(this.focusTimer);
|
||||
|
||||
// Ignore any key other than tab
|
||||
if (event.type === 'keydown' && event.which !== 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store reference to event timeStamp
|
||||
if (event.type === 'keydown') {
|
||||
this.lastKeyDown = event.timeStamp;
|
||||
}
|
||||
|
||||
// Remove current classes
|
||||
const removeCurrent = () => {
|
||||
const className = player.config.classNames.tabFocus;
|
||||
const current = getElements.call(player, `.${className}`);
|
||||
toggleClass(current, className, false);
|
||||
};
|
||||
|
||||
// Determine if a key was pressed to trigger this event
|
||||
const wasKeyDown = event.timeStamp - this.lastKeyDown <= 20;
|
||||
|
||||
// Ignore focus events if a key was pressed prior
|
||||
if (event.type === 'focus' && !wasKeyDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove all current
|
||||
removeCurrent();
|
||||
|
||||
// Delay the adding of classname until the focus has changed
|
||||
// This event fires before the focusin event
|
||||
this.focusTimer = setTimeout(() => {
|
||||
const focused = document.activeElement;
|
||||
|
||||
// Ignore if current focus element isn't inside the player
|
||||
if (!elements.container.contains(focused)) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
// Global window & document listeners
|
||||
global(toggle = true) {
|
||||
const { player } = this;
|
||||
|
||||
// Keyboard shortcuts
|
||||
if (this.player.config.keyboard.global) {
|
||||
toggleListener.call(this.player, window, 'keydown keyup', this.handleKey, toggle, false);
|
||||
if (player.config.keyboard.global) {
|
||||
toggleListener.call(player, window, 'keydown keyup', this.handleKey, toggle, false);
|
||||
}
|
||||
|
||||
// Click anywhere closes menu
|
||||
toggleListener.call(this.player, document.body, 'click', this.toggleMenu, toggle);
|
||||
toggleListener.call(player, document.body, 'click', this.toggleMenu, toggle);
|
||||
|
||||
// Detect touch by events
|
||||
once.call(this.player, document.body, 'touchstart', this.firstTouch);
|
||||
once.call(player, document.body, 'touchstart', this.firstTouch);
|
||||
|
||||
// Tab focus detection
|
||||
toggleListener.call(player, document.body, 'keydown focus blur', this.setTabFocus, toggle, false, true);
|
||||
}
|
||||
|
||||
// Container listeners
|
||||
container() {
|
||||
const { player } = this;
|
||||
const { elements } = player;
|
||||
|
||||
// Keyboard shortcuts
|
||||
if (!this.player.config.keyboard.global && this.player.config.keyboard.focused) {
|
||||
on.call(this.player, this.player.elements.container, 'keydown keyup', this.handleKey, false);
|
||||
if (!player.config.keyboard.global && player.config.keyboard.focused) {
|
||||
on.call(player, elements.container, 'keydown keyup', this.handleKey, false);
|
||||
}
|
||||
|
||||
// Detect tab focus
|
||||
// Remove class on blur/focusout
|
||||
on.call(this.player, this.player.elements.container, 'focusout', event => {
|
||||
toggleClass(event.target, this.player.config.classNames.tabFocus, false);
|
||||
});
|
||||
// Add classname to tabbed elements
|
||||
on.call(this.player, this.player.elements.container, 'keydown', event => {
|
||||
if (event.keyCode !== 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delay the adding of classname until the focus has changed
|
||||
// This event fires before the focusin event
|
||||
setTimeout(() => {
|
||||
toggleClass(getFocusElement(), this.player.config.classNames.tabFocus, true);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// Toggle controls on mouse events and entering fullscreen
|
||||
on.call(
|
||||
this.player,
|
||||
this.player.elements.container,
|
||||
player,
|
||||
elements.container,
|
||||
'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen',
|
||||
event => {
|
||||
const { controls } = this.player.elements;
|
||||
const { controls } = elements;
|
||||
|
||||
// Remove button states for fullscreen
|
||||
if (event.type === 'enterfullscreen') {
|
||||
if (controls && event.type === 'enterfullscreen') {
|
||||
controls.pressed = false;
|
||||
controls.hover = false;
|
||||
}
|
||||
@@ -236,114 +288,117 @@ class Listeners {
|
||||
let delay = 0;
|
||||
|
||||
if (show) {
|
||||
ui.toggleControls.call(this.player, true);
|
||||
ui.toggleControls.call(player, true);
|
||||
// Use longer timeout for touch devices
|
||||
delay = this.player.touch ? 3000 : 2000;
|
||||
delay = player.touch ? 3000 : 2000;
|
||||
}
|
||||
|
||||
// Clear timer
|
||||
clearTimeout(this.player.timers.controls);
|
||||
// Timer to prevent flicker when seeking
|
||||
this.player.timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
|
||||
clearTimeout(player.timers.controls);
|
||||
|
||||
// Set new timer to prevent flicker when seeking
|
||||
player.timers.controls = setTimeout(() => ui.toggleControls.call(player, false), delay);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Listen for media events
|
||||
media() {
|
||||
const { player } = this;
|
||||
const { elements } = player;
|
||||
|
||||
// Time change on media
|
||||
on.call(this.player, this.player.media, 'timeupdate seeking seeked', event =>
|
||||
controls.timeUpdate.call(this.player, event),
|
||||
);
|
||||
on.call(player, player.media, 'timeupdate seeking seeked', event => controls.timeUpdate.call(player, event));
|
||||
|
||||
// Display duration
|
||||
on.call(this.player, this.player.media, 'durationchange loadeddata loadedmetadata', event =>
|
||||
controls.durationUpdate.call(this.player, event),
|
||||
on.call(player, player.media, 'durationchange loadeddata loadedmetadata', event =>
|
||||
controls.durationUpdate.call(player, event),
|
||||
);
|
||||
|
||||
// Check for audio tracks on load
|
||||
// We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
|
||||
on.call(this.player, this.player.media, 'canplay', () => {
|
||||
toggleHidden(this.player.elements.volume, !this.player.hasAudio);
|
||||
toggleHidden(this.player.elements.buttons.mute, !this.player.hasAudio);
|
||||
on.call(player, player.media, 'canplay loadeddata', () => {
|
||||
toggleHidden(elements.volume, !player.hasAudio);
|
||||
toggleHidden(elements.buttons.mute, !player.hasAudio);
|
||||
});
|
||||
|
||||
// Handle the media finishing
|
||||
on.call(this.player, this.player.media, 'ended', () => {
|
||||
on.call(player, player.media, 'ended', () => {
|
||||
// Show poster on end
|
||||
if (this.player.isHTML5 && this.player.isVideo && this.player.config.resetOnEnd) {
|
||||
if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) {
|
||||
// Restart
|
||||
this.player.restart();
|
||||
player.restart();
|
||||
}
|
||||
});
|
||||
|
||||
// Check for buffer progress
|
||||
on.call(this.player, this.player.media, 'progress playing seeking seeked', event =>
|
||||
controls.updateProgress.call(this.player, event),
|
||||
on.call(player, player.media, 'progress playing seeking seeked', event =>
|
||||
controls.updateProgress.call(player, event),
|
||||
);
|
||||
|
||||
// Handle volume changes
|
||||
on.call(this.player, this.player.media, 'volumechange', event =>
|
||||
controls.updateVolume.call(this.player, event),
|
||||
);
|
||||
on.call(player, player.media, 'volumechange', event => controls.updateVolume.call(player, event));
|
||||
|
||||
// Handle play/pause
|
||||
on.call(this.player, this.player.media, 'playing play pause ended emptied timeupdate', event =>
|
||||
ui.checkPlaying.call(this.player, event),
|
||||
on.call(player, player.media, 'playing play pause ended emptied timeupdate', event =>
|
||||
ui.checkPlaying.call(player, event),
|
||||
);
|
||||
|
||||
// Loading state
|
||||
on.call(this.player, this.player.media, 'waiting canplay seeked playing', event =>
|
||||
ui.checkLoading.call(this.player, event),
|
||||
);
|
||||
on.call(player, player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(player, event));
|
||||
|
||||
// If autoplay, then load advertisement if required
|
||||
// TODO: Show some sort of loading state while the ad manager loads else there's a delay before ad shows
|
||||
on.call(this.player, this.player.media, 'playing', () => {
|
||||
if (!this.player.ads) {
|
||||
on.call(player, player.media, 'playing', () => {
|
||||
if (!player.ads) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If ads are enabled, wait for them first
|
||||
if (this.player.ads.enabled && !this.player.ads.initialized) {
|
||||
if (player.ads.enabled && !player.ads.initialized) {
|
||||
// Wait for manager response
|
||||
this.player.ads.managerPromise.then(() => this.player.ads.play()).catch(() => this.player.play());
|
||||
player.ads.managerPromise.then(() => player.ads.play()).catch(() => player.play());
|
||||
}
|
||||
});
|
||||
|
||||
// Click video
|
||||
if (this.player.supported.ui && this.player.config.clickToPlay && !this.player.isAudio) {
|
||||
if (player.supported.ui && player.config.clickToPlay && !player.isAudio) {
|
||||
// Re-fetch the wrapper
|
||||
const wrapper = getElement.call(this.player, `.${this.player.config.classNames.video}`);
|
||||
const wrapper = getElement.call(player, `.${player.config.classNames.video}`);
|
||||
|
||||
// Bail if there's no wrapper (this should never happen)
|
||||
if (!is.element(wrapper)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// On click play, pause ore restart
|
||||
on.call(this.player, wrapper, 'click', () => {
|
||||
// Touch devices will just show controls (if we're hiding controls)
|
||||
if (this.player.config.hideControls && this.player.touch && !this.player.paused) {
|
||||
// On click play, pause or restart
|
||||
on.call(player, elements.container, 'click', event => {
|
||||
const targets = [elements.container, wrapper];
|
||||
|
||||
// Ignore if click if not container or in video wrapper
|
||||
if (!targets.includes(event.target) && !wrapper.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.player.paused) {
|
||||
this.player.play();
|
||||
} else if (this.player.ended) {
|
||||
this.player.restart();
|
||||
this.player.play();
|
||||
// Touch devices will just show controls (if hidden)
|
||||
if (player.touch && player.config.hideControls) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.ended) {
|
||||
player.restart();
|
||||
player.play();
|
||||
} else {
|
||||
this.player.pause();
|
||||
player.togglePlay();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Disable right click
|
||||
if (this.player.supported.ui && this.player.config.disableContextMenu) {
|
||||
if (player.supported.ui && player.config.disableContextMenu) {
|
||||
on.call(
|
||||
this.player,
|
||||
this.player.elements.wrapper,
|
||||
player,
|
||||
elements.wrapper,
|
||||
'contextmenu',
|
||||
event => {
|
||||
event.preventDefault();
|
||||
@@ -353,218 +408,248 @@ class Listeners {
|
||||
}
|
||||
|
||||
// Volume change
|
||||
on.call(this.player, this.player.media, 'volumechange', () => {
|
||||
on.call(player, player.media, 'volumechange', () => {
|
||||
// Save to storage
|
||||
this.player.storage.set({ volume: this.player.volume, muted: this.player.muted });
|
||||
player.storage.set({
|
||||
volume: player.volume,
|
||||
muted: player.muted,
|
||||
});
|
||||
});
|
||||
|
||||
// Speed change
|
||||
on.call(this.player, this.player.media, 'ratechange', () => {
|
||||
on.call(player, player.media, 'ratechange', () => {
|
||||
// Update UI
|
||||
controls.updateSetting.call(this.player, 'speed');
|
||||
controls.updateSetting.call(player, 'speed');
|
||||
|
||||
// Save to storage
|
||||
this.player.storage.set({ speed: this.player.speed });
|
||||
});
|
||||
|
||||
// Quality request
|
||||
on.call(this.player, this.player.media, 'qualityrequested', event => {
|
||||
// Save to storage
|
||||
this.player.storage.set({ quality: event.detail.quality });
|
||||
player.storage.set({ speed: player.speed });
|
||||
});
|
||||
|
||||
// Quality change
|
||||
on.call(this.player, this.player.media, 'qualitychange', event => {
|
||||
on.call(player, player.media, 'qualitychange', event => {
|
||||
// Update UI
|
||||
controls.updateSetting.call(this.player, 'quality', null, event.detail.quality);
|
||||
controls.updateSetting.call(player, 'quality', null, event.detail.quality);
|
||||
});
|
||||
|
||||
// Update download link when ready and if quality changes
|
||||
on.call(player, player.media, 'ready qualitychange', () => {
|
||||
controls.setDownloadLink.call(player);
|
||||
});
|
||||
|
||||
// Proxy events to container
|
||||
// Bubble up key events for Edge
|
||||
const proxyEvents = this.player.config.events.concat(['keyup', 'keydown']).join(' ');
|
||||
on.call(this.player, this.player.media, proxyEvents, event => {
|
||||
const proxyEvents = player.config.events.concat(['keyup', 'keydown']).join(' ');
|
||||
|
||||
on.call(player, player.media, proxyEvents, event => {
|
||||
let { detail = {} } = event;
|
||||
|
||||
// Get error details from media
|
||||
if (event.type === 'error') {
|
||||
detail = this.player.media.error;
|
||||
detail = player.media.error;
|
||||
}
|
||||
|
||||
triggerEvent.call(this.player, this.player.elements.container, event.type, true, detail);
|
||||
triggerEvent.call(player, elements.container, event.type, true, detail);
|
||||
});
|
||||
}
|
||||
|
||||
// Run default and custom handlers
|
||||
proxy(event, defaultHandler, customHandlerKey) {
|
||||
const { player } = this;
|
||||
const customHandler = player.config.listeners[customHandlerKey];
|
||||
const hasCustomHandler = is.function(customHandler);
|
||||
let returned = true;
|
||||
|
||||
// Execute custom handler
|
||||
if (hasCustomHandler) {
|
||||
returned = customHandler.call(player, event);
|
||||
}
|
||||
|
||||
// Only call default handler if not prevented in custom handler
|
||||
if (returned && is.function(defaultHandler)) {
|
||||
defaultHandler.call(player, event);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger custom and default handlers
|
||||
bind(element, type, defaultHandler, customHandlerKey, passive = true) {
|
||||
const { player } = this;
|
||||
const customHandler = player.config.listeners[customHandlerKey];
|
||||
const hasCustomHandler = is.function(customHandler);
|
||||
|
||||
on.call(
|
||||
player,
|
||||
element,
|
||||
type,
|
||||
event => this.proxy(event, defaultHandler, customHandlerKey),
|
||||
passive && !hasCustomHandler,
|
||||
);
|
||||
}
|
||||
|
||||
// Listen for control events
|
||||
controls() {
|
||||
const { player } = this;
|
||||
const { elements } = player;
|
||||
|
||||
// IE doesn't support input event, so we fallback to change
|
||||
const inputEvent = browser.isIE ? 'change' : 'input';
|
||||
|
||||
// Run default and custom handlers
|
||||
const proxy = (event, defaultHandler, customHandlerKey) => {
|
||||
const customHandler = this.player.config.listeners[customHandlerKey];
|
||||
const hasCustomHandler = is.function(customHandler);
|
||||
let returned = true;
|
||||
|
||||
// Execute custom handler
|
||||
if (hasCustomHandler) {
|
||||
returned = customHandler.call(this.player, event);
|
||||
}
|
||||
|
||||
// Only call default handler if not prevented in custom handler
|
||||
if (returned && is.function(defaultHandler)) {
|
||||
defaultHandler.call(this.player, event);
|
||||
}
|
||||
};
|
||||
|
||||
// Trigger custom and default handlers
|
||||
const bind = (element, type, defaultHandler, customHandlerKey, passive = true) => {
|
||||
const customHandler = this.player.config.listeners[customHandlerKey];
|
||||
const hasCustomHandler = is.function(customHandler);
|
||||
|
||||
on.call(
|
||||
this.player,
|
||||
element,
|
||||
type,
|
||||
event => proxy(event, defaultHandler, customHandlerKey),
|
||||
passive && !hasCustomHandler,
|
||||
);
|
||||
};
|
||||
|
||||
// Play/pause toggle
|
||||
Array.from(this.player.elements.buttons.play).forEach(button => {
|
||||
bind(button, 'click', this.player.togglePlay, 'play');
|
||||
});
|
||||
if (elements.buttons.play) {
|
||||
Array.from(elements.buttons.play).forEach(button => {
|
||||
this.bind(button, 'click', player.togglePlay, 'play');
|
||||
});
|
||||
}
|
||||
|
||||
// Pause
|
||||
bind(this.player.elements.buttons.restart, 'click', this.player.restart, 'restart');
|
||||
this.bind(elements.buttons.restart, 'click', player.restart, 'restart');
|
||||
|
||||
// Rewind
|
||||
bind(this.player.elements.buttons.rewind, 'click', this.player.rewind, 'rewind');
|
||||
this.bind(elements.buttons.rewind, 'click', player.rewind, 'rewind');
|
||||
|
||||
// Rewind
|
||||
bind(this.player.elements.buttons.fastForward, 'click', this.player.forward, 'fastForward');
|
||||
this.bind(elements.buttons.fastForward, 'click', player.forward, 'fastForward');
|
||||
|
||||
// Mute toggle
|
||||
bind(
|
||||
this.player.elements.buttons.mute,
|
||||
this.bind(
|
||||
elements.buttons.mute,
|
||||
'click',
|
||||
() => {
|
||||
this.player.muted = !this.player.muted;
|
||||
player.muted = !player.muted;
|
||||
},
|
||||
'mute',
|
||||
);
|
||||
|
||||
// Captions toggle
|
||||
bind(this.player.elements.buttons.captions, 'click', () => this.player.toggleCaptions());
|
||||
this.bind(elements.buttons.captions, 'click', () => player.toggleCaptions());
|
||||
|
||||
// Fullscreen toggle
|
||||
bind(
|
||||
this.player.elements.buttons.fullscreen,
|
||||
// Download
|
||||
this.bind(
|
||||
elements.buttons.download,
|
||||
'click',
|
||||
() => {
|
||||
this.player.fullscreen.toggle();
|
||||
triggerEvent.call(player, player.media, 'download');
|
||||
},
|
||||
'download',
|
||||
);
|
||||
|
||||
// Fullscreen toggle
|
||||
this.bind(
|
||||
elements.buttons.fullscreen,
|
||||
'click',
|
||||
() => {
|
||||
player.fullscreen.toggle();
|
||||
},
|
||||
'fullscreen',
|
||||
);
|
||||
|
||||
// Picture-in-Picture
|
||||
bind(
|
||||
this.player.elements.buttons.pip,
|
||||
this.bind(
|
||||
elements.buttons.pip,
|
||||
'click',
|
||||
() => {
|
||||
this.player.pip = 'toggle';
|
||||
player.pip = 'toggle';
|
||||
},
|
||||
'pip',
|
||||
);
|
||||
|
||||
// Airplay
|
||||
bind(this.player.elements.buttons.airplay, 'click', this.player.airplay, 'airplay');
|
||||
this.bind(elements.buttons.airplay, 'click', player.airplay, 'airplay');
|
||||
|
||||
// Settings menu
|
||||
bind(this.player.elements.buttons.settings, 'click', event => {
|
||||
controls.toggleMenu.call(this.player, event);
|
||||
});
|
||||
|
||||
// Settings menu
|
||||
bind(this.player.elements.settings.form, 'click', event => {
|
||||
// Settings menu - click toggle
|
||||
this.bind(elements.buttons.settings, 'click', event => {
|
||||
// Prevent the document click listener closing the menu
|
||||
event.stopPropagation();
|
||||
|
||||
// Go back to home tab on click
|
||||
const showHomeTab = () => {
|
||||
const id = `plyr-settings-${this.player.id}-home`;
|
||||
controls.showTab.call(this.player, id);
|
||||
};
|
||||
controls.toggleMenu.call(player, event);
|
||||
});
|
||||
|
||||
// Settings menu items - use event delegation as items are added/removed
|
||||
if (matches(event.target, this.player.config.selectors.inputs.language)) {
|
||||
proxy(
|
||||
event,
|
||||
() => {
|
||||
this.player.currentTrack = Number(event.target.value);
|
||||
showHomeTab();
|
||||
},
|
||||
'language',
|
||||
);
|
||||
} else if (matches(event.target, this.player.config.selectors.inputs.quality)) {
|
||||
proxy(
|
||||
event,
|
||||
() => {
|
||||
this.player.quality = event.target.value;
|
||||
showHomeTab();
|
||||
},
|
||||
'quality',
|
||||
);
|
||||
} else if (matches(event.target, this.player.config.selectors.inputs.speed)) {
|
||||
proxy(
|
||||
event,
|
||||
() => {
|
||||
this.player.speed = parseFloat(event.target.value);
|
||||
showHomeTab();
|
||||
},
|
||||
'speed',
|
||||
);
|
||||
} else {
|
||||
const tab = event.target;
|
||||
controls.showTab.call(this.player, tab.getAttribute('aria-controls'));
|
||||
// Settings menu - keyboard toggle
|
||||
// We have to bind to keyup otherwise Firefox triggers a click when a keydown event handler shifts focus
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1220143
|
||||
this.bind(
|
||||
elements.buttons.settings,
|
||||
'keyup',
|
||||
event => {
|
||||
const code = event.which;
|
||||
|
||||
// We only care about space and return
|
||||
if (![13, 32].includes(code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Because return triggers a click anyway, all we need to do is set focus
|
||||
if (code === 13) {
|
||||
controls.focusFirstMenuItem.call(player, null, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent scroll
|
||||
event.preventDefault();
|
||||
|
||||
// Prevent playing video (Firefox)
|
||||
event.stopPropagation();
|
||||
|
||||
// Toggle menu
|
||||
controls.toggleMenu.call(player, event);
|
||||
},
|
||||
null,
|
||||
false, // Can't be passive as we're preventing default
|
||||
);
|
||||
|
||||
// Escape closes menu
|
||||
this.bind(elements.settings.menu, 'keydown', event => {
|
||||
if (event.which === 27) {
|
||||
controls.toggleMenu.call(player, event);
|
||||
}
|
||||
});
|
||||
|
||||
// Set range input alternative "value", which matches the tooltip time (#954)
|
||||
bind(this.player.elements.inputs.seek, 'mousedown mousemove', event => {
|
||||
const clientRect = this.player.elements.progress.getBoundingClientRect();
|
||||
const percent = 100 / clientRect.width * (event.pageX - clientRect.left);
|
||||
this.bind(elements.inputs.seek, 'mousedown mousemove', event => {
|
||||
const rect = elements.progress.getBoundingClientRect();
|
||||
const percent = (100 / rect.width) * (event.pageX - rect.left);
|
||||
event.currentTarget.setAttribute('seek-value', percent);
|
||||
});
|
||||
|
||||
// Pause while seeking
|
||||
bind(this.player.elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
|
||||
this.bind(elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {
|
||||
const seek = event.currentTarget;
|
||||
|
||||
const code = event.keyCode ? event.keyCode : event.which;
|
||||
const eventType = event.type;
|
||||
const attribute = 'play-on-seeked';
|
||||
|
||||
if ((eventType === 'keydown' || eventType === 'keyup') && (code !== 39 && code !== 37)) {
|
||||
if (is.keyboardEvent(event) && (code !== 39 && code !== 37)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Record seek time so we can prevent hiding controls for a few seconds after seek
|
||||
player.lastSeekTime = Date.now();
|
||||
|
||||
// Was playing before?
|
||||
const play = seek.hasAttribute('play-on-seeked');
|
||||
const play = seek.hasAttribute(attribute);
|
||||
|
||||
// Done seeking
|
||||
const done = ['mouseup', 'touchend', 'keyup'].includes(event.type);
|
||||
|
||||
// If we're done seeking and it was playing, resume playback
|
||||
if (play && done) {
|
||||
seek.removeAttribute('play-on-seeked');
|
||||
this.player.play();
|
||||
} else if (!done && this.player.playing) {
|
||||
seek.setAttribute('play-on-seeked', '');
|
||||
this.player.pause();
|
||||
seek.removeAttribute(attribute);
|
||||
player.play();
|
||||
} else if (!done && player.playing) {
|
||||
seek.setAttribute(attribute, '');
|
||||
player.pause();
|
||||
}
|
||||
});
|
||||
|
||||
// Fix range inputs on iOS
|
||||
// Super weird iOS bug where after you interact with an <input type="range">,
|
||||
// it takes over further interactions on the page. This is a hack
|
||||
if (browser.isIos) {
|
||||
const inputs = getElements.call(player, 'input[type="range"]');
|
||||
Array.from(inputs).forEach(input => this.bind(input, inputEvent, event => repaint(event.target)));
|
||||
}
|
||||
|
||||
// Seek
|
||||
bind(
|
||||
this.player.elements.inputs.seek,
|
||||
this.bind(
|
||||
elements.inputs.seek,
|
||||
inputEvent,
|
||||
event => {
|
||||
const seek = event.currentTarget;
|
||||
@@ -578,123 +663,104 @@ class Listeners {
|
||||
|
||||
seek.removeAttribute('seek-value');
|
||||
|
||||
this.player.currentTime = seekTo / seek.max * this.player.duration;
|
||||
player.currentTime = (seekTo / seek.max) * player.duration;
|
||||
},
|
||||
'seek',
|
||||
);
|
||||
|
||||
// Current time invert
|
||||
// Only if one time element is used for both currentTime and duration
|
||||
if (this.player.config.toggleInvert && !is.element(this.player.elements.display.duration)) {
|
||||
bind(this.player.elements.display.currentTime, 'click', () => {
|
||||
// Do nothing if we're at the start
|
||||
if (this.player.currentTime === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.player.config.invertTime = !this.player.config.invertTime;
|
||||
|
||||
controls.timeUpdate.call(this.player);
|
||||
});
|
||||
}
|
||||
|
||||
// Volume
|
||||
bind(
|
||||
this.player.elements.inputs.volume,
|
||||
inputEvent,
|
||||
event => {
|
||||
this.player.volume = event.target.value;
|
||||
},
|
||||
'volume',
|
||||
// Seek tooltip
|
||||
this.bind(elements.progress, 'mouseenter mouseleave mousemove', event =>
|
||||
controls.updateSeekTooltip.call(player, event),
|
||||
);
|
||||
|
||||
// Polyfill for lower fill in <input type="range"> for webkit
|
||||
if (browser.isWebkit) {
|
||||
Array.from(getElements.call(this.player, 'input[type="range"]')).forEach(element => {
|
||||
bind(element, 'input', event => controls.updateRangeFill.call(this.player, event.target));
|
||||
Array.from(getElements.call(player, 'input[type="range"]')).forEach(element => {
|
||||
this.bind(element, 'input', event => controls.updateRangeFill.call(player, event.target));
|
||||
});
|
||||
}
|
||||
|
||||
// Seek tooltip
|
||||
bind(this.player.elements.progress, 'mouseenter mouseleave mousemove', event =>
|
||||
controls.updateSeekTooltip.call(this.player, event),
|
||||
// Current time invert
|
||||
// Only if one time element is used for both currentTime and duration
|
||||
if (player.config.toggleInvert && !is.element(elements.display.duration)) {
|
||||
this.bind(elements.display.currentTime, 'click', () => {
|
||||
// Do nothing if we're at the start
|
||||
if (player.currentTime === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.config.invertTime = !player.config.invertTime;
|
||||
|
||||
controls.timeUpdate.call(player);
|
||||
});
|
||||
}
|
||||
|
||||
// Volume
|
||||
this.bind(
|
||||
elements.inputs.volume,
|
||||
inputEvent,
|
||||
event => {
|
||||
player.volume = event.target.value;
|
||||
},
|
||||
'volume',
|
||||
);
|
||||
|
||||
// Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
|
||||
bind(this.player.elements.controls, 'mouseenter mouseleave', event => {
|
||||
this.player.elements.controls.hover = !this.player.touch && event.type === 'mouseenter';
|
||||
this.bind(elements.controls, 'mouseenter mouseleave', event => {
|
||||
elements.controls.hover = !player.touch && event.type === 'mouseenter';
|
||||
});
|
||||
|
||||
// Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
|
||||
bind(this.player.elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
|
||||
this.player.elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
||||
this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {
|
||||
elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
|
||||
});
|
||||
|
||||
// Focus in/out on controls
|
||||
bind(this.player.elements.controls, 'focusin focusout', event => {
|
||||
const { config, elements, timers } = this.player;
|
||||
// Show controls when they receive focus (e.g., when using keyboard tab key)
|
||||
this.bind(elements.controls, 'focusin', () => {
|
||||
const { config, elements, timers } = player;
|
||||
|
||||
// Skip transition to prevent focus from scrolling the parent element
|
||||
toggleClass(elements.controls, config.classNames.noTransition, event.type === 'focusin');
|
||||
toggleClass(elements.controls, config.classNames.noTransition, true);
|
||||
|
||||
// Toggle
|
||||
ui.toggleControls.call(this.player, event.type === 'focusin');
|
||||
ui.toggleControls.call(player, true);
|
||||
|
||||
// If focusin, hide again after delay
|
||||
if (event.type === 'focusin') {
|
||||
// Restore transition
|
||||
setTimeout(() => {
|
||||
toggleClass(elements.controls, config.classNames.noTransition, false);
|
||||
}, 0);
|
||||
// Restore transition
|
||||
setTimeout(() => {
|
||||
toggleClass(elements.controls, config.classNames.noTransition, false);
|
||||
}, 0);
|
||||
|
||||
// Delay a little more for keyboard users
|
||||
const delay = this.touch ? 3000 : 4000;
|
||||
// Delay a little more for mouse users
|
||||
const delay = this.touch ? 3000 : 4000;
|
||||
|
||||
// Clear timer
|
||||
clearTimeout(timers.controls);
|
||||
// Hide
|
||||
timers.controls = setTimeout(() => ui.toggleControls.call(this.player, false), delay);
|
||||
}
|
||||
// Clear timer
|
||||
clearTimeout(timers.controls);
|
||||
|
||||
// Hide again after delay
|
||||
timers.controls = setTimeout(() => ui.toggleControls.call(player, false), delay);
|
||||
});
|
||||
|
||||
// Mouse wheel for volume
|
||||
bind(
|
||||
this.player.elements.inputs.volume,
|
||||
this.bind(
|
||||
elements.inputs.volume,
|
||||
'wheel',
|
||||
event => {
|
||||
// Detect "natural" scroll - suppored on OS X Safari only
|
||||
// Other browsers on OS X will be inverted until support improves
|
||||
const inverted = event.webkitDirectionInvertedFromDevice;
|
||||
const step = 1 / 50;
|
||||
let direction = 0;
|
||||
|
||||
// Scroll down (or up on natural) to decrease
|
||||
if (event.deltaY < 0 || event.deltaX > 0) {
|
||||
if (inverted) {
|
||||
this.player.decreaseVolume(step);
|
||||
direction = -1;
|
||||
} else {
|
||||
this.player.increaseVolume(step);
|
||||
direction = 1;
|
||||
}
|
||||
}
|
||||
// Get delta from event. Invert if `inverted` is true
|
||||
const [x, y] = [event.deltaX, -event.deltaY].map(value => (inverted ? -value : value));
|
||||
|
||||
// Scroll up (or down on natural) to increase
|
||||
if (event.deltaY > 0 || event.deltaX < 0) {
|
||||
if (inverted) {
|
||||
this.player.increaseVolume(step);
|
||||
direction = 1;
|
||||
} else {
|
||||
this.player.decreaseVolume(step);
|
||||
direction = -1;
|
||||
}
|
||||
}
|
||||
// Using the biggest delta, normalize to 1 or -1 (or 0 if no delta)
|
||||
const direction = Math.sign(Math.abs(x) > Math.abs(y) ? x : y);
|
||||
|
||||
// Change the volume by 2%
|
||||
player.increaseVolume(direction / 50);
|
||||
|
||||
// Don't break page scrolling at max and min
|
||||
if (
|
||||
(direction === 1 && this.player.media.volume < 1) ||
|
||||
(direction === -1 && this.player.media.volume > 0)
|
||||
) {
|
||||
const { volume } = player.media;
|
||||
if ((direction === 1 && volume < 1) || (direction === -1 && volume > 0)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
|
||||
/* global google */
|
||||
|
||||
import i18n from '../i18n';
|
||||
import { createElement } from './../utils/elements';
|
||||
import { triggerEvent } from './../utils/events';
|
||||
import is from './../utils/is';
|
||||
import loadScript from './../utils/loadScript';
|
||||
import { formatTime } from './../utils/time';
|
||||
import { buildUrlParams } from './../utils/urls';
|
||||
import { createElement } from '../utils/elements';
|
||||
import { triggerEvent } from '../utils/events';
|
||||
import i18n from '../utils/i18n';
|
||||
import is from '../utils/is';
|
||||
import loadScript from '../utils/loadScript';
|
||||
import { formatTime } from '../utils/time';
|
||||
import { buildUrlParams } from '../utils/urls';
|
||||
|
||||
class Ads {
|
||||
/**
|
||||
@@ -49,7 +49,9 @@ class Ads {
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return this.player.isVideo && this.player.config.ads.enabled && !is.empty(this.publisherId);
|
||||
return (
|
||||
this.player.isHTML5 && this.player.isVideo && this.player.config.ads.enabled && !is.empty(this.publisherId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +100,7 @@ class Ads {
|
||||
const params = {
|
||||
AV_PUBLISHERID: '58c25bb0073ef448b1087ad6',
|
||||
AV_CHANNELID: '5a0458dc28a06145e4519d21',
|
||||
AV_URL: location.hostname,
|
||||
AV_URL: window.location.hostname,
|
||||
cb: Date.now(),
|
||||
AV_WIDTH: 640,
|
||||
AV_HEIGHT: 480,
|
||||
@@ -205,6 +207,11 @@ class Ads {
|
||||
* @param {Event} adsManagerLoadedEvent
|
||||
*/
|
||||
onAdsManagerLoaded(event) {
|
||||
// Load could occur after a source change (race condition)
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the ads manager
|
||||
const settings = new google.ima.AdsRenderingSettings();
|
||||
|
||||
@@ -238,10 +245,6 @@ class Ads {
|
||||
});
|
||||
}
|
||||
|
||||
// Get skippable state
|
||||
// TODO: Skip button
|
||||
// this.player.debug.warn(this.manager.getAdSkippableState());
|
||||
|
||||
// Set volume to match player
|
||||
this.manager.setVolume(this.player.volume);
|
||||
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
// Vimeo plugin
|
||||
// ==========================================================================
|
||||
|
||||
import captions from './../captions';
|
||||
import controls from './../controls';
|
||||
import ui from './../ui';
|
||||
import { createElement, replaceElement, toggleClass } from './../utils/elements';
|
||||
import { triggerEvent } from './../utils/events';
|
||||
import fetch from './../utils/fetch';
|
||||
import is from './../utils/is';
|
||||
import loadScript from './../utils/loadScript';
|
||||
import { format, stripHTML } from './../utils/strings';
|
||||
import { buildUrlParams } from './../utils/urls';
|
||||
import captions from '../captions';
|
||||
import controls from '../controls';
|
||||
import ui from '../ui';
|
||||
import { createElement, replaceElement, toggleClass } from '../utils/elements';
|
||||
import { triggerEvent } from '../utils/events';
|
||||
import fetch from '../utils/fetch';
|
||||
import is from '../utils/is';
|
||||
import loadScript from '../utils/loadScript';
|
||||
import { format, stripHTML } from '../utils/strings';
|
||||
import { buildUrlParams } from '../utils/urls';
|
||||
|
||||
// Parse Vimeo ID from URL
|
||||
function parseId(url) {
|
||||
@@ -70,8 +70,9 @@ const vimeo = {
|
||||
// Set aspect ratio
|
||||
// For Vimeo we have an extra 300% height <div> to hide the standard controls and UI
|
||||
setAspectRatio(input) {
|
||||
const [x, y] = (is.string(input) ? input : this.config.ratio).split(':');
|
||||
const padding = 100 / x * y;
|
||||
const [x, y] = (is.string(input) ? input : this.config.ratio).split(':').map(Number);
|
||||
const padding = (100 / x) * y;
|
||||
vimeo.padding = padding;
|
||||
this.elements.wrapper.style.paddingBottom = `${padding}%`;
|
||||
|
||||
if (this.supported.ui) {
|
||||
@@ -278,6 +279,7 @@ const vimeo = {
|
||||
.getVideoUrl()
|
||||
.then(value => {
|
||||
currentSrc = value;
|
||||
controls.setDownloadLink.call(player);
|
||||
})
|
||||
.catch(error => {
|
||||
this.debug.warn(error);
|
||||
@@ -298,8 +300,8 @@ const vimeo = {
|
||||
|
||||
// Set aspect ratio based on video size
|
||||
Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {
|
||||
const ratio = getAspectRatio(dimensions[0], dimensions[1]);
|
||||
vimeo.setAspectRatio.call(this, ratio);
|
||||
vimeo.ratio = getAspectRatio(dimensions[0], dimensions[1]);
|
||||
vimeo.setAspectRatio.call(this, vimeo.ratio);
|
||||
});
|
||||
|
||||
// Set autopause
|
||||
@@ -403,6 +405,22 @@ const vimeo = {
|
||||
triggerEvent.call(player, player.media, 'error');
|
||||
});
|
||||
|
||||
// Set height/width on fullscreen
|
||||
player.on('enterfullscreen exitfullscreen', event => {
|
||||
const { target } = player.fullscreen;
|
||||
|
||||
// Ignore for iOS native
|
||||
if (target !== player.elements.container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toggle = event.type === 'enterfullscreen';
|
||||
const [x, y] = vimeo.ratio.split(':').map(Number);
|
||||
const dimension = x > y ? 'width' : 'height';
|
||||
|
||||
target.style[dimension] = toggle ? `${vimeo.padding}%` : null;
|
||||
});
|
||||
|
||||
// Rebuild UI
|
||||
setTimeout(() => ui.build.call(player), 0);
|
||||
},
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
// YouTube plugin
|
||||
// ==========================================================================
|
||||
|
||||
import controls from './../controls';
|
||||
import ui from './../ui';
|
||||
import { dedupe } from './../utils/arrays';
|
||||
import { createElement, replaceElement, toggleClass } from './../utils/elements';
|
||||
import { triggerEvent } from './../utils/events';
|
||||
import fetch from './../utils/fetch';
|
||||
import is from './../utils/is';
|
||||
import loadImage from './../utils/loadImage';
|
||||
import loadScript from './../utils/loadScript';
|
||||
import { format, generateId } from './../utils/strings';
|
||||
import ui from '../ui';
|
||||
import { createElement, replaceElement, toggleClass } from '../utils/elements';
|
||||
import { triggerEvent } from '../utils/events';
|
||||
import fetch from '../utils/fetch';
|
||||
import is from '../utils/is';
|
||||
import loadImage from '../utils/loadImage';
|
||||
import loadScript from '../utils/loadScript';
|
||||
import { format, generateId } from '../utils/strings';
|
||||
|
||||
// Parse YouTube ID from URL
|
||||
function parseId(url) {
|
||||
@@ -23,37 +21,6 @@ function parseId(url) {
|
||||
return url.match(regex) ? RegExp.$2 : url;
|
||||
}
|
||||
|
||||
// Standardise YouTube quality unit
|
||||
function mapQualityUnit(input) {
|
||||
const qualities = {
|
||||
hd2160: 2160,
|
||||
hd1440: 1440,
|
||||
hd1080: 1080,
|
||||
hd720: 720,
|
||||
large: 480,
|
||||
medium: 360,
|
||||
small: 240,
|
||||
tiny: 144,
|
||||
};
|
||||
|
||||
const entry = Object.entries(qualities).find(entry => entry.includes(input));
|
||||
|
||||
if (entry) {
|
||||
// Get the match corresponding to the input
|
||||
return entry.find(value => value !== input);
|
||||
}
|
||||
|
||||
return 'default';
|
||||
}
|
||||
|
||||
function mapQualityUnits(levels) {
|
||||
if (is.empty(levels)) {
|
||||
return levels;
|
||||
}
|
||||
|
||||
return dedupe(levels.map(level => mapQualityUnit(level)));
|
||||
}
|
||||
|
||||
// Set playback state and trigger change (only on actual change)
|
||||
function assurePlaybackState(play) {
|
||||
if (play && !this.embed.hasPlayed) {
|
||||
@@ -188,6 +155,7 @@ const youtube = {
|
||||
videoId,
|
||||
playerVars: {
|
||||
autoplay: player.config.autoplay ? 1 : 0, // Autoplay
|
||||
hl: player.config.hl, // iframe interface language
|
||||
controls: player.supported.ui ? 0 : 1, // Only show controls if not fully supported
|
||||
rel: 0, // No related vids
|
||||
showinfo: 0, // Hide info
|
||||
@@ -224,11 +192,6 @@ const youtube = {
|
||||
triggerEvent.call(player, player.media, 'error');
|
||||
}
|
||||
},
|
||||
onPlaybackQualityChange() {
|
||||
triggerEvent.call(player, player.media, 'qualitychange', false, {
|
||||
quality: player.media.quality,
|
||||
});
|
||||
},
|
||||
onPlaybackRateChange(event) {
|
||||
// Get the instance
|
||||
const instance = event.target;
|
||||
@@ -239,6 +202,10 @@ const youtube = {
|
||||
triggerEvent.call(player, player.media, 'ratechange');
|
||||
},
|
||||
onReady(event) {
|
||||
// Bail if onReady has already been called. See issue #1108
|
||||
if (is.function(player.media.play)) {
|
||||
return;
|
||||
}
|
||||
// Get the instance
|
||||
const instance = event.target;
|
||||
|
||||
@@ -270,8 +237,8 @@ const youtube = {
|
||||
return Number(instance.getCurrentTime());
|
||||
},
|
||||
set(time) {
|
||||
// If paused, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
|
||||
if (player.paused) {
|
||||
// If paused and never played, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
|
||||
if (player.paused && !player.embed.hasPlayed) {
|
||||
player.embed.mute();
|
||||
}
|
||||
|
||||
@@ -294,16 +261,6 @@ const youtube = {
|
||||
},
|
||||
});
|
||||
|
||||
// Quality
|
||||
Object.defineProperty(player.media, 'quality', {
|
||||
get() {
|
||||
return mapQualityUnit(instance.getPlaybackQuality());
|
||||
},
|
||||
set(input) {
|
||||
instance.setPlaybackQuality(mapQualityUnit(input));
|
||||
},
|
||||
});
|
||||
|
||||
// Volume
|
||||
let { volume } = player.config;
|
||||
Object.defineProperty(player.media, 'volume', {
|
||||
@@ -452,12 +409,6 @@ const youtube = {
|
||||
player.media.duration = instance.getDuration();
|
||||
triggerEvent.call(player, player.media, 'durationchange');
|
||||
}
|
||||
|
||||
// Get quality
|
||||
controls.setQualityMenu.call(
|
||||
player,
|
||||
mapQualityUnits(instance.getAvailableQualityLevels()),
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// ==========================================================================
|
||||
// Plyr
|
||||
// plyr.js v3.3.14
|
||||
// plyr.js v3.4.7
|
||||
// https://github.com/sampotts/plyr
|
||||
// License: The MIT License (MIT)
|
||||
// ==========================================================================
|
||||
|
||||
import captions from './captions';
|
||||
import defaults from './config/defaults';
|
||||
import { pip } from './config/states';
|
||||
import { getProviderByUrl, providers, types } from './config/types';
|
||||
import Console from './console';
|
||||
import controls from './controls';
|
||||
@@ -75,16 +76,17 @@ class Plyr {
|
||||
// Elements cache
|
||||
this.elements = {
|
||||
container: null,
|
||||
captions: null,
|
||||
buttons: {},
|
||||
display: {},
|
||||
progress: {},
|
||||
inputs: {},
|
||||
settings: {
|
||||
popup: null,
|
||||
menu: null,
|
||||
panes: {},
|
||||
tabs: {},
|
||||
panels: {},
|
||||
buttons: {},
|
||||
},
|
||||
captions: null,
|
||||
};
|
||||
|
||||
// Captions
|
||||
@@ -171,7 +173,7 @@ class Plyr {
|
||||
this.elements.container.className = '';
|
||||
|
||||
// Get attributes from URL and set config
|
||||
if (url.searchParams.length) {
|
||||
if (url.search.length) {
|
||||
const truthy = ['1', 'true'];
|
||||
|
||||
if (truthy.includes(url.searchParams.get('autoplay'))) {
|
||||
@@ -185,6 +187,7 @@ class Plyr {
|
||||
// YouTube requires the playsinline in the URL
|
||||
if (this.isYouTube) {
|
||||
this.config.playsinline = truthy.includes(url.searchParams.get('playsinline'));
|
||||
this.config.hl = url.searchParams.get('hl'); // TODO: Should this be setting language?
|
||||
} else {
|
||||
this.config.playsinline = true;
|
||||
}
|
||||
@@ -220,7 +223,7 @@ class Plyr {
|
||||
if (this.media.hasAttribute('autoplay')) {
|
||||
this.config.autoplay = true;
|
||||
}
|
||||
if (this.media.hasAttribute('playsinline')) {
|
||||
if (this.media.hasAttribute('playsinline') || this.media.hasAttribute('webkit-playsinline')) {
|
||||
this.config.playsinline = true;
|
||||
}
|
||||
if (this.media.hasAttribute('muted')) {
|
||||
@@ -292,12 +295,17 @@ class Plyr {
|
||||
this.fullscreen = new Fullscreen(this);
|
||||
|
||||
// Setup ads if provided
|
||||
this.ads = new Ads(this);
|
||||
if (this.config.ads.enabled) {
|
||||
this.ads = new Ads(this);
|
||||
}
|
||||
|
||||
// Autoplay if required
|
||||
if (this.config.autoplay) {
|
||||
this.play();
|
||||
}
|
||||
|
||||
// Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
|
||||
this.lastSeekTime = 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
@@ -310,18 +318,23 @@ class Plyr {
|
||||
get isHTML5() {
|
||||
return Boolean(this.provider === providers.html5);
|
||||
}
|
||||
|
||||
get isEmbed() {
|
||||
return Boolean(this.isYouTube || this.isVimeo);
|
||||
}
|
||||
|
||||
get isYouTube() {
|
||||
return Boolean(this.provider === providers.youtube);
|
||||
}
|
||||
|
||||
get isVimeo() {
|
||||
return Boolean(this.provider === providers.vimeo);
|
||||
}
|
||||
|
||||
get isVideo() {
|
||||
return Boolean(this.type === types.video);
|
||||
}
|
||||
|
||||
get isAudio() {
|
||||
return Boolean(this.type === types.audio);
|
||||
}
|
||||
@@ -489,8 +502,9 @@ class Plyr {
|
||||
// Faux duration set via config
|
||||
const fauxDuration = parseFloat(this.config.duration);
|
||||
|
||||
// Media duration can be NaN before the media has loaded
|
||||
const duration = (this.media || {}).duration || 0;
|
||||
// Media duration can be NaN or Infinity before the media has loaded
|
||||
const realDuration = (this.media || {}).duration;
|
||||
const duration = !is.number(realDuration) || realDuration === Infinity ? 0 : realDuration;
|
||||
|
||||
// If config duration is funky, use regular duration
|
||||
return fauxDuration || duration;
|
||||
@@ -553,7 +567,7 @@ class Plyr {
|
||||
*/
|
||||
increaseVolume(step) {
|
||||
const volume = this.media.muted ? 0 : this.volume;
|
||||
this.volume = volume + (is.number(step) ? step : 1);
|
||||
this.volume = volume + (is.number(step) ? step : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -561,8 +575,7 @@ class Plyr {
|
||||
* @param {boolean} step - How much to decrease by (between 0 and 1)
|
||||
*/
|
||||
decreaseVolume(step) {
|
||||
const volume = this.media.muted ? 0 : this.volume;
|
||||
this.volume = volume - (is.number(step) ? step : 1);
|
||||
this.increaseVolume(-step);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -683,20 +696,27 @@ class Plyr {
|
||||
config.default,
|
||||
].find(is.number);
|
||||
|
||||
let updateStorage = true;
|
||||
|
||||
if (!options.includes(quality)) {
|
||||
const value = closest(options, quality);
|
||||
this.debug.warn(`Unsupported quality option: ${quality}, using ${value} instead`);
|
||||
quality = value;
|
||||
}
|
||||
|
||||
// Trigger request event
|
||||
triggerEvent.call(this, this.media, 'qualityrequested', false, { quality });
|
||||
// Don't update storage if quality is not supported
|
||||
updateStorage = false;
|
||||
}
|
||||
|
||||
// Update config
|
||||
config.selected = quality;
|
||||
|
||||
// Set quality
|
||||
this.media.quality = quality;
|
||||
|
||||
// Save to storage
|
||||
if (updateStorage) {
|
||||
this.storage.set({ quality });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -782,6 +802,15 @@ class Plyr {
|
||||
return this.media.currentSrc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a download URL (either source or custom)
|
||||
*/
|
||||
get download() {
|
||||
const { download } = this.config.urls;
|
||||
|
||||
return is.url(download) ? download : this.source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the poster image for a video
|
||||
* @param {input} - the URL for the new poster image
|
||||
@@ -868,21 +897,28 @@ class Plyr {
|
||||
* TODO: detect outside changes
|
||||
*/
|
||||
set pip(input) {
|
||||
const states = {
|
||||
pip: 'picture-in-picture',
|
||||
inline: 'inline',
|
||||
};
|
||||
|
||||
// Bail if no support
|
||||
if (!support.pip) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle based on current state if not passed
|
||||
const toggle = is.boolean(input) ? input : this.pip === states.inline;
|
||||
const toggle = is.boolean(input) ? input : !this.pip;
|
||||
|
||||
// Toggle based on current state
|
||||
this.media.webkitSetPresentationMode(toggle ? states.pip : states.inline);
|
||||
// Safari
|
||||
if (is.function(this.media.webkitSetPresentationMode)) {
|
||||
this.media.webkitSetPresentationMode(toggle ? pip.active : pip.inactive);
|
||||
}
|
||||
|
||||
// Chrome
|
||||
if (is.function(this.media.requestPictureInPicture)) {
|
||||
if (!this.pip && toggle) {
|
||||
this.media.requestPictureInPicture();
|
||||
} else if (this.pip && !toggle) {
|
||||
document.exitPictureInPicture();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -893,7 +929,13 @@ class Plyr {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.media.webkitPresentationMode;
|
||||
// Safari
|
||||
if (!is.empty(this.media.webkitPresentationMode)) {
|
||||
return this.media.webkitPresentationMode === pip.active;
|
||||
}
|
||||
|
||||
// Chrome
|
||||
return this.media === document.pictureInPictureElement;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -927,13 +969,16 @@ class Plyr {
|
||||
if (hiding && this.config.controls.includes('settings') && !is.empty(this.config.settings)) {
|
||||
controls.toggleMenu.call(this, false);
|
||||
}
|
||||
|
||||
// Trigger event on change
|
||||
if (hiding !== isHidden) {
|
||||
const eventName = hiding ? 'controlshidden' : 'controlsshown';
|
||||
triggerEvent.call(this, this.media, eventName);
|
||||
}
|
||||
|
||||
return !hiding;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -945,6 +990,7 @@ class Plyr {
|
||||
on(event, callback) {
|
||||
on.call(this, this.elements.container, event, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listeners once
|
||||
* @param {string} event - Event type
|
||||
@@ -953,6 +999,7 @@ class Plyr {
|
||||
once(event, callback) {
|
||||
once.call(this, this.elements.container, event, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove event listeners
|
||||
* @param {string} event - Event type
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// ==========================================================================
|
||||
// Plyr Polyfilled Build
|
||||
// plyr.js v3.3.14
|
||||
// plyr.js v3.4.7
|
||||
// https://github.com/sampotts/plyr
|
||||
// License: The MIT License (MIT)
|
||||
// ==========================================================================
|
||||
|
||||
import 'babel-polyfill';
|
||||
import 'custom-event-polyfill';
|
||||
import 'url-polyfill';
|
||||
import Plyr from './plyr';
|
||||
|
||||
@@ -114,12 +114,9 @@ const source = {
|
||||
// HTML5 stuff
|
||||
if (this.isHTML5) {
|
||||
// Setup captions
|
||||
if ('tracks' in input) {
|
||||
if (Object.keys(input).includes('tracks')) {
|
||||
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
|
||||
@@ -128,6 +125,11 @@ const source = {
|
||||
ui.build.call(this);
|
||||
}
|
||||
|
||||
if (this.isHTML5) {
|
||||
// Load HTML5 sources
|
||||
this.media.load();
|
||||
}
|
||||
|
||||
// Update the fullscreen support
|
||||
this.fullscreen.update();
|
||||
},
|
||||
|
||||
@@ -36,8 +36,26 @@ const support = {
|
||||
},
|
||||
|
||||
// Picture-in-picture support
|
||||
// Safari only currently
|
||||
pip: (() => !browser.isIPhone && is.function(createElement('video').webkitSetPresentationMode))(),
|
||||
// Safari & Chrome only currently
|
||||
pip: (() => {
|
||||
if (browser.isIPhone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Safari
|
||||
// https://developer.apple.com/documentation/webkitjs/adding_picture_in_picture_to_your_safari_media_controls
|
||||
if (is.function(createElement('video').webkitSetPresentationMode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Chrome
|
||||
// https://developers.google.com/web/updates/2018/10/watch-video-using-picture-in-picture
|
||||
if (document.pictureInPictureEnabled && !createElement('video').disablePictureInPicture) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})(),
|
||||
|
||||
// Airplay support
|
||||
// Safari only currently
|
||||
@@ -52,25 +70,21 @@ const support = {
|
||||
// Related: http://www.leanbackplayer.com/test/h5mt.html
|
||||
mime(inputType) {
|
||||
const [mediaType] = inputType.split('/');
|
||||
let type = inputType;
|
||||
|
||||
// Verify we're using HTML5 and there's no media type mismatch
|
||||
if (!this.isHTML5 || mediaType !== this.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let type;
|
||||
if (inputType && inputType.includes('codecs=')) {
|
||||
// Use input directly
|
||||
type = inputType;
|
||||
} else if (inputType === 'audio/mpeg') {
|
||||
// Skip codec
|
||||
type = 'audio/mpeg;';
|
||||
} else if (inputType in defaultCodecs) {
|
||||
// Use codec
|
||||
type = `${inputType}; codecs="${defaultCodecs[inputType]}"`;
|
||||
// Add codec if required
|
||||
if (Object.keys(defaultCodecs).includes(type)) {
|
||||
type += `; codecs="${defaultCodecs[inputType]}"`;
|
||||
}
|
||||
|
||||
try {
|
||||
return Boolean(type && this.media.canPlayType(type).replace(/no/, ''));
|
||||
} catch (err) {
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
import captions from './captions';
|
||||
import controls from './controls';
|
||||
import i18n from './i18n';
|
||||
import support from './support';
|
||||
import browser from './utils/browser';
|
||||
import { getElement, toggleClass } from './utils/elements';
|
||||
import { ready, triggerEvent } from './utils/events';
|
||||
import i18n from './utils/i18n';
|
||||
import is from './utils/is';
|
||||
import loadImage from './utils/loadImage';
|
||||
|
||||
@@ -135,11 +135,9 @@ const ui = {
|
||||
}
|
||||
|
||||
// If there's a play button, set label
|
||||
if (is.nodeList(this.elements.buttons.play)) {
|
||||
Array.from(this.elements.buttons.play).forEach(button => {
|
||||
button.setAttribute('aria-label', label);
|
||||
});
|
||||
}
|
||||
Array.from(this.elements.buttons.play || []).forEach(button => {
|
||||
button.setAttribute('aria-label', label);
|
||||
});
|
||||
|
||||
// Set iframe title
|
||||
// https://github.com/sampotts/plyr/issues/124
|
||||
@@ -214,7 +212,7 @@ const ui = {
|
||||
toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);
|
||||
|
||||
// Set state
|
||||
Array.from(this.elements.buttons.play).forEach(target => {
|
||||
Array.from(this.elements.buttons.play || []).forEach(target => {
|
||||
target.pressed = this.playing;
|
||||
});
|
||||
|
||||
@@ -249,8 +247,11 @@ const ui = {
|
||||
const { controls } = this.elements;
|
||||
|
||||
if (controls && this.config.hideControls) {
|
||||
// Show controls if force, loading, paused, or button interaction, otherwise hide
|
||||
this.toggleControls(Boolean(force || this.loading || this.paused || controls.pressed || controls.hover));
|
||||
// Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
|
||||
const recentTouchSeek = (this.touch && this.lastSeekTime + 2000 > Date.now());
|
||||
|
||||
// Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
|
||||
this.toggleControls(Boolean(force || this.loading || this.paused || controls.pressed || controls.hover || recentTouchSeek));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,8 +23,12 @@ export const transitionEndEvent = (() => {
|
||||
// Force repaint of element
|
||||
export function repaint(element) {
|
||||
setTimeout(() => {
|
||||
toggleHidden(element, true);
|
||||
element.offsetHeight; // eslint-disable-line
|
||||
toggleHidden(element, false);
|
||||
try {
|
||||
toggleHidden(element, true);
|
||||
element.offsetHeight; // eslint-disable-line
|
||||
toggleHidden(element, false);
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@@ -70,12 +70,19 @@ export function createElement(type, attributes, text) {
|
||||
|
||||
// Inaert an element after another
|
||||
export function insertAfter(element, target) {
|
||||
if (!is.element(element) || !is.element(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
target.parentNode.insertBefore(element, target.nextSibling);
|
||||
}
|
||||
|
||||
// Insert a DocumentFragment
|
||||
export function insertElement(type, parent, attributes, text) {
|
||||
// Inject the new <element>
|
||||
if (!is.element(parent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent.appendChild(createElement(type, attributes, text));
|
||||
}
|
||||
|
||||
@@ -95,6 +102,10 @@ export function removeElement(element) {
|
||||
|
||||
// Remove all child elements
|
||||
export function emptyElement(element) {
|
||||
if (!is.element(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { length } = element.childNodes;
|
||||
|
||||
while (length > 0) {
|
||||
@@ -180,7 +191,7 @@ export function toggleHidden(element, hidden) {
|
||||
let hide = hidden;
|
||||
|
||||
if (!is.boolean(hide)) {
|
||||
hide = !element.hasAttribute('hidden');
|
||||
hide = !element.hidden;
|
||||
}
|
||||
|
||||
if (hide) {
|
||||
@@ -192,6 +203,10 @@ export function toggleHidden(element, hidden) {
|
||||
|
||||
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
|
||||
export function toggleClass(element, className, force) {
|
||||
if (is.nodeList(element)) {
|
||||
return Array.from(element).map(e => toggleClass(e, className, force));
|
||||
}
|
||||
|
||||
if (is.element(element)) {
|
||||
let method = 'toggle';
|
||||
if (typeof force !== 'undefined') {
|
||||
@@ -202,7 +217,7 @@ export function toggleClass(element, className, force) {
|
||||
return element.classList.contains(className);
|
||||
}
|
||||
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Has class name
|
||||
@@ -238,19 +253,6 @@ export function getElement(selector) {
|
||||
return this.elements.container.querySelector(selector);
|
||||
}
|
||||
|
||||
// Get the focused element
|
||||
export function getFocusElement() {
|
||||
let focused = document.activeElement;
|
||||
|
||||
if (!focused || focused === document.body) {
|
||||
focused = null;
|
||||
} else {
|
||||
focused = document.querySelector(':focus');
|
||||
}
|
||||
|
||||
return focused;
|
||||
}
|
||||
|
||||
// Trap focus inside container
|
||||
export function trapFocus(element = null, toggle = false) {
|
||||
if (!is.element(element)) {
|
||||
@@ -268,7 +270,7 @@ export function trapFocus(element = null, toggle = false) {
|
||||
}
|
||||
|
||||
// Get the current focused element
|
||||
const focused = getFocusElement();
|
||||
const focused = document.activeElement;
|
||||
|
||||
if (focused === last && !event.shiftKey) {
|
||||
// Move focus to first element that can be tabbed if Shift isn't used
|
||||
@@ -283,3 +285,18 @@ export function trapFocus(element = null, toggle = false) {
|
||||
|
||||
toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
|
||||
}
|
||||
|
||||
// Set focus and tab focus class
|
||||
export function setFocus(element = null, tabFocus = false) {
|
||||
if (!is.element(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set regular focus
|
||||
element.focus({ preventScroll: true });
|
||||
|
||||
// If we want to mimic keyboard focus via tab
|
||||
if (tabFocus) {
|
||||
toggleClass(element, this.config.classNames.tabFocus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,18 @@
|
||||
// Plyr internationalization
|
||||
// ==========================================================================
|
||||
|
||||
import is from './utils/is';
|
||||
import { getDeep } from './utils/objects';
|
||||
import { replaceAll } from './utils/strings';
|
||||
import is from './is';
|
||||
import { getDeep } from './objects';
|
||||
import { replaceAll } from './strings';
|
||||
|
||||
// Skip i18n for abbreviations and brand names
|
||||
const resources = {
|
||||
pip: 'PIP',
|
||||
airplay: 'AirPlay',
|
||||
html5: 'HTML5',
|
||||
vimeo: 'Vimeo',
|
||||
youtube: 'YouTube',
|
||||
};
|
||||
|
||||
const i18n = {
|
||||
get(key = '', config = {}) {
|
||||
@@ -15,6 +24,10 @@ const i18n = {
|
||||
let string = getDeep(config.i18n, key);
|
||||
|
||||
if (is.empty(string)) {
|
||||
if (Object.keys(resources).includes(key)) {
|
||||
return resources[key];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -3,65 +3,68 @@
|
||||
// ==========================================================================
|
||||
|
||||
const getConstructor = input => (input !== null && typeof input !== 'undefined' ? input.constructor : null);
|
||||
|
||||
const instanceOf = (input, constructor) => Boolean(input && constructor && input instanceof constructor);
|
||||
const isNullOrUndefined = input => input === null || typeof input === 'undefined';
|
||||
const isObject = input => getConstructor(input) === Object;
|
||||
const isNumber = input => getConstructor(input) === Number && !Number.isNaN(input);
|
||||
const isString = input => getConstructor(input) === String;
|
||||
const isBoolean = input => getConstructor(input) === Boolean;
|
||||
const isFunction = input => getConstructor(input) === Function;
|
||||
const isArray = input => Array.isArray(input);
|
||||
const isWeakMap = input => instanceOf(input, WeakMap);
|
||||
const isNodeList = input => instanceOf(input, NodeList);
|
||||
const isElement = input => instanceOf(input, Element);
|
||||
const isTextNode = input => getConstructor(input) === Text;
|
||||
const isEvent = input => instanceOf(input, Event);
|
||||
const isKeyboardEvent = input => instanceOf(input, KeyboardEvent);
|
||||
const isCue = input => instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
|
||||
const isTrack = input => instanceOf(input, TextTrack) || (!isNullOrUndefined(input) && isString(input.kind));
|
||||
|
||||
const is = {
|
||||
object(input) {
|
||||
return getConstructor(input) === Object;
|
||||
},
|
||||
number(input) {
|
||||
return getConstructor(input) === Number && !Number.isNaN(input);
|
||||
},
|
||||
string(input) {
|
||||
return getConstructor(input) === String;
|
||||
},
|
||||
boolean(input) {
|
||||
return getConstructor(input) === Boolean;
|
||||
},
|
||||
function(input) {
|
||||
return getConstructor(input) === Function;
|
||||
},
|
||||
array(input) {
|
||||
return !is.nullOrUndefined(input) && Array.isArray(input);
|
||||
},
|
||||
weakMap(input) {
|
||||
return instanceOf(input, WeakMap);
|
||||
},
|
||||
nodeList(input) {
|
||||
return instanceOf(input, NodeList);
|
||||
},
|
||||
element(input) {
|
||||
return instanceOf(input, Element);
|
||||
},
|
||||
textNode(input) {
|
||||
return getConstructor(input) === Text;
|
||||
},
|
||||
event(input) {
|
||||
return instanceOf(input, Event);
|
||||
},
|
||||
cue(input) {
|
||||
return instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);
|
||||
},
|
||||
track(input) {
|
||||
return instanceOf(input, TextTrack) || (!is.nullOrUndefined(input) && is.string(input.kind));
|
||||
},
|
||||
url(input) {
|
||||
return (
|
||||
!is.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 (
|
||||
is.nullOrUndefined(input) ||
|
||||
((is.string(input) || is.array(input) || is.nodeList(input)) && !input.length) ||
|
||||
(is.object(input) && !Object.keys(input).length)
|
||||
);
|
||||
},
|
||||
const isEmpty = input =>
|
||||
isNullOrUndefined(input) ||
|
||||
((isString(input) || isArray(input) || isNodeList(input)) && !input.length) ||
|
||||
(isObject(input) && !Object.keys(input).length);
|
||||
|
||||
const isUrl = input => {
|
||||
// Accept a URL object
|
||||
if (instanceOf(input, window.URL)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Must be string from here
|
||||
if (!isString(input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the protocol if required
|
||||
let string = input;
|
||||
if (!input.startsWith('http://') || !input.startsWith('https://')) {
|
||||
string = `http://${input}`;
|
||||
}
|
||||
|
||||
try {
|
||||
return !isEmpty(new URL(string).hostname);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default is;
|
||||
export default {
|
||||
nullOrUndefined: isNullOrUndefined,
|
||||
object: isObject,
|
||||
number: isNumber,
|
||||
string: isString,
|
||||
boolean: isBoolean,
|
||||
function: isFunction,
|
||||
array: isArray,
|
||||
weakMap: isWeakMap,
|
||||
nodeList: isNodeList,
|
||||
element: isElement,
|
||||
textNode: isTextNode,
|
||||
event: isEvent,
|
||||
keyboardEvent: isKeyboardEvent,
|
||||
cue: isCue,
|
||||
track: isTrack,
|
||||
url: isUrl,
|
||||
empty: isEmpty,
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Sprite loader
|
||||
// ==========================================================================
|
||||
|
||||
import Storage from './../storage';
|
||||
import Storage from '../storage';
|
||||
import fetch from './fetch';
|
||||
import is from './is';
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export function getPercentage(current, max) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (current / max * 100).toFixed(2);
|
||||
return ((current / max) * 100).toFixed(2);
|
||||
}
|
||||
|
||||
// Replace all occurances of a string in a string
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
padding: $plyr-control-spacing;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
transform: translateY(-($plyr-control-spacing * 4));
|
||||
transition: transform 0.4s ease-in-out;
|
||||
width: 100%;
|
||||
|
||||
@@ -53,6 +52,8 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.plyr--hide-controls .plyr__captions {
|
||||
transform: translateY(-($plyr-control-spacing * 1.5));
|
||||
// If the lower controls are shown and not empty
|
||||
.plyr:not(.plyr--hide-controls) .plyr__controls:not(:empty) ~ .plyr__captions {
|
||||
transform: translateY(-($plyr-control-spacing * 4));
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any link styling
|
||||
a.plyr__control {
|
||||
text-decoration: none;
|
||||
|
||||
&::after,
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Change icons on state change
|
||||
.plyr__control:not(.plyr__control--pressed) .icon--pressed,
|
||||
.plyr__control.plyr__control--pressed .icon--not-pressed,
|
||||
@@ -41,7 +51,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Audio styles
|
||||
// Audio control
|
||||
.plyr--audio .plyr__control {
|
||||
&.plyr__tab-focus,
|
||||
&:hover,
|
||||
@@ -51,6 +61,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Video control
|
||||
.plyr--video .plyr__control {
|
||||
svg {
|
||||
filter: drop-shadow(0 1px 1px rgba(#000, 0.15));
|
||||
}
|
||||
|
||||
// Hover and tab focus
|
||||
&.plyr__tab-focus,
|
||||
&:hover,
|
||||
&[aria-expanded='true'] {
|
||||
background: $plyr-video-control-bg-hover;
|
||||
color: $plyr-video-control-color-hover;
|
||||
}
|
||||
}
|
||||
|
||||
// Large play button (video only)
|
||||
.plyr__control--overlaid {
|
||||
background: rgba($plyr-video-control-bg-hover, 0.8);
|
||||
@@ -66,11 +91,10 @@
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 2;
|
||||
|
||||
// Offset icon to make the play button look right
|
||||
svg {
|
||||
height: $plyr-control-icon-size-large;
|
||||
left: 2px; // Offset to make the play button look right
|
||||
left: 2px;
|
||||
position: relative;
|
||||
width: $plyr-control-icon-size-large;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
|
||||
@@ -11,67 +11,44 @@
|
||||
.plyr__controls {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
text-align: center;
|
||||
|
||||
// Spacing
|
||||
> .plyr__control,
|
||||
.plyr__progress,
|
||||
.plyr__time,
|
||||
.plyr__menu {
|
||||
margin-left: ($plyr-control-spacing / 2);
|
||||
|
||||
&:first-child,
|
||||
&:first-child + [data-plyr='pause'] {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.plyr__menu,
|
||||
.plyr__volume {
|
||||
margin-left: ($plyr-control-spacing / 2);
|
||||
}
|
||||
|
||||
.plyr__menu + .plyr__control,
|
||||
> .plyr__control + .plyr__menu,
|
||||
> .plyr__control + .plyr__control,
|
||||
.plyr__progress + .plyr__control {
|
||||
margin-left: floor($plyr-control-spacing / 4);
|
||||
}
|
||||
|
||||
> .plyr__control:first-child,
|
||||
> .plyr__control:first-child + [data-plyr='pause'] {
|
||||
margin-left: 0;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
// Hide empty controls
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: $plyr-bp-sm) {
|
||||
> .plyr__control,
|
||||
.plyr__menu,
|
||||
.plyr__progress,
|
||||
.plyr__time,
|
||||
.plyr__menu {
|
||||
.plyr__volume {
|
||||
margin-left: $plyr-control-spacing;
|
||||
}
|
||||
|
||||
> .plyr__control + .plyr__control,
|
||||
.plyr__menu + .plyr__control,
|
||||
> .plyr__control + .plyr__menu {
|
||||
margin-left: ($plyr-control-spacing / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Video controls
|
||||
.plyr--video .plyr__controls {
|
||||
background: linear-gradient(rgba($plyr-video-controls-bg, 0), rgba($plyr-video-controls-bg, 0.7));
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
bottom: 0;
|
||||
color: $plyr-video-control-color;
|
||||
left: 0;
|
||||
padding: ($plyr-control-spacing * 3.5) $plyr-control-spacing $plyr-control-spacing;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out;
|
||||
z-index: 2;
|
||||
|
||||
.plyr__control {
|
||||
svg {
|
||||
filter: drop-shadow(0 1px 1px rgba(#000, 0.15));
|
||||
}
|
||||
|
||||
// Hover and tab focus
|
||||
&.plyr__tab-focus,
|
||||
&:hover,
|
||||
&[aria-expanded='true'] {
|
||||
background: $plyr-video-control-bg-hover;
|
||||
color: $plyr-video-control-color-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +60,29 @@
|
||||
padding: $plyr-control-spacing;
|
||||
}
|
||||
|
||||
// Hide controls
|
||||
// Video controls
|
||||
.plyr--video .plyr__controls {
|
||||
background: linear-gradient(
|
||||
rgba($plyr-video-controls-bg, 0),
|
||||
rgba($plyr-video-controls-bg, 0.7)
|
||||
);
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
bottom: 0;
|
||||
color: $plyr-video-control-color;
|
||||
left: 0;
|
||||
padding: ($plyr-control-spacing * 2) ($plyr-control-spacing / 2) ($plyr-control-spacing / 2);
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transition: opacity 0.4s ease-in-out, transform 0.4s ease-in-out;
|
||||
z-index: 3;
|
||||
|
||||
@media (min-width: $plyr-bp-sm) {
|
||||
padding: ($plyr-control-spacing * 3.5) $plyr-control-spacing $plyr-control-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide video controls
|
||||
.plyr--video.plyr--hide-controls .plyr__controls {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -27,11 +27,6 @@ $embed-padding: ((100 / 16) * 9);
|
||||
$height: 240;
|
||||
$offset: to-percentage(($height - $embed-padding) / ($height / 50));
|
||||
|
||||
// To allow mouse events to be captured if full support
|
||||
iframe {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Only used for Vimeo
|
||||
> .plyr__video-embed__container {
|
||||
padding-bottom: to-percentage($height);
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
|
||||
> div {
|
||||
overflow: hidden;
|
||||
transition: height 0.35s cubic-bezier(0.4, 0, 0.2, 1), width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition: height 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
// Arrow
|
||||
@@ -54,18 +55,16 @@
|
||||
width: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
[role='menu'] {
|
||||
padding: $plyr-control-padding;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 2px;
|
||||
[role='menuitem'],
|
||||
[role='menuitemradio'] {
|
||||
margin-top: 2px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,10 +74,17 @@
|
||||
color: $plyr-menu-color;
|
||||
display: flex;
|
||||
font-size: $plyr-font-size-menu;
|
||||
padding: ceil($plyr-control-padding / 2) ($plyr-control-padding * 2);
|
||||
padding: ceil($plyr-control-padding / 2)
|
||||
ceil($plyr-control-padding * 1.5);
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
|
||||
> span {
|
||||
align-items: inherit;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border: 4px solid transparent;
|
||||
content: '';
|
||||
@@ -135,50 +141,49 @@
|
||||
}
|
||||
}
|
||||
|
||||
label.plyr__control {
|
||||
.plyr__control[role='menuitemradio'] {
|
||||
padding-left: $plyr-control-padding;
|
||||
|
||||
input[type='radio'] + span {
|
||||
background: rgba(#000, 0.1);
|
||||
&::before,
|
||||
&::after {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: rgba(#000, 0.1);
|
||||
content: '';
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
height: 16px;
|
||||
margin-right: $plyr-control-spacing;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
width: 16px;
|
||||
|
||||
&::after {
|
||||
background: #fff;
|
||||
border-radius: 100%;
|
||||
content: '';
|
||||
height: 6px;
|
||||
left: 5px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
transform: scale(0);
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='radio']:checked + span {
|
||||
background: $plyr-color-main;
|
||||
&::after {
|
||||
background: #fff;
|
||||
border: 0;
|
||||
height: 6px;
|
||||
left: 12px;
|
||||
opacity: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) scale(0);
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&[aria-checked='true'] {
|
||||
&::before {
|
||||
background: $plyr-color-main;
|
||||
}
|
||||
&::after {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
transform: translateY(-50%) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
input[type='radio']:focus + span {
|
||||
@include plyr-tab-focus();
|
||||
}
|
||||
|
||||
&.plyr__tab-focus input[type='radio'] + span,
|
||||
&:hover input[type='radio'] + span {
|
||||
&.plyr__tab-focus::before,
|
||||
&:hover::before {
|
||||
background: rgba(#000, 0.1);
|
||||
}
|
||||
}
|
||||
@@ -188,7 +193,7 @@
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
margin-right: -$plyr-control-padding;
|
||||
margin-right: -($plyr-control-padding - 2);
|
||||
overflow: hidden;
|
||||
padding-left: ceil($plyr-control-padding * 3.5);
|
||||
pointer-events: none;
|
||||
|
||||
@@ -12,10 +12,9 @@
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
transition: opacity 0.2s ease;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.plyr--stopped.plyr__poster-enabled .plyr__poster {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// --------------------------------------------------------------
|
||||
|
||||
.plyr__progress {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
left: $plyr-range-thumb-height / 2;
|
||||
margin-right: $plyr-range-thumb-height;
|
||||
|
||||
@@ -19,7 +19,11 @@
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
@include plyr-range-track();
|
||||
background-image: linear-gradient(to right, currentColor var(--value, 0%), transparent var(--value, 0%));
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
currentColor var(--value, 0%),
|
||||
transparent var(--value, 0%)
|
||||
);
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
@@ -140,15 +144,21 @@
|
||||
// Pressed styles
|
||||
&:active {
|
||||
&::-webkit-slider-thumb {
|
||||
@include plyr-range-thumb-active($plyr-audio-range-thumb-shadow-color);
|
||||
@include plyr-range-thumb-active(
|
||||
$plyr-audio-range-thumb-shadow-color
|
||||
);
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
@include plyr-range-thumb-active($plyr-audio-range-thumb-shadow-color);
|
||||
@include plyr-range-thumb-active(
|
||||
$plyr-audio-range-thumb-shadow-color
|
||||
);
|
||||
}
|
||||
|
||||
&::-ms-thumb {
|
||||
@include plyr-range-thumb-active($plyr-audio-range-thumb-shadow-color);
|
||||
@include plyr-range-thumb-active(
|
||||
$plyr-audio-range-thumb-shadow-color
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
color: $plyr-tooltip-color;
|
||||
font-size: $plyr-font-size-small;
|
||||
font-weight: $plyr-font-weight-regular;
|
||||
left: 50%;
|
||||
line-height: 1.3;
|
||||
margin-bottom: ($plyr-tooltip-padding * 2);
|
||||
opacity: 0;
|
||||
@@ -64,6 +65,7 @@
|
||||
|
||||
// Last tooltip
|
||||
.plyr__controls > .plyr__control:last-child .plyr__tooltip {
|
||||
left: auto;
|
||||
right: 0;
|
||||
transform: translate(0, 10px) scale(0.8);
|
||||
transform-origin: 100% 100%;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// --------------------------------------------------------------
|
||||
|
||||
.plyr--video {
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
|
||||
// Menu open
|
||||
|
||||
@@ -3,20 +3,23 @@
|
||||
// --------------------------------------------------------------
|
||||
|
||||
.plyr__volume {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
input[type='range'] {
|
||||
margin-left: ($plyr-control-spacing / 2);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@media (min-width: $plyr-bp-sm) {
|
||||
max-width: 50px;
|
||||
max-width: 90px;
|
||||
}
|
||||
|
||||
@media (min-width: $plyr-bp-md) {
|
||||
max-width: 80px;
|
||||
max-width: 110px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// Nicer focus styles
|
||||
// ---------------------------------------
|
||||
@mixin plyr-tab-focus($color: $plyr-tab-focus-default-color) {
|
||||
box-shadow: 0 0 0 3px rgba($color, 0.35);
|
||||
box-shadow: 0 0 0 5px rgba($color, 0.5);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
border: 0;
|
||||
border-radius: ($plyr-range-track-height / 2);
|
||||
height: $plyr-range-track-height;
|
||||
transition: all 0.3s ease;
|
||||
transition: box-shadow 0.3s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
border: 0;
|
||||
border-radius: 100%;
|
||||
box-shadow: $plyr-range-thumb-shadow;
|
||||
box-sizing: border-box;
|
||||
height: $plyr-range-thumb-height;
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// ==========================================================================
|
||||
|
||||
$plyr-control-icon-size: 18px !default;
|
||||
$plyr-control-icon-size-large: 20px !default;
|
||||
$plyr-control-spacing: 10px !default;
|
||||
$plyr-control-padding: ($plyr-control-spacing * 0.7) !default;
|
||||
$plyr-control-radius: 3px !default;
|
||||
|
||||
@@ -12,7 +12,7 @@ $plyr-range-thumb-border: 2px solid transparent !default;
|
||||
$plyr-range-thumb-shadow: 0 1px 1px rgba(#000, 0.15), 0 0 0 1px rgba($plyr-color-gunmetal, 0.2) !default;
|
||||
|
||||
// Track
|
||||
$plyr-range-track-height: 6px !default;
|
||||
$plyr-range-track-height: 4px !default;
|
||||
$plyr-range-max-height: ($plyr-range-thumb-active-shadow-width * 2) + $plyr-range-thumb-height !default;
|
||||
|
||||
// Fill
|
||||
|
||||
@@ -17,4 +17,4 @@ $plyr-font-weight-bold: 600 !default;
|
||||
|
||||
$plyr-line-height: 1.7 !default;
|
||||
|
||||
$plyr-font-smoothing: true !default;
|
||||
$plyr-font-smoothing: false !default;
|
||||
|
||||
@@ -22,3 +22,7 @@
|
||||
width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.plyr [hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path d="M16,1 L2,1 C1.447,1 1,1.447 1,2 L1,12 C1,12.553 1.447,13 2,13 L5,13 L5,11 L3,11 L3,3 L15,3 L15,11 L13,11 L13,13 L16,13 C16.553,13 17,12.553 17,12 L17,2 C17,1.447 16.553,1 16,1 L16,1 Z"></path>
|
||||
<polygon points="4 17 14 17 9 11"></polygon>
|
||||
|
||||
|
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 374 B |
@@ -1,6 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill-rule="evenodd" fill-opacity="0.5">
|
||||
<path d="M1,1 C0.4,1 0,1.4 0,2 L0,13 C0,13.6 0.4,14 1,14 L5.6,14 L8.3,16.7 C8.5,16.9 8.7,17 9,17 C9.3,17 9.5,16.9 9.7,16.7 L12.4,14 L17,14 C17.6,14 18,13.6 18,13 L18,2 C18,1.4 17.6,1 17,1 L1,1 Z M5.52,11.15 C7.51,11.15 8.53,9.83 8.8,8.74 L7.51,8.35 C7.32,9.01 6.73,9.8 5.52,9.8 C4.38,9.8 3.32,8.97 3.32,7.46 C3.32,5.85 4.44,5.09 5.5,5.09 C6.73,5.09 7.28,5.84 7.45,6.52 L8.75,6.11 C8.47,4.96 7.46,3.76 5.5,3.76 C3.6,3.76 1.89,5.2 1.89,7.46 C1.89,9.72 3.54,11.15 5.52,11.15 Z M13.09,11.15 C15.08,11.15 16.1,9.83 16.37,8.74 L15.08,8.35 C14.89,9.01 14.3,9.8 13.09,9.8 C11.95,9.8 10.89,8.97 10.89,7.46 C10.89,5.85 12.01,5.09 13.07,5.09 C14.3,5.09 14.85,5.84 15.02,6.52 L16.32,6.11 C16.04,4.96 15.03,3.76 13.07,3.76 C11.17,3.76 9.46,5.2 9.46,7.46 C9.46,9.72 11.11,11.15 13.09,11.15 Z"></path>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 945 B |
@@ -1,6 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill-rule="evenodd">
|
||||
<path d="M1,1 C0.4,1 0,1.4 0,2 L0,13 C0,13.6 0.4,14 1,14 L5.6,14 L8.3,16.7 C8.5,16.9 8.7,17 9,17 C9.3,17 9.5,16.9 9.7,16.7 L12.4,14 L17,14 C17.6,14 18,13.6 18,13 L18,2 C18,1.4 17.6,1 17,1 L1,1 Z M5.52,11.15 C7.51,11.15 8.53,9.83 8.8,8.74 L7.51,8.35 C7.32,9.01 6.73,9.8 5.52,9.8 C4.38,9.8 3.32,8.97 3.32,7.46 C3.32,5.85 4.44,5.09 5.5,5.09 C6.73,5.09 7.28,5.84 7.45,6.52 L8.75,6.11 C8.47,4.96 7.46,3.76 5.5,3.76 C3.6,3.76 1.89,5.2 1.89,7.46 C1.89,9.72 3.54,11.15 5.52,11.15 Z M13.09,11.15 C15.08,11.15 16.1,9.83 16.37,8.74 L15.08,8.35 C14.89,9.01 14.3,9.8 13.09,9.8 C11.95,9.8 10.89,8.97 10.89,7.46 C10.89,5.85 12.01,5.09 13.07,5.09 C14.3,5.09 14.85,5.84 15.02,6.52 L16.32,6.11 C16.04,4.96 15.03,3.76 13.07,3.76 C11.17,3.76 9.46,5.2 9.46,7.46 C9.46,9.72 11.11,11.15 13.09,11.15 Z"></path>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 926 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(2 1)">
|
||||
<path d="M7,12 C7.3,12 7.5,11.9 7.7,11.7 L13.4,6 L12,4.6 L8,8.6 L8,0 L6,0 L6,8.6 L2,4.6 L0.6,6 L6.3,11.7 C6.5,11.9 6.7,12 7,12 Z" />
|
||||
<rect width="14" height="2" y="14" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 325 B |
@@ -1,7 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<polygon points="10 3 13.6 3 9.6 7 11 8.4 15 4.4 15 8 17 8 17 1 10 1"></polygon>
|
||||
<polygon points="7 9.6 3 13.6 3 10 1 10 1 17 8 17 8 15 4.4 15 8.4 11"></polygon>
|
||||
</g>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<polygon points="10 3 13.6 3 9.6 7 11 8.4 15 4.4 15 8 17 8 17 1 10 1"></polygon>
|
||||
<polygon points="7 9.6 3 13.6 3 10 1 10 1 17 8 17 8 15 4.4 15 8.4 11"></polygon>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 264 B |
@@ -1,7 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<polygon points="1 12 4.6 12 0.6 16 2 17.4 6 13.4 6 17 8 17 8 10 1 10"></polygon>
|
||||
<polygon points="16 0.6 12 4.6 12 1 10 1 10 8 17 8 17 6 13.4 6 17.4 2"></polygon>
|
||||
</g>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<polygon points="1 12 4.6 12 0.6 16 2 17.4 6 13.4 6 17 8 17 8 10 1 10"></polygon>
|
||||
<polygon points="16 0.6 12 4.6 12 1 10 1 10 8 17 8 17 6 13.4 6 17.4 2"></polygon>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 266 B |
@@ -1,6 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<polygon points="7.875 7.17142857 0 1 0 17 7.875 10.8285714 7.875 17 18 9 7.875 1"></polygon>
|
||||
</g>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<polygon points="7.875 7.17142857 0 1 0 17 7.875 10.8285714 7.875 17 18 9 7.875 1"></polygon>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 192 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<path d="M16,3.3 C15.9,4.9 14.8,7 12.7,9.7 C10.5,12.5 8.7,13.9 7.2,13.9 C6.3,13.9 5.5,13 4.8,11.3 C4,8.9 3.4,4 2,4 C1.9,4 1.5,4.3 0.8,4.8 L0,3.8 C0.8,3.1 3.5,0.4 4.7,0.3 C5.9,0.2 6.7,1 7,2.8 C7.3,4.8 7.8,8.9 8.8,8.9 C9.7,8.9 11.3,5.5 11.4,4.9 C11.5,4 11.1,3 9.1,3.8 C9.9,1.2 11.4,-8.8817842e-16 13.6,-8.8817842e-16 C15.3,0.1 16.1,1.2 16,3.3 Z"
|
||||
transform="translate(1 2)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 470 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||
<path d="M15.8,2.8 C15.6,1.5 15,0.6 13.6,0.4 C11.4,0 8,0 8,0 C8,0 4.6,0 2.4,0.4 C1,0.6 0.3,1.5 0.2,2.8 C0,4.1 0,6 0,6 C0,6 0,7.9 0.2,9.2 C0.4,10.5 1,11.4 2.4,11.6 C4.6,12 8,12 8,12 C8,12 11.4,12 13.6,11.6 C15,11.3 15.6,10.5 15.8,9.2 C16,7.9 16,6 16,6 C16,6 16,4.1 15.8,2.8 Z M6,9 L6,3 L11,6 L6,9 Z"
|
||||
transform="translate(1 3)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 425 B |
@@ -1,7 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<polygon points="12.4 12.5 14.5 10.4 16.6 12.5 18 11.1 15.9 9 18 6.9 16.6 5.5 14.5 7.6 12.4 5.5 11 6.9 13.1 9 11 11.1"></polygon>
|
||||
<path d="M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"></path>
|
||||
</g>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<polygon points="12.4 12.5 14.5 10.4 16.6 12.5 18 11.1 15.9 9 18 6.9 16.6 5.5 14.5 7.6 12.4 5.5 11 6.9 13.1 9 11 11.1"></polygon>
|
||||
<path d="M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 750 B After Width: | Height: | Size: 613 B |
@@ -1,7 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<path d="M6,1 L3,1 C2.4,1 2,1.4 2,2 L2,16 C2,16.6 2.4,17 3,17 L6,17 C6.6,17 7,16.6 7,16 L7,2 C7,1.4 6.6,1 6,1 L6,1 Z"></path>
|
||||
<path d="M12,1 C11.4,1 11,1.4 11,2 L11,16 C11,16.6 11.4,17 12,17 L15,17 C15.6,17 16,16.6 16,16 L16,2 C16,1.4 15.6,1 15,1 L12,1 Z"></path>
|
||||
</g>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6,1 L3,1 C2.4,1 2,1.4 2,2 L2,16 C2,16.6 2.4,17 3,17 L6,17 C6.6,17 7,16.6 7,16 L7,2 C7,1.4 6.6,1 6,1 L6,1 Z"></path>
|
||||
<path d="M12,1 C11.4,1 11,1.4 11,2 L11,16 C11,16.6 11.4,17 12,17 L15,17 C15.6,17 16,16.6 16,16 L16,2 C16,1.4 15.6,1 15,1 L12,1 Z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 503 B After Width: | Height: | Size: 366 B |
@@ -1,7 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<polygon points="13.293 3.293 7.022 9.564 8.436 10.978 14.707 4.707 17 7 17 1 11 1"></polygon>
|
||||
<path d="M13,15 L3,15 L3,5 L8,5 L8,3 L2,3 C1.448,3 1,3.448 1,4 L1,16 C1,16.552 1.448,17 2,17 L14,17 C14.552,17 15,16.552 15,16 L15,10 L13,10 L13,15 L13,15 Z"></path>
|
||||
</g>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<polygon points="13.293 3.293 7.022 9.564 8.436 10.978 14.707 4.707 17 7 17 1 11 1"></polygon>
|
||||
<path d="M13,15 L3,15 L3,5 L8,5 L8,3 L2,3 C1.448,3 1,3.448 1,4 L1,16 C1,16.552 1.448,17 2,17 L14,17 C14.552,17 15,16.552 15,16 L15,10 L13,10 L13,15 L13,15 Z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 500 B After Width: | Height: | Size: 363 B |
@@ -1,6 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<path d="M15.5615866,8.10002147 L3.87056367,0.225209313 C3.05219207,-0.33727727 2,0.225209313 2,1.12518784 L2,16.8748122 C2,17.7747907 3.05219207,18.3372773 3.87056367,17.7747907 L15.5615866,9.89997853 C16.1461378,9.44998927 16.1461378,8.55001073 15.5615866,8.10002147 L15.5615866,8.10002147 Z"></path>
|
||||
</g>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.5615866,8.10002147 L3.87056367,0.225209313 C3.05219207,-0.33727727 2,0.225209313 2,1.12518784 L2,16.8748122 C2,17.7747907 3.05219207,18.3372773 3.87056367,17.7747907 L15.5615866,9.89997853 C16.1461378,9.44998927 16.1461378,8.55001073 15.5615866,8.10002147 L15.5615866,8.10002147 Z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 534 B After Width: | Height: | Size: 401 B |
@@ -1,6 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<path d="M9.7,1.2 L10.4,7.6 L12.5,5.5 C14.4,7.4 14.4,10.6 12.5,12.5 C11.6,13.5 10.3,14 9,14 C7.7,14 6.4,13.5 5.5,12.5 C3.6,10.6 3.6,7.4 5.5,5.5 C6.1,4.9 6.9,4.4 7.8,4.2 L7.2,2.3 C6,2.6 4.9,3.2 4,4.1 C1.3,6.8 1.3,11.2 4,14 C5.3,15.3 7.1,16 8.9,16 C10.8,16 12.5,15.3 13.8,14 C16.5,11.3 16.5,6.9 13.8,4.1 L16,1.9 L9.7,1.2 L9.7,1.2 Z"></path>
|
||||
</g>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.7,1.2 L10.4,7.6 L12.5,5.5 C14.4,7.4 14.4,10.6 12.5,12.5 C11.6,13.5 10.3,14 9,14 C7.7,14 6.4,13.5 5.5,12.5 C3.6,10.6 3.6,7.4 5.5,5.5 C6.1,4.9 6.9,4.4 7.8,4.2 L7.2,2.3 C6,2.6 4.9,3.2 4,4.1 C1.3,6.8 1.3,11.2 4,14 C5.3,15.3 7.1,16 8.9,16 C10.8,16 12.5,15.3 13.8,14 C16.5,11.3 16.5,6.9 13.8,4.1 L16,1.9 L9.7,1.2 L9.7,1.2 Z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 570 B After Width: | Height: | Size: 437 B |
@@ -1,6 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<polygon points="10.125 1 0 9 10.125 17 10.125 10.8285714 18 17 18 1 10.125 7.17142857"></polygon>
|
||||
</g>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<polygon points="10.125 1 0 9 10.125 17 10.125 10.8285714 18 17 18 1 10.125 7.17142857"></polygon>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 330 B After Width: | Height: | Size: 197 B |
@@ -1,6 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<path d="M16.135,7.784 C14.832,7.458 14.214,5.966 14.905,4.815 C15.227,4.279 15.13,3.817 14.811,3.499 L14.501,3.189 C14.183,2.871 13.721,2.774 13.185,3.095 C12.033,3.786 10.541,3.168 10.216,1.865 C10.065,1.258 9.669,1 9.219,1 L8.781,1 C8.331,1 7.936,1.258 7.784,1.865 C7.458,3.168 5.966,3.786 4.815,3.095 C4.279,2.773 3.816,2.87 3.498,3.188 L3.188,3.498 C2.87,3.816 2.773,4.279 3.095,4.815 C3.786,5.967 3.168,7.459 1.865,7.784 C1.26,7.935 1,8.33 1,8.781 L1,9.219 C1,9.669 1.258,10.064 1.865,10.216 C3.168,10.542 3.786,12.034 3.095,13.185 C2.773,13.721 2.87,14.183 3.189,14.501 L3.499,14.811 C3.818,15.13 4.281,15.226 4.815,14.905 C5.967,14.214 7.459,14.832 7.784,16.135 C7.935,16.742 8.331,17 8.781,17 L9.219,17 C9.669,17 10.064,16.742 10.216,16.135 C10.542,14.832 12.034,14.214 13.185,14.905 C13.72,15.226 14.182,15.13 14.501,14.811 L14.811,14.501 C15.129,14.183 15.226,13.72 14.905,13.185 C14.214,12.033 14.832,10.541 16.135,10.216 C16.742,10.065 17,9.669 17,9.219 L17,8.781 C17,8.33 16.74,7.935 16.135,7.784 L16.135,7.784 Z M9,12 C7.343,12 6,10.657 6,9 C6,7.343 7.343,6 9,6 C10.657,6 12,7.343 12,9 C12,10.657 10.657,12 9,12 L9,12 Z"></path>
|
||||
</g>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.135,7.784 C14.832,7.458 14.214,5.966 14.905,4.815 C15.227,4.279 15.13,3.817 14.811,3.499 L14.501,3.189 C14.183,2.871 13.721,2.774 13.185,3.095 C12.033,3.786 10.541,3.168 10.216,1.865 C10.065,1.258 9.669,1 9.219,1 L8.781,1 C8.331,1 7.936,1.258 7.784,1.865 C7.458,3.168 5.966,3.786 4.815,3.095 C4.279,2.773 3.816,2.87 3.498,3.188 L3.188,3.498 C2.87,3.816 2.773,4.279 3.095,4.815 C3.786,5.967 3.168,7.459 1.865,7.784 C1.26,7.935 1,8.33 1,8.781 L1,9.219 C1,9.669 1.258,10.064 1.865,10.216 C3.168,10.542 3.786,12.034 3.095,13.185 C2.773,13.721 2.87,14.183 3.189,14.501 L3.499,14.811 C3.818,15.13 4.281,15.226 4.815,14.905 C5.967,14.214 7.459,14.832 7.784,16.135 C7.935,16.742 8.331,17 8.781,17 L9.219,17 C9.669,17 10.064,16.742 10.216,16.135 C10.542,14.832 12.034,14.214 13.185,14.905 C13.72,15.226 14.182,15.13 14.501,14.811 L14.811,14.501 C15.129,14.183 15.226,13.72 14.905,13.185 C14.214,12.033 14.832,10.541 16.135,10.216 C16.742,10.065 17,9.669 17,9.219 L17,8.781 C17,8.33 16.74,7.935 16.135,7.784 L16.135,7.784 Z M9,12 C7.343,12 6,10.657 6,9 C6,7.343 7.343,6 9,6 C10.657,6 12,7.343 12,9 C12,10.657 10.657,12 9,12 L9,12 Z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<path d="M15.5999996,3.3 C15.1999996,2.9 14.5999996,2.9 14.1999996,3.3 C13.7999996,3.7 13.7999996,4.3 14.1999996,4.7 C15.3999996,5.9 15.9999996,7.4 15.9999996,9 C15.9999996,10.6 15.3999996,12.1 14.1999996,13.3 C13.7999996,13.7 13.7999996,14.3 14.1999996,14.7 C14.3999996,14.9 14.6999996,15 14.8999996,15 C15.1999996,15 15.3999996,14.9 15.5999996,14.7 C17.0999996,13.2 17.9999996,11.2 17.9999996,9 C17.9999996,6.8 17.0999996,4.8 15.5999996,3.3 L15.5999996,3.3 Z"></path>
|
||||
<path d="M11.2819745,5.28197449 C10.9060085,5.65794047 10.9060085,6.22188944 11.2819745,6.59785542 C12.0171538,7.33303477 12.2772954,8.05605449 12.2772954,9.00000021 C12.2772954,9.93588462 11.851678,10.9172014 11.2819745,11.4869049 C10.9060085,11.8628709 10.9060085,12.4268199 11.2819745,12.8027859 C11.4271642,12.9479755 11.9176724,13.0649528 12.2998149,12.9592565 C12.4124479,12.9281035 12.5156669,12.8776063 12.5978555,12.8027859 C13.773371,11.732654 14.1311161,10.1597914 14.1312523,9.00000021 C14.1312723,8.8299555 14.1286311,8.66015647 14.119665,8.4897429 C14.0674781,7.49784946 13.8010171,6.48513613 12.5978554,5.28197449 C12.2218894,4.9060085 11.6579405,4.9060085 11.2819745,5.28197449 Z"></path>
|
||||
<path d="M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"></path>
|
||||
</g>
|
||||
<svg width="18px" height="18px" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.5999996,3.3 C15.1999996,2.9 14.5999996,2.9 14.1999996,3.3 C13.7999996,3.7 13.7999996,4.3 14.1999996,4.7 C15.3999996,5.9 15.9999996,7.4 15.9999996,9 C15.9999996,10.6 15.3999996,12.1 14.1999996,13.3 C13.7999996,13.7 13.7999996,14.3 14.1999996,14.7 C14.3999996,14.9 14.6999996,15 14.8999996,15 C15.1999996,15 15.3999996,14.9 15.5999996,14.7 C17.0999996,13.2 17.9999996,11.2 17.9999996,9 C17.9999996,6.8 17.0999996,4.8 15.5999996,3.3 L15.5999996,3.3 Z"></path>
|
||||
<path d="M11.2819745,5.28197449 C10.9060085,5.65794047 10.9060085,6.22188944 11.2819745,6.59785542 C12.0171538,7.33303477 12.2772954,8.05605449 12.2772954,9.00000021 C12.2772954,9.93588462 11.851678,10.9172014 11.2819745,11.4869049 C10.9060085,11.8628709 10.9060085,12.4268199 11.2819745,12.8027859 C11.4271642,12.9479755 11.9176724,13.0649528 12.2998149,12.9592565 C12.4124479,12.9281035 12.5156669,12.8776063 12.5978555,12.8027859 C13.773371,11.732654 14.1311161,10.1597914 14.1312523,9.00000021 C14.1312723,8.8299555 14.1286311,8.66015647 14.119665,8.4897429 C14.0674781,7.49784946 13.8010171,6.48513613 12.5978554,5.28197449 C12.2218894,4.9060085 11.6579405,4.9060085 11.2819745,5.28197449 Z"></path>
|
||||
<path d="M3.78571429,6.00820648 L0.714285714,6.00820648 C0.285714286,6.00820648 0,6.30901277 0,6.76022222 L0,11.2723167 C0,11.7235261 0.285714286,12.0243324 0.714285714,12.0243324 L3.78571429,12.0243324 L7.85714286,15.8819922 C8.35714286,16.1827985 9,15.8819922 9,15.2803796 L9,2.75215925 C9,2.15054666 8.35714286,1.77453879 7.85714286,2.15054666 L3.78571429,6.00820648 Z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.6 KiB |