Comparar commits
173 Commits
v5.15.0
...
v6.0.0-RC.8
| Autor | SHA1 | Data | |
|---|---|---|---|
| c7fd68d4ae | |||
| e98c65d7a6 | |||
| 29d733d0d1 | |||
| 0908d914ac | |||
| 9274457e4c | |||
| 0d19a05972 | |||
| dc37cb24f2 | |||
| 6d876ee6ef | |||
| 46dd0aac11 | |||
| 16c1e0adc0 | |||
| 7d12c9ea85 | |||
| 312b10ca44 | |||
| 022305706c | |||
| f2b5a057fe | |||
| dbfba28a10 | |||
| 61e20786a9 | |||
| 0fd7aad850 | |||
| d79b8a7013 | |||
| 41be5dca4e | |||
| 424fa51c28 | |||
| 90030d5e99 | |||
| af1c6e374c | |||
| c829fcff5f | |||
| 23823556d4 | |||
| ddde644c78 | |||
| cb890a965c | |||
| 1770f00018 | |||
| 05e64948c5 | |||
| bf787bd5cc | |||
| 67634cf216 | |||
| 88ee6af431 | |||
| c611f9f358 | |||
| 2ee133f6f8 | |||
| 5ffe1cd49e | |||
| d9ec7bc1ba | |||
| e5af0a5e21 | |||
| db901c54d9 | |||
| 74eb5d4772 | |||
| f95815bcd0 | |||
| 61d427c7ee | |||
| a9f8fcb2a2 | |||
| 5265624410 | |||
| fb88ae2bfc | |||
| a2b1a33606 | |||
| 9ef2d07b41 | |||
| 139567738f | |||
| d5a619d10e | |||
| dc9ed1cf5f | |||
| 326398d312 | |||
| 0f57341e38 | |||
| dacf0ca133 | |||
| 3bbf0199e6 | |||
| 6874fa2824 | |||
| 715f5847da | |||
| 96a387f723 | |||
| f558648f44 | |||
| 8c1302ee34 | |||
| 4388beae91 | |||
| 576ac19214 | |||
| db0112053f | |||
| 1f7a842348 | |||
| 0d0dea4da8 | |||
| 181a19f5fd | |||
| 1cb0a97b0b | |||
| 127cd7827e | |||
| 405b29b8f1 | |||
| e1b48042dd | |||
| a04f387a72 | |||
| 0da93249d3 | |||
| caff93fbf0 | |||
| 65dc81a4e1 | |||
| 55408683fb | |||
| dc4c1eb88b | |||
| 3c1108c5dc | |||
| c3b1d689f6 | |||
| 17143fd9fe | |||
| 7ab52d1a59 | |||
| 2433915c64 | |||
| 0ac126935a | |||
| eddc1d71a3 | |||
| 1b1ba04271 | |||
| 6208e4bc72 | |||
| 3aa79ae9b1 | |||
| 6541467ad8 | |||
| 0fc2c1c7a4 | |||
| be27f2aa6e | |||
| 5748c360af | |||
| e176b56843 | |||
| 70d2eb10cf | |||
| 516c9f9362 | |||
| 4f79e1e299 | |||
| 60bcc99302 | |||
| da2a1e07f4 | |||
| 04f23c1a62 | |||
| 29c6141de9 | |||
| a8f2e43274 | |||
| fcb5aa8383 | |||
| 2da4e76ef0 | |||
| 6ad1e5c97b | |||
| 49496195ed | |||
| 24d2e7ba33 | |||
| 5bde16a1bc | |||
| cb42fcfb02 | |||
| 5b8b41e546 | |||
| f87ada1ee0 | |||
| 19b429bddc | |||
| c8f5e3a3fc | |||
| 22aade1b61 | |||
| e34335b012 | |||
| 866a3f37d8 | |||
| 37a6811fb6 | |||
| 03529163b6 | |||
| 2e2ac6f870 | |||
| 4979ea78d5 | |||
| 3585af0fe2 | |||
| d7d7cfeb9f | |||
| 4c3b60c3b6 | |||
| c2545ddb6d | |||
| 05b39fe281 | |||
| 7b0d738e8f | |||
| 8e7d8cc959 | |||
| 8888e2b5b5 | |||
| 1463e50f7b | |||
| a0ba8e2360 | |||
| 924fb279ee | |||
| e642295468 | |||
| de3945db15 | |||
| 58f2349302 | |||
| e3424f6a4d | |||
| 34aab3f357 | |||
| b387437aed | |||
| 524f868e31 | |||
| 1ba1f5aabd | |||
| 49bed07039 | |||
| 7bafcc2a55 | |||
| 2037e18235 | |||
| 8d1653aebc | |||
| 8f07f5d57c | |||
| 0bba3196d8 | |||
| 091bdf9261 | |||
| 29ffbfbc87 | |||
| 57af15ce8b | |||
| ce6acc832a | |||
| 8622b2648e | |||
| 73b6316f3b | |||
| 844e4f0107 | |||
| b07143d276 | |||
| e12bedbb45 | |||
| c340dbcccd | |||
| 20141202c1 | |||
| 5377ffc176 | |||
| f8aed4dc32 | |||
| 6578ed98ac | |||
| bb9b710d95 | |||
| 305e5ea192 | |||
| 94fd5c12c8 | |||
| 2ceed0a4eb | |||
| bbe82530aa | |||
| d290db1765 | |||
| f35de1c98d | |||
| 02da69741a | |||
| 0ce7cd4fe3 | |||
| d120ea29b7 | |||
| c239bd5c5a | |||
| b5472145bf | |||
| 9ec55879a1 | |||
| 83d453b498 | |||
| e5a240a631 | |||
| 9c74116578 | |||
| b914c76247 | |||
| 26d4e7b0bf | |||
| ac0b03f2f7 | |||
| b7c384eb5b |
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"presets": [ ["es2015", {"loose": true}] ],
|
||||
"plugins": ["transform-es3-property-literals", "transform-es3-member-expression-literals", "inline-json"]
|
||||
"presets": [ "es3", ["es2015", {"loose": true}] ],
|
||||
"plugins": ["inline-json"]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,38 @@
|
||||
{
|
||||
"source": {
|
||||
"include": [ "src/js/" ],
|
||||
"includePattern": ".js$"
|
||||
},
|
||||
"opts": {
|
||||
"destination": "docs/api",
|
||||
"readme": "docs/index.md",
|
||||
"template": "node_modules/tui-jsdoc-template",
|
||||
"package": "package.json",
|
||||
"recurse": true,
|
||||
"tutorials": "docs/guides",
|
||||
"encoding": "utf8"
|
||||
},
|
||||
"templates": {
|
||||
"default": {
|
||||
"staticFiles": {
|
||||
"include": ["build/docs/"]
|
||||
}
|
||||
},
|
||||
"logo": {
|
||||
"url": "http://videojs.com/img/logo.png",
|
||||
"height": "30px",
|
||||
"width": "214px"
|
||||
},
|
||||
"name": "Video.js Documentation",
|
||||
"tabNames": {
|
||||
"tutorials": "Guides"
|
||||
},
|
||||
"footerText": "<span class='copyright'><a href='http://videojs.com'>Video.js</a> is a free and open source HTML5 video player. © <a href='https://brightcove.com' target='_blank'>Brightcove, Inc</a>. <a href='https://github.com/videojs/video.js/blob/master/LICENSE' class='button blue' target='_blank'>View license</a></span> <ul class='other-links'><li><a href='http://videojs.com' class='button white'><i class='fa fa-external-link'></i> Video.js</a></li> <li><a href='https://twitter.com/videojs' class='button white' target='_blank'><i class='fa fa-twitter'></i> @videojs</a></li> <li><a href='http://github.com/videojs/video.js' class='button white' target='_blank'><i class='fa fa-github-alt'></i> Source</a></li> </ul>",
|
||||
"css": [
|
||||
"styles/videojs.css",
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"
|
||||
]
|
||||
},
|
||||
"plugins": ["plugins/markdown"],
|
||||
"markdown": {
|
||||
"tags": ["example"]
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
!dist/**
|
||||
!es5/**
|
||||
!src/css/**
|
||||
!docs/api/**
|
||||
|
||||
@@ -1,3 +1,44 @@
|
||||
<a name="5.16.0"></a>
|
||||
# [5.16.0](https://github.com/videojs/video.js/compare/v5.15.1...v5.16.0) (2017-01-12)
|
||||
|
||||
### Features
|
||||
|
||||
* Show big play button on pause if specified ([#3892](https://github.com/videojs/video.js/issues/3892)) ([b547214](https://github.com/videojs/video.js/commit/b547214))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* give techs a name ([#3934](https://github.com/videojs/video.js/issues/3934)) ([94fd5c1](https://github.com/videojs/video.js/commit/94fd5c1)), closes [#1786](https://github.com/videojs/video.js/issues/1786)
|
||||
* Pause player before seeking in seek bar mousedown ([#3921](https://github.com/videojs/video.js/issues/3921)) ([2ceed0a](https://github.com/videojs/video.js/commit/2ceed0a)), closes [#3839](https://github.com/videojs/video.js/issues/3839) [#3886](https://github.com/videojs/video.js/issues/3886)
|
||||
* player el ingest when parent doesn't have `hasAttribute` method ([#3929](https://github.com/videojs/video.js/issues/3929)) ([bbe8253](https://github.com/videojs/video.js/commit/bbe8253))
|
||||
* showing custom poster with controls disabled ([#3933](https://github.com/videojs/video.js/issues/3933)) ([305e5ea](https://github.com/videojs/video.js/commit/305e5ea)), closes [#1625](https://github.com/videojs/video.js/issues/1625)
|
||||
|
||||
### Chores
|
||||
|
||||
* better dev experience ([#3896](https://github.com/videojs/video.js/issues/3896)) ([9ec5587](https://github.com/videojs/video.js/commit/9ec5587))
|
||||
* don't run tests on travis if only docs were changed ([#3908](https://github.com/videojs/video.js/issues/3908)) ([c239bd5](https://github.com/videojs/video.js/commit/c239bd5))
|
||||
* **development:** fix `npm start` file watching ([#3922](https://github.com/videojs/video.js/issues/3922)) ([02da697](https://github.com/videojs/video.js/commit/02da697))
|
||||
* **release:** add es5 folder to the tagged commit ([#3913](https://github.com/videojs/video.js/issues/3913)) ([d120ea2](https://github.com/videojs/video.js/commit/d120ea2))
|
||||
* **sass:** upgrade to latest version of grunt-sass ([#3897](https://github.com/videojs/video.js/issues/3897)) ([83d453b](https://github.com/videojs/video.js/commit/83d453b)), closes [#3692](https://github.com/videojs/video.js/issues/3692)
|
||||
* fix typo in collaborator guide ([#3931](https://github.com/videojs/video.js/issues/3931)) ([f35de1c](https://github.com/videojs/video.js/commit/f35de1c))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* require `videojs-vtt.js` via require rather than concat ([#3919](https://github.com/videojs/video.js/issues/3919)) ([d290db1](https://github.com/videojs/video.js/commit/d290db1))
|
||||
|
||||
### Documentation
|
||||
|
||||
* **faq:** add a question about autoplay ([#3898](https://github.com/videojs/video.js/issues/3898)) ([e5a240a](https://github.com/videojs/video.js/commit/e5a240a))
|
||||
* **faq:** add FAQ question about RTMP url ([#3899](https://github.com/videojs/video.js/issues/3899)) ([9c74116](https://github.com/videojs/video.js/commit/9c74116))
|
||||
* **troubleshooting:** updates to troubleshooting doc ([#3912](https://github.com/videojs/video.js/issues/3912)) ([0ce7cd4](https://github.com/videojs/video.js/commit/0ce7cd4))
|
||||
|
||||
<a name="5.15.1"></a>
|
||||
## [5.15.1](https://github.com/videojs/video.js/compare/v5.15.0...v5.15.1) (2016-12-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* extra warn logs on already initialized player references ([#3888](https://github.com/videojs/video.js/issues/3888)) ([b7c384e](https://github.com/videojs/video.js/commit/b7c384e))
|
||||
* Support require()-ing video.js ([#3889](https://github.com/videojs/video.js/issues/3889)) ([ac0b03f](https://github.com/videojs/video.js/commit/ac0b03f)), closes [#3869](https://github.com/videojs/video.js/issues/3869)
|
||||
|
||||
<a name="5.15.0"></a>
|
||||
# [5.15.0](https://github.com/videojs/video.js/compare/v5.14.1...v5.15.0) (2016-12-22)
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Our Pledge](#our-pledge)
|
||||
* [Our Standards](#our-standards)
|
||||
* [Our Responsibilities](#our-responsibilities)
|
||||
* [Scope](#scope)
|
||||
* [Other Community Standards](#other-community-standards)
|
||||
* [Enforcement](#enforcement)
|
||||
* [Further Enforcement](#further-enforcement)
|
||||
* [Who Watches the Watchers?](#who-watches-the-watchers)
|
||||
* [Attribution](#attribution)
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Other Community Standards
|
||||
|
||||
As a project on GitHub, this project is additionally covered by the [GitHub Community Guidelines](https://help.github.com/articles/github-community-guidelines/).
|
||||
|
||||
Additionally, as a project hosted on npm, is is covered by [npm, Inc's Code of Conduct](https://www.npmjs.com/policies/conduct).
|
||||
|
||||
Enforcement of those guidelines after violations overlapping with the above are the responsibility of the entities, and enforcement may happen in any or all of the services/communities.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at gary@videojs.com or @gkatsev on [slack][]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Further Enforcement
|
||||
|
||||
If you've already followed the [initial enforcement steps](#enforcement), these are the steps maintainers will take for further enforcement, as needed:
|
||||
|
||||
1. Repeat the request to stop.
|
||||
1. If the person doubles down, they will have offending messages removed or edited by a maintainers given an official warning. The PR or Issue may be locked.
|
||||
1. If the behavior continues or is repeated later, the person will be blocked from participating for 24 hours.
|
||||
1. If the behavior continues or is repeated after the temporary block, a long-term (6-12mo) ban will be used.
|
||||
|
||||
On top of this, maintainers may remove any offending messages, images, contributions, etc, as they deem necessary.
|
||||
|
||||
Maintainers reserve full rights to skip any of these steps, at their discretion, if the violation is considered to be a serious and/or immediate threat to the health and well-being of members of the community. These include any threats, serious physical or verbal attacks, and other such behavior that would be completely unacceptable in any social setting that puts our members at risk.
|
||||
|
||||
Members expelled from events or venues with any sort of paid attendance will not be refunded.
|
||||
|
||||
### Who Watches the Watchers?
|
||||
|
||||
Maintainers and other leaders who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. These may include anything from removal from the maintainer team to a permanent ban from the community.
|
||||
|
||||
Additionally, as a project hosted on both GitHub and npm, [their own Codes of Conducts may be applied against maintainers of this project](#other-community-standards), externally of this project's procedures.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
The [Other Community Standards](#other-community-standards), [Further Enforcement](#further-enforcement),
|
||||
and [Who Watches the Watchers?](#who-watches-the-watchers) sections are based on [weallbehave][weallbehave],
|
||||
which is based on the [WeAllJS Code of Conduct][wealljs].
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
|
||||
[wealljs]: https://wealljs.org/code-of-conduct
|
||||
|
||||
[weallbehave]: https://npm.im/weallbehave
|
||||
|
||||
[slack]: http://slack.videojs.com
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
## Issues and Pull Requests
|
||||
|
||||
Full courtesey should always be shown in video.js projects.
|
||||
Full courtesy should always be shown in video.js projects.
|
||||
|
||||
Collaborators may manage issues they feel qualified to handle, being mindful of our guidelines.
|
||||
|
||||
|
||||
+8
-2
@@ -140,13 +140,17 @@ npm install
|
||||
|
||||
#### Running tests
|
||||
|
||||
To run the tests all you need to do is run
|
||||
Tests can be run either from the shell or from the browser.
|
||||
|
||||
To run the tests from the shell, just run
|
||||
|
||||
```sh
|
||||
npm test
|
||||
```
|
||||
|
||||
This will build video.js locally and run the tests using [Karma](https://karma-runner.github.io/1.0/index.html) which runs our tests in actual browsers.
|
||||
This will build video.js locally and run the test suite using [Karma](https://karma-runner.github.io/1.0/index.html), which runs our tests in actual browsers.
|
||||
|
||||
To run tests from the browser, first start a local server with `npm start` (this also watches for changes and rebuilds video.js and the test files as necessary). Then navigate to `http://localhost:9999/test`, and you'll see a page that displays the results of all the tests. To rerun the tests after making changes, just refresh the page. To run an individual test, click the "Rerun" link next to the test's title.
|
||||
|
||||
#### Building videojs
|
||||
|
||||
@@ -275,6 +279,8 @@ Tests attached to bug fixes should fail before the change and succeed with it.
|
||||
npm test
|
||||
```
|
||||
|
||||
See [Running tests](#running-tests) for more information.
|
||||
|
||||
#### Step 6: Push
|
||||
|
||||
```sh
|
||||
|
||||
+13
@@ -10,6 +10,13 @@
|
||||
|
||||
> Video.js is a web video player built from the ground up for an HTML5 world. It supports HTML5 and Flash video, as well as YouTube and Vimeo (through [plugins][plugins]). It supports video playback on desktops and mobile devices. This project was started mid 2010, and the player is now used on over ~~50,000~~ ~~100,000~~ ~~200,000~~ [400,000 websites][builtwith].
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Quick Start](#quick-start)
|
||||
* [Contributing](#contributing)
|
||||
* [Code of Conduct](#code-of-conduct)
|
||||
* [License](#license)
|
||||
|
||||
## Quick Start
|
||||
|
||||
Thanks to the awesome folks over at [Fastly][fastly], there's a free, CDN hosted version of Video.js that anyone can use. Add these tags to your document's `<head>`:
|
||||
@@ -79,6 +86,10 @@ Video.js is a free and open source library, and we appreciate any help you're wi
|
||||
|
||||
_Video.js uses [BrowserStack][browserstack] for compatibility testing._
|
||||
|
||||
## [Code of Conduct][coc]
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct][coc]. By participating in this project you agree to abide by its terms.
|
||||
|
||||
## [License][license]
|
||||
|
||||
Video.js is [licensed][license] under the Apache License, Version 2.0.
|
||||
@@ -120,3 +131,5 @@ Video.js is [licensed][license] under the Apache License, Version 2.0.
|
||||
[travis-link]: https://travis-ci.org/videojs/video.js
|
||||
|
||||
[vjs]: http://videojs.com
|
||||
|
||||
[coc]: CODE_OF_CONDUCT.md
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"main": [
|
||||
"dist/video.js",
|
||||
"dist/video-js.css"
|
||||
],
|
||||
"moduleType": "es6",
|
||||
"keywords": [
|
||||
"videojs",
|
||||
"html5",
|
||||
"flash",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"build"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import sh from 'shelljs';
|
||||
import path from 'path';
|
||||
|
||||
|
||||
export default function(commit, commitRange) {
|
||||
const SINGLE_COMMIT = `git diff-tree --no-commit-id --name-only -r ${commit}`;
|
||||
const COMMIT_RANGE = `git diff --name-only ${commitRange}`;
|
||||
|
||||
let command = SINGLE_COMMIT;
|
||||
|
||||
if (commitRange) {
|
||||
command = COMMIT_RANGE
|
||||
}
|
||||
|
||||
const output = sh.exec(command, {async: false, silent: true}).stdout;
|
||||
|
||||
const files = output.split('\n').filter(Boolean);
|
||||
return files.every((file) => file.startsWith('docs') || path.extname(file) === '.md');
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
#resizer,
|
||||
footer {
|
||||
background-color: #ECEEF1;
|
||||
color: #868688;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
padding: 3px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
footer .copyright {
|
||||
float: left;
|
||||
}
|
||||
|
||||
footer .other-links {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
float: right;
|
||||
}
|
||||
|
||||
footer .other-links li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
footer .logo {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
var replace = require("replace");
|
||||
var path = require('path')
|
||||
var apiPath = path.join(__dirname, '..', 'docs', 'api');
|
||||
|
||||
var replacements = [
|
||||
{find: /\/docs\/guides\/(.+)\.md/g, replace: 'tutorial-$1.html'},
|
||||
{find: /tutorial-tech.html/g, replace: 'tutorial-tech_.html'},
|
||||
{find: /\/docs\/guides\//g, replace: '#'}
|
||||
];
|
||||
|
||||
|
||||
replacements.forEach(function(obj) {
|
||||
replace({
|
||||
regex: obj.find,
|
||||
replacement: obj.replace,
|
||||
paths: [apiPath],
|
||||
recursive: true,
|
||||
silent: true
|
||||
});
|
||||
});
|
||||
+83
-63
@@ -1,6 +1,7 @@
|
||||
import {gruntCustomizer, gruntOptionsMaker} from './options-customizer.js';
|
||||
import chg from 'chg';
|
||||
import npmRun from 'npm-run';
|
||||
import isDocsOnly from './docs-only.js';
|
||||
|
||||
module.exports = function(grunt) {
|
||||
require('time-grunt')(grunt);
|
||||
@@ -127,9 +128,14 @@ module.exports = function(grunt) {
|
||||
},
|
||||
dist: {},
|
||||
watch: {
|
||||
novtt: {
|
||||
files: ['build/temp/video.js'],
|
||||
tasks: ['concat:novtt']
|
||||
dist: {
|
||||
files: [
|
||||
'build/temp/video.js',
|
||||
'build/temp/alt/video.novtt.js',
|
||||
'build/temp/video-js.css',
|
||||
'build/temp/alt/video-js-cdn.css'
|
||||
],
|
||||
tasks: ['copy:dist']
|
||||
},
|
||||
minify: {
|
||||
files: ['build/temp/video.js'],
|
||||
@@ -139,13 +145,9 @@ module.exports = function(grunt) {
|
||||
files: ['src/css/**/*'],
|
||||
tasks: ['skin']
|
||||
},
|
||||
babel: {
|
||||
files: ['src/js/**/*.js'],
|
||||
tasks: ['babel:es5']
|
||||
},
|
||||
jshint: {
|
||||
files: ['src/**/*', 'test/unit/**/*.js', 'Gruntfile.js'],
|
||||
tasks: 'jshint'
|
||||
lang: {
|
||||
files: ['lang/**/*.json'],
|
||||
tasks: ['vjslanguages']
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
@@ -265,25 +267,25 @@ module.exports = function(grunt) {
|
||||
options: {
|
||||
release: 'major'
|
||||
},
|
||||
src: ['package.json', 'component.json']
|
||||
src: ['package.json']
|
||||
},
|
||||
minor: {
|
||||
options: {
|
||||
release: 'minor'
|
||||
},
|
||||
src: ['package.json', 'component.json']
|
||||
src: ['package.json']
|
||||
},
|
||||
patch: {
|
||||
options: {
|
||||
release: 'patch'
|
||||
},
|
||||
src: ['package.json', 'component.json']
|
||||
src: ['package.json']
|
||||
},
|
||||
prerelease: {
|
||||
options: {
|
||||
release: 'prerelease'
|
||||
},
|
||||
src: ['package.json', 'component.json']
|
||||
src: ['package.json']
|
||||
},
|
||||
css: {
|
||||
options: {
|
||||
@@ -320,32 +322,39 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
browserify: {
|
||||
options: browserifyGruntOptions(),
|
||||
build: {
|
||||
options: browserifyGruntOptions(),
|
||||
files: {
|
||||
'build/temp/video.js': ['es5/video.js']
|
||||
}
|
||||
},
|
||||
dist: {
|
||||
buildnovtt: {
|
||||
options: browserifyGruntOptions({transform: [
|
||||
['aliasify', {aliases: {'videojs-vtt.js': false}}]
|
||||
]}),
|
||||
files: {
|
||||
'build/temp/alt/video.novtt.js': ['es5/video.js']
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options: browserifyGruntOptions({
|
||||
transform: [
|
||||
['browserify-versionify', {
|
||||
placeholder: '../node_modules/videojs-vtt.js/dist/vtt.js',
|
||||
version: 'https://cdn.rawgit.com/gkatsev/vtt.js/vjs-v0.12.1/dist/vtt.min.js'
|
||||
}],
|
||||
]
|
||||
watch: true,
|
||||
keepAlive: true,
|
||||
}),
|
||||
files: {
|
||||
'build/temp/video.js': ['es5/video.js']
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options: {
|
||||
watchnovtt: {
|
||||
options: browserifyGruntOptions({
|
||||
transform: [
|
||||
['aliasify', {aliases: {'videojs-vtt.js': false}}]
|
||||
],
|
||||
watch: true,
|
||||
keepAlive: true
|
||||
},
|
||||
keepAlive: true,
|
||||
}),
|
||||
files: {
|
||||
'build/temp/video.js': ['es5/video.js']
|
||||
'build/temp/alt/video.novtt.js': ['es5/video.js']
|
||||
}
|
||||
},
|
||||
tests: {
|
||||
@@ -384,14 +393,6 @@ module.exports = function(grunt) {
|
||||
options: {
|
||||
separator: '\n'
|
||||
},
|
||||
novtt: {
|
||||
src: ['build/temp/video.js'],
|
||||
dest: 'build/temp/alt/video.novtt.js'
|
||||
},
|
||||
vtt: {
|
||||
src: ['build/temp/video.js', 'node_modules/videojs-vtt.js/dist/vtt.js'],
|
||||
dest: 'build/temp/video.js'
|
||||
},
|
||||
ie8_addition: {
|
||||
src: ['build/temp/video-js.css', 'src/css/ie8.css'],
|
||||
dest: 'build/temp/video-js.css'
|
||||
@@ -402,14 +403,24 @@ module.exports = function(grunt) {
|
||||
logConcurrentOutput: true
|
||||
},
|
||||
tests: [
|
||||
'watch:babel',
|
||||
'shell:babel',
|
||||
'browserify:tests'
|
||||
],
|
||||
dev: [
|
||||
'shell:babel',
|
||||
'browserify:watch',
|
||||
'browserify:watchnovtt',
|
||||
'browserify:tests',
|
||||
'watch:skin',
|
||||
'watch:lang',
|
||||
'watch:dist'
|
||||
],
|
||||
// Run multiple watch tasks in parallel
|
||||
// Needed so watchify can cache intelligently
|
||||
watchAll: [
|
||||
'watch',
|
||||
'browserify:watch',
|
||||
'browserify:watchnovtt',
|
||||
'browserify:tests',
|
||||
'karma:watch'
|
||||
],
|
||||
@@ -437,8 +448,14 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
shell: {
|
||||
babel: {
|
||||
command: 'npm run babel -- --watch',
|
||||
options: {
|
||||
preferLocal: true
|
||||
}
|
||||
},
|
||||
lint: {
|
||||
command: 'npm run lint',
|
||||
command: 'npm run lint -- --errors',
|
||||
options: {
|
||||
preferLocal: true
|
||||
}
|
||||
@@ -456,7 +473,7 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
webpack: {
|
||||
command: 'webpack test/require/webpack.js build/temp/webpack.js',
|
||||
command: 'webpack --hide-modules test/require/webpack.js build/temp/webpack.js',
|
||||
options: {
|
||||
preferLocal: true
|
||||
}
|
||||
@@ -471,8 +488,8 @@ module.exports = function(grunt) {
|
||||
error: true
|
||||
},
|
||||
ignore: [
|
||||
// Ignore the warning about needing <optgroup> elements
|
||||
'WCAG2AA.Principle1.Guideline1_3.1_3_1.H85.2'
|
||||
// Ignore warning about contrast of the "vjs-no-js" fallback link
|
||||
'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.BgImage'
|
||||
]
|
||||
|
||||
},
|
||||
@@ -486,17 +503,15 @@ module.exports = function(grunt) {
|
||||
require('load-grunt-tasks')(grunt);
|
||||
grunt.loadNpmTasks('videojs-doc-generator');
|
||||
grunt.loadNpmTasks('chg');
|
||||
grunt.loadNpmTasks('gkatsev-grunt-sass');
|
||||
grunt.loadNpmTasks('grunt-accessibility');
|
||||
|
||||
const buildDependents = [
|
||||
grunt.registerTask('build', [
|
||||
'shell:lint',
|
||||
'clean:build',
|
||||
|
||||
'babel:es5',
|
||||
'browserify:build',
|
||||
'concat:novtt',
|
||||
'concat:vtt',
|
||||
'browserify:buildnovtt',
|
||||
'usebanner:novtt',
|
||||
'usebanner:vtt',
|
||||
'uglify',
|
||||
@@ -509,18 +524,11 @@ module.exports = function(grunt) {
|
||||
'copy:swf',
|
||||
'copy:ie8',
|
||||
'vjslanguages'
|
||||
];
|
||||
|
||||
grunt.registerTask('build', buildDependents);
|
||||
|
||||
grunt.registerTask(
|
||||
'build:dist',
|
||||
buildDependents.map(task => task === 'browserify:build' ? 'browserify:dist' : task)
|
||||
);
|
||||
]);
|
||||
|
||||
grunt.registerTask('dist', [
|
||||
'clean:dist',
|
||||
'build:dist',
|
||||
'build',
|
||||
'copy:dist',
|
||||
'copy:examples',
|
||||
'zip:dist'
|
||||
@@ -532,19 +540,31 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('default', ['test']);
|
||||
|
||||
// The test script includes coveralls only when the TRAVIS env var is set.
|
||||
grunt.registerTask('test', [
|
||||
'build',
|
||||
'shell:noderequire',
|
||||
'shell:browserify',
|
||||
'shell:webpack',
|
||||
'karma:defaults',
|
||||
'test-a11y'].concat(process.env.TRAVIS && 'coveralls').filter(Boolean));
|
||||
grunt.registerTask('test', function() {
|
||||
const tasks = [
|
||||
'build',
|
||||
'shell:noderequire',
|
||||
'shell:browserify',
|
||||
'shell:webpack',
|
||||
'karma:defaults',
|
||||
'test-a11y'
|
||||
];
|
||||
|
||||
if (process.env.TRAVIS) {
|
||||
if (isDocsOnly(process.env.TRAVIS_COMMIT, process.env.TRAVIS_COMMIT_RANGE)) {
|
||||
grunt.log.write('Not running any tests because only docs were changed');
|
||||
return;
|
||||
}
|
||||
|
||||
tasks.concat(process.env.TRAVIS && 'coveralls').filter(Boolean);
|
||||
}
|
||||
|
||||
grunt.task.run(tasks);
|
||||
});
|
||||
|
||||
// Run while developing
|
||||
grunt.registerTask('dev', ['build', 'connect:dev', 'concurrent:watchSandbox']);
|
||||
|
||||
grunt.registerTask('dev', ['connect:dev', 'concurrent:dev']);
|
||||
grunt.registerTask('watchAll', ['build', 'connect:dev', 'concurrent:watchAll']);
|
||||
|
||||
grunt.registerTask('test-a11y', ['copy:a11y', 'accessibility']);
|
||||
|
||||
// Pick your testing, or run both in different terminals
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
var safeParse = require("safe-json-parse/tuple");
|
||||
var tuple = safeParse(process.env.npm_config_argv);
|
||||
var npm_config_argv = tuple[1]
|
||||
|
||||
if (tuple[0]) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var sh = require('shelljs');
|
||||
var version = process.env.npm_package_version;
|
||||
var prereleaseType = npm_config_argv['remain'][0];
|
||||
var approvedTypes = {
|
||||
'major': 1,
|
||||
'minor': 1,
|
||||
'patch': 1
|
||||
}
|
||||
|
||||
if (prereleaseType in approvedTypes) {
|
||||
sh.exec('npm run changelog');
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"version": "5.15.0",
|
||||
"keywords": [
|
||||
"videojs",
|
||||
"html5",
|
||||
"flash",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"scripts": ["dist/video-js/video.dev.js"],
|
||||
"styles": ["dist/video-js/video-js.css"],
|
||||
"files": ["dist/video-js/video-js.swf"],
|
||||
"fonts": [
|
||||
"dist/video-js/font/vjs.eot",
|
||||
"dist/video-js/font/vjs.svg",
|
||||
"dist/video-js/font/vjs.ttf",
|
||||
"dist/video-js/font/vjs.woff"
|
||||
],
|
||||
"main": "dist/video-js/video.dev.js"
|
||||
}
|
||||
+2
-2
@@ -287,7 +287,7 @@
|
||||
[ "git push upstream master", "Push the dev branch changes to the repo" ],
|
||||
[ "git checkout temp-release-branch", "Checkout the temp branch again" ],
|
||||
[ "grunt dist", "Build the dist" ],
|
||||
[ "git add dist --force", "Add the (otherwise ignored) release files" ],
|
||||
[ "git add es5 dist --force", "Add the (otherwise ignored) release files" ],
|
||||
[ "git commit -m 'v{{version}} dist'", "Commit the dist changes" ],
|
||||
[ "git tag -a v{{version}} -m 'v{{version}}'", "Tag the release" ],
|
||||
[ "git push upstream --tags", "Push the new tag to the repo" ],
|
||||
@@ -311,7 +311,7 @@
|
||||
[ "git push upstream stable", "Push the release branch changes to the repo" ],
|
||||
[ "git checkout -b temp-release-branch stable","Create a temporary branch for the dist" ],
|
||||
[ "grunt dist", "Build the dist" ],
|
||||
[ "git add dist --force", "Add the (otherwise ignored) release files" ],
|
||||
[ "git add es5 dist --force", "Add the (otherwise ignored) release files" ],
|
||||
[ "git commit -m 'v{{version}} dist'", "Commit the dist changes" ],
|
||||
[ "git tag -a v{{version}} -m 'v{{version}}'", "Tag the release" ],
|
||||
[ "git push upstream --tags", "Push the new tag to the repo" ],
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
useful way! -->
|
||||
<video id="example_video_1" class="video-js vjs-default-skin" controls preload="none" width="640" height="360"
|
||||
data-setup='{ "html5" : { "nativeTextTracks" : false } }'>
|
||||
<source src="https://archive.org/download/ElephantsDream/ed_hd.mp4" type="video/mp4">
|
||||
<source src="https://archive.org/download/ElephantsDream/ed_hd.ogv" type="video/ogv">
|
||||
<source src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/ed_hd.mp4" type="video/mp4">
|
||||
<source src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/ed_hd.ogg" type="video/ogg">
|
||||
|
||||
<track kind="captions" src="captions.en.vtt" srclang="en" label="English" default></track><!-- Tracks need an ending tag thanks to IE9 -->
|
||||
<track kind="captions" src="captions.sv.vtt" srclang="sv" label="Swedish"></track>
|
||||
|
||||
@@ -25,7 +25,7 @@ cross-browser implementation of audio tracks.
|
||||
They must be added programmatically.
|
||||
* Video.js only stores track representations. Switching audio tracks for playback is
|
||||
_not handled by Video.js_ and must be handled elsewhere - for example,
|
||||
[videojs-contrib-hls](http://github.com/videojs/videojs-contrib-hls) handles switching
|
||||
[videojs-contrib-hls][hls] handles switching
|
||||
audio tracks to support track selection through the UI.
|
||||
|
||||
## Working with Audio Tracks
|
||||
@@ -91,7 +91,7 @@ player.audioTracks().removeTrack(track);
|
||||
## API
|
||||
|
||||
For more complete information, refer to the
|
||||
[Video.js API docs](http://docs.videojs.com/docs/api/index.html), specifically:
|
||||
[Video.js API docs](http://docs.videojs.com/), specifically:
|
||||
|
||||
* `Player#audioTracks`
|
||||
* `AudioTrackList`
|
||||
@@ -140,7 +140,7 @@ The valid [BCP 47](https://tools.ietf.org/html/bcp47) code for the language of t
|
||||
track, e.g. `"en"` for English or `"es"` for Spanish.
|
||||
|
||||
For supported language translations, please see the [languages folder (/lang)](https://github.com/videojs/video.js/tree/master/lang)
|
||||
located in the Video.js root and refer to the [languages guide](languages.md) for more
|
||||
located in the Video.js root and refer to the [languages guide][languages-guide] for more
|
||||
information on languages in Video.js.
|
||||
|
||||
#### `enabled`
|
||||
@@ -154,4 +154,8 @@ than one, the last one to be enabled will end up being the only one. While the s
|
||||
allows for more than one track to be enabled, Safari and most implementations only allow
|
||||
one audio track to be enabled at a time.
|
||||
|
||||
[languages-guide]: /docs/guides/languages.md
|
||||
|
||||
[spec-audiotrack]: https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack
|
||||
|
||||
[hls]: http://github.com/videojs/videojs-contrib-hls
|
||||
|
||||
+15
-14
@@ -10,26 +10,26 @@ The architecture of the Video.js player is centered around components. The `Play
|
||||
* [Basic Example](#basic-example)
|
||||
* [Using Options](#using-options)
|
||||
* [Event Listening](#event-listening)
|
||||
* [using on](#using-on)
|
||||
* [Using on](#using-on)
|
||||
* [Using off](#using-off)
|
||||
* [Using one](#using-one)
|
||||
* [Using trigger](#using-trigger)
|
||||
* [Default Component Tree](#default-component-tree)
|
||||
* [Specific Component Details](#specific-component-details)
|
||||
* [Progress Control](#progress-control)
|
||||
* [Volume Panel](#volume-panel)
|
||||
* [Text Track Settings](#text-track-settings)
|
||||
|
||||
## What is a Component?
|
||||
|
||||
A component is a JavaScript object that has the following features:
|
||||
|
||||
* An associated DOM element.
|
||||
* An associated DOM element, in almost all cases.
|
||||
* An association to a `Player` object.
|
||||
* The ability to manage any number of child components.
|
||||
* The ability to listen for and trigger events.
|
||||
* A lifecycle of initialization and disposal.
|
||||
|
||||
For more specifics on the programmatic interface of a component, see [the component API docs](http://docs.videojs.com/docs/api/component.html).
|
||||
For more specifics on the programmatic interface of a component, see [the component API docs][api].
|
||||
|
||||
## Creating a Component
|
||||
|
||||
@@ -75,7 +75,7 @@ console.log(button.el());
|
||||
|
||||
## Component Children
|
||||
|
||||
Again, refer to [the component API docs](http://docs.videojs.com/docs/api/component.html) for complete details on methods available for managing component structures.
|
||||
Again, refer to [the component API docs][api] for complete details on methods available for managing component structures.
|
||||
|
||||
### Basic Example
|
||||
|
||||
@@ -195,8 +195,8 @@ myComponent.trigger('eventType');
|
||||
// does nothing
|
||||
```
|
||||
|
||||
If myFunc gets excluded, *all* listeners for the event type will get removed. If
|
||||
eventType gets excluded, *all* listeners will get removed from the component.
|
||||
If myFunc gets excluded, _all_ listeners for the event type will get removed. If
|
||||
eventType gets excluded, _all_ listeners will get removed from the component.
|
||||
You can use `off` to remove listeners that get added to other elements or
|
||||
components using:
|
||||
|
||||
@@ -285,6 +285,7 @@ The default component structure of the Video.js player looks something like this
|
||||
|
||||
```tree
|
||||
Player
|
||||
├── MediaLoader (has no DOM element)
|
||||
├── PosterImage
|
||||
├── TextTrackDisplay
|
||||
├── LoadingSpinner
|
||||
@@ -316,24 +317,24 @@ Player
|
||||
|
||||
## Specific Component Details
|
||||
|
||||
### Progress Control
|
||||
### Volume Panel
|
||||
|
||||
The progress control has a grandchild component, the mouse time display, which shows a time tooltip that follows the mouse cursor.
|
||||
The `VolumePanel` includes the `MuteToggle` and the `VolumeControl` Components, which will be hidden if volume changes are not supported. There is one important option for the `VolumePanel` which can make your `VolumeControl` appear vertically over the `MuteToggle`. This can be set by passing `VolumePanel` `{inline: false}` as the default behavior is a horizontal `VolumeControl` with `{inline: true}`.
|
||||
|
||||
By default, the progress control is sandwiched inside the control bar between the volume menu button and the remaining time display. Some skins attempt to move the it above the control bar and have it span the full width of the player. In these cases, it is less than ideal to have the tooltips leave the bounds of the player. This can be prevented by setting the `keepTooltipsInside` option on the progress control.
|
||||
Example of a vertical `VolumeControl`
|
||||
|
||||
```js
|
||||
let player = videojs('myplayer', {
|
||||
controlBar: {
|
||||
progressControl: {
|
||||
keepTooltipsInside: true
|
||||
volumePanel: {
|
||||
inline: false
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
> **Note:** This makes the tooltips use a real element instead of pseudo-elements so targeting them with CSS is different.
|
||||
|
||||
### Text Track Settings
|
||||
|
||||
The text track settings component is only available when using emulated text tracks.
|
||||
|
||||
[api]: http://docs.videojs.com/Component.html
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# Debugging
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Logging](#logging)
|
||||
* [API Overview](#api-overview)
|
||||
* [Log Safely](#log-safely)
|
||||
* [Log Objects Usefully](#log-objects-usefully)
|
||||
* [Log Levels](#log-levels)
|
||||
* [Available Log Levels](#available-log-levels)
|
||||
* [History](#history)
|
||||
|
||||
## Logging
|
||||
|
||||
Video.js includes a lightweight wrapper - `videojs.log` - around a subset of [the `console` API][console]. The available methods are `videojs.log`, `videojs.log.warn`, and `videojs.log.error`.
|
||||
|
||||
### API Overview
|
||||
|
||||
Most of these methods should be fairly self-explanatory, but for complete details, see [the API docs][api].
|
||||
|
||||
| Method | Alias Of | Matching Level(s) |
|
||||
| ------------------------------- | --------------- | ----------------- |
|
||||
| `videojs.log()` | `console.log` | all |
|
||||
| `videojs.log.warn()` | `console.warn` | all, warn |
|
||||
| `videojs.log.error()` | `console.error` | all, warn, error |
|
||||
| `videojs.log.level()` | n/a | n/a |
|
||||
| `videojs.log.history()` | n/a | n/a |
|
||||
| `videojs.log.history.clear()` | n/a | n/a |
|
||||
| `videojs.log.history.disable()` | n/a | n/a |
|
||||
| `videojs.log.history.enable()` | n/a | n/a |
|
||||
|
||||
For descriptions of these features, please refer to the sections below.
|
||||
|
||||
### Log Safely
|
||||
|
||||
Unlike the `console`, it's safe to leave `videojs.log` calls in your code. They won't throw errors when the `console` doesn't exist.
|
||||
|
||||
### Log Objects Usefully
|
||||
|
||||
Similar to the `console`, any number of mixed-type values can be passed to `videojs.log` methods:
|
||||
|
||||
```js
|
||||
videojs.log('this is a string', {butThis: 'is an object'});
|
||||
```
|
||||
|
||||
However, certain browser consoles (namely, IE10 and lower) do not support non-string values. Video.js improves on this situation by passing objects through `JSON.stringify` before logging them in IE10 and below. In other words, instead of the above producing this:
|
||||
|
||||
```txt
|
||||
VIDEOJS: this is a string [object Object]
|
||||
```
|
||||
|
||||
it will produce this:
|
||||
|
||||
```txt
|
||||
VIDEOJS: this is a string {"butThis": "is an object"}
|
||||
```
|
||||
|
||||
### Log Levels
|
||||
|
||||
Unlike the `console`, `videojs.log` includes the concept of logging levels. These levels toggle logging methods on or off.
|
||||
|
||||
Levels are exposed through the `videojs.log.level` method. This method acts as both a getter and setter for the current logging level. With no arguments, it returns the current logging level:
|
||||
|
||||
```js
|
||||
videojs.log.level(); // "all"
|
||||
```
|
||||
|
||||
By passing a string, the logging level can be changed to one of the available logging levels:
|
||||
|
||||
```js
|
||||
videojs.log.level('error'); // show only error messages and suppress others
|
||||
videojs.log('foo'); // does nothing
|
||||
videojs.log.warn('foo'); // does nothing
|
||||
videojs.log.error('foo'); // logs "foo" as an error
|
||||
```
|
||||
|
||||
### Available Log Levels
|
||||
|
||||
* **all** (default): enables all logging methods
|
||||
* **error**: only show `log.error` messages
|
||||
* **off**: disable all logging methods
|
||||
* **warn**: only show `log.warn` _and_ `log.error` messages
|
||||
|
||||
### History
|
||||
|
||||
> **Note:** In Video.js 5, `videojs.log.history` was an array. As of Video.js 6, it is a function which returns an array. This change was made to provide a richer, safer logging history API.
|
||||
|
||||
By default, the `videojs.log` module tracks a history of _everything_ passed to it regardless of logging level:
|
||||
|
||||
```js
|
||||
videojs.log.history(); // an array of everything that's been logged up to now
|
||||
```
|
||||
|
||||
This will work even when logging is set to **off**.
|
||||
|
||||
This can be useful, but it can also be a source of memory leaks. For example, logged objects will be retained in history even if references are removed everywhere else!
|
||||
|
||||
To avoid this problem, history can be disabled or enabled via method calls (using the `disable` and `enable` methods respectively). Disabling history is as easy as:
|
||||
|
||||
```js
|
||||
videojs.log.history.disable();
|
||||
```
|
||||
|
||||
Finally, the history (if enabled) can be cleared at any time via:
|
||||
|
||||
```js
|
||||
videojs.log.history.clear();
|
||||
```
|
||||
|
||||
[api]: http://docs.videojs.com/
|
||||
|
||||
[console]: https://developer.mozilla.org/en-US/docs/Web/API/Console
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
* [Overview](#overview)
|
||||
* [on() and addEventListener()](#on-and-addeventlistener)
|
||||
* [off() and removeEventListener](#off-and-removeeventlistener)
|
||||
* [off() and removeEventListener()](#off-and-removeeventlistener)
|
||||
* [one()](#one)
|
||||
* [trigger() and dispatchEvent](#trigger-and-dispatchevent)
|
||||
* [trigger() and dispatchEvent()](#trigger-and-dispatchevent)
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
* [If you don't think you can fix the issue or add the feature](#if-you-dont-think-you-can-fix-the-issue-or-add-the-feature)
|
||||
* [Q: What is a reduced test case?](#q-what-is-a-reduced-test-case)
|
||||
* [Q: What media formats does video.js support?](#q-what-media-formats-does-videojs-support)
|
||||
* [Q: How to I autoplay the video?](#q-how-to-i-autoplay-the-video)
|
||||
* [Q: How can I autoplay a video on a mobile device?](#q-how-can-i-autoplay-a-video-on-a-mobile-device)
|
||||
* [Q: How can I play RTMP video in video.js?](#q-how-can-i-play-rtmp-video-in-videojs)
|
||||
* [Q: How can I hide the links to my video/subtitles/audio/tracks?](#q-how-can-i-hide-the-links-to-my-videosubtitlesaudiotracks)
|
||||
* [Q: Can I turn off video.js logging?](#q-can-i-turn-off-videojs-logging)
|
||||
* [Q: What is a plugin?](#q-what-is-a-plugin)
|
||||
* [Q: How do I make a plugin for video.js?](#q-how-do-i-make-a-plugin-for-videojs)
|
||||
* [Q: Where can I find a list of video.js plugins?](#q-where-can-i-find-a-list-of-videojs-plugins)
|
||||
@@ -34,13 +38,14 @@
|
||||
* [Q: Does video.js have any support for advertisement integrations?](#q-does-videojs-have-any-support-for-advertisement-integrations)
|
||||
* [Q: Can video.js be required in node.js?](#q-can-videojs-be-required-in-nodejs)
|
||||
* [Q: Does video.js work with webpack?](#q-does-videojs-work-with-webpack)
|
||||
* [Q: Does video.js work with react?](#q-does-videojs-work-with-react)
|
||||
|
||||
## Q: What is video.js?
|
||||
|
||||
video.js is an extendable framework/library around the native video element. It does the following:
|
||||
|
||||
* Offers a plugin API so that different types of video can be handed to the native
|
||||
video element (e.g. HLS, Flash, HTML5 video, etc).
|
||||
video element (e.g. [HLS][hls], [Flash][flash], HTML5 video, etc).
|
||||
* Unifies the native video api across browsers (polyfilling support for features
|
||||
if necessary)
|
||||
* Offers an extendable and themable UI
|
||||
@@ -107,14 +112,53 @@ with your issue.
|
||||
A reduced test case is an example of the problem that you are facing in isolation.
|
||||
Think of it as example page that reproduces the issue in the least amount of possible code.
|
||||
We have a [starter example][starter-example] for reduced test cases. To learn more
|
||||
about reduced test cases visit [css-tricks](https://css-tricks.com/reduced-test-cases/)
|
||||
about reduced test cases visit [css-tricks][reduced-test-case]
|
||||
|
||||
## Q: What media formats does video.js support?
|
||||
|
||||
This depends on the formats supported by the browser's HTML5 video element, and the playback
|
||||
techs made available to video.js. For example, video.js 5 includes the Flash tech by default which
|
||||
enables the playback of FLV video where the Flash plugin is available. For more information
|
||||
on media formats see the [troubleshooting guide][troubleshooting].
|
||||
techs/plugins made available to video.js. For more information on media formats see the [troubleshooting guide][troubleshooting].
|
||||
|
||||
## Q: How to I autoplay the video?
|
||||
|
||||
Video.js supports the standard html5 `autoplay` attribute on the video element.
|
||||
It also supports it as an option to video.js or as a method invocation on the player.
|
||||
|
||||
```html
|
||||
<video autoplay controls class="video-js">
|
||||
```
|
||||
|
||||
```js
|
||||
var player = videojs('my-video', {
|
||||
autoplay: true
|
||||
});
|
||||
|
||||
// or
|
||||
|
||||
player.autoplay(true);
|
||||
```
|
||||
|
||||
### Q: How can I autoplay a video on a mobile device?
|
||||
|
||||
Most mobile devices have blocked autoplaying videos until recently.
|
||||
For mobile devices that don't support autoplaying, autoplay isn't supported by video.js.
|
||||
For those devices that support autoplaying, like iOS10 and Chrome for Android 53+,
|
||||
you must mute the video or have a video without audio tracks to be able to play it.
|
||||
For example:
|
||||
|
||||
```html
|
||||
<video muted autoplay playsinline>
|
||||
```
|
||||
|
||||
Will make an inline, muted, autoplaying video on an iPhone with iOS10.
|
||||
|
||||
## Q: How can I play RTMP video in video.js?
|
||||
|
||||
Make sure that the Flash tech is available -- RTMP is not playable on browsers without Flash including mobile. Flash will only be available on video.js 6 with the [videojs-flash package][flash], in previous versions it was builtin to video.js. Then, just set the rtmp source with an appropriate type -- `rtmp/mp4` or `rtmp/flv`.
|
||||
The main thing to be aware of is that video.js splits the connection url and stream name with the `&` character.
|
||||
So, you'd want to update the url to follow that format. For example: `rtmp://example.com/live&foo` or `rtmp://example.com/fms&mp4:path/to/file.mp4`.
|
||||
|
||||
If the server requires query parameters for authentication, these should be added to the connection part url, for example `rtmp://example.com/live?token=1234&foo`.
|
||||
|
||||
## Q: How can I hide the links to my video/subtitles/audio/tracks?
|
||||
|
||||
@@ -124,6 +168,16 @@ help but are outside of the scope of video.js.
|
||||
|
||||
For content that must be highly secure [videojs-contrib-eme][eme] adds DRM support.
|
||||
|
||||
## Q: Can I turn off video.js logging?
|
||||
|
||||
Yes! This can be achieved by adding the following code _after_ including Video.js, but _before_ creating any player(s):
|
||||
|
||||
```js
|
||||
videojs.log.level('off');
|
||||
```
|
||||
|
||||
For more information, including which logging levels are available, check out the [debugging guide][debug-guide].
|
||||
|
||||
## Q: What is a plugin?
|
||||
|
||||
A plugin is a group of reusable functionality that can be re-used by others. For instance a plugin could add
|
||||
@@ -157,7 +211,7 @@ See the [video.js github wiki][skins-list].
|
||||
|
||||
Yes! It can be used to play audio only files in a `<video>` or `<audio>` tag. The
|
||||
difference being that the `<audio>` tag will not have a blank display area and the `<video>`
|
||||
tag will. Note that audio only will not work with the Flash playback tech.
|
||||
tag will. Note that audio only will not work with the Flash playback tech. The Flash playback tech will only be included in versions of video.js before 6. In Video.js 6 you will need to use the [videojs-flash package][flash].
|
||||
|
||||
## Q: Does video.js support audio tracks?
|
||||
|
||||
@@ -180,13 +234,12 @@ project which adds support.
|
||||
|
||||
## Q: Does video.js support MPEG Dash video?
|
||||
|
||||
video.js itself does not support MPEG DASH, however an offical project called [videojs-contrib-dash][dash]
|
||||
video.js itself does not support MPEG DASH, however an official project called [videojs-contrib-dash][dash]
|
||||
adds support for MPEG DASH video.
|
||||
|
||||
## Q: Does video.js support live video?
|
||||
|
||||
Yes! Video.js adds support for live videos via the Flash tech which supports RTMP streams.
|
||||
The official HLS tech, [videojs-contrib-hls][hls], will add support for live HLS video
|
||||
Yes! Video.js adds support for live videos via the Flash tech tech which supports RTMP streams. In Video.js 6 you will have to use [videojs-flash][flash] to get this. In previous versions the Flash tech was builtin. The official HLS tech, [videojs-contrib-hls][hls], will add support for live HLS video
|
||||
if you add it to your page with video.js.
|
||||
|
||||
## Q: Can video.js wrap around YouTube videos?
|
||||
@@ -213,22 +266,36 @@ Yes! Please [submit an issue or open a pull request][pr-issue-question] if this
|
||||
|
||||
Yes! Please [submit an issue or open a pull request][pr-issue-question] if this does not work.
|
||||
|
||||
[plugin-guide]: plugins.md
|
||||
Be sure to use `require('!style-loader!css-loader!video.js/dist/video-js.css')` to inject video.js CSS.
|
||||
|
||||
## Q: Does video.js work with react?
|
||||
|
||||
Yes! See [ReactJS integration example][react-guide].
|
||||
|
||||
[reduced-test-case]: https://css-tricks.com/reduced-test-cases/
|
||||
|
||||
[react-guide]: /docs/guides/react.md
|
||||
|
||||
[plugin-guide]: /docs/guides/plugins.md
|
||||
|
||||
[install-guide]: http://videojs.com/getting-started/
|
||||
|
||||
[troubleshooting]: troubleshooting.md
|
||||
[troubleshooting]: /docs/guides/troubleshooting.md
|
||||
|
||||
[video-tracks]: video-tracks.md
|
||||
[video-tracks]: /docs/guides/video-tracks.md
|
||||
|
||||
[audio-tracks]: audio-tracks.md
|
||||
[audio-tracks]: /docs/guides/audio-tracks.md
|
||||
|
||||
[text-tracks]: text-tracks.md
|
||||
[text-tracks]: /docs/guides/text-tracks.md
|
||||
|
||||
[debug-guide]: /docs/guides/debugging.md
|
||||
|
||||
[pr-issue-question]: #q-i-think-i-found-a-bug-with-videojs-or-i-want-to-add-a-feature-what-should-i-do
|
||||
|
||||
[hls]: http://github.com/videojs/videojs-contrib-hls
|
||||
|
||||
[flash]: https://github.com/videojs/videojs-flash
|
||||
|
||||
[dash]: http://github.com/videojs/videojs-contrib-dash
|
||||
|
||||
[eme]: https://github.com/videojs/videojs-contrib-eme
|
||||
@@ -262,3 +329,4 @@ Yes! Please [submit an issue or open a pull request][pr-issue-question] if this
|
||||
[semver]: http://semver.org/
|
||||
|
||||
[starter-example]: http://jsbin.com/axedog/edit?html,output
|
||||
|
||||
@@ -58,12 +58,12 @@ Finally, each file's extension is always `.json`.
|
||||
|
||||
### Updating an Existing Translation
|
||||
|
||||
If there is a [missing translation](../translations-needed.md), mistake, or room for improvement in an existing translation, don't hesitate to open a pull request!
|
||||
If there is a [missing translation](/docs/translations-needed.md), mistake, or room for improvement in an existing translation, don't hesitate to open a pull request!
|
||||
|
||||
1. Edit the relevant JSON file and make the necessary changes.
|
||||
1. Verify the language compiles by running `grunt dist`.
|
||||
1. Verify the translation appears properly in the player UI.
|
||||
1. Run `grunt check-translations` to update the [missing translation document](../translations-needed.md).
|
||||
1. Run `grunt check-translations` to update the [missing translation document](/docs/translations-needed.md).
|
||||
1. Commit and open a pull request on GitHub.
|
||||
|
||||
### Writing a New Translation
|
||||
@@ -103,7 +103,7 @@ videojs.addLanguage('es', {
|
||||
|
||||
### Per-Player Languages
|
||||
|
||||
In addition to providing languages to Video.js itself, individual `Player` instances can be provided custom language support via [the `languages` option](options.md#languages):
|
||||
In addition to providing languages to Video.js itself, individual `Player` instances can be provided custom language support via [the `languages` option](/docs/guides/options.md#languages):
|
||||
|
||||
```js
|
||||
// Provide a custom definition of Spanish to this player.
|
||||
@@ -116,7 +116,7 @@ videojs('my-player', {
|
||||
|
||||
### Setting Default Player Language
|
||||
|
||||
Player instances can also have a default language via [the `language` option](options.md#language):
|
||||
Player instances can also have a default language via [the `language` option](/docs/guides/options.md#language):
|
||||
|
||||
```js
|
||||
// Set the default language to Spanish for this player.
|
||||
@@ -143,14 +143,14 @@ The player language is set to one of the following in descending priority:
|
||||
|
||||
## References
|
||||
|
||||
For information on translation/localization in plugins, see [the plugins guide](plugins.md).
|
||||
For information on translation/localization in plugins, see [the plugins guide](/docs/guides/plugins.md).
|
||||
|
||||
Standard languages codes [are defined by the IANA][lang-codes].
|
||||
|
||||
For all existing/supported languages, please see the [languages lolder (`lang/`)][lang-supported] folder located in the project root.
|
||||
|
||||
[lang-en]: https://github.com/videojs/video.js/tree/master/lang/en.json
|
||||
[lang-en]: /lang/en.json
|
||||
|
||||
[lang-supported]: https://github.com/videojs/video.js/tree/master/lang
|
||||
[lang-supported]: /lang
|
||||
|
||||
[lang-codes]: http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# Using the Modal Dialog Component
|
||||
|
||||
The `ModalDialog` component is part of Video.js core and provides a baked-in UI for full-player overlays.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Creating a ModalDialog](#creating-a-modaldialog)
|
||||
* [Example Using createModal()](#example-using-createmodal)
|
||||
* [Example Using the ModalDialog Constructor](#example-using-the-modaldialog-constructor)
|
||||
* [Styling Modals Independently](#styling-modals-independently)
|
||||
|
||||
## Creating a ModalDialog
|
||||
|
||||
Aside from the [built-in Video.js component-creation methods][creating-component], the player includes a `createModal()` helper method.
|
||||
|
||||
We'll demonstrate both approaches in this document by creating a modal that opens when the player becomes paused and resumes playback when it is closed.
|
||||
|
||||
### Example Using `createModal()`
|
||||
|
||||
The `createModal()` method is intended for creating one-off modals that need to open for some temporary purpose. Therefore, they open themselves immediately upon creation and, by default, dispose themselves immediately upon closing.
|
||||
|
||||
```js
|
||||
var player = videojs('my-player');
|
||||
|
||||
player.on('pause', function() {
|
||||
|
||||
// Modals are temporary by default. They dispose themselves when they are
|
||||
// closed; so, we can create a new one each time the player is paused and
|
||||
// not worry about leaving extra nodes hanging around.
|
||||
var modal = player.createModal('This is a modal!');
|
||||
|
||||
// When the modal closes, resume playback.
|
||||
modal.on('modalclose', function() {
|
||||
player.play();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
The `createModal()` method also takes a second argument - an object containing options for the modal. Refer to [the API documentation][api-doc] for a full set of options.
|
||||
|
||||
### Example Using the `ModalDialog` Constructor
|
||||
|
||||
Unlike when using `createModal()`, a modal created with any of the [common component creation methods][creating-component] _does not_ open by default. This makes this approach better suited to modals that are expected to live in the DOM indefinitely.
|
||||
|
||||
```js
|
||||
var player = videojs('my-player');
|
||||
var ModalDialog = videojs.getComponent('ModalDialog');
|
||||
|
||||
var modal = new ModalDialog(player, {
|
||||
|
||||
// We don't want this modal to go away when it closes.
|
||||
temporary: false
|
||||
});
|
||||
|
||||
player.addChild(modal);
|
||||
|
||||
player.on('pause', function() {
|
||||
modal.open();
|
||||
});
|
||||
|
||||
player.on('play', function() {
|
||||
modal.close();
|
||||
});
|
||||
```
|
||||
|
||||
Both of these examples are equivalent when it comes to the user's experience. Implementors should use whichever better suits their use-case.
|
||||
|
||||
## Styling Modals Independently
|
||||
|
||||
A common need for modals is to style them independently from one another. The recommended approach for this is to add a custom class to your modal and target that using CSS:
|
||||
|
||||
```js
|
||||
modal.addClass('vjs-my-fancy-modal');
|
||||
```
|
||||
|
||||
[api-doc]: http://docs.videojs.com/ModalDialog.html
|
||||
|
||||
[creating-component]: /docs/guides/components.md#creating-a-component
|
||||
@@ -1,6 +1,6 @@
|
||||
# Video.js Options Reference
|
||||
|
||||
> **Note:** This document is only a reference for available options. To learn about passing options to Video.js, see [the setup guide](setup.md#options).
|
||||
> **Note:** This document is only a reference for available options. To learn about passing options to Video.js, see [the setup guide](/docs/guides/setup.md#options).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
## Standard `<video>` Element Options
|
||||
|
||||
Each of these options is also available as a [standard `<video>` element attribute][video-attrs]; so, they can be defined in all three manners [outlined in the setup guide](setup.md#options). Typically, defaults are not listed as this is left to browser vendors.
|
||||
Each of these options is also available as a [standard `<video>` element attribute][video-attrs]; so, they can be defined in all three manners [outlined in the setup guide](/docs/guides/setup.md#options). Typically, defaults are not listed as this is left to browser vendors.
|
||||
|
||||
### `autoplay`
|
||||
|
||||
@@ -151,7 +151,7 @@ The `inactivityTimeout` determines how many milliseconds of inactivity is requir
|
||||
|
||||
A [language code][lang-codes] matching one of the available languages in the player. This sets the initial language for a player, but it can always be changed.
|
||||
|
||||
Learn more about [languages in Video.js](languages.md).
|
||||
Learn more about [languages in Video.js][languages].
|
||||
|
||||
### `languages`
|
||||
|
||||
@@ -159,7 +159,7 @@ Learn more about [languages in Video.js](languages.md).
|
||||
|
||||
Customize which languages are available in a player. The keys of this object will be [language codes][lang-codes] and the values will be objects with English keys and translated values.
|
||||
|
||||
Learn more about [languages in Video.js](languages.md).
|
||||
Learn more about [languages in Video.js][languages]
|
||||
|
||||
> **Note**: Generally, this option is not needed and it would be better to pass your custom languages to `videojs.addLanguage()`, so they are available in all players!
|
||||
|
||||
@@ -201,13 +201,13 @@ player.boo({baz: false});
|
||||
|
||||
Although, since the `plugins` option is an object, the order of initialization is not guaranteed!
|
||||
|
||||
See [the plugins guide](plugins.md) for more information on Video.js plugins.
|
||||
See [the plugins guide][plugins] for more information on Video.js plugins.
|
||||
|
||||
### `sourceOrder`
|
||||
|
||||
> Type: `boolean`, Default: `false`
|
||||
>
|
||||
> **Note:** In video.js 6.0, this option will default to `true`.
|
||||
> **Note:** In video.js 6.0, this option will default to `true`. and that [videojs-flash](https://github.com/videojs/videojs-flash) will be required to use the flash tech.
|
||||
|
||||
Tells Video.js to prefer the order of [`sources`](#sources) over [`techOrder`](#techorder) in selecting a source and playback tech.
|
||||
|
||||
@@ -275,9 +275,9 @@ Using `<source>` elements will have the same effect:
|
||||
|
||||
### `techOrder`
|
||||
|
||||
> Type: `Array`, Default: `['html5', 'flash']`
|
||||
> Type: `Array`, Default: `['html5']`
|
||||
|
||||
Defines the order in which Video.js techs are preferred. By default, this means that the `Html5` tech is preferred, but Video.js will fall back to `Flash` if no `Html5`-compatible source can be found.
|
||||
Defines the order in which Video.js techs are preferred. By default, this means that the `Html5` tech is preferred. Other regisetered techs will be added after this tech in the order in which they are registered.
|
||||
|
||||
### `vtt.js`
|
||||
|
||||
@@ -291,7 +291,7 @@ This option will be used in the "novtt" build of video.js (i.e. `video.novtt.js`
|
||||
|
||||
The Video.js player is a component. Like all components, you can define what children it includes, what order they appear in, and what options are passed to them.
|
||||
|
||||
This is meant to be a quick reference; so, for more detailed information on components in Video.js, check out the [components guide](components.md).
|
||||
This is meant to be a quick reference; so, for more detailed information on components in Video.js, check out the [components guide](/docs/guides/components.md).
|
||||
|
||||
### `children`
|
||||
|
||||
@@ -374,6 +374,10 @@ Only supported by the `Html5` tech, this option can be set to `true` to force na
|
||||
|
||||
Can be set to `false` to force emulation of text tracks instead of native support. The `nativeCaptions` option also exists, but is simply an alias to `nativeTextTracks`.
|
||||
|
||||
[plugins]: /docs/guides/plugins.md
|
||||
|
||||
[languages]: /docs/guides/languages.md
|
||||
|
||||
[ios-10-updates]: https://webkit.org/blog/6784/new-video-policies-for-ios/
|
||||
|
||||
[lang-codes]: http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Player Workflows
|
||||
|
||||
This document outlines many considerations for using Video.js for advanced player workflows. Be sure to read [the setup guide](setup.md) first!
|
||||
This document outlines many considerations for using Video.js for advanced player workflows. Be sure to read [the setup guide](/docs/guides/setup.md) first!
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -31,9 +31,9 @@ After an instance has been created it can be accessed globally in two ways:
|
||||
|
||||
No matter the term used for it, web applications are becoming common. Not everything is a static, load-once-and-done web page anymore! This means that developers need to be able to manage the full lifecycle of a video player - from creation to destruction. Video.js supports player removal through the `dispose()` method.
|
||||
|
||||
### [`dispose()`](http://docs.videojs.com/docs/api/player.html#Methodsdispose)
|
||||
### [`dispose()`](http://docs.videojs.com/Player.html#dispose)
|
||||
|
||||
This method is available on all Video.js players and [components](http://docs.videojs.com/docs/api/component.html#Methodsdispose). It is _the only_ supported method of removing a Video.js player from both the DOM and memory. For example, the following code sets up a player and then disposes it when media playback is complete:
|
||||
This method is available on all Video.js players and [components](http://docs.videojs.com/Component.html#dispose). It is _the only_ supported method of removing a Video.js player from both the DOM and memory. For example, the following code sets up a player and then disposes it when media playback is complete:
|
||||
|
||||
```js
|
||||
var player = videojs('my-player');
|
||||
@@ -367,6 +367,8 @@ Coming soon...
|
||||
|
||||
### React
|
||||
|
||||
See [ReactJS integration example](/docs/guides/react.md)
|
||||
|
||||
### Ember
|
||||
|
||||
### Angular
|
||||
|
||||
+300
-27
@@ -1,58 +1,331 @@
|
||||
# Plugins
|
||||
# Video.js Plugins
|
||||
|
||||
If you've built something cool with Video.js, you can easily share it with the rest of the world by creating a plugin. Although, you can roll your own, you can also use [generator-videojs-plugin](https://github.com/dmlap/generator-videojs-plugin), a [Yeoman](http://yeoman.io) generator that provides scaffolding for video.js plugins including:
|
||||
One of the great strengths of Video.js is its ecosystem of plugins that allow authors from all over the world to share their video player customizations. This includes everything from the simplest UI tweaks to new [playback technologies and source handlers][tech]!
|
||||
|
||||
* [Grunt](http://gruntjs.com) for build management
|
||||
* [npm](https://www.npmjs.org) for dependency management
|
||||
* [QUnit](http://qunitjs.com) for testing
|
||||
Because we view plugins as such an important part of Video.js, the organization is committed to maintaining a robust set of tools for plugin authorship:
|
||||
|
||||
## Step 1: Write Some Javascript
|
||||
* [generator-videojs-plugin][generator]
|
||||
|
||||
You may have already done this step. Code up something interesting and then wrap it in a function. At the most basic level, that's all a video.js plugin is. By convention, plugins take a hash of options as their first argument:
|
||||
A [Yeoman][yeoman] generator for scaffolding a Video.js plugin project. Additionally, it offers a set of [conventions for plugin authorship][standards] that, if followed, make authorship, contribution, and usage consistent and predictable.
|
||||
|
||||
In short, the generator sets up plugin authors to focus on writing their plugin - not messing with tools.
|
||||
|
||||
* [videojs-spellbook][spellbook]
|
||||
|
||||
As of version 3, the plugin generator includes a new dependency: [videojs-spellbook][spellbook]. Spellbook is a kitchen sink plugin development tool: it builds plugins, creates tags, runs a development server, and more.
|
||||
|
||||
The benefit of Spellbook is that you can run the generator _once_ and receive updates and bugfixes in Spellbook without having to run the generator again and deal with Yeoman conflicts and other headaches.
|
||||
|
||||
As long as your plugin project follows the [conventions][standards], Spellbook should work on it!
|
||||
|
||||
## Writing a Basic Plugin
|
||||
|
||||
If you've written a Video.js plugin before, the basic plugin concept should be familiar. It's similar to a jQuery plugin in that the core idea is that you're adding a method to the player.
|
||||
|
||||
### Write a JavaScript Function
|
||||
|
||||
A basic plugin is a plain JavaScript function:
|
||||
|
||||
```js
|
||||
function examplePlugin(options) {
|
||||
this.on('play', function(e) {
|
||||
console.log('playback has started!');
|
||||
|
||||
if (options.customClass) {
|
||||
this.addClass(options.customClass);
|
||||
}
|
||||
|
||||
this.on('playing', function() {
|
||||
videojs.log('playback began!');
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
By convention, plugins are passed an `options` object; however, you can realistically accept whatever arguments you want. This example plugin will add a custom class (whatever is passed in as `options.customClass`) and, whenever playback begins, it will log a message to the browser console.
|
||||
|
||||
> **Note:** The value of `this` in the plugin function is the player instance; so, you have access to [its complete API][api-player].
|
||||
|
||||
### Register a Basic Plugin
|
||||
|
||||
Now that we have a function that does something with a player, all that's left is to register the plugin with Video.js:
|
||||
|
||||
```js
|
||||
videojs.registerPlugin('examplePlugin', examplePlugin);
|
||||
```
|
||||
|
||||
After that, any player will automatically have an `examplePlugin` method on its prototype!
|
||||
|
||||
> **Note:** The only stipulation with the name of the plugin is that it cannot conflict with any existing plugin or player method.
|
||||
|
||||
## Writing an Advanced Plugin
|
||||
|
||||
Video.js 6 introduces advanced plugins: these are plugins that share a similar API with basic plugins, but are class-based and offer a range of extra features out of the box.
|
||||
|
||||
While reading the following sections, you may want to refer to the [Plugin API docs][api-plugin] for more detail.
|
||||
|
||||
### Write a JavaScript Class/Constructor
|
||||
|
||||
If you're familiar with creating [components][components], this process is similar. An advanced plugin starts with a JavaScript class (a.k.a. a constructor function).
|
||||
|
||||
If you're using ES6 already, you can use that syntax with your transpiler/language of choice (Babel, TypeScript, etc):
|
||||
|
||||
```js
|
||||
const Plugin = videojs.getPlugin('plugin');
|
||||
|
||||
class ExamplePlugin extends Plugin {
|
||||
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
if (options.customClass) {
|
||||
player.addClass(options.customClass);
|
||||
}
|
||||
|
||||
player.on('playing', function() {
|
||||
videojs.log('playback began!');
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or with ES5:
|
||||
|
||||
```js
|
||||
var Plugin = videojs.getPlugin('plugin');
|
||||
|
||||
var ExamplePlugin = videojs.extend(Plugin, {
|
||||
|
||||
constructor: function(player, options) {
|
||||
Plugin.call(this, player, options);
|
||||
|
||||
if (options.customClass) {
|
||||
player.addClass(options.customClass);
|
||||
}
|
||||
|
||||
player.on('playing', function() {
|
||||
videojs.log('playback began!');
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
For now, this example advanced plugin does the exact same thing as the basic plugin described above - not to worry, we will make it more interesting as we continue!
|
||||
|
||||
### Register a Advanced Plugin
|
||||
|
||||
The registration process for advanced plugins is identical to [the process for basic plugins](#register-a-basic-plugin).
|
||||
|
||||
```js
|
||||
videojs.registerPlugin('examplePlugin', ExamplePlugin);
|
||||
```
|
||||
|
||||
> **Note:** Because ES6 classes are syntactic sugar on top of existing constructor function and prototype architecture in JavaScript, in all cases `registerPlugin`'s second argument is a function.
|
||||
|
||||
### Key Differences from Basic Plugins
|
||||
|
||||
Advanced plugins have two key differences from basic plugins that are important to understand before describing their advanced features.
|
||||
|
||||
#### The Value of `this`
|
||||
|
||||
With basic plugins, the value of `this` in the plugin function will be the _player_.
|
||||
|
||||
With advanced plugins, the value of `this` is the _instance of the plugin class_. The player is passed to the plugin constructor as its first argument (and is automatically applied to the plugin instance as the `player` property) and any further arguments are passed after that.
|
||||
|
||||
#### The Player Plugin Name Property
|
||||
|
||||
Both basic plugins and advanced plugins are set up by calling a method on a player with a name matching the plugin (e.g., `player.examplePlugin()`).
|
||||
|
||||
However, with advanced plugins, this method acts like a factory function and it is _replaced_ for the current player by a new function which returns the plugin instance:
|
||||
|
||||
```js
|
||||
// `examplePlugin` has not been called, so it is a factory function.
|
||||
player.examplePlugin();
|
||||
|
||||
// `examplePlugin` is now a function that returns the same instance of
|
||||
// `ExamplePlugin` that was generated by the previous call.
|
||||
player.examplePlugin().someMethodName();
|
||||
```
|
||||
|
||||
With basic plugins, the method does not change - it is always the same function. It is up to the authors of basic plugins to deal with multiple calls to their plugin function.
|
||||
|
||||
### Features of Advanced Plugins
|
||||
|
||||
Up to this point, our example advanced plugin is functionally identical to our example basic plugin. However, advanced plugins bring with them a great deal of benefit that is not built into basic plugins.
|
||||
|
||||
#### Events
|
||||
|
||||
Like components, advanced plugins offer an implementation of events. This includes:
|
||||
|
||||
* The ability to listen for events on the plugin instance using `on` or `one`:
|
||||
|
||||
```js
|
||||
player.examplePlugin().on('example-event', function() {
|
||||
videojs.log('example plugin received an example-event');
|
||||
});
|
||||
```
|
||||
|
||||
* The ability to `trigger` custom events on a plugin instance:
|
||||
|
||||
```js
|
||||
player.examplePlugin().trigger('example-event');
|
||||
```
|
||||
|
||||
* The ability to stop listening to custom events on a plugin instance using `off`:
|
||||
|
||||
```js
|
||||
player.examplePlugin().off('example-event');
|
||||
```
|
||||
|
||||
By offering a built-in events system, advanced plugins offer a wider range of options for code structure with a pattern familiar to most web developers.
|
||||
|
||||
#### Statefulness
|
||||
|
||||
A new concept introduced for advanced plugins is _statefulness_. This is similar to React components' `state` property and `setState` method.
|
||||
|
||||
Advanced plugin instances each have a `state` property, which is a plain JavaScript object - it can contain any keys and values the plugin author wants.
|
||||
|
||||
A default `state` can be provided by adding a static property to a plugin constructor:
|
||||
|
||||
```js
|
||||
ExamplePlugin.defaultState = {
|
||||
customClass: 'default-custom-class'
|
||||
};
|
||||
```
|
||||
|
||||
When it's activated, `this` will be the Video.js player your plugin is attached to. You can use anything you'd like in the [Video.js API](./api.md) when you're writing a plugin: change the `src`, mess up the DOM, or listen for and emit your own events.
|
||||
|
||||
## Step 2: Registering A Plugin
|
||||
|
||||
It's time to give the rest of the world the opportunity to be awed by your genius. When your plugin is loaded, it needs to let Video.js know this amazing new functionality is now available:
|
||||
When the `state` is updated via the `setState` method, the plugin instance fires a `"statechanged"` event, but _only if something changed!_ This event can be used as a signal to update the DOM or perform some other action. The event object passed to listeners for this event includes, an object describing the changes that occurred on the `state` property:
|
||||
|
||||
```js
|
||||
videojs.plugin('examplePlugin', examplePlugin);
|
||||
player.examplePlugin().on('statechanged', function(e) {
|
||||
if (e.changes && e.changes.customClass) {
|
||||
this.player
|
||||
.removeClass(e.changes.customClass.from)
|
||||
.addClass(e.changes.customClass.to);
|
||||
}
|
||||
});
|
||||
|
||||
player.examplePlugin().setState({customClass: 'another-custom-class'});
|
||||
```
|
||||
|
||||
From this point on, your plugin will be added to the Video.js prototype and will show up as a property on every instance created. Make sure you choose a unique name that doesn't clash with any of the properties already in Video.js. Which leads us to...
|
||||
#### Lifecycle
|
||||
|
||||
## Step 3: Using A Plugin
|
||||
|
||||
There are two ways to initialize a plugin. If you're creating your video tag dynamically, you can specify the plugins you'd like to initialize with it and any options you want to pass to them:
|
||||
Like components, advanced plugins have a lifecycle. They can be created with their factory function and they can be destroyed using their `dispose` method:
|
||||
|
||||
```js
|
||||
videojs('vidId', {
|
||||
// set up a example plugin instance
|
||||
player.examplePlugin();
|
||||
|
||||
// dispose of it anytime thereafter
|
||||
player.examplePlugin().dispose();
|
||||
```
|
||||
|
||||
The `dispose` method has several effects:
|
||||
|
||||
* Triggers a `"dispose"` event on the plugin instance.
|
||||
* Cleans up all event listeners on the plugin instance, which helps avoid errors caused by events being triggered after an object is cleaned up.
|
||||
* Removes plugin state and references to the player to avoid memory leaks.
|
||||
* Reverts the player's named property (e.g. `player.examplePlugin`) _back_ to the original factory function, so the plugin can be set up again.
|
||||
|
||||
In addition, if the player is disposed, the disposal of all its advanced plugin instances will be triggered as well.
|
||||
|
||||
### Advanced Example Advanced Plugin
|
||||
|
||||
What follows is a complete ES6 advanced plugin that logs a custom message when the player's state changes between playing and paused. It uses all the described advanced features:
|
||||
|
||||
```js
|
||||
import videojs from 'video.js';
|
||||
|
||||
const Plugin = videojs.getPlugin('plugin');
|
||||
|
||||
class Advanced extends Plugin {
|
||||
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
// Whenever the player emits a playing or paused event, we update the
|
||||
// state if necessary.
|
||||
this.on(player, ['playing', 'paused'], this.updateState);
|
||||
this.on('statechanged', this.logState);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
videojs.log('the advanced plugin is being disposed');
|
||||
}
|
||||
|
||||
updateState() {
|
||||
this.setState({playing: !player.paused()});
|
||||
}
|
||||
|
||||
logState(changed) {
|
||||
videojs.log(`the player is now ${this.state.playing ? 'playing' : 'paused'}`);
|
||||
}
|
||||
}
|
||||
|
||||
videojs.registerPlugin('advanced', Advanced);
|
||||
|
||||
const player = videojs('example-player');
|
||||
|
||||
player.advanced();
|
||||
|
||||
// This will begin playback, which will trigger a "playing" event, which will
|
||||
// update the state of the plugin, which will cause a message to be logged.
|
||||
player.play();
|
||||
|
||||
// This will pause playback, which will trigger a "paused" event, which will
|
||||
// update the state of the plugin, which will cause a message to be logged.
|
||||
player.pause();
|
||||
|
||||
player.advanced().dispose();
|
||||
|
||||
// This will begin playback, but the plugin has been disposed, so it will not
|
||||
// log any messages.
|
||||
player.play();
|
||||
```
|
||||
|
||||
This example may be a bit pointless in reality, but it demonstrates the sort of flexibility offered by advanced plugins over basic plugins.
|
||||
|
||||
## Setting up a Plugin
|
||||
|
||||
There are two ways to set up (or initialize) a plugin on a player. Both ways work identically for both basic and advanced plugins.
|
||||
|
||||
The first way is during creation of the player. Using the `plugins` option, a plugin can be automatically set up on a player:
|
||||
|
||||
```js
|
||||
videojs('example-player', {
|
||||
plugins: {
|
||||
examplePlugin: {
|
||||
exampleOption: true
|
||||
customClass: 'example-class'
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
If you've already initialized your video tag, you can activate a plugin at any time by calling its setup function directly:
|
||||
Otherwise, a plugin can be manually set up:
|
||||
|
||||
```js
|
||||
var video = videojs('cool-vid');
|
||||
video.examplePlugin({ exampleOption: true });
|
||||
var player = videojs('example-player');
|
||||
player.examplePlugin({customClass: 'example-class'});
|
||||
```
|
||||
|
||||
That's it. Head on over to the [Video.js wiki](https://github.com/videojs/video.js/wiki/Plugins) and add your plugin to the list so everyone else can check it out.
|
||||
These two methods are functionally identical - use whichever you prefer!
|
||||
|
||||
## How should I use the Video.js icons in my plugin?
|
||||
## References
|
||||
|
||||
If you'd like to use any of the icons available in the [Video.js icon set](http://videojs.github.io/font/), please target them via the CSS class names instead of codepoints. The codepoints _may_ change between versions of the font, so using the class names ensures that your plugin will stay up to date with any font changes.
|
||||
* [Player API][api-player]
|
||||
* [Plugin API][api-plugin]
|
||||
* [Plugin Generator][generator]
|
||||
* [Plugin Conventions][standards]
|
||||
|
||||
[components]: /docs/guides/components.md
|
||||
|
||||
[tech]: /docs/guides/tech.md
|
||||
|
||||
[api-player]: http://docs.videojs.com/Player.html
|
||||
|
||||
[api-plugin]: http://docs.videojs.com/Plugin.html
|
||||
|
||||
[generator]: https://github.com/videojs/generator-videojs-plugin
|
||||
|
||||
[spellbook]: https://github.com/videojs/spellbook
|
||||
|
||||
[standards]: https://github.com/videojs/generator-videojs-plugin/blob/master/docs/standards.md
|
||||
|
||||
[yeoman]: http://yeoman.io
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# video.js and ReactJS integration
|
||||
|
||||
Here's a basic ReactJS player implementation.
|
||||
|
||||
It just instantiates the video.js player on `componentDidMount` and destroys it on `componentWillUnmount`.
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import videojs from 'video.js'
|
||||
|
||||
export default class VideoPlayer extends React.Component {
|
||||
componentDidMount() {
|
||||
// instantiate video.js
|
||||
this.player = videojs(this.videoNode, this.props, function onPlayerReady() {
|
||||
console.log('onPlayerReady', this)
|
||||
});
|
||||
}
|
||||
|
||||
// destroy player on unmount
|
||||
componentWillUnmount() {
|
||||
if (this.player) {
|
||||
this.player.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
// wrap the player in a div with a `data-vjs-player` attribute
|
||||
// so videojs won't create additional wrapper in the DOM
|
||||
// see https://github.com/videojs/video.js/pull/3856
|
||||
render() {
|
||||
return (
|
||||
<div data-vjs-player>
|
||||
<video ref={ node => this.videoNode = node } className="video-js"></video>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can then use it like this: (see [options guide][options] for option information)
|
||||
|
||||
```jsx
|
||||
const videoJsOptions = {
|
||||
autoplay: true,
|
||||
controls: true,
|
||||
sources: [{
|
||||
src: '/path/to/video.mp4',
|
||||
type: 'video/mp4'
|
||||
}]
|
||||
}
|
||||
|
||||
return <VideoPlayer { ...videoJsOptions } />
|
||||
```
|
||||
|
||||
Dont forget to include the video.js CSS, located at `video.js/dist/video-js.css`.
|
||||
|
||||
[options]: /docs/guides/options.md
|
||||
@@ -82,7 +82,7 @@ videojs(document.querySelector('.video-js'));
|
||||
|
||||
## Options
|
||||
|
||||
> **Note:** This guide only covers how to pass options during player setup. For a complete reference on _all_ available options, see the [options guide](options.md).
|
||||
> **Note:** This guide only covers how to pass options during player setup. For a complete reference on _all_ available options, see the [options guide](/docs/guides/options.md).
|
||||
|
||||
There are three ways to pass options to Video.js. Because Video.js decorates an HTML5 `<video>` element, many of the options available are also available as [standard `<video>` tag attributes][video-attrs]:
|
||||
|
||||
@@ -175,7 +175,9 @@ In each case, the callback is called asynchronously - _even if the player is alr
|
||||
|
||||
## Advanced Player Workflows
|
||||
|
||||
For a discussion of more advanced player workflows, see the [player workflows guide](player-workflows.md).
|
||||
For a discussion of more advanced player workflows, see the [player workflows guide][player-workflows].
|
||||
|
||||
[player-workflows]: /docs/guides/player-workflows.md
|
||||
|
||||
[boolean-attrs]: https://www.w3.org/TR/2011/WD-html5-20110525/common-microsyntaxes.html#boolean-attributes
|
||||
|
||||
@@ -185,7 +187,7 @@ For a discussion of more advanced player workflows, see the [player workflows gu
|
||||
|
||||
[video-attrs]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#Attributes
|
||||
|
||||
[videojs]: http://docs.videojs.com/docs/api/video.html
|
||||
[videojs]: http://docs.videojs.com/module-videojs.html
|
||||
|
||||
[w3c-media-events]: https://www.w3.org/2010/05/video/mediaevents.html
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ The boolean `default` attribute can be used to indicate that a track's mode shou
|
||||
|
||||
The valid [BCP 47](https://tools.ietf.org/html/bcp47) code for the language of the text track, e.g. `"en"` for English or `"es"` for Spanish.
|
||||
|
||||
For supported language translations, please see the [languages folder (/lang)](https://github.com/videojs/video.js/tree/master/lang) folder located in the Video.js root and refer to the [languages guide](./languages.md) for more information on languages in Video.js.
|
||||
For supported language translations, please see the [languages folder (/lang)](https://github.com/videojs/video.js/tree/master/lang) folder located in the Video.js root and refer to the [languages guide](/docs/guides/languages.md) for more information on languages in Video.js.
|
||||
|
||||
### Text Tracks from Another Domain
|
||||
|
||||
@@ -214,7 +214,7 @@ In general, `"descriptions"` tracks are of lower precedence than `"captions"` an
|
||||
|
||||
## API
|
||||
|
||||
For more complete information, refer to the [Video.js API docs](http://docs.videojs.com/docs/api/index.html).
|
||||
For more complete information, refer to the [Video.js API docs](http://docs.videojs.com/).
|
||||
|
||||
### Remote Text Tracks
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@ There are currently three types of tracks:
|
||||
* [Video Tracks](#video-tracks)
|
||||
* [Text Tracks](#text-tracks)
|
||||
|
||||
## [Audio Tracks](./audio-tracks.md)
|
||||
## [Audio Tracks](/docs/guides/audio-tracks.md)
|
||||
|
||||
Audio tracks allow the selection of alternate audio for a video.
|
||||
|
||||
## [Video Tracks](./video-tracks.md)
|
||||
## [Video Tracks](/docs/guides/video-tracks.md)
|
||||
|
||||
Video tracks allow the selection of alternate video content.
|
||||
|
||||
## [Text Tracks](./text-tracks.md)
|
||||
## [Text Tracks](/docs/guides/text-tracks.md)
|
||||
|
||||
Text tracks are used to display subtitles and captions and add a menu for navigating between chapters in a video.
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
* [Choosing a video format](#choosing-a-video-format)
|
||||
* [I want to have a single source and don't care about live/adaptive streaming:](#i-want-to-have-a-single-source-and-dont-care-about-liveadaptive-streaming)
|
||||
* [I need adaptive streaming or live streaming](#i-need-adaptive-streaming-or-live-streaming)
|
||||
* [Make sure you are using formats that video.js can play:](#make-sure-you-are-using-formats-that-videojs-can-play)
|
||||
* [Make sure that the codec for that is being used in the file container is supported:](#make-sure-that-the-codec-for-that-is-being-used-in-the-file-container-is-supported)
|
||||
* [Make sure you are using formats that Video.js can play:](#make-sure-you-are-using-formats-that-videojs-can-play)
|
||||
* [Make sure that the codec used in the file container is supported:](#make-sure-that-the-codec-used-in-the-file-container-is-supported)
|
||||
* [If you are using Flash videos:](#if-you-are-using-flash-videos)
|
||||
* [Problems when hosting media](#problems-when-hosting-media)
|
||||
* [Problems with Fullscreen](#problems-with-fullscreen)
|
||||
* [Problems with fullscreen](#problems-with-fullscreen)
|
||||
* [Problems with playback](#problems-with-playback)
|
||||
* [video.js Errors](#videojs-errors)
|
||||
* [Video.js Errors](#videojs-errors)
|
||||
* [vdata123456 errors](#vdata123456-errors)
|
||||
|
||||
## Problems with media formats
|
||||
@@ -22,7 +22,7 @@
|
||||
#### I want to have a single source and don't care about live/adaptive streaming:
|
||||
|
||||
Most browsers now play MP4 with h264 video. If you want to have a single source, and neither live streaming
|
||||
nor adaptive streaming is a consideration, MP4 is a good choice.
|
||||
nor adaptive streaming is a consideration, MP4 with h264 video and acc audio is a good choice.
|
||||
|
||||
#### I need adaptive streaming or live streaming
|
||||
|
||||
@@ -30,17 +30,17 @@ Use HLS with [videojs-contrib-hls][hls] or
|
||||
Use Dash with [videojs-contrib-dash][dash].
|
||||
HLS is more convenient as mobile browsers have native support.
|
||||
|
||||
### Make sure you are using formats that video.js can play:
|
||||
### Make sure you are using formats that Video.js can play:
|
||||
|
||||
* Does your browser/OS support the type of media that you are trying to play?
|
||||
* Do you have a video.js plugin that will add support for a media format to video.js? For Example:
|
||||
* Do you have a Video.js plugin that will add support for a media format to Video.js? For example:
|
||||
* [videojs-youtube][youtube]
|
||||
* [videojs-contrib-hls][hls]
|
||||
* [videojs-contrib-dash][dash]
|
||||
* Verify that you are using the correct [mime-type/content-type][media-types] for your videos.
|
||||
This is used to determine if video.js can play a certain type of media.
|
||||
This is used to determine if Video.js can play a certain type of media.
|
||||
|
||||
### Make sure that the codec for that is being used in the file container is supported:
|
||||
### Make sure that the codec used in the file container is supported:
|
||||
|
||||
* MP4 in browsers typically only supports h264 video and MP3 or AAC audio
|
||||
* Some low end phones save video in 3GP format but give it an MP4 extension. These files will not play.
|
||||
@@ -48,7 +48,7 @@ HLS is more convenient as mobile browsers have native support.
|
||||
### If you are using Flash videos:
|
||||
|
||||
* Make sure that Flash is installed
|
||||
* Make sure the Flash tech is included with video.js (in `video.js >= v6.0.0` it won't be, see [videojs-flash][flash])
|
||||
* Make sure the Flash tech is included with Video.js (in `video.js >= v6.0.0` it won't be by default, see [videojs-flash][flash])
|
||||
* Flash media include RTMP streams and FLV format media.
|
||||
* SWF is not a media format
|
||||
|
||||
@@ -63,7 +63,7 @@ HLS is more convenient as mobile browsers have native support.
|
||||
* You are using [videojs-contrib-hls][hls], [videojs-contrib-dash][dash] and your media is served from a different domain than your page.
|
||||
* You are using [text tracks][text-tracks] (captions, subtitles, etc.) and they are being served from a different domain than your page.
|
||||
|
||||
## Problems with Fullscreen
|
||||
## Problems with fullscreen
|
||||
|
||||
* If your player is in an iframe, the parent iframes must have the following attributes for fullscreen to be allowed:
|
||||
* `allowfullscreen`
|
||||
@@ -72,13 +72,13 @@ HLS is more convenient as mobile browsers have native support.
|
||||
|
||||
## Problems with playback
|
||||
|
||||
* Make sure that the media host supports byte-range requests, this could be breaking playback. See [Problems when Hosting media][hosting-media] for more info.
|
||||
* Make sure that the media host supports byte-range requests, this could be breaking playback. See [Problems when hosting media][hosting-media] for more info.
|
||||
* If your media is taking a long time to start playback or the entire mediadownloads before playback:
|
||||
* It is likely that metadata for the media has not been included at the start of the media. In MP4 terms this is called
|
||||
the "moov atom". Many encoders are configured to do this by default, others may require you to choose
|
||||
a "fast start" or "optimize for streaming" option.
|
||||
|
||||
## video.js Errors
|
||||
## Video.js Errors
|
||||
|
||||
### vdata123456 errors
|
||||
|
||||
@@ -91,7 +91,7 @@ To fix this issue please make sure that all event listeners are cleaned up on di
|
||||
|
||||
[hosting-media]: #problems-when-hosting-media
|
||||
|
||||
[text-tracks]: text-tracks.md
|
||||
[text-tracks]: /docs/guides/text-tracks.md
|
||||
|
||||
[hls]: https://github.com/videojs/videojs-contrib-hls
|
||||
|
||||
@@ -83,7 +83,7 @@ player.videoTracks().removeTrack(track);
|
||||
|
||||
## API
|
||||
|
||||
For more complete information, refer to the [Video.js API docs](http://docs.videojs.com/docs/api/index.html), specifically:
|
||||
For more complete information, refer to the [Video.js API docs](http://docs.videojs.com/), specifically:
|
||||
|
||||
* `Player#videoTracks`
|
||||
* `VideoTrackList`
|
||||
@@ -127,7 +127,7 @@ The label for the track that will be shown to the user. For example, in a menu t
|
||||
|
||||
The valid [BCP 47](https://tools.ietf.org/html/bcp47) code for the language of the video track, e.g. `"en"` for English or `"es"` for Spanish.
|
||||
|
||||
For supported language translations, please see the [languages folder (/lang)](https://github.com/videojs/video.js/tree/master/lang) folder located in the Video.js root and refer to the [languages guide](./languages.md) for more information on languages in Video.js.
|
||||
For supported language translations, please see the [languages folder (/lang)](https://github.com/videojs/video.js/tree/master/lang) folder located in the Video.js root and refer to the [languages guide](/docs/guides/languages.md) for more information on languages in Video.js.
|
||||
|
||||
#### `selected`
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
* [videojs()](#videojs)
|
||||
* [options](#options)
|
||||
* [getComponent()](#getcomponent)
|
||||
* [registerComponent](#registercomponent)
|
||||
* [registerComponent()](#registercomponent)
|
||||
* [getTech()](#gettech)
|
||||
* [registerTech](#registertech)
|
||||
* [registerTech()](#registertech)
|
||||
* [extend()](#extend)
|
||||
* [mergeOptions()](#mergeoptions)
|
||||
* [bind()](#bind)
|
||||
* [plugin()](#plugin)
|
||||
* [xhr](#xhr)
|
||||
* [xhr()](#xhr)
|
||||
|
||||
## `videojs()`
|
||||
|
||||
@@ -43,9 +43,9 @@ var VjsButton = videojs.getComponent('Button');
|
||||
// Subclass the component (see 'extend' doc for more info)
|
||||
var MySpecialButton = videojs.extend(VjsButton, {});
|
||||
// Register the new component
|
||||
VjsButton.registerComponent('MySepcialButton', MySepcialButton);
|
||||
videojs.registerComponent('MySpecialButton', MySpecialButton);
|
||||
// (optionally) add the new component as a default player child
|
||||
myPlayer.addChild('MySepcialButton');
|
||||
myPlayer.addChild('MySpecialButton');
|
||||
```
|
||||
|
||||
## `getTech()`
|
||||
@@ -63,7 +63,7 @@ var html5 = new Html5(options);
|
||||
var Html5 = videojs.getTech('Html5');
|
||||
var MyTech = videojs.extend(Html5, {});
|
||||
// Register the new Tech
|
||||
VjsButton.registerTech('Tech', MyTech);
|
||||
videojs.registerTech('MyTech', MyTech);
|
||||
var player = videojs('myplayer', {
|
||||
techOrder: ['myTech', 'html5']
|
||||
});
|
||||
@@ -128,7 +128,7 @@ videojs.bind(someObj, function() {
|
||||
|
||||
## `plugin()`
|
||||
|
||||
**See the [plugin guide](plugins.md) in the docs for a more detailed example**
|
||||
**See the [plugin guide](/docs/guides/plugins.md) in the docs for a more detailed example**
|
||||
|
||||
```js
|
||||
// Make a plugin that alerts when the player plays
|
||||
|
||||
+16
-16
@@ -1,6 +1,6 @@
|
||||
# [Video.js][vjs-website] Documentation
|
||||
|
||||
There are two categories of docs: [Guides](guides) and [API docs][api].
|
||||
There are two categories of docs: [Guides](/docs/guides/) and [API docs][api].
|
||||
|
||||
Guides explain general topics and use cases (e.g. setup). API docs are automatically generated from the codebase and give specific details about functions, properties, and events.
|
||||
|
||||
@@ -26,57 +26,57 @@ Guides explain general topics and use cases (e.g. setup). API docs are automatic
|
||||
|
||||
## Resolving Issues
|
||||
|
||||
### [FAQ](guides/faq.md)
|
||||
### [FAQ](/docs/guides/faq.md)
|
||||
|
||||
The frequently asked questions for video.js.
|
||||
|
||||
### [Troubleshooting](guides/troubleshooting.md)
|
||||
### [Troubleshooting](/docs/guides/troubleshooting.md)
|
||||
|
||||
Troubleshooting help for video.js.
|
||||
|
||||
## [Guides](guides)
|
||||
## [Guides](/docs/guides/)
|
||||
|
||||
### Getting Started
|
||||
|
||||
#### [Setup](guides/setup.md)
|
||||
#### [Setup](/docs/guides/setup.md)
|
||||
|
||||
The setup guide covers all methods of setting up Video.js players.
|
||||
|
||||
#### [Player Workflows](guides/player-workflows.md)
|
||||
#### [Player Workflows](/docs/guides/player-workflows.md)
|
||||
|
||||
After mastering the basics of setup move over to this guide for some more advanced player workflows.
|
||||
|
||||
#### [Options](guides/options.md)
|
||||
#### [Options](/docs/guides/options.md)
|
||||
|
||||
There are a number of options that can be used to change how the player behaves, starting with the HTML5 media options like autoplay and preload, and expanding to Video.js specific options.
|
||||
|
||||
#### [Tracks](guides/tracks.md)
|
||||
#### [Tracks](/docs/guides/tracks.md)
|
||||
|
||||
Tracks are used for displaying text information over a video, selecting different audio tracks for a video, or selecting different video tracks.
|
||||
|
||||
### Customizing
|
||||
|
||||
#### [Skins](guides/skins.md)
|
||||
#### [Skins](/docs/guides/skins.md)
|
||||
|
||||
You can change the look of the player across playback technologies just by editing a CSS file. The skins documentation gives you a intro to how the HTML and CSS of the default skin is put together. For a list of skins you can check the [video.js wiki][skins-list].
|
||||
|
||||
#### [Plugins](guides/plugins.md)
|
||||
#### [Plugins](/docs/guides/plugins.md)
|
||||
|
||||
You can package up interesting Video.js customizations and reuse them elsewhere. Find out how to build your own plugin or [use one created by someone else][plugins-list].
|
||||
|
||||
#### [Components](guides/components.md)
|
||||
#### [Components](/docs/guides/components.md)
|
||||
|
||||
Video.js is built around a collection of components. These are the building blocks of the player UI.
|
||||
|
||||
#### [Tech](guides/tech.md)
|
||||
#### [Tech](/docs/guides/tech.md)
|
||||
|
||||
A "tech" is the shorthand we're using to describe any video playback technology - be it HTML5 video, Flash, . Basically anything that has a unique API to audio or video. Additional playback technologies can be added relatively easily.
|
||||
|
||||
#### [Languages](guides/languages.md)
|
||||
#### [Languages](/docs/guides/languages.md)
|
||||
|
||||
Video.js has multi-language support! Follow this guide to see how you can contribute to and use languages.
|
||||
|
||||
#### [Hooks](guides/hooks.md)
|
||||
#### [Hooks](/docs/guides/hooks.md)
|
||||
|
||||
A "hook" is functionality that wants to do when videojs creates a player. Right now only `beforesetup` and `setup` are supported. See the guide for more information on that.
|
||||
|
||||
@@ -88,8 +88,8 @@ You can refer to the [full list of API docs][api], but the most relevant API doc
|
||||
|
||||
[skins-list]: https://github.com/videojs/video.js/wiki/Skins
|
||||
|
||||
[api]: http://docs.videojs.com/docs/api/index.html
|
||||
[api]: http://docs.videojs.com/
|
||||
|
||||
[api-player]: http://docs.videojs.com/docs/api/player.html
|
||||
[api-player]: http://docs.videojs.com/Player.html
|
||||
|
||||
[vjs-website]: http://videojs.com
|
||||
|
||||
+1276
-48
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+37
-1
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"Play": "Wiedergabe",
|
||||
"Pause": "Pause",
|
||||
"Replay": "Erneut abspielen",
|
||||
"Current Time": "Aktueller Zeitpunkt",
|
||||
"Duration Time": "Dauer",
|
||||
"Remaining Time": "Verbleibende Zeit",
|
||||
@@ -31,10 +32,45 @@
|
||||
", opens captions settings dialog": ", öffnet Einstellungen für Untertitel",
|
||||
", opens subtitles settings dialog": ", öffnet Einstellungen für Untertitel",
|
||||
", selected": ", ausgewählt",
|
||||
"captions settings": "Untertiteleinstellungen",
|
||||
"subtitles settings": "Untertiteleinstellungen",
|
||||
"descriptions settings": "Einstellungen für Beschreibungen",
|
||||
"Close Modal Dialog": "Modales Fenster schließen",
|
||||
"Descriptions": "Beschreibungen",
|
||||
"descriptions off": "Beschreibungen aus",
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "Die Entschlüsselungsschlüssel für den verschlüsselten Medieninhalt sind nicht verfügbar.",
|
||||
", opens descriptions settings dialog": ", öffnet Einstellungen für Beschreibungen",
|
||||
"Audio Track": "Tonspur"
|
||||
"Audio Track": "Tonspur",
|
||||
"Text": "Schrift",
|
||||
"White": "Weiß",
|
||||
"Black": "Schwarz",
|
||||
"Red": "Rot",
|
||||
"Green": "Grün",
|
||||
"Blue": "Blau",
|
||||
"Yellow": "Gelb",
|
||||
"Magenta": "Magenta",
|
||||
"Cyan": "Türkis",
|
||||
"Background": "Hintergrund",
|
||||
"Window": "Fenster",
|
||||
"Transparent": "Durchsichtig",
|
||||
"Semi-Transparent": "Halbdurchsichtig",
|
||||
"Opaque": "Undurchsictig",
|
||||
"Font Size": "Schriftgröße",
|
||||
"Text Edge Style": "Textkantenstil",
|
||||
"None": "Kein",
|
||||
"Raised": "Erhoben",
|
||||
"Depressed": "Gedrückt",
|
||||
"Uniform": "Uniform",
|
||||
"Dropshadow": "Schlagschatten",
|
||||
"Font Family": "Schristfamilie",
|
||||
"Proportional Sans-Serif": "Proportionale Sans-Serif",
|
||||
"Monospace Sans-Serif": "Monospace Sans-Serif",
|
||||
"Proportional Serif": "Proportionale Serif",
|
||||
"Monospace Serif": "Monospace Serif",
|
||||
"Casual": "Zwanglos",
|
||||
"Script": "Schreibeschrift",
|
||||
"Small Caps": "Small-Caps",
|
||||
"Done": "Fertig",
|
||||
"Caption Settings Dialog": "Einstellungsdialog für Untertitel",
|
||||
"Beginning of dialog window. Escape will cancel and close the window.": "Anfang des Dialogfensters. Esc bricht ab und schließt das Fenster."
|
||||
}
|
||||
|
||||
+46
-2
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"Audio Player": "Audio Player",
|
||||
"Video Player": "Video Player",
|
||||
"Play": "Play",
|
||||
"Pause": "Pause",
|
||||
"Replay": "Replay",
|
||||
"Current Time": "Current Time",
|
||||
"Duration Time": "Duration Time",
|
||||
"Remaining Time": "Remaining Time",
|
||||
@@ -8,6 +11,8 @@
|
||||
"LIVE": "LIVE",
|
||||
"Loaded": "Loaded",
|
||||
"Progress": "Progress",
|
||||
"Progress Bar": "Progress Bar",
|
||||
"progress bar timing: currentTime={1} duration={2}": "{1} of {2}",
|
||||
"Fullscreen": "Fullscreen",
|
||||
"Non-Fullscreen": "Non-Fullscreen",
|
||||
"Mute": "Mute",
|
||||
@@ -18,10 +23,10 @@
|
||||
"Captions": "Captions",
|
||||
"captions off": "captions off",
|
||||
"Chapters": "Chapters",
|
||||
"Close Modal Dialog": "Close Modal Dialog",
|
||||
"Descriptions": "Descriptions",
|
||||
"descriptions off": "descriptions off",
|
||||
"Audio Track": "Audio Track",
|
||||
"Volume Level": "Volume Level",
|
||||
"You aborted the media playback": "You aborted the media playback",
|
||||
"A network error caused the media download to fail part-way.": "A network error caused the media download to fail part-way.",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "The media could not be loaded, either because the server or network failed or because the format is not supported.",
|
||||
@@ -30,11 +35,50 @@
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "The media is encrypted and we do not have the keys to decrypt it.",
|
||||
"Play Video": "Play Video",
|
||||
"Close": "Close",
|
||||
"Close Modal Dialog": "Close Modal Dialog",
|
||||
"Modal Window": "Modal Window",
|
||||
"This is a modal window": "This is a modal window",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "This modal can be closed by pressing the Escape key or activating the close button.",
|
||||
", opens captions settings dialog": ", opens captions settings dialog",
|
||||
", opens subtitles settings dialog": ", opens subtitles settings dialog",
|
||||
", opens descriptions settings dialog": ", opens descriptions settings dialog",
|
||||
", selected": ", selected"
|
||||
", selected": ", selected",
|
||||
"captions settings": "captions settings",
|
||||
"subtitles settings": "subititles settings",
|
||||
"descriptions settings": "descriptions settings",
|
||||
"Text": "Text",
|
||||
"White": "White",
|
||||
"Black": "Black",
|
||||
"Red": "Red",
|
||||
"Green": "Green",
|
||||
"Blue": "Blue",
|
||||
"Yellow": "Yellow",
|
||||
"Magenta": "Magenta",
|
||||
"Cyan": "Cyan",
|
||||
"Background": "Background",
|
||||
"Window": "Window",
|
||||
"Transparent": "Transparent",
|
||||
"Semi-Transparent": "Semi-Transparent",
|
||||
"Opaque": "Opaque",
|
||||
"Font Size": "Font Size",
|
||||
"Text Edge Style": "Text Edge Style",
|
||||
"None": "None",
|
||||
"Raised": "Raised",
|
||||
"Depressed": "Depressed",
|
||||
"Uniform": "Uniform",
|
||||
"Dropshadow": "Dropshadow",
|
||||
"Font Family": "Font Family",
|
||||
"Proportional Sans-Serif": "Proportional Sans-Serif",
|
||||
"Monospace Sans-Serif": "Monospace Sans-Serif",
|
||||
"Proportional Serif": "Proportional Serif",
|
||||
"Monospace Serif": "Monospace Serif",
|
||||
"Casual": "Casual",
|
||||
"Script": "Script",
|
||||
"Small Caps": "Small Caps",
|
||||
"Reset": "Reset",
|
||||
"all settings to the default values": "all settings to the default values",
|
||||
"Done": "Done",
|
||||
"Caption Settings Dialog": "Caption Settings Dialog",
|
||||
"Beginning of dialog window. Escape will cancel and close the window.": "Beginning of dialog window. Escape will cancel and close the window.",
|
||||
"End of dialog window.": "End of dialog window."
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"Play": "Reproducción",
|
||||
"Play Video": "Reproducción Vídeo",
|
||||
"Pause": "Pausa",
|
||||
"Current Time": "Tiempo reproducido",
|
||||
"Duration Time": "Duración total",
|
||||
|
||||
+46
-2
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"Audio Player": "Lecteur audio",
|
||||
"Video Player": "Lecteur vidéo",
|
||||
"Play": "Lecture",
|
||||
"Pause": "Pause",
|
||||
"Replay": "Revoir",
|
||||
"Current Time": "Temps actuel",
|
||||
"Duration Time": "Durée",
|
||||
"Remaining Time": "Temps restant",
|
||||
@@ -8,6 +11,8 @@
|
||||
"LIVE": "EN DIRECT",
|
||||
"Loaded": "Chargé",
|
||||
"Progress": "Progression",
|
||||
"Progress Bar": "Barre de progression",
|
||||
"progress bar timing: currentTime={1} duration={2}": "{1} de {2}",
|
||||
"Fullscreen": "Plein écran",
|
||||
"Non-Fullscreen": "Fenêtré",
|
||||
"Mute": "Sourdine",
|
||||
@@ -18,10 +23,10 @@
|
||||
"Captions": "Sous-titres transcrits",
|
||||
"captions off": "Sous-titres transcrits désactivés",
|
||||
"Chapters": "Chapitres",
|
||||
"Close Modal Dialog": "Fermer la boîte de dialogue modale",
|
||||
"Descriptions": "Descriptions",
|
||||
"descriptions off": "descriptions désactivées",
|
||||
"Audio Track": "Piste audio",
|
||||
"Volume Level": "Niveau de volume",
|
||||
"You aborted the media playback": "Vous avez interrompu la lecture de la vidéo.",
|
||||
"A network error caused the media download to fail part-way.": "Une erreur de réseau a interrompu le téléchargement de la vidéo.",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Cette vidéo n'a pas pu être chargée, soit parce que le serveur ou le réseau a échoué ou parce que le format n'est pas reconnu.",
|
||||
@@ -30,11 +35,50 @@
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "Le média est chiffré et nous n'avons pas les clés pour le déchiffrer.",
|
||||
"Play Video": "Lire la vidéo",
|
||||
"Close": "Fermer",
|
||||
"Close Modal Dialog": "Fermer la boîte de dialogue modale",
|
||||
"Modal Window": "Fenêtre modale",
|
||||
"This is a modal window": "Ceci est une fenêtre modale",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "Ce modal peut être fermé en appuyant sur la touche Échap ou activer le bouton de fermeture.",
|
||||
", opens captions settings dialog": ", ouvrir les paramètres des sous-titres transcrits",
|
||||
", opens subtitles settings dialog": ", ouvrir les paramètres des sous-titres",
|
||||
", opens descriptions settings dialog": ", ouvrir les paramètres des descriptions",
|
||||
", selected": ", sélectionné"
|
||||
", selected": ", sélectionné",
|
||||
"captions settings": "Paramètres des sous-titres transcrits",
|
||||
"subtitles settings": "Paramètres des sous-titres",
|
||||
"descriptions settings": "Paramètres des descriptions",
|
||||
"Text": "Texte",
|
||||
"White": "Blanc",
|
||||
"Black": "Noir",
|
||||
"Red": "Rouge",
|
||||
"Green": "Vert",
|
||||
"Blue": "Bleu",
|
||||
"Yellow": "Jaune",
|
||||
"Magenta": "Magenta",
|
||||
"Cyan": "Cyan",
|
||||
"Background": "Arrière-plan",
|
||||
"Window": "Fenêtre",
|
||||
"Transparent": "Transparent",
|
||||
"Semi-Transparent": "Semi-transparent",
|
||||
"Opaque": "Opaque",
|
||||
"Font Size": "Taille des caractères",
|
||||
"Text Edge Style": "Style des contours du texte",
|
||||
"None": "Aucun",
|
||||
"Raised": "Élevé",
|
||||
"Depressed": "Enfoncé",
|
||||
"Uniform": "Uniforme",
|
||||
"Dropshadow": "Ombre portée",
|
||||
"Font Family": "Famille de polices",
|
||||
"Proportional Sans-Serif": "Polices à chasse variable sans empattement (Proportional Sans-Serif)",
|
||||
"Monospace Sans-Serif": "Polices à chasse fixe sans empattement (Monospace Sans-Serif)",
|
||||
"Proportional Serif": "Polices à chasse variable avec empattement (Proportional Serif)",
|
||||
"Monospace Serif": "Polices à chasse fixe avec empattement (Monospace Serif)",
|
||||
"Casual": "Manuscrite",
|
||||
"Script": "Scripte",
|
||||
"Small Caps": "Petites capitales",
|
||||
"Reset": "Réinitialiser",
|
||||
"all settings to the default values": "tous les paramètres aux valeurs par défaut",
|
||||
"Done": "Terminé",
|
||||
"Caption Settings Dialog": "Boîte de dialogue des paramètres des sous-titres transcrits",
|
||||
"Beginning of dialog window. Escape will cancel and close the window.": "Début de la fenêtre de dialogue. La touche d'échappement annulera et fermera la fenêtre.",
|
||||
"End of dialog window.": "Fin de la fenêtre de dialogue."
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"Play": "Reproduzir",
|
||||
"Pause": "Parar",
|
||||
"Replay": "Reiniciar",
|
||||
"Current Time": "Tempo Atual",
|
||||
"Duration Time": "Duração",
|
||||
"Remaining Time": "Tempo Restante",
|
||||
"Stream Type": "Tipo de Stream",
|
||||
"LIVE": "EM DIRETO",
|
||||
"Loaded": "Carregado",
|
||||
"Progress": "Progresso",
|
||||
"Fullscreen": "Ecrã inteiro",
|
||||
"Non-Fullscreen": "Ecrã normal",
|
||||
"Mute": "Desativar som",
|
||||
"Unmute": "Ativar som",
|
||||
"Playback Rate": "Velocidade de reprodução",
|
||||
"Subtitles": "Legendas",
|
||||
"subtitles off": "desativar legendas",
|
||||
"Captions": "Anotações",
|
||||
"captions off": "desativar anotações",
|
||||
"Chapters": "Capítulos",
|
||||
"Close Modal Dialog": "Fechar Janela Modal",
|
||||
"Descriptions": "Descrições",
|
||||
"descriptions off": "desativar descrições",
|
||||
"Audio Track": "Faixa Áudio",
|
||||
"You aborted the media playback": "Parou a reprodução do vídeo.",
|
||||
"A network error caused the media download to fail part-way.": "Um erro na rede fez o vídeo falhar parcialmente.",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "O vídeo não pode ser carregado, ou porque houve um problema na rede ou no servidor, ou porque formato do vídeo não é compatível.",
|
||||
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "A reprodução foi interrompida por um problema com o vídeo ou porque o formato não é compatível com o seu navegador.",
|
||||
"No compatible source was found for this media.": "Não foi encontrada uma fonte de vídeo compatível.",
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "O vídeo está encriptado e não há uma chave para o desencriptar.",
|
||||
"Play Video": "Reproduzir Vídeo",
|
||||
"Close": "Fechar",
|
||||
"Modal Window": "Janela Modal",
|
||||
"This is a modal window": "Isto é uma janela modal",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "Esta modal pode ser fechada pressionando a tecla ESC ou ativando o botão de fechar.",
|
||||
", opens captions settings dialog": ", abre janela com definições de legendas",
|
||||
", opens subtitles settings dialog": ", abre janela com definições de legendas",
|
||||
", opens descriptions settings dialog": ", abre janela com definições de descrições",
|
||||
", selected": ", seleccionado"
|
||||
}
|
||||
+46
-4
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"Play": "Oynat",
|
||||
"Pause": "Duraklat",
|
||||
"Replay": "Yeniden Oynat",
|
||||
"Current Time": "Süre",
|
||||
"Duration Time": "Toplam Süre",
|
||||
"Remaining Time": "Kalan Süre",
|
||||
@@ -15,20 +16,61 @@
|
||||
"Playback Rate": "Oynatma Hızı",
|
||||
"Subtitles": "Altyazı",
|
||||
"subtitles off": "Altyazı Kapalı",
|
||||
"Captions": "Ek Açıklamalar",
|
||||
"captions off": "Ek Açıklamalar Kapalı",
|
||||
"Captions": "Altyazı",
|
||||
"captions off": "Altyazı Kapalı",
|
||||
"Chapters": "Bölümler",
|
||||
"Close Modal Dialog": "Dialogu Kapat",
|
||||
"Descriptions": "Açıklamalar",
|
||||
"descriptions off": "Açıklamalar kapalı",
|
||||
"Audio Track": "Ses Dosyası",
|
||||
"You aborted the media playback": "Video oynatmayı iptal ettiniz",
|
||||
"A network error caused the media download to fail part-way.": "Video indirilirken bağlantı sorunu oluştu.",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Video oynatılamadı, ağ ya da sunucu hatası veya belirtilen format desteklenmiyor.",
|
||||
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Tarayıcınız desteklemediği için videoda hata oluştu.",
|
||||
"No compatible source was found for this media.": "Video için kaynak bulunamadı.",
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "Video, şifrelenmiş bir kaynaktan geliyor ve oynatmak için gerekli anahtar bulunamadı.",
|
||||
"Play Video": "Videoyu Oynat",
|
||||
"Close": "Kapat",
|
||||
"Modal Window": "Modal Penceresi",
|
||||
"This is a modal window": "Bu bir modal penceresidir",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "Bu modal ESC tuşuna basarak ya da kapata tıklanarak kapatılabilir.",
|
||||
", opens captions settings dialog": ", ek açıklama ayarları menüsünü açar",
|
||||
", opens captions settings dialog": ", altyazı ayarları menüsünü açar",
|
||||
", opens subtitles settings dialog": ", altyazı ayarları menüsünü açar",
|
||||
", selected": ", seçildi"
|
||||
", opens descriptions settings dialog": ", açıklama ayarları menüsünü açar",
|
||||
", selected": ", seçildi",
|
||||
"captions settings": "altyazı ayarları",
|
||||
"subtitles settings": "altyazı ayarları",
|
||||
"descriptions settings": "açıklama ayarları",
|
||||
"Text": "Yazı",
|
||||
"White": "Beyaz",
|
||||
"Black": "Siyah",
|
||||
"Red": "Kırmızı",
|
||||
"Green": "Yeşil",
|
||||
"Blue": "Mavi",
|
||||
"Yellow": "Sarı",
|
||||
"Magenta": "Macenta",
|
||||
"Cyan": "Açık Mavi (Camgöbeği)",
|
||||
"Background": "Arka plan",
|
||||
"Window": "Pencere",
|
||||
"Transparent": "Saydam",
|
||||
"Semi-Transparent": "Yarı-Saydam",
|
||||
"Opaque": "Mat",
|
||||
"Font Size": "Yazı Boyutu",
|
||||
"Text Edge Style": "Yazı Kenarlıkları",
|
||||
"None": "Hiçbiri",
|
||||
"Raised": "Kabartılmış",
|
||||
"Depressed": "Yassı",
|
||||
"Uniform": "Düz",
|
||||
"Dropshadow": "Gölgeli",
|
||||
"Font Family": "Yazı Tipi",
|
||||
"Proportional Sans-Serif": "Orantılı Sans-Serif",
|
||||
"Monospace Sans-Serif": "Eşaralıklı Sans-Serif",
|
||||
"Proportional Serif": "Orantılı Serif",
|
||||
"Monospace Serif": "Eşaralıklı Serif",
|
||||
"Casual": "Gündelik",
|
||||
"Script": "El Yazısı",
|
||||
"Small Caps": "Küçük Boyutlu Büyük Harfli",
|
||||
"Done": "Tamam",
|
||||
"Caption Settings Dialog": "Altyazı Ayarları Menüsü",
|
||||
"Beginning of dialog window. Escape will cancel and close the window.": "Diyalog penceresinin başlangıcı. ESC tuşu işlemi iptal edip pencereyi kapatacaktır."
|
||||
}
|
||||
|
||||
+21
-6
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"Play": "Phát",
|
||||
"Pause": "Tạm dừng",
|
||||
"Replay": "Phát lại",
|
||||
"Current Time": "Thời gian hiện tại",
|
||||
"Duration Time": "Độ dài",
|
||||
"Remaining Time": "Thời gian còn lại",
|
||||
@@ -12,15 +13,29 @@
|
||||
"Non-Fullscreen": "Thoát toàn màn hình",
|
||||
"Mute": "Tắt tiếng",
|
||||
"Unmute": "Bật âm thanh",
|
||||
"Playback Rate": "Tốc độ phát",
|
||||
"Playback Rate": "Tỉ lệ phát lại",
|
||||
"Subtitles": "Phụ đề",
|
||||
"subtitles off": "Tắt phụ đề",
|
||||
"subtitles off": "tắt phụ đề",
|
||||
"Captions": "Chú thích",
|
||||
"captions off": "Tắt chú thích",
|
||||
"captions off": "tắt chú thích",
|
||||
"Chapters": "Chương",
|
||||
"You aborted the media playback": "Bạn đã hủy việc phát media.",
|
||||
"Close Modal Dialog": "Đóng hộp thoại",
|
||||
"Descriptions": "Mô tả",
|
||||
"descriptions off": "tắt mô tả",
|
||||
"Audio Track": "Track âm thanh",
|
||||
"You aborted the media playback": "Bạn đã hủy việc phát lại media.",
|
||||
"A network error caused the media download to fail part-way.": "Một lỗi mạng dẫn đến việc tải media bị lỗi.",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Video không tải được, mạng hay server có lỗi hoặc định dạng không được hỗ trợ.",
|
||||
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Phát media đã bị hủy do một sai lỗi hoặc media sử dụng những tính năng trình duyệt không hỗ trợ.",
|
||||
"No compatible source was found for this media.": "Không có nguồn tương thích cho media này."
|
||||
}
|
||||
"No compatible source was found for this media.": "Không có nguồn tương thích cho media này.",
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "Media đã được mã hóa và chúng tôi không thể giải mã nó.",
|
||||
"Play Video": "Phát Video",
|
||||
"Close": "Đóng",
|
||||
"Modal Window": "Cửa sổ",
|
||||
"This is a modal window": "Đây là một cửa sổ",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "Cửa sổ này có thể đóng lại bằng việc nhấn phím Esc hoặc kích hoạt nút đóng.",
|
||||
", opens captions settings dialog": ", mở hộp thoại thiết lập chú thích",
|
||||
", opens subtitles settings dialog": ", mở hộp thoại thiết lập phụ đề",
|
||||
", opens descriptions settings dialog": ", mở hộp thoại thiết lập mô tả",
|
||||
", selected": ", đã chọn"
|
||||
}
|
||||
+35
-22
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"version": "5.15.0",
|
||||
"version": "6.0.0-RC.8",
|
||||
"main": "./es5/video.js",
|
||||
"style": "./dist/video-js.css",
|
||||
"copyright": "Copyright Brightcove, Inc. <https://www.brightcove.com/>",
|
||||
@@ -22,12 +22,19 @@
|
||||
"clean": "grunt clean",
|
||||
"grunt": "grunt",
|
||||
"lint": "vjsstandard",
|
||||
"start": "grunt watchAll",
|
||||
"start": "grunt dev",
|
||||
"test": "grunt test",
|
||||
"docs": "npm run docs:lint && npm run docs:api",
|
||||
"docs:api": "jsdoc -r src/js -d docs/api -c .jsdoc.json",
|
||||
"jsdoc": "jsdoc",
|
||||
"predocs:api": "node -e \"var s=require('shelljs'),d=['docs/api'];s.rm('-rf',d);\"",
|
||||
"docs:api": "jsdoc -c .jsdoc.json",
|
||||
"postdocs:api": "node ./build/fix-api-docs.js",
|
||||
"docs:lint": "remark -- './**/*.md'",
|
||||
"docs:fix": "remark --output -- './**/*.md'"
|
||||
"docs:fix": "remark --output -- './**/*.md'",
|
||||
"babel": "babel src/js -d es5",
|
||||
"prepublish": "not-in-install && run-p docs:api build || in-install",
|
||||
"prepush": "npm run lint -- --errors",
|
||||
"version": "node build/version.js && git add CHANGELOG.md"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -40,17 +47,16 @@
|
||||
"tsml": "1.0.1",
|
||||
"videojs-font": "2.0.0",
|
||||
"videojs-ie8": "1.1.2",
|
||||
"videojs-swf": "5.1.0",
|
||||
"videojs-vtt.js": "0.12.1",
|
||||
"xhr": "2.2.2"
|
||||
"videojs-vtt.js": "0.12.3",
|
||||
"xhr": "2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aliasify": "^2.1.0",
|
||||
"babel-cli": "^6.11.4",
|
||||
"babel-plugin-inline-json": "^1.1.1",
|
||||
"babel-plugin-transform-es3-member-expression-literals": "^6.8.0",
|
||||
"babel-plugin-transform-es3-property-literals": "^6.8.0",
|
||||
"babel-plugin-transform-runtime": "^6.9.0",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"babel-preset-es3": "^1.0.1",
|
||||
"babel-register": "^6.9.0",
|
||||
"babelify": "^7.3.0",
|
||||
"blanket": "^1.1.6",
|
||||
@@ -63,8 +69,6 @@
|
||||
"conventional-changelog-videojs": "^3.0.0",
|
||||
"es5-shim": "^4.1.3",
|
||||
"es6-shim": "^0.35.1",
|
||||
"ghooks": "^1.3.2",
|
||||
"gkatsev-grunt-sass": "^1.1.1",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-accessibility": "^5.0.0",
|
||||
"grunt-babel": "^6.0.0",
|
||||
@@ -83,13 +87,16 @@
|
||||
"grunt-fastly": "^0.1.3",
|
||||
"grunt-github-releaser": "^0.1.17",
|
||||
"grunt-karma": "^2.0.0",
|
||||
"grunt-sass": "^2.0.0",
|
||||
"grunt-shell": "^2.0.0",
|
||||
"grunt-version": "~1.1.1",
|
||||
"grunt-videojs-languages": "0.0.4",
|
||||
"grunt-zip": "0.17.1",
|
||||
"husky": "^0.13.1",
|
||||
"in-publish": "^2.0.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"jsdoc": "^3.4.2",
|
||||
"karma": "^1.2.0",
|
||||
"karma": "1.3.0",
|
||||
"karma-browserify": "^5.1.0",
|
||||
"karma-browserstack-launcher": "^1.0.1",
|
||||
"karma-chrome-launcher": "^2.0.0",
|
||||
@@ -105,18 +112,23 @@
|
||||
"lodash": "^4.16.6",
|
||||
"markdown-table": "^1.0.0",
|
||||
"npm-run": "^4.1.0",
|
||||
"npm-run-all": "^4.0.2",
|
||||
"proxyquireify": "^3.0.0",
|
||||
"qunitjs": "1.23.1",
|
||||
"remark-cli": "^2.1.0",
|
||||
"remark-lint": "^5.2.0",
|
||||
"remark-toc": "^3.1.0",
|
||||
"remark-validate-links": "^5.0.0",
|
||||
"remark-cli": "^3.0.0",
|
||||
"remark-lint": "^6.0.0",
|
||||
"remark-toc": "^4.0.0",
|
||||
"remark-validate-links": "^6.0.0",
|
||||
"replace": "^0.3.0",
|
||||
"shelljs": "^0.7.5",
|
||||
"sinon": "^1.16.1",
|
||||
"time-grunt": "^1.1.1",
|
||||
"uglify-js": "~2.7.3",
|
||||
"tui-jsdoc-template": "^1.1.0",
|
||||
"uglify-js": "~2.8.8",
|
||||
"videojs-doc-generator": "0.0.1",
|
||||
"videojs-flash": "^1.0.0-RC.0",
|
||||
"videojs-standard": "^6.0.1",
|
||||
"webpack": "^1.13.2"
|
||||
"webpack": "^2.3.0"
|
||||
},
|
||||
"vjsstandard": {
|
||||
"ignore": [
|
||||
@@ -132,9 +144,10 @@
|
||||
"**/test/karma.conf.js"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"ghooks": {
|
||||
"pre-push": "npm run lint -- --errors"
|
||||
}
|
||||
"greenkeeper": {
|
||||
"ignore": [
|
||||
"qunitjs",
|
||||
"sinon"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-GB">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Video.js Sandbox</title>
|
||||
|
||||
<!-- Add ES5 shim and sham for IE8 -->
|
||||
<script src="../build/temp/ie8/videojs-ie8.js"></script>
|
||||
|
||||
<!-- Load the source files -->
|
||||
<link href="../build/temp/video-js.css" rel="stylesheet" type="text/css">
|
||||
<script src="../build/temp/video.js"></script>
|
||||
<script src="../node_modules/videojs-flash/dist/videojs-flash.js"></script>
|
||||
|
||||
<!-- Set the location of the flash SWF -->
|
||||
<script>
|
||||
videojs.options.flash.swf = '../build/temp/video-js.swf';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-color:#eee; border: 1px solid #777; padding: 10px; margin-bottom: 20px; font-size: .8em; line-height: 1.5em; font-family: Verdana, sans-serif;">
|
||||
<p>You can use /sandbox/ for writing and testing your own code. Nothing in /sandbox/ will get checked into the repo, except files that end in .example (so don't edit or add those files). To get started make a copy of index.html.example and rename it to index.html.</p>
|
||||
<pre>cp sandbox/index.html.example sandbox/index.html</pre>
|
||||
<pre>npm run start</pre>
|
||||
<pre>open http://localhost:9999/sandbox/index.html</pre>
|
||||
</div>
|
||||
|
||||
<video id="vid1" class="video-js vjs-default-skin" lang="en" controls preload="auto" width="640" height="360" poster="//d2zihajmogu5jn.cloudfront.net/elephantsdream/poster.png">
|
||||
<source src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/ed_hd.mp4" type="video/mp4">
|
||||
<source src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/ed_hd.ogg" type="video/ogg">
|
||||
<track kind="captions" src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/captions.en.vtt" srclang="en" label="English"></track><!-- Tracks need an ending tag thanks to IE9 -->
|
||||
<track kind="subtitles" src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/captions.ar.vtt" srclang="ar" label="Arabic"></track><!-- Tracks need an ending tag thanks to IE9 -->
|
||||
<track kind="subtitles" src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/captions.sv.vtt" srclang="sv" label="Swedish"></track><!-- Tracks need an ending tag thanks to IE9 -->
|
||||
<track kind="subtitles" src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/captions.ru.vtt" srclang="ru" label="Russian"></track><!-- Tracks need an ending tag thanks to IE9 -->
|
||||
<track kind="subtitles" src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/captions.ja.vtt" srclang="ja" label="Japanese"></track><!-- Tracks need an ending tag thanks to IE9 -->
|
||||
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
|
||||
</video>
|
||||
<p>This player has the captions-only captionsButton, the subtiles-only subtitlesButton and the subsCapsButton which shows both kinds. Typically you'll use either just the subsCapsButton alone, or one or both of the captionsButton and subtitlesButton.</p>
|
||||
<script>
|
||||
var vid = document.getElementById("vid1");
|
||||
var player = videojs(vid, {
|
||||
controlBar: {
|
||||
children: [
|
||||
'playToggle',
|
||||
'volumePanel',
|
||||
'currentTimeDisplay',
|
||||
'timeDivider',
|
||||
'durationDisplay',
|
||||
'progressControl',
|
||||
'liveDisplay',
|
||||
'remainingTimeDisplay',
|
||||
'customControlSpacer',
|
||||
'playbackRateMenuButton',
|
||||
'chaptersButton',
|
||||
'descriptionsButton',
|
||||
'subtitlesButton',
|
||||
'captionsButton',
|
||||
'subsCapsButton',
|
||||
'audioTrackButton',
|
||||
'fullscreenToggle'
|
||||
]
|
||||
}
|
||||
});
|
||||
console.log(player.language());
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -24,8 +24,8 @@
|
||||
useful way! -->
|
||||
<video id="example_video_1" class="video-js vjs-default-skin" controls preload="none" width="640" height="360"
|
||||
data-setup='{ "html5" : { "nativeTextTracks" : false } }'>
|
||||
<source src="https://archive.org/download/ElephantsDream/ed_hd.mp4" type="video/mp4">
|
||||
<source src="https://archive.org/download/ElephantsDream/ed_hd.ogv" type="video/ogv">
|
||||
<source src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/ed_hd.mp4" type="video/mp4">
|
||||
<source src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/ed_hd.ogg" type="video/ogg">
|
||||
|
||||
<track kind="captions" src="../docs/examples/elephantsdream/captions.en.vtt" srclang="en" label="English" default></track><!-- Tracks need an ending tag thanks to IE9 -->
|
||||
<track kind="captions" src="../docs/examples/elephantsdream/captions.sv.vtt" srclang="sv" label="Swedish"></track>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Video.js Sandbox</title>
|
||||
|
||||
<!-- Add ES5 shim and sham for IE8 -->
|
||||
<script src="../build/temp/ie8/videojs-ie8.js"></script>
|
||||
|
||||
<!-- Load the source files -->
|
||||
<link href="../build/temp/video-js.css" rel="stylesheet" type="text/css">
|
||||
<script src="../build/temp/video.js"></script>
|
||||
<script src="../node_modules/videojs-flash/dist/videojs-flash.js"></script>
|
||||
|
||||
<!-- Set the location of the flash SWF -->
|
||||
<script>
|
||||
videojs.options.flash.swf = '../build/temp/video-js.swf';
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-color:#eee; border: 1px solid #777; padding: 10px; margin-bottom: 20px; font-size: .8em; line-height: 1.5em; font-family: Verdana, sans-serif;">
|
||||
<p>You can use /sandbox/ for writing and testing your own code. Nothing in /sandbox/ will get checked into the repo, except files that end in .example (so don't edit or add those files). To get started make a copy of index.html.example and rename it to index.html.</p>
|
||||
<pre>cp sandbox/flash.html.example sandbox/flash.html</pre>
|
||||
<pre>npm run start</pre>
|
||||
<pre>open http://localhost:9999/sandbox/flash.html</pre>
|
||||
</div>
|
||||
|
||||
<video id="vid1" class="video-js vjs-default-skin" controls preload="auto" width="640" height="264"
|
||||
poster="http://vjs.zencdn.net/v/oceans.png"
|
||||
data-setup=''>
|
||||
<source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
|
||||
<source src="http://vjs.zencdn.net/v/oceans.webm" type="video/webm">
|
||||
<source src="http://vjs.zencdn.net/v/oceans.ogv" type="video/ogg">
|
||||
<track kind="captions" src="../docs/examples/shared/example-captions.vtt" srclang="en" label="English"></track><!-- Tracks need an ending tag thanks to IE9 -->
|
||||
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
|
||||
</video>
|
||||
|
||||
<script>
|
||||
var vid = document.getElementById("vid1");
|
||||
var player = videojs(vid, {techOrder: ['flash', 'html5']});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -10,6 +10,7 @@
|
||||
<!-- Load the source files -->
|
||||
<link href="../build/temp/video-js.css" rel="stylesheet" type="text/css">
|
||||
<script src="../build/temp/video.js"></script>
|
||||
<script src="../node_modules/videojs-flash/dist/videojs-flash.js"></script>
|
||||
|
||||
<!-- Set the location of the flash SWF -->
|
||||
<script>
|
||||
@@ -21,8 +22,7 @@
|
||||
<div style="background-color:#eee; border: 1px solid #777; padding: 10px; margin-bottom: 20px; font-size: .8em; line-height: 1.5em; font-family: Verdana, sans-serif;">
|
||||
<p>You can use /sandbox/ for writing and testing your own code. Nothing in /sandbox/ will get checked into the repo, except files that end in .example (so don't edit or add those files). To get started make a copy of index.html.example and rename it to index.html.</p>
|
||||
<pre>cp sandbox/index.html.example sandbox/index.html</pre>
|
||||
<pre>grunt watch</pre>
|
||||
<pre>grunt connect</pre>
|
||||
<pre>npm run start</pre>
|
||||
<pre>open http://localhost:9999/sandbox/index.html</pre>
|
||||
</div>
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
|
||||
<script>
|
||||
var vid = document.getElementById("vid1");
|
||||
var player = videojs(vid);
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
@mixin transition($string: $transition--default) {
|
||||
-webkit-transition: $string;
|
||||
-moz-transition: $string;
|
||||
-ms-transition: $string;
|
||||
-o-transition: $string;
|
||||
transition: $string;
|
||||
}
|
||||
@@ -24,7 +25,6 @@
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
.vjs-current-time, .vjs-time-divider, .vjs-duration, .vjs-remaining-time,
|
||||
.vjs-playback-rate, .vjs-progress-control,
|
||||
.vjs-mute-control, .vjs-volume-control, .vjs-volume-menu-button,
|
||||
.vjs-mute-control, .vjs-volume-control,
|
||||
.vjs-chapters-button, .vjs-descriptions-button, .vjs-captions-button,
|
||||
.vjs-subtitles-button, .vjs-audio-button { display: none; }
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
.video-js.vjs-layout-x-small:not(.vjs-fullscreen) {
|
||||
.vjs-current-time, .vjs-time-divider, .vjs-duration, .vjs-remaining-time,
|
||||
.vjs-playback-rate,
|
||||
.vjs-mute-control, .vjs-volume-control, .vjs-volume-menu-button,
|
||||
.vjs-mute-control, .vjs-volume-control,
|
||||
.vjs-chapters-button, .vjs-descriptions-button, .vjs-captions-button,
|
||||
.vjs-subtitles-button, .vjs-audio-button { display: none; }
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.video-js .vjs-audio-button {
|
||||
.video-js .vjs-audio-button .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-audio;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
@include border-radius(0.3em);
|
||||
@include transition(all 0.4s);
|
||||
|
||||
@extend .vjs-icon-play;
|
||||
|
||||
// Since the big play button doesn't inherit from vjs-control, we need to specify a bit more than
|
||||
// other buttons for the icon.
|
||||
&:before {
|
||||
& .vjs-icon-placeholder:before {
|
||||
@extend .vjs-icon-play;
|
||||
|
||||
@extend %icon-default;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,6 @@
|
||||
|
||||
.video-js:hover .vjs-big-play-button,
|
||||
.video-js .vjs-big-play-button:focus {
|
||||
outline: 0;
|
||||
border-color: $primary-foreground-color;
|
||||
|
||||
@include background-color-with-alpha($secondary-background-color, $secondary-background-transparency);
|
||||
@@ -50,3 +49,8 @@
|
||||
.vjs-error .vjs-big-play-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Show big play button if video is paused and .vjs-show-big-play-button-on-pause is set on video element
|
||||
.vjs-has-started.vjs-paused.vjs-show-big-play-button-on-pause .vjs-big-play-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -1,86 +1,50 @@
|
||||
.vjs-caption-settings {
|
||||
position: relative;
|
||||
top: 1em;
|
||||
.vjs-modal-dialog.vjs-text-track-settings {
|
||||
background-color: $primary-background-color;
|
||||
background-color: rgba($primary-background-color, 0.75);
|
||||
color: $primary-foreground-color;
|
||||
margin: 0 auto;
|
||||
padding: 0.5em;
|
||||
height: 16em;
|
||||
font-size: 12px;
|
||||
width: 40em;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
.vjs-caption-settings .vjs-tracksettings {
|
||||
top: 0;
|
||||
bottom: 1em;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
overflow: auto;
|
||||
// Layout divs
|
||||
.vjs-text-track-settings .vjs-modal-dialog-content {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.vjs-caption-settings .vjs-tracksettings-colors,
|
||||
.vjs-caption-settings .vjs-tracksettings-font {
|
||||
float: left;
|
||||
}
|
||||
.vjs-caption-settings .vjs-tracksettings-colors:after,
|
||||
.vjs-caption-settings .vjs-tracksettings-font:after,
|
||||
.vjs-caption-settings .vjs-tracksettings-controls:after {
|
||||
clear: both;
|
||||
.vjs-text-track-settings .vjs-track-settings-colors,
|
||||
.vjs-text-track-settings .vjs-track-settings-font,
|
||||
.vjs-text-track-settings .vjs-track-settings-controls {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.vjs-caption-settings .vjs-tracksettings-controls {
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
right: 1em;
|
||||
.vjs-text-track-settings .vjs-track-settings-controls {
|
||||
text-align: right;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.vjs-caption-settings .vjs-tracksetting {
|
||||
// Form elements
|
||||
.vjs-text-track-settings fieldset {
|
||||
margin: 5px;
|
||||
padding: 3px;
|
||||
min-height: 40px;
|
||||
border: none;
|
||||
}
|
||||
.vjs-caption-settings .vjs-tracksetting label,
|
||||
.vjs-caption-settings .vjs-tracksetting legend {
|
||||
display: block;
|
||||
width: 100px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.vjs-caption-settings .vjs-tracksetting span {
|
||||
display: inline;
|
||||
.vjs-text-track-settings fieldset span {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
vertical-align: top;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.vjs-caption-settings .vjs-tracksetting > div {
|
||||
margin-bottom: 5px;
|
||||
min-height: 20px;
|
||||
.vjs-text-track-settings legend {
|
||||
color: $primary-foreground-color;
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.vjs-caption-settings .vjs-tracksetting > div:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.vjs-caption-settings label > input {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.vjs-caption-settings fieldset {
|
||||
margin-top: 1em;
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
// Hide labels within fieldsets, so they are only for screen reader users
|
||||
.vjs-caption-settings fieldset .vjs-label {
|
||||
// Hide labels, so they are only for screen reader users
|
||||
.vjs-text-track-settings .vjs-label {
|
||||
position: absolute;
|
||||
clip: rect(1px 1px 1px 1px); /* for Internet Explorer */
|
||||
clip: rect(1px 1px 1px 1px); // for Internet Explorer
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
display: block;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
height: 1px;
|
||||
@@ -88,7 +52,25 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vjs-caption-settings input[type="button"] {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
.vjs-track-settings-controls button:focus,
|
||||
.vjs-track-settings-controls button:active {
|
||||
outline-style: solid;
|
||||
outline-width: medium;
|
||||
background-image: linear-gradient(0deg, $primary-foreground-color 88%, $secondary-background-color 100%);
|
||||
}
|
||||
|
||||
.vjs-track-settings-controls button:hover {
|
||||
color: rgba(#2B333F, 0.75);
|
||||
}
|
||||
|
||||
.vjs-track-settings-controls button {
|
||||
background-color: $primary-foreground-color;
|
||||
background-image: linear-gradient(-180deg, $primary-foreground-color 88%, $secondary-background-color 100%);
|
||||
color: #2B333F;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.vjs-track-settings-controls .vjs-default-button {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.video-js .vjs-captions-button {
|
||||
.video-js .vjs-captions-button .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-captions;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.video-js .vjs-chapters-button {
|
||||
.video-js .vjs-chapters-button .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-chapters;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
.video-js .vjs-control.vjs-close-button {
|
||||
@extend .vjs-icon-cancel;
|
||||
& .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-cancel;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
height: 3em;
|
||||
position: absolute;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// It's used on both real buttons (play button)
|
||||
// and div buttons (menu buttons)
|
||||
.video-js .vjs-control {
|
||||
outline: none;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
@@ -11,12 +10,12 @@
|
||||
width: 4em;
|
||||
@include flex(none);
|
||||
|
||||
&:before {
|
||||
font-size: 1.8em;
|
||||
line-height: 1.67;
|
||||
}
|
||||
.vjs-button > .vjs-icon-placeholder:before {
|
||||
font-size: 1.8em;
|
||||
line-height: 1.67;
|
||||
|
||||
@extend %icon-default;
|
||||
}
|
||||
@extend %icon-default;
|
||||
}
|
||||
|
||||
// Replacement for focus outline
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.video-js .vjs-descriptions-button {
|
||||
.video-js .vjs-descriptions-button .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-audio-description;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
.video-js .vjs-fullscreen-control {
|
||||
cursor: pointer;
|
||||
@include flex(none);
|
||||
@extend .vjs-icon-fullscreen-enter;
|
||||
|
||||
& .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-fullscreen-enter;
|
||||
}
|
||||
}
|
||||
// Switch to the exit icon when the player is in fullscreen
|
||||
.video-js.vjs-fullscreen .vjs-fullscreen-control {
|
||||
.video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-fullscreen-exit;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.video-js[tabindex="-1"] {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
// All elements inherit border-box sizing
|
||||
.video-js *,
|
||||
.video-js *:before,
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
.video-js .vjs-modal-dialog {
|
||||
@extend %fill-parent;
|
||||
@include linear-gradient(180deg, rgba(0, 0, 0, 0.8), rgba(255, 255, 255, 0));
|
||||
|
||||
// This allows scrolling of content if need be.
|
||||
overflow: auto;
|
||||
|
||||
// When combined with `overflow: auto;`, this fixes gaps left by
|
||||
// scrollbars in older IE versions.
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
// Reset box-sizing inside the modal dialog.
|
||||
.video-js .vjs-modal-dialog > * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.vjs-modal-dialog .vjs-modal-dialog-content {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
.video-js .vjs-play-control {
|
||||
.video-js .vjs-play-control .vjs-icon-placeholder {
|
||||
cursor: pointer;
|
||||
@include flex(none);
|
||||
@extend .vjs-icon-play;
|
||||
}
|
||||
.video-js .vjs-play-control.vjs-playing {
|
||||
.video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-pause;
|
||||
}
|
||||
.video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-replay;
|
||||
}
|
||||
|
||||
@@ -36,12 +36,6 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Hide the poster when controls are disabled because it's clickable
|
||||
// and the native poster can take over
|
||||
.vjs-controls-disabled .vjs-poster {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Hide the poster when native controls are used otherwise it covers them
|
||||
.vjs-using-native-controls .vjs-poster {
|
||||
display: none;
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
// .vjs-progress-control / ProgressControl
|
||||
//
|
||||
// Let's talk pixel math!
|
||||
// Start with a base font size of 10px (assuming that hasn't changed)
|
||||
// No Hover:
|
||||
// - Progress holder is 3px
|
||||
// - Progress handle is 9px
|
||||
// - Progress handle is pulled up 3px to center it.
|
||||
//
|
||||
// Hover:
|
||||
// - Progress holder becomes 5px
|
||||
// - Progress handle becomes 15px
|
||||
// - Progress handle is pulled up 5px to center it
|
||||
//
|
||||
|
||||
// This is the container for all progress bar-related components/elements.
|
||||
.video-js .vjs-progress-control {
|
||||
cursor: pointer;
|
||||
@include flex(auto);
|
||||
@include display-flex(center);
|
||||
min-width: 4em;
|
||||
@@ -22,39 +12,45 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Box containing play and load progresses. Also acts as seek scrubber.
|
||||
.vjs-no-flex .vjs-progress-control {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
// .vjs-progress-holder / SeekBar
|
||||
//
|
||||
// Box containing play and load progress bars. It also acts as seek scrubber.
|
||||
.video-js .vjs-progress-holder {
|
||||
@include flex(auto);
|
||||
@include transition(all 0.2s);
|
||||
height: 0.3em;
|
||||
}
|
||||
|
||||
// We need an increased hit area on hover
|
||||
.video-js .vjs-progress-control .vjs-progress-holder {
|
||||
|
||||
// This is one of the rare cases where we are using a pixel dimension. The
|
||||
// reason is that the progress holder font-size changes on hover. With the
|
||||
// default em-based margins, this means it gets narrower and causes issues
|
||||
// with mouseover behaviors/math.
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
// This increases the size of the progress holder so there is an increased
|
||||
// hit area for clicks/touches.
|
||||
.video-js .vjs-progress-control:hover .vjs-progress-holder {
|
||||
font-size: 1.666666666666666666em;
|
||||
}
|
||||
|
||||
/* If we let the font size grow as much as everything else, the current time tooltip ends up
|
||||
ginormous. If you'd like to enable the current time tooltip all the time, this should be disabled
|
||||
to avoid a weird hitch when you roll off the hover. */
|
||||
|
||||
// Also show the current time tooltip
|
||||
.video-js .vjs-progress-control:hover .vjs-time-tooltip,
|
||||
.video-js .vjs-progress-control:hover .vjs-mouse-display:after,
|
||||
.video-js .vjs-progress-control:hover .vjs-play-progress:after {
|
||||
font-family: $text-font-family;
|
||||
visibility: visible;
|
||||
font-size: 0.6em;
|
||||
}
|
||||
|
||||
// Progress Bars
|
||||
// .vjs-play-progress / PlayProgressBar and .vjs-load-progress / LoadProgressBar
|
||||
//
|
||||
// These are bars that appear within the progress control to communicate the
|
||||
// amount of media that has played back and the amount of media that has
|
||||
// loaded, respectively.
|
||||
.video-js .vjs-progress-holder .vjs-play-progress,
|
||||
.video-js .vjs-progress-holder .vjs-load-progress,
|
||||
.video-js .vjs-progress-holder .vjs-tooltip-progress-bar,
|
||||
.video-js .vjs-progress-holder .vjs-load-progress div {
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 0.3em;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
// updated by javascript during playback
|
||||
@@ -64,86 +60,83 @@
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.video-js .vjs-mouse-display {
|
||||
@extend .vjs-icon-circle;
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.video-js .vjs-play-progress {
|
||||
background-color: $primary-foreground-color;
|
||||
@extend .vjs-icon-circle;
|
||||
|
||||
// Progress handle
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: -0.333333333333333em;
|
||||
right: -0.5em;
|
||||
font-size: 0.9em;
|
||||
position: absolute;
|
||||
right: -0.5em;
|
||||
top: -0.333333333333333em;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Current Time "tooltip"
|
||||
// By default this is hidden and only shown when hovering over the progress control
|
||||
.video-js .vjs-time-tooltip,
|
||||
.video-js .vjs-mouse-display:after,
|
||||
.video-js .vjs-play-progress:after {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: -3.4em;
|
||||
right: -1.9em;
|
||||
font-size: 0.9em;
|
||||
color: #000;
|
||||
content: attr(data-current-time);
|
||||
padding: 6px 8px 8px 8px;
|
||||
@include background-color-with-alpha(#fff, 0.8);
|
||||
@include border-radius(0.3em);
|
||||
}
|
||||
|
||||
.video-js .vjs-time-tooltip,
|
||||
.video-js .vjs-play-progress:before,
|
||||
.video-js .vjs-play-progress:after {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.video-js .vjs-progress-control .vjs-keep-tooltips-inside:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-js .vjs-load-progress {
|
||||
// For IE8 we'll lighten the color
|
||||
// For IE8, we'll lighten the color
|
||||
background: lighten($secondary-background-color, 25%);
|
||||
// Otherwise we'll rely on stacked opacities
|
||||
// Otherwise, we'll rely on stacked opacities
|
||||
background: rgba($secondary-background-color, $secondary-background-transparency);
|
||||
}
|
||||
|
||||
// there are child elements of the load progress bar that represent the
|
||||
// specific time ranges that have been buffered
|
||||
// There are child elements of the load progress bar that represent the
|
||||
// specific time ranges that have been buffered.
|
||||
.video-js .vjs-load-progress div {
|
||||
// For IE8 we'll lighten the color
|
||||
// For IE8, we'll lighten the color
|
||||
background: lighten($secondary-background-color, 50%);
|
||||
// Otherwise we'll rely on stacked opacities
|
||||
// Otherwise, we'll rely on stacked opacities
|
||||
background: rgba($secondary-background-color, 0.75);
|
||||
}
|
||||
|
||||
.video-js.vjs-no-flex .vjs-progress-control {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
// .vjs-time-tooltip
|
||||
//
|
||||
// These elements are displayed above the progress bar.
|
||||
//
|
||||
// By default, they are hidden and only shown when hovering over the progress
|
||||
// control.
|
||||
.video-js .vjs-time-tooltip {
|
||||
display: inline-block;
|
||||
height: 2.4em;
|
||||
position: relative;
|
||||
@include background-color-with-alpha(#fff, 0.8);
|
||||
@include border-radius(0.3em);
|
||||
color: #000;
|
||||
|
||||
// By floating the tooltips to the right, their right edge becomes aligned
|
||||
// with the right edge of their parent element. However, in order to have them
|
||||
// centered, they must be pulled further to the right via positioning (e.g.
|
||||
// `right: -10px;`. This part is left to JavaScript.
|
||||
float: right;
|
||||
right: -1.9em;
|
||||
}
|
||||
font-family: $text-font-family;
|
||||
|
||||
.vjs-tooltip-progress-bar {
|
||||
// The font-size should translate to a consistent 10px for time tooltips in
|
||||
// all states. This is tricky because the .vjs-progress-holder element
|
||||
// changes its font-size when the .vjs-progress-control is hovered.
|
||||
font-size: 1em;
|
||||
padding: 6px 8px 8px 8px;
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
top: -3.4em;
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.video-js .vjs-progress-holder:focus .vjs-time-tooltip {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-js .vjs-progress-control:hover .vjs-time-tooltip,
|
||||
.video-js .vjs-progress-control:hover .vjs-progress-holder:focus .vjs-time-tooltip {
|
||||
display: block;
|
||||
|
||||
// Ensure that we maintain a font-size of ~10px.
|
||||
font-size: 0.6em;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
// .vjs-mouse-display / MouseTimeDisplay
|
||||
//
|
||||
// This element tracks the mouse position along the progress control and
|
||||
// includes a tooltip, which displays the time at that point in the media.
|
||||
.video-js .vjs-progress-control .vjs-mouse-display {
|
||||
display: none;
|
||||
position: absolute;
|
||||
@@ -152,25 +145,27 @@
|
||||
background-color: #000;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.vjs-no-flex .vjs-progress-control .vjs-mouse-display {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.video-js .vjs-progress-control:hover .vjs-mouse-display {
|
||||
display: block;
|
||||
}
|
||||
.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display,
|
||||
.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display:after {
|
||||
|
||||
.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
$trans: visibility 1.0s, opacity 1.0s;
|
||||
@include transition($trans);
|
||||
}
|
||||
.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display,
|
||||
.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display:after {
|
||||
|
||||
.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display {
|
||||
display: none;
|
||||
}
|
||||
.vjs-mouse-display .vjs-time-tooltip,
|
||||
.video-js .vjs-progress-control .vjs-mouse-display:after {
|
||||
|
||||
.vjs-mouse-display .vjs-time-tooltip {
|
||||
color: #fff;
|
||||
@include background-color-with-alpha(#000, 0.8);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
.video-js .vjs-slider {
|
||||
outline: 0;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// North America uses 'CC' icon
|
||||
.video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder,
|
||||
.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-captions;
|
||||
}
|
||||
|
||||
// ROW uses 'subtitles'
|
||||
// Double selector because @extend puts these rules above the captions icon
|
||||
.video-js .vjs-subs-caps-button .vjs-icon-placeholder,
|
||||
.video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder,
|
||||
.video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder,
|
||||
.video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder,
|
||||
.video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-subtitles;
|
||||
}
|
||||
|
||||
.video-js .vjs-subs-caps-button + .vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder {
|
||||
position: absolute;
|
||||
}
|
||||
.video-js .vjs-subs-caps-button + .vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before {
|
||||
font-family: VideoJS;
|
||||
content: "\f10d";
|
||||
font-size: 1.5em;
|
||||
line-height: inherit;
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
.video-js .vjs-subtitles-button {
|
||||
.video-js .vjs-subtitles-button .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-subtitles;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
// IE 8 + IE 9 width: auto container fix
|
||||
.vjs-no-flex .vjs-remaining-time.vjs-time-control.vjs-control {
|
||||
width: 0px !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.video-js .vjs-duration,
|
||||
.vjs-no-flex .vjs-duration {
|
||||
display: none;
|
||||
|
||||
@@ -1,27 +1,131 @@
|
||||
.video-js .vjs-mute-control,
|
||||
.video-js .vjs-volume-menu-button {
|
||||
.video-js .vjs-mute-control {
|
||||
cursor: pointer;
|
||||
@include flex(none);
|
||||
@extend .vjs-icon-volume-high;
|
||||
// padding here is for IE < 9, it doesn't do width: auto from
|
||||
// another style correctly
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
padding-bottom: 3em;
|
||||
|
||||
& .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-volume-high;
|
||||
}
|
||||
}
|
||||
|
||||
.video-js .vjs-mute-control.vjs-vol-0,
|
||||
.video-js .vjs-volume-menu-button.vjs-vol-0 {
|
||||
.video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-volume-mute;
|
||||
}
|
||||
.video-js .vjs-mute-control.vjs-vol-1,
|
||||
.video-js .vjs-volume-menu-button.vjs-vol-1 {
|
||||
.video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-volume-low;
|
||||
}
|
||||
.video-js .vjs-mute-control.vjs-vol-2,
|
||||
.video-js .vjs-volume-menu-button.vjs-vol-2 {
|
||||
.video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder {
|
||||
@extend .vjs-icon-volume-mid;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-control {
|
||||
cursor: pointer;
|
||||
margin-right: 1em;
|
||||
@include display-flex;
|
||||
}
|
||||
.video-js .vjs-volume-control.vjs-volume-horizontal {
|
||||
width: 5em;
|
||||
@include flex(none);
|
||||
@include display-flex(center);
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-panel .vjs-volume-control {
|
||||
visibility: visible;
|
||||
opacity: 0;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin-left: -1px;
|
||||
|
||||
}
|
||||
.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
|
||||
& .vjs-volume-bar,
|
||||
& .vjs-volume-level {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
|
||||
}
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-panel {
|
||||
&:hover .vjs-volume-control,
|
||||
&:active .vjs-volume-control,
|
||||
&:focus .vjs-volume-control,
|
||||
& .vjs-volume-control:hover ,
|
||||
& .vjs-volume-control:active ,
|
||||
& .vjs-volume-control:focus ,
|
||||
& .vjs-mute-control:hover ~ .vjs-volume-control,
|
||||
& .vjs-mute-control:active ~ .vjs-volume-control,
|
||||
& .vjs-mute-control:focus ~ .vjs-volume-control,
|
||||
& .vjs-volume-control.vjs-slider-active {
|
||||
&.vjs-volume-horizontal {
|
||||
width: 5em;
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
|
||||
&.vjs-volume-vertical {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
|
||||
& .vjs-volume-bar,
|
||||
& .vjs-volume-level {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
|
||||
}
|
||||
}
|
||||
|
||||
$transition-property: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s;
|
||||
@include transition($transition-property);
|
||||
}
|
||||
|
||||
&.vjs-volume-panel-horizontal {
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active,
|
||||
&.vjs-slider-active {
|
||||
width: 9em;
|
||||
|
||||
@include transition(width 0.1s);
|
||||
}
|
||||
}
|
||||
|
||||
@include transition(width 1s);
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical {
|
||||
height: 8em;
|
||||
width: 3em;
|
||||
left: -3.5em;
|
||||
|
||||
$transition-property: visibility 1s, opacity 1s, height 1s 1s, width 1s 1s, left 1s 1s, top 1s 1s;
|
||||
@include transition($transition-property)
|
||||
}
|
||||
.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
||||
$transition-property: visibility 1s, opacity 1s, height 1s 1s, width 1s, left 1s 1s, top 1s 1s;
|
||||
@include transition($transition-property)
|
||||
}
|
||||
|
||||
.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
||||
width: 5em;
|
||||
height: 3em;
|
||||
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
|
||||
@include transition(none);
|
||||
}
|
||||
|
||||
.video-js.vjs-no-flex .vjs-volume-control.vjs-volume-vertical,
|
||||
.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical {
|
||||
position: absolute;
|
||||
bottom: 3em;
|
||||
left: 0.5em;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-panel {
|
||||
@include display-flex;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-bar {
|
||||
@@ -74,6 +178,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-panel.vjs-volume-panel-vertical {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
// Assumes volume starts at 1.0.
|
||||
.vjs-volume-bar.vjs-slider-vertical .vjs-volume-level {
|
||||
height: 100%;
|
||||
@@ -83,53 +191,14 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// The volume menu button is like menu buttons (captions/subtitles) but works
|
||||
// a little differently. It needs to be possible to tab to the volume slider
|
||||
// without hitting space bar on the menu button. To do this we're not using
|
||||
// display:none to hide the slider menu by default, and instead setting the
|
||||
// width and height to zero.
|
||||
.vjs-menu-button-popup.vjs-volume-menu-button .vjs-menu {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top-color: transparent;
|
||||
.video-js .vjs-volume-vertical {
|
||||
width: 3em;
|
||||
height: 8em;
|
||||
bottom: 8em;
|
||||
|
||||
@include background-color-with-alpha($primary-background-color, $primary-background-transparency);
|
||||
}
|
||||
|
||||
.vjs-menu-button-popup.vjs-volume-menu-button-vertical .vjs-menu {
|
||||
left: 0.5em;
|
||||
height: 8em;
|
||||
}
|
||||
.vjs-menu-button-popup.vjs-volume-menu-button-horizontal .vjs-menu {
|
||||
.video-js .vjs-volume-horizontal .vjs-menu {
|
||||
left: -2em;
|
||||
}
|
||||
|
||||
.vjs-menu-button-popup.vjs-volume-menu-button .vjs-menu-content {
|
||||
height: 0;
|
||||
width: 0;
|
||||
|
||||
// Avoids unnecessary scrollbars in the menu content. Primarily noticed in Chrome on Linux.
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.vjs-volume-menu-button-vertical:hover .vjs-menu-content,
|
||||
.vjs-volume-menu-button-vertical:focus .vjs-menu-content,
|
||||
.vjs-volume-menu-button-vertical.vjs-slider-active .vjs-menu-content,
|
||||
.vjs-volume-menu-button-vertical .vjs-lock-showing .vjs-menu-content {
|
||||
height: 8em;
|
||||
width: 2.9em;
|
||||
}
|
||||
|
||||
.vjs-volume-menu-button-horizontal:hover .vjs-menu-content,
|
||||
.vjs-volume-menu-button-horizontal:focus .vjs-menu-content,
|
||||
.vjs-volume-menu-button-horizontal .vjs-slider-active .vjs-menu-content,
|
||||
.vjs-volume-menu-button-horizontal .vjs-lock-showing .vjs-menu-content {
|
||||
height: 2.9em;
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
.vjs-volume-menu-button.vjs-menu-button-inline .vjs-menu-content {
|
||||
// An inline volume should never have a menu background color.
|
||||
// This protects it from external changes to background colors.
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
@@ -18,11 +18,6 @@
|
||||
// This width is currently specific to the inline volume bar.
|
||||
width: 12em;
|
||||
}
|
||||
// Don't transition when tabbing in reverse to the volume menu
|
||||
// because it looks weird
|
||||
.video-js .vjs-menu-button-inline.vjs-slider-active {
|
||||
@include transition(none);
|
||||
}
|
||||
|
||||
.vjs-menu-button-inline .vjs-menu {
|
||||
opacity: 0;
|
||||
|
||||
@@ -16,8 +16,19 @@
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
font-family: $text-font-family;
|
||||
|
||||
// This allows scrolling of content if need be.
|
||||
overflow: auto;
|
||||
|
||||
// When combined with `overflow: auto;`, this fixes gaps left by
|
||||
// scrollbars in older IE versions.
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
// Reset box-sizing inside the menu.
|
||||
.vjs-menu .vjs-menu-content > * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// prevent menus from opening while scrubbing (FF, IE)
|
||||
@@ -37,7 +48,6 @@
|
||||
|
||||
.vjs-menu li.vjs-menu-item:focus,
|
||||
.vjs-menu li.vjs-menu-item:hover {
|
||||
outline: 0;
|
||||
@include background-color-with-alpha($secondary-background-color, $secondary-background-transparency);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
@import "components/big-play";
|
||||
@import "components/button";
|
||||
@import "components/close-button";
|
||||
@import "components/modal-dialog";
|
||||
|
||||
@import "components/menu/menu";
|
||||
@import "components/menu/menu-popup";
|
||||
@@ -35,9 +36,9 @@
|
||||
@import "components/chapters";
|
||||
@import "components/descriptions";
|
||||
@import "components/subtitles";
|
||||
@import "components/subs-caps";
|
||||
@import "components/audio";
|
||||
@import "components/adaptive";
|
||||
@import "components/captions-settings";
|
||||
@import "components/modal-dialog";
|
||||
|
||||
@import "print";
|
||||
|
||||
@@ -34,7 +34,23 @@ class BigPlayButton extends Button {
|
||||
* @listens click
|
||||
*/
|
||||
handleClick(event) {
|
||||
this.player_.play();
|
||||
const playPromise = this.player_.play();
|
||||
|
||||
const cb = this.player_.getChild('controlBar');
|
||||
const playToggle = cb && cb.getChild('playToggle');
|
||||
|
||||
if (!playToggle) {
|
||||
this.player_.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (playPromise) {
|
||||
playPromise.then(() => playToggle.focus());
|
||||
} else {
|
||||
this.setTimeout(function() {
|
||||
playToggle.focus();
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-17
@@ -16,8 +16,9 @@ class Button extends ClickableComponent {
|
||||
/**
|
||||
* Create the `Button`s DOM element.
|
||||
*
|
||||
* @param {string} [tag=button]
|
||||
* Element's node type. e.g. 'button'
|
||||
* @param {string} [tag="button"]
|
||||
* The element's node type. This argument is IGNORED: no matter what
|
||||
* is passed, it will always create a `button` element.
|
||||
*
|
||||
* @param {Object} [props={}]
|
||||
* An object of properties that should be set on the element.
|
||||
@@ -28,25 +29,14 @@ class Button extends ClickableComponent {
|
||||
* @return {Element}
|
||||
* The element that gets created.
|
||||
*/
|
||||
createEl(tag = 'button', props = {}, attributes = {}) {
|
||||
createEl(tag, props = {}, attributes = {}) {
|
||||
tag = 'button';
|
||||
|
||||
props = assign({
|
||||
innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
|
||||
className: this.buildCSSClass()
|
||||
}, props);
|
||||
|
||||
if (tag !== 'button') {
|
||||
log.warn(`Creating a Button with an HTML element of ${tag} is deprecated; use ClickableComponent instead.`);
|
||||
|
||||
// Add properties for clickable element which is not a native HTML button
|
||||
props = assign({
|
||||
tabIndex: 0
|
||||
}, props);
|
||||
|
||||
// Add ARIA attributes for clickable element which is not a native HTML button
|
||||
attributes = assign({
|
||||
role: 'button'
|
||||
}, attributes);
|
||||
}
|
||||
|
||||
// Add attributes for button element
|
||||
attributes = assign({
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ class ClickableComponent extends Component {
|
||||
*/
|
||||
createEl(tag = 'div', props = {}, attributes = {}) {
|
||||
props = assign({
|
||||
innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
|
||||
className: this.buildCSSClass(),
|
||||
tabIndex: 0
|
||||
}, props);
|
||||
@@ -108,9 +109,8 @@ class ClickableComponent extends Component {
|
||||
* @param {Element} [el=this.el()]
|
||||
* Element to set the title on.
|
||||
*
|
||||
* @return {string|ClickableComponent}
|
||||
* @return {string}
|
||||
* - The control text when getting
|
||||
* - Returns itself when setting; method can be chained.
|
||||
*/
|
||||
controlText(text, el = this.el()) {
|
||||
if (!text) {
|
||||
@@ -121,9 +121,10 @@ class ClickableComponent extends Component {
|
||||
|
||||
this.controlText_ = text;
|
||||
this.controlTextEl_.innerHTML = localizedText;
|
||||
el.setAttribute('title', localizedText);
|
||||
|
||||
return this;
|
||||
if (!this.nonIconControl) {
|
||||
// Set title attribute if only an icon is shown
|
||||
el.setAttribute('title', localizedText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,9 +139,6 @@ class ClickableComponent extends Component {
|
||||
|
||||
/**
|
||||
* Enable this `Component`s element.
|
||||
*
|
||||
* @return {ClickableComponent}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
enable() {
|
||||
this.removeClass('vjs-disabled');
|
||||
@@ -152,14 +150,10 @@ class ClickableComponent extends Component {
|
||||
this.on('click', this.handleClick);
|
||||
this.on('focus', this.handleFocus);
|
||||
this.on('blur', this.handleBlur);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable this `Component`s element.
|
||||
*
|
||||
* @return {ClickableComponent}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
disable() {
|
||||
this.addClass('vjs-disabled');
|
||||
@@ -171,7 +165,6 @@ class ClickableComponent extends Component {
|
||||
this.off('click', this.handleClick);
|
||||
this.off('focus', this.handleFocus);
|
||||
this.off('blur', this.handleBlur);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,7 +219,7 @@ class ClickableComponent extends Component {
|
||||
// Support Space (32) or Enter (13) key operation to fire a click event
|
||||
if (event.which === 32 || event.which === 13) {
|
||||
event.preventDefault();
|
||||
this.handleClick(event);
|
||||
this.trigger('click');
|
||||
} else if (super.handleKeyPress) {
|
||||
|
||||
// Pass keypress handling up for unsupported keys
|
||||
|
||||
+223
-358
@@ -4,10 +4,12 @@
|
||||
* @file component.js
|
||||
*/
|
||||
import window from 'global/window';
|
||||
import evented from './mixins/evented';
|
||||
import stateful from './mixins/stateful';
|
||||
import * as Dom from './utils/dom.js';
|
||||
import * as DomData from './utils/dom-data';
|
||||
import * as Fn from './utils/fn.js';
|
||||
import * as Guid from './utils/guid.js';
|
||||
import * as Events from './utils/events.js';
|
||||
import log from './utils/log.js';
|
||||
import toTitleCase from './utils/to-title-case.js';
|
||||
import mergeOptions from './utils/merge-options.js';
|
||||
@@ -82,6 +84,10 @@ class Component {
|
||||
this.el_ = this.createEl();
|
||||
}
|
||||
|
||||
// Make this an evented object and use `el_`, if available, as its event bus
|
||||
evented(this, {eventBusKey: this.el_ ? 'el_' : null});
|
||||
stateful(this, this.constructor.defaultState);
|
||||
|
||||
this.children_ = [];
|
||||
this.childIndex_ = {};
|
||||
this.childNameIndex_ = {};
|
||||
@@ -133,16 +139,15 @@ class Component {
|
||||
this.childIndex_ = null;
|
||||
this.childNameIndex_ = null;
|
||||
|
||||
// Remove all event listeners.
|
||||
this.off();
|
||||
if (this.el_) {
|
||||
// Remove element from DOM
|
||||
if (this.el_.parentNode) {
|
||||
this.el_.parentNode.removeChild(this.el_);
|
||||
}
|
||||
|
||||
// Remove element from DOM
|
||||
if (this.el_.parentNode) {
|
||||
this.el_.parentNode.removeChild(this.el_);
|
||||
DomData.removeData(this.el_);
|
||||
this.el_ = null;
|
||||
}
|
||||
|
||||
Dom.removeElData(this.el_);
|
||||
this.el_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,34 +216,70 @@ class Component {
|
||||
/**
|
||||
* Localize a string given the string in english.
|
||||
*
|
||||
* If tokens are provided, it'll try and run a simple token replacement on the provided string.
|
||||
* The tokens it loooks for look like `{1}` with the index being 1-indexed into the tokens array.
|
||||
*
|
||||
* If a `defaultValue` is provided, it'll use that over `string`,
|
||||
* if a value isn't found in provided language files.
|
||||
* This is useful if you want to have a descriptive key for token replacement
|
||||
* but have a succinct localized string and not require `en.json` to be included.
|
||||
*
|
||||
* Currently, it is used for the progress bar timing.
|
||||
* ```js
|
||||
* {
|
||||
* "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
|
||||
* }
|
||||
* ```
|
||||
* It is then used like so:
|
||||
* ```js
|
||||
* this.localize('progress bar timing: currentTime={1} duration{2}',
|
||||
* [this.player_.currentTime(), this.player_.duration()],
|
||||
* '{1} of {2}');
|
||||
* ```
|
||||
*
|
||||
* Which outputs something like: `01:23 of 24:56`.
|
||||
*
|
||||
*
|
||||
* @param {string} string
|
||||
* The string to localize.
|
||||
* The string to localize and the key to lookup in the language files.
|
||||
* @param {string[]} [tokens]
|
||||
* If the current item has token replacements, provide the tokens here.
|
||||
* @param {string} [defaultValue]
|
||||
* Defaults to `string`. Can be a default value to use for token replacement
|
||||
* if the lookup key is needed to be separate.
|
||||
*
|
||||
* @return {string}
|
||||
* The localized string or if no localization exists the english string.
|
||||
*/
|
||||
localize(string) {
|
||||
localize(string, tokens, defaultValue = string) {
|
||||
const code = this.player_.language && this.player_.language();
|
||||
const languages = this.player_.languages && this.player_.languages();
|
||||
const language = languages && languages[code];
|
||||
const primaryCode = code && code.split('-')[0];
|
||||
const primaryLang = languages && languages[primaryCode];
|
||||
|
||||
if (!code || !languages) {
|
||||
return string;
|
||||
}
|
||||
|
||||
const language = languages[code];
|
||||
let localizedString = defaultValue;
|
||||
|
||||
if (language && language[string]) {
|
||||
return language[string];
|
||||
localizedString = language[string];
|
||||
} else if (primaryLang && primaryLang[string]) {
|
||||
localizedString = primaryLang[string];
|
||||
}
|
||||
|
||||
const primaryCode = code.split('-')[0];
|
||||
const primaryLang = languages[primaryCode];
|
||||
if (tokens) {
|
||||
localizedString = localizedString.replace(/\{(\d+)\}/g, function(match, index) {
|
||||
const value = tokens[index - 1];
|
||||
let ret = value;
|
||||
|
||||
if (primaryLang && primaryLang[string]) {
|
||||
return primaryLang[string];
|
||||
if (typeof value === 'undefined') {
|
||||
ret = match;
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
return string;
|
||||
return localizedString;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,21 +382,6 @@ class Component {
|
||||
if (typeof child === 'string') {
|
||||
componentName = toTitleCase(child);
|
||||
|
||||
// Options can also be specified as a boolean,
|
||||
// so convert to an empty object if false.
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
// Same as above, but true is deprecated so show a warning.
|
||||
if (options === true) {
|
||||
log.warn('Initializing a child component with `true` is deprecated.' +
|
||||
'Children should be defined in an array when possible, ' +
|
||||
'but if necessary use an object instead of `true`.'
|
||||
);
|
||||
options = {};
|
||||
}
|
||||
|
||||
const componentClassName = options.componentClass || componentName;
|
||||
|
||||
// Set name through options
|
||||
@@ -392,7 +418,7 @@ class Component {
|
||||
|
||||
// If a name wasn't used to create the component, check if we can use the
|
||||
// name function of the component
|
||||
componentName = componentName || (component.name && component.name());
|
||||
componentName = componentName || (component.name && toTitleCase(component.name()));
|
||||
|
||||
if (componentName) {
|
||||
this.childNameIndex_[componentName] = component;
|
||||
@@ -564,188 +590,9 @@ class Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an `event listener` to this `Component`s element.
|
||||
*
|
||||
* The benefit of using this over the following:
|
||||
* - `VjsEvents.on(otherElement, 'eventName', myFunc)`
|
||||
* - `otherComponent.on('eventName', myFunc)`
|
||||
*
|
||||
* 1. Is that the listeners will get cleaned up when either component gets disposed.
|
||||
* 1. It will also bind `myComponent` as the context of `myFunc`.
|
||||
* > NOTE: If you remove the element from the DOM that has used `on` you need to
|
||||
* clean up references using: `myComponent.trigger(el, 'dispose')`
|
||||
* This will also allow the browser to garbage collect it. In special
|
||||
* cases such as with `window` and `document`, which are both permanent,
|
||||
* this is not necessary.
|
||||
*
|
||||
* @param {string|Component|string[]} [first]
|
||||
* The event name, and array of event names, or another `Component`.
|
||||
*
|
||||
* @param {EventTarget~EventListener|string|string[]} [second]
|
||||
* The listener function, an event name, or an Array of events names.
|
||||
*
|
||||
* @param {EventTarget~EventListener} [third]
|
||||
* The event handler if `first` is a `Component` and `second` is an event name
|
||||
* or an Array of event names.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*
|
||||
* @listens Component#dispose
|
||||
*/
|
||||
on(first, second, third) {
|
||||
if (typeof first === 'string' || Array.isArray(first)) {
|
||||
Events.on(this.el_, first, Fn.bind(this, second));
|
||||
|
||||
// Targeting another component or element
|
||||
} else {
|
||||
const target = first;
|
||||
const type = second;
|
||||
const fn = Fn.bind(this, third);
|
||||
|
||||
// When this component is disposed, remove the listener from the other component
|
||||
const removeOnDispose = () => this.off(target, type, fn);
|
||||
|
||||
// Use the same function ID so we can remove it later it using the ID
|
||||
// of the original listener
|
||||
removeOnDispose.guid = fn.guid;
|
||||
this.on('dispose', removeOnDispose);
|
||||
|
||||
// If the other component is disposed first we need to clean the reference
|
||||
// to the other component in this component's removeOnDispose listener
|
||||
// Otherwise we create a memory leak.
|
||||
const cleanRemover = () => this.off('dispose', removeOnDispose);
|
||||
|
||||
// Add the same function ID so we can easily remove it later
|
||||
cleanRemover.guid = fn.guid;
|
||||
|
||||
// Check if this is a DOM node
|
||||
if (first.nodeName) {
|
||||
// Add the listener to the other element
|
||||
Events.on(target, type, fn);
|
||||
Events.on(target, 'dispose', cleanRemover);
|
||||
|
||||
// Should be a component
|
||||
// Not using `instanceof Component` because it makes mock players difficult
|
||||
} else if (typeof first.on === 'function') {
|
||||
// Add the listener to the other component
|
||||
target.on(type, fn);
|
||||
target.on('dispose', cleanRemover);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an event listener from this `Component`s element. If the second argument is
|
||||
* exluded all listeners for the type passed in as the first argument will be removed.
|
||||
*
|
||||
* @param {string|Component|string[]} [first]
|
||||
* The event name, and array of event names, or another `Component`.
|
||||
*
|
||||
* @param {EventTarget~EventListener|string|string[]} [second]
|
||||
* The listener function, an event name, or an Array of events names.
|
||||
*
|
||||
* @param {EventTarget~EventListener} [third]
|
||||
* The event handler if `first` is a `Component` and `second` is an event name
|
||||
* or an Array of event names.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
off(first, second, third) {
|
||||
if (!first || typeof first === 'string' || Array.isArray(first)) {
|
||||
Events.off(this.el_, first, second);
|
||||
} else {
|
||||
const target = first;
|
||||
const type = second;
|
||||
// Ensure there's at least a guid, even if the function hasn't been used
|
||||
const fn = Fn.bind(this, third);
|
||||
|
||||
// Remove the dispose listener on this component,
|
||||
// which was given the same guid as the event listener
|
||||
this.off('dispose', fn);
|
||||
|
||||
if (first.nodeName) {
|
||||
// Remove the listener
|
||||
Events.off(target, type, fn);
|
||||
// Remove the listener for cleaning the dispose listener
|
||||
Events.off(target, 'dispose', fn);
|
||||
} else {
|
||||
target.off(type, fn);
|
||||
target.off('dispose', fn);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener that gets triggered only once and then gets removed.
|
||||
*
|
||||
* @param {string|Component|string[]} [first]
|
||||
* The event name, and array of event names, or another `Component`.
|
||||
*
|
||||
* @param {EventTarget~EventListener|string|string[]} [second]
|
||||
* The listener function, an event name, or an Array of events names.
|
||||
*
|
||||
* @param {EventTarget~EventListener} [third]
|
||||
* The event handler if `first` is a `Component` and `second` is an event name
|
||||
* or an Array of event names.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
one(first, second, third) {
|
||||
if (typeof first === 'string' || Array.isArray(first)) {
|
||||
Events.one(this.el_, first, Fn.bind(this, second));
|
||||
} else {
|
||||
const target = first;
|
||||
const type = second;
|
||||
const fn = Fn.bind(this, third);
|
||||
|
||||
const newFunc = () => {
|
||||
this.off(target, type, newFunc);
|
||||
fn.apply(null, arguments);
|
||||
};
|
||||
|
||||
// Keep the same function ID so we can remove it later
|
||||
newFunc.guid = fn.guid;
|
||||
|
||||
this.on(target, type, newFunc);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an event on an element.
|
||||
*
|
||||
* @param {EventTarget~Event|Object|string} event
|
||||
* The event name, and Event, or an event-like object with a type attribute
|
||||
* set to the event name.
|
||||
*
|
||||
* @param {Object} [hash]
|
||||
* Data hash to pass along with the event
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
trigger(event, hash) {
|
||||
Events.trigger(this.el_, event, hash);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a listener to the component's ready state. If the ready event has already
|
||||
* happened it will trigger the function immediately.
|
||||
*
|
||||
* @param {Component~ReadyCallback} fn
|
||||
* A function to call when ready is triggered.
|
||||
*
|
||||
* @param {boolean} [sync=false]
|
||||
* Execute the listener synchronously if `Component` is ready.
|
||||
* Bind a listener to the component's ready state.
|
||||
* Different from event listeners in that if the ready event has already happened
|
||||
* it will trigger the function immediately.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
@@ -764,7 +611,6 @@ class Component {
|
||||
this.readyQueue_.push(fn);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -854,7 +700,7 @@ class Component {
|
||||
* - False if the `Component` does not have the class`
|
||||
*/
|
||||
hasClass(classToCheck) {
|
||||
return Dom.hasElClass(this.el_, classToCheck);
|
||||
return Dom.hasClass(this.el_, classToCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -862,13 +708,9 @@ class Component {
|
||||
*
|
||||
* @param {string} classToAdd
|
||||
* CSS class name to add
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
addClass(classToAdd) {
|
||||
Dom.addElClass(this.el_, classToAdd);
|
||||
return this;
|
||||
Dom.addClass(this.el_, classToAdd);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -876,13 +718,9 @@ class Component {
|
||||
*
|
||||
* @param {string} classToRemove
|
||||
* CSS class name to remove
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
removeClass(classToRemove) {
|
||||
Dom.removeElClass(this.el_, classToRemove);
|
||||
return this;
|
||||
Dom.removeClass(this.el_, classToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -895,65 +733,45 @@ class Component {
|
||||
*
|
||||
* @param {boolean|Dom~predicate} [predicate]
|
||||
* An {@link Dom~predicate} function or a boolean
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
toggleClass(classToToggle, predicate) {
|
||||
Dom.toggleElClass(this.el_, classToToggle, predicate);
|
||||
return this;
|
||||
Dom.toggleClass(this.el_, classToToggle, predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the `Component`s element if it is hidden by removing the
|
||||
* 'vjs-hidden' class name from it.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
show() {
|
||||
this.removeClass('vjs-hidden');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the `Component`s element if it is currently showing by adding the
|
||||
* 'vjs-hidden` class name to it.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
hide() {
|
||||
this.addClass('vjs-hidden');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
|
||||
* class name to it. Used during fadeIn/fadeOut.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
lockShowing() {
|
||||
this.addClass('vjs-lock-showing');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
|
||||
* class name from it. Used during fadeIn/fadeOut.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
unlockShowing() {
|
||||
this.removeClass('vjs-lock-showing');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -984,14 +802,10 @@ class Component {
|
||||
* @param {string} value
|
||||
* Value to set the attribute to.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*
|
||||
* @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
|
||||
*/
|
||||
setAttribute(attribute, value) {
|
||||
Dom.setAttribute(this.el_, attribute, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1000,14 +814,10 @@ class Component {
|
||||
* @param {string} attribute
|
||||
* Name of the attribute to remove.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*
|
||||
* @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
|
||||
*/
|
||||
removeAttribute(attribute) {
|
||||
Dom.removeAttribute(this.el_, attribute);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1018,12 +828,11 @@ class Component {
|
||||
* The width that you want to set postfixed with '%', 'px' or nothing.
|
||||
*
|
||||
* @param {boolean} [skipListeners]
|
||||
* Skip the resize event trigger
|
||||
* Skip the componentresize event trigger
|
||||
*
|
||||
* @return {Component|number|string}
|
||||
* - The width when getting, zero if there is no width. Can be a string
|
||||
* @return {number|string}
|
||||
* The width when getting, zero if there is no width. Can be a string
|
||||
* postpixed with '%' or 'px'.
|
||||
* - Returns itself when setting; method can be chained.
|
||||
*/
|
||||
width(num, skipListeners) {
|
||||
return this.dimension('width', num, skipListeners);
|
||||
@@ -1037,12 +846,11 @@ class Component {
|
||||
* The height that you want to set postfixed with '%', 'px' or nothing.
|
||||
*
|
||||
* @param {boolean} [skipListeners]
|
||||
* Skip the resize event trigger
|
||||
* Skip the componentresize event trigger
|
||||
*
|
||||
* @return {Component|number|string}
|
||||
* - The width when getting, zero if there is no width. Can be a string
|
||||
* postpixed with '%' or 'px'.
|
||||
* - Returns itself when setting; method can be chained.
|
||||
* @return {number|string}
|
||||
* The width when getting, zero if there is no width. Can be a string
|
||||
* postpixed with '%' or 'px'.
|
||||
*/
|
||||
height(num, skipListeners) {
|
||||
return this.dimension('height', num, skipListeners);
|
||||
@@ -1056,13 +864,11 @@ class Component {
|
||||
*
|
||||
* @param {number|string} height
|
||||
* Height to set the `Component`s element to.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
dimensions(width, height) {
|
||||
// Skip resize listeners on width for optimization
|
||||
return this.width(width, true).height(height);
|
||||
// Skip componentresize listeners on width for optimization
|
||||
this.width(width, true);
|
||||
this.height(height);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1079,7 +885,7 @@ class Component {
|
||||
* - If you want the computed style of the component, use {@link Component#currentWidth}
|
||||
* and {@link {Component#currentHeight}
|
||||
*
|
||||
* @fires Component#resize
|
||||
* @fires Component#componentresize
|
||||
*
|
||||
* @param {string} widthOrHeight
|
||||
8 'width' or 'height'
|
||||
@@ -1088,11 +894,10 @@ class Component {
|
||||
8 New dimension
|
||||
*
|
||||
* @param {boolean} [skipListeners]
|
||||
* Skip resize event trigger
|
||||
* Skip componentresize event trigger
|
||||
*
|
||||
* @return {Component}
|
||||
* - the dimension when getting or 0 if unset
|
||||
* - Returns itself when setting; method can be chained.
|
||||
* @return {number}
|
||||
* The dimension when getting or 0 if unset
|
||||
*/
|
||||
dimension(widthOrHeight, num, skipListeners) {
|
||||
if (num !== undefined) {
|
||||
@@ -1115,14 +920,13 @@ class Component {
|
||||
/**
|
||||
* Triggered when a component is resized.
|
||||
*
|
||||
* @event Component#resize
|
||||
* @event Component#componentresize
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('resize');
|
||||
this.trigger('componentresize');
|
||||
}
|
||||
|
||||
// Return component
|
||||
return this;
|
||||
return;
|
||||
}
|
||||
|
||||
// Not setting a value, so getting it
|
||||
@@ -1232,6 +1036,20 @@ class Component {
|
||||
return this.currentDimension('height');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the focus to this component
|
||||
*/
|
||||
focus() {
|
||||
this.el_.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the focus from this component
|
||||
*/
|
||||
blur() {
|
||||
this.el_.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a 'tap' events when touch event support gets detected. This gets used to
|
||||
* support toggling the controls through a tap on the video. They get enabled
|
||||
@@ -1522,6 +1340,81 @@ class Component {
|
||||
return intervalId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues up a callback to be passed to requestAnimationFrame (rAF), but
|
||||
* with a few extra bonuses:
|
||||
*
|
||||
* - Supports browsers that do not support rAF by falling back to
|
||||
* {@link Component#setTimeout}.
|
||||
*
|
||||
* - The callback is turned into a {@link Component~GenericCallback} (i.e.
|
||||
* bound to the component).
|
||||
*
|
||||
* - Automatic cancellation of the rAF callback is handled if the component
|
||||
* is disposed before it is called.
|
||||
*
|
||||
* @param {Component~GenericCallback} fn
|
||||
* A function that will be bound to this component and executed just
|
||||
* before the browser's next repaint.
|
||||
*
|
||||
* @return {number}
|
||||
* Returns an rAF ID that gets used to identify the timeout. It can
|
||||
* also be used in {@link Component#cancelAnimationFrame} to cancel
|
||||
* the animation frame callback.
|
||||
*
|
||||
* @listens Component#dispose
|
||||
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
|
||||
*/
|
||||
requestAnimationFrame(fn) {
|
||||
if (this.supportsRaf_) {
|
||||
fn = Fn.bind(this, fn);
|
||||
|
||||
const id = window.requestAnimationFrame(fn);
|
||||
const disposeFn = () => this.cancelAnimationFrame(id);
|
||||
|
||||
disposeFn.guid = `vjs-raf-${id}`;
|
||||
this.on('dispose', disposeFn);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
// Fall back to using a timer.
|
||||
return this.setTimeout(fn, 1000 / 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a queued callback passed to {@link Component#requestAnimationFrame}
|
||||
* (rAF).
|
||||
*
|
||||
* If you queue an rAF callback via {@link Component#requestAnimationFrame},
|
||||
* use this function instead of `window.cancelAnimationFrame`. If you don't,
|
||||
* your dispose listener will not get cleaned up until {@link Component#dispose}!
|
||||
*
|
||||
* @param {number} id
|
||||
* The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
|
||||
*
|
||||
* @return {number}
|
||||
* Returns the rAF ID that was cleared.
|
||||
*
|
||||
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
|
||||
*/
|
||||
cancelAnimationFrame(id) {
|
||||
if (this.supportsRaf_) {
|
||||
window.cancelAnimationFrame(id);
|
||||
|
||||
const disposeFn = function() {};
|
||||
|
||||
disposeFn.guid = `vjs-raf-${id}`;
|
||||
|
||||
this.off('dispose', disposeFn);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
// Fall back to using a timer.
|
||||
return this.clearTimeout(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a `Component` with `videojs` given the name and the component.
|
||||
*
|
||||
@@ -1535,15 +1428,34 @@ class Component {
|
||||
* @param {string} name
|
||||
* The name of the `Component` to register.
|
||||
*
|
||||
* @param {Component} comp
|
||||
* @param {Component} ComponentToRegister
|
||||
* The `Component` class to register.
|
||||
*
|
||||
* @return {Component}
|
||||
* The `Component` that was registered.
|
||||
*/
|
||||
static registerComponent(name, comp) {
|
||||
if (!name) {
|
||||
return;
|
||||
static registerComponent(name, ComponentToRegister) {
|
||||
if (typeof name !== 'string' || !name) {
|
||||
throw new Error(`Illegal component name, "${name}"; must be a non-empty string.`);
|
||||
}
|
||||
|
||||
const Tech = Component.getComponent('Tech');
|
||||
|
||||
// We need to make sure this check is only done if Tech has been registered.
|
||||
const isTech = Tech && Tech.isTech(ComponentToRegister);
|
||||
const isComp = Component === ComponentToRegister ||
|
||||
Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
|
||||
|
||||
if (isTech || !isComp) {
|
||||
let reason;
|
||||
|
||||
if (isTech) {
|
||||
reason = 'techs must be registered using Tech.registerTech()';
|
||||
} else {
|
||||
reason = 'must be a Component subclass';
|
||||
}
|
||||
|
||||
throw new Error(`Illegal component, "${name}"; ${reason}.`);
|
||||
}
|
||||
|
||||
name = toTitleCase(name);
|
||||
@@ -1552,23 +1464,26 @@ class Component {
|
||||
Component.components_ = {};
|
||||
}
|
||||
|
||||
if (name === 'Player' && Component.components_[name]) {
|
||||
const Player = Component.components_[name];
|
||||
const Player = Component.getComponent('Player');
|
||||
|
||||
if (name === 'Player' && Player && Player.players) {
|
||||
const players = Player.players;
|
||||
const playerNames = Object.keys(players);
|
||||
|
||||
// If we have players that were disposed, then their name will still be
|
||||
// in Players.players. So, we must loop through and verify that the value
|
||||
// for each item is not null. This allows registration of the Player component
|
||||
// after all players have been disposed or before any were created.
|
||||
if (Player.players &&
|
||||
Object.keys(Player.players).length > 0 &&
|
||||
Object.keys(Player.players).map((playerName) => Player.players[playerName]).every(Boolean)) {
|
||||
throw new Error('Can not register Player component after player has been created');
|
||||
if (players &&
|
||||
playerNames.length > 0 &&
|
||||
playerNames.map((pname) => players[pname]).every(Boolean)) {
|
||||
throw new Error('Can not register Player component after player has been created.');
|
||||
}
|
||||
}
|
||||
|
||||
Component.components_[name] = comp;
|
||||
Component.components_[name] = ComponentToRegister;
|
||||
|
||||
return comp;
|
||||
return ComponentToRegister;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1595,70 +1510,20 @@ class Component {
|
||||
if (Component.components_ && Component.components_[name]) {
|
||||
return Component.components_[name];
|
||||
}
|
||||
|
||||
if (window && window.videojs && window.videojs[name]) {
|
||||
log.warn(`The ${name} component was added to the videojs object when it should be registered using videojs.registerComponent(name, component)`);
|
||||
|
||||
return window.videojs[name];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the constructor using the supplied init method or uses the init of the
|
||||
* parent object.
|
||||
*
|
||||
* @param {Object} [props={}]
|
||||
* An object of properties.
|
||||
*
|
||||
* @return {Object}
|
||||
* the extended object.
|
||||
*
|
||||
* @deprecated since version 5
|
||||
*/
|
||||
static extend(props) {
|
||||
props = props || {};
|
||||
|
||||
log.warn('Component.extend({}) has been deprecated, ' +
|
||||
' use videojs.extend(Component, {}) instead'
|
||||
);
|
||||
|
||||
// Set up the constructor using the supplied init method
|
||||
// or using the init of the parent object
|
||||
// Make sure to check the unobfuscated version for external libs
|
||||
const init = props.init || props.init || this.prototype.init ||
|
||||
this.prototype.init || function() {};
|
||||
// In Resig's simple class inheritance (previously used) the constructor
|
||||
// is a function that calls `this.init.apply(arguments)`
|
||||
// However that would prevent us from using `ParentObject.call(this);`
|
||||
// in a Child constructor because the `this` in `this.init`
|
||||
// would still refer to the Child and cause an infinite loop.
|
||||
// We would instead have to do
|
||||
// `ParentObject.prototype.init.apply(this, arguments);`
|
||||
// Bleh. We're not creating a _super() function, so it's good to keep
|
||||
// the parent constructor reference simple.
|
||||
const subObj = function() {
|
||||
init.apply(this, arguments);
|
||||
};
|
||||
|
||||
// Inherit from this object's prototype
|
||||
subObj.prototype = Object.create(this.prototype);
|
||||
// Reset the constructor property for subObj otherwise
|
||||
// instances of subObj would have the constructor of the parent Object
|
||||
subObj.prototype.constructor = subObj;
|
||||
|
||||
// Make the class extendable
|
||||
subObj.extend = Component.extend;
|
||||
|
||||
// Extend subObj's prototype with functions and other properties from props
|
||||
for (const name in props) {
|
||||
if (props.hasOwnProperty(name)) {
|
||||
subObj.prototype[name] = props[name];
|
||||
}
|
||||
}
|
||||
|
||||
return subObj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this component supports `requestAnimationFrame`.
|
||||
*
|
||||
* This is exposed primarily for testing purposes.
|
||||
*
|
||||
* @private
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Component.prototype.supportsRaf_ = typeof window.requestAnimationFrame === 'function' &&
|
||||
typeof window.cancelAnimationFrame === 'function';
|
||||
|
||||
Component.registerComponent('Component', Component);
|
||||
|
||||
export default Component;
|
||||
|
||||
@@ -22,11 +22,9 @@ class AudioTrackButton extends TrackButton {
|
||||
* The key/value store of player options.
|
||||
*/
|
||||
constructor(player, options = {}) {
|
||||
options.tracks = player.audioTracks && player.audioTracks();
|
||||
options.tracks = player.audioTracks();
|
||||
|
||||
super(player, options);
|
||||
|
||||
this.el_.setAttribute('aria-label', 'Audio Menu');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,6 +37,10 @@ class AudioTrackButton extends TrackButton {
|
||||
return `vjs-audio-button ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
buildWrapperCSSClass() {
|
||||
return `vjs-audio-button ${super.buildWrapperCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a menu item for each audio track
|
||||
*
|
||||
@@ -49,11 +51,10 @@ class AudioTrackButton extends TrackButton {
|
||||
* An array of menu items
|
||||
*/
|
||||
createItems(items = []) {
|
||||
const tracks = this.player_.audioTracks && this.player_.audioTracks();
|
||||
// if there's only one audio track, there no point in showing it
|
||||
this.hideThreshold_ = 1;
|
||||
|
||||
if (!tracks) {
|
||||
return items;
|
||||
}
|
||||
const tracks = this.player_.audioTracks();
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
const track = tracks[i];
|
||||
|
||||
@@ -33,14 +33,12 @@ class AudioTrackMenuItem extends MenuItem {
|
||||
|
||||
this.track = track;
|
||||
|
||||
if (tracks) {
|
||||
const changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
const changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
this.on('dispose', () => {
|
||||
tracks.removeEventListener('change', changeHandler);
|
||||
});
|
||||
}
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
this.on('dispose', () => {
|
||||
tracks.removeEventListener('change', changeHandler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,10 +57,6 @@ class AudioTrackMenuItem extends MenuItem {
|
||||
|
||||
super.handleClick(event);
|
||||
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
const track = tracks[i];
|
||||
|
||||
|
||||
@@ -12,13 +12,12 @@ import './time-controls/remaining-time-display.js';
|
||||
import './live-display.js';
|
||||
import './progress-control/progress-control.js';
|
||||
import './fullscreen-toggle.js';
|
||||
import './volume-control/volume-control.js';
|
||||
import './volume-menu-button.js';
|
||||
import './mute-toggle.js';
|
||||
import './volume-panel.js';
|
||||
import './text-track-controls/chapters-button.js';
|
||||
import './text-track-controls/descriptions-button.js';
|
||||
import './text-track-controls/subtitles-button.js';
|
||||
import './text-track-controls/captions-button.js';
|
||||
import './text-track-controls/subs-caps-button.js';
|
||||
import './audio-track-controls/audio-track-button.js';
|
||||
import './playback-rate-menu/playback-rate-menu-button.js';
|
||||
import './spacer-controls/custom-control-spacer.js';
|
||||
@@ -41,7 +40,8 @@ class ControlBar extends Component {
|
||||
className: 'vjs-control-bar',
|
||||
dir: 'ltr'
|
||||
}, {
|
||||
// The control bar is a group, so it can contain menuitems
|
||||
// The control bar is a group, but we don't aria-label it to avoid
|
||||
// over-announcing by JAWS
|
||||
role: 'group'
|
||||
});
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class ControlBar extends Component {
|
||||
ControlBar.prototype.options_ = {
|
||||
children: [
|
||||
'playToggle',
|
||||
'volumeMenuButton',
|
||||
'volumePanel',
|
||||
'currentTimeDisplay',
|
||||
'timeDivider',
|
||||
'durationDisplay',
|
||||
@@ -67,8 +67,7 @@ ControlBar.prototype.options_ = {
|
||||
'playbackRateMenuButton',
|
||||
'chaptersButton',
|
||||
'descriptionsButton',
|
||||
'subtitlesButton',
|
||||
'captionsButton',
|
||||
'subsCapsButton',
|
||||
'audioTrackButton',
|
||||
'fullscreenToggle'
|
||||
]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import Button from '../button';
|
||||
import Component from '../component';
|
||||
import * as Dom from '../utils/dom.js';
|
||||
import checkVolumeSupport from './volume-control/check-volume-support';
|
||||
|
||||
/**
|
||||
* A button component for muting the audio.
|
||||
@@ -24,23 +25,10 @@ class MuteToggle extends Button {
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'volumechange', this.update);
|
||||
// hide this control if volume support is missing
|
||||
checkVolumeSupport(this, player);
|
||||
|
||||
// hide mute toggle if the current tech doesn't support volume control
|
||||
if (player.tech_ && player.tech_.featuresVolumeControl === false) {
|
||||
this.addClass('vjs-hidden');
|
||||
}
|
||||
|
||||
this.on(player, 'loadstart', function() {
|
||||
// We need to update the button to account for a default muted state.
|
||||
this.update();
|
||||
|
||||
if (player.tech_.featuresVolumeControl === false) {
|
||||
this.addClass('vjs-hidden');
|
||||
} else {
|
||||
this.removeClass('vjs-hidden');
|
||||
}
|
||||
});
|
||||
this.on(player, ['loadstart', 'volumechange'], this.update);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,19 +53,47 @@ class MuteToggle extends Button {
|
||||
* @listens click
|
||||
*/
|
||||
handleClick(event) {
|
||||
this.player_.muted(this.player_.muted() ? false : true);
|
||||
const vol = this.player_.volume();
|
||||
const lastVolume = this.player_.lastVolume_();
|
||||
|
||||
if (vol === 0) {
|
||||
const volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
|
||||
|
||||
this.player_.volume(volumeToSet);
|
||||
this.player_.muted(false);
|
||||
} else {
|
||||
this.player_.muted(this.player_.muted() ? false : true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state of volume.
|
||||
* Update the `MuteToggle` button based on the state of `volume` and `muted`
|
||||
* on the player.
|
||||
*
|
||||
* @param {EventTarget~Event} [event]
|
||||
* The {@link Player#loadstart} event if this function was called through an
|
||||
* event.
|
||||
* The {@link Player#loadstart} event if this function was called
|
||||
* through an event.
|
||||
*
|
||||
* @listens Player#loadstart
|
||||
* @listens Player#volumechange
|
||||
*/
|
||||
update(event) {
|
||||
this.updateIcon_();
|
||||
this.updateControlText_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the appearance of the `MuteToggle` icon.
|
||||
*
|
||||
* Possible states (given `level` variable below):
|
||||
* - 0: crossed out
|
||||
* - 1: zero bars of volume
|
||||
* - 2: one bar of volume
|
||||
* - 3: two bars of volume
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateIcon_() {
|
||||
const vol = this.player_.volume();
|
||||
let level = 3;
|
||||
|
||||
@@ -89,20 +105,27 @@ class MuteToggle extends Button {
|
||||
level = 2;
|
||||
}
|
||||
|
||||
// Don't rewrite the button text if the actual text doesn't change.
|
||||
// This causes unnecessary and confusing information for screen reader users.
|
||||
// This check is needed because this function gets called every time the volume level is changed.
|
||||
const toMute = this.player_.muted() ? 'Unmute' : 'Mute';
|
||||
|
||||
if (this.controlText() !== toMute) {
|
||||
this.controlText(toMute);
|
||||
}
|
||||
|
||||
// TODO improve muted icon classes
|
||||
for (let i = 0; i < 4; i++) {
|
||||
Dom.removeElClass(this.el_, `vjs-vol-${i}`);
|
||||
Dom.removeClass(this.el_, `vjs-vol-${i}`);
|
||||
}
|
||||
Dom.addClass(this.el_, `vjs-vol-${level}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* If `muted` has changed on the player, update the control text
|
||||
* (`title` attribute on `vjs-mute-control` element and content of
|
||||
* `vjs-control-text` element).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateControlText_() {
|
||||
const soundOff = this.player_.muted() || this.player_.volume() === 0;
|
||||
const text = soundOff ? 'Unmute' : 'Mute';
|
||||
|
||||
if (this.controlText() !== text) {
|
||||
this.controlText(text);
|
||||
}
|
||||
Dom.addElClass(this.el_, `vjs-vol-${level}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ class PlayToggle extends Button {
|
||||
|
||||
this.on(player, 'play', this.handlePlay);
|
||||
this.on(player, 'pause', this.handlePause);
|
||||
this.on(player, 'ended', this.handleEnded);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,6 +66,7 @@ class PlayToggle extends Button {
|
||||
* @listens Player#play
|
||||
*/
|
||||
handlePlay(event) {
|
||||
this.removeClass('vjs-ended');
|
||||
this.removeClass('vjs-paused');
|
||||
this.addClass('vjs-playing');
|
||||
// change the button text to "Pause"
|
||||
@@ -86,6 +88,16 @@ class PlayToggle extends Button {
|
||||
this.controlText('Play');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the vjs-ended class to the element so it can change appearance
|
||||
*
|
||||
*/
|
||||
handleEnded(event) {
|
||||
this.removeClass('vjs-playing');
|
||||
this.addClass('vjs-ended');
|
||||
// change the button text to "Replay"
|
||||
this.controlText('Replay');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,6 +62,10 @@ class PlaybackRateMenuButton extends MenuButton {
|
||||
return `vjs-playback-rate ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
buildWrapperCSSClass() {
|
||||
return `vjs-playback-rate ${super.buildWrapperCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the playback rate menu
|
||||
*
|
||||
|
||||
@@ -27,6 +27,8 @@ class PlaybackRateMenuItem extends MenuItem {
|
||||
// Modify options for parent MenuItem class's init.
|
||||
options.label = label;
|
||||
options.selected = rate === 1;
|
||||
options.selectable = true;
|
||||
|
||||
super(player, options);
|
||||
|
||||
this.label = label;
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
* @file mouse-time-display.js
|
||||
*/
|
||||
import Component from '../../component.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
import computedStyle from '../../utils/computed-style.js';
|
||||
|
||||
import './time-tooltip';
|
||||
|
||||
/**
|
||||
* The Mouse Time Display component shows the time you will seek to
|
||||
* when hovering over the progress bar
|
||||
* The {@link MouseTimeDisplay} component tracks mouse movement over the
|
||||
* {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
|
||||
* indicating the time which is represented by a given point in the
|
||||
* {@link ProgressControl}.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
@@ -19,36 +21,18 @@ class MouseTimeDisplay extends Component {
|
||||
* Creates an instance of this class.
|
||||
*
|
||||
* @param {Player} player
|
||||
* The `Player` that this class should be attached to.
|
||||
* The {@link Player} that this class should be attached to.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* The key/value store of player options.
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
if (options.playerOptions &&
|
||||
options.playerOptions.controlBar &&
|
||||
options.playerOptions.controlBar.progressControl &&
|
||||
options.playerOptions.controlBar.progressControl.keepTooltipsInside) {
|
||||
this.keepTooltipsInside = options.playerOptions.controlBar.progressControl.keepTooltipsInside;
|
||||
}
|
||||
|
||||
if (this.keepTooltipsInside) {
|
||||
this.tooltip = Dom.createEl('div', {className: 'vjs-time-tooltip'});
|
||||
this.el().appendChild(this.tooltip);
|
||||
this.addClass('vjs-keep-tooltips-inside');
|
||||
}
|
||||
|
||||
this.update(0, 0);
|
||||
|
||||
player.on('ready', () => {
|
||||
this.on(player.controlBar.progressControl.el(), 'mousemove', Fn.throttle(Fn.bind(this, this.handleMouseMove), 25));
|
||||
});
|
||||
this.update = Fn.throttle(Fn.bind(this, this.update), 25);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the `Component`'s DOM element
|
||||
* Create the DOM element for this class.
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created.
|
||||
@@ -60,94 +44,44 @@ class MouseTimeDisplay extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the mouse move event on the `MouseTimeDisplay`.
|
||||
* Enqueues updates to its own DOM as well as the DOM of its
|
||||
* {@link TimeTooltip} child.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `mousemove` event that caused this to event to run.
|
||||
* @param {Object} seekBarRect
|
||||
* The `ClientRect` for the {@link SeekBar} element.
|
||||
*
|
||||
* @listen mousemove
|
||||
* @param {number} seekBarPoint
|
||||
* A number from 0 to 1, representing a horizontal reference point
|
||||
* from the left edge of the {@link SeekBar}
|
||||
*/
|
||||
handleMouseMove(event) {
|
||||
const duration = this.player_.duration();
|
||||
const newTime = this.calculateDistance(event) * duration;
|
||||
const position = event.pageX - Dom.findElPosition(this.el().parentNode).left;
|
||||
update(seekBarRect, seekBarPoint) {
|
||||
|
||||
this.update(newTime, position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the time and posistion of the `MouseTimeDisplay`.
|
||||
*
|
||||
* @param {number} newTime
|
||||
* Time to change the `MouseTimeDisplay` to.
|
||||
*
|
||||
* @param {nubmer} position
|
||||
* Postion from the left of the in pixels.
|
||||
*/
|
||||
update(newTime, position) {
|
||||
const time = formatTime(newTime, this.player_.duration());
|
||||
|
||||
this.el().style.left = position + 'px';
|
||||
this.el().setAttribute('data-current-time', time);
|
||||
|
||||
if (this.keepTooltipsInside) {
|
||||
const clampedPosition = this.clampPosition_(position);
|
||||
const difference = position - clampedPosition + 1;
|
||||
const tooltipWidth = parseFloat(computedStyle(this.tooltip, 'width'));
|
||||
const tooltipWidthHalf = tooltipWidth / 2;
|
||||
|
||||
this.tooltip.innerHTML = time;
|
||||
this.tooltip.style.right = `-${tooltipWidthHalf - difference}px`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mouse pointers x coordinate in pixels.
|
||||
*
|
||||
* @param {EventTarget~Event} [event]
|
||||
* The `mousemove` event that was passed to this function by
|
||||
* {@link MouseTimeDisplay#handleMouseMove}
|
||||
*
|
||||
* @return {number}
|
||||
* THe x position in pixels of the mouse pointer.
|
||||
*/
|
||||
calculateDistance(event) {
|
||||
return Dom.getPointerPosition(this.el().parentNode, event).x;
|
||||
}
|
||||
|
||||
/**
|
||||
* This takes in a horizontal position for the bar and returns a clamped position.
|
||||
* Clamped position means that it will keep the position greater than half the width
|
||||
* of the tooltip and smaller than the player width minus half the width o the tooltip.
|
||||
* It will only clamp the position if `keepTooltipsInside` option is set.
|
||||
*
|
||||
* @param {number} position
|
||||
* The position the bar wants to be
|
||||
*
|
||||
* @return {number}
|
||||
* The (potentially) new clamped position.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
clampPosition_(position) {
|
||||
if (!this.keepTooltipsInside) {
|
||||
return position;
|
||||
// If there is an existing rAF ID, cancel it so we don't over-queue.
|
||||
if (this.rafId_) {
|
||||
this.cancelAnimationFrame(this.rafId_);
|
||||
}
|
||||
|
||||
const playerWidth = parseFloat(computedStyle(this.player().el(), 'width'));
|
||||
const tooltipWidth = parseFloat(computedStyle(this.tooltip, 'width'));
|
||||
const tooltipWidthHalf = tooltipWidth / 2;
|
||||
let actualPosition = position;
|
||||
this.rafId_ = this.requestAnimationFrame(() => {
|
||||
const duration = this.player_.duration();
|
||||
const content = formatTime(seekBarPoint * duration, duration);
|
||||
|
||||
if (position < tooltipWidthHalf) {
|
||||
actualPosition = Math.ceil(tooltipWidthHalf);
|
||||
} else if (position > (playerWidth - tooltipWidthHalf)) {
|
||||
actualPosition = Math.floor(playerWidth - tooltipWidthHalf);
|
||||
}
|
||||
|
||||
return actualPosition;
|
||||
this.el_.style.left = `${seekBarRect.width * seekBarPoint}px`;
|
||||
this.getChild('timeTooltip').update(seekBarRect, seekBarPoint, content);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default options for `MouseTimeDisplay`
|
||||
*
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
MouseTimeDisplay.prototype.options_ = {
|
||||
children: [
|
||||
'timeTooltip'
|
||||
]
|
||||
};
|
||||
|
||||
Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
|
||||
export default MouseTimeDisplay;
|
||||
|
||||
@@ -2,45 +2,21 @@
|
||||
* @file play-progress-bar.js
|
||||
*/
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import {IE_VERSION, IS_IOS, IS_ANDROID} from '../../utils/browser.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
|
||||
import './time-tooltip';
|
||||
|
||||
/**
|
||||
* Shows play progress
|
||||
* Used by {@link SeekBar} to display media playback progress as part of the
|
||||
* {@link ProgressControl}.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class PlayProgressBar extends Component {
|
||||
|
||||
/**
|
||||
* Creates an instance of this class.
|
||||
*
|
||||
* @param {Player} player
|
||||
* The `Player` that this class should be attached to.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* The key/value store of player options.
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.updateDataAttr();
|
||||
this.on(player, 'timeupdate', this.updateDataAttr);
|
||||
player.ready(Fn.bind(this, this.updateDataAttr));
|
||||
|
||||
if (options.playerOptions &&
|
||||
options.playerOptions.controlBar &&
|
||||
options.playerOptions.controlBar.progressControl &&
|
||||
options.playerOptions.controlBar.progressControl.keepTooltipsInside) {
|
||||
this.keepTooltipsInside = options.playerOptions.controlBar.progressControl.keepTooltipsInside;
|
||||
}
|
||||
|
||||
if (this.keepTooltipsInside) {
|
||||
this.addClass('vjs-keep-tooltips-inside');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the `Component`'s DOM element
|
||||
* Create the the DOM element for this class.
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created.
|
||||
@@ -53,19 +29,51 @@ class PlayProgressBar extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the data-current-time attribute on the `PlayProgressBar`.
|
||||
* Enqueues updates to its own DOM as well as the DOM of its
|
||||
* {@link TimeTooltip} child.
|
||||
*
|
||||
* @param {EventTarget~Event} [event]
|
||||
* The `timeupdate` event that caused this to run.
|
||||
* @param {Object} seekBarRect
|
||||
* The `ClientRect` for the {@link SeekBar} element.
|
||||
*
|
||||
* @listens Player#timeupdate
|
||||
* @param {number} seekBarPoint
|
||||
* A number from 0 to 1, representing a horizontal reference point
|
||||
* from the left edge of the {@link SeekBar}
|
||||
*/
|
||||
updateDataAttr(event) {
|
||||
const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
update(seekBarRect, seekBarPoint) {
|
||||
|
||||
this.el_.setAttribute('data-current-time', formatTime(time, this.player_.duration()));
|
||||
// If there is an existing rAF ID, cancel it so we don't over-queue.
|
||||
if (this.rafId_) {
|
||||
this.cancelAnimationFrame(this.rafId_);
|
||||
}
|
||||
|
||||
this.rafId_ = this.requestAnimationFrame(() => {
|
||||
const time = (this.player_.scrubbing()) ?
|
||||
this.player_.getCache().currentTime :
|
||||
this.player_.currentTime();
|
||||
|
||||
const content = formatTime(time, this.player_.duration());
|
||||
const timeTooltip = this.getChild('timeTooltip');
|
||||
|
||||
if (timeTooltip) {
|
||||
timeTooltip.update(seekBarRect, seekBarPoint, content);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default options for {@link PlayProgressBar}.
|
||||
*
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
PlayProgressBar.prototype.options_ = {
|
||||
children: []
|
||||
};
|
||||
|
||||
// Time tooltips should not be added to a player on mobile devices or IE8
|
||||
if ((!IE_VERSION || IE_VERSION > 8) && !IS_IOS && !IS_ANDROID) {
|
||||
PlayProgressBar.prototype.options_.children.push('timeTooltip');
|
||||
}
|
||||
|
||||
Component.registerComponent('PlayProgressBar', PlayProgressBar);
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
* @file progress-control.js
|
||||
*/
|
||||
import Component from '../../component.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import {throttle, bind} from '../../utils/fn.js';
|
||||
|
||||
import './seek-bar.js';
|
||||
import './mouse-time-display.js';
|
||||
|
||||
/**
|
||||
* The Progress Control component contains the seek bar, load progress,
|
||||
@@ -14,6 +15,24 @@ import './mouse-time-display.js';
|
||||
*/
|
||||
class ProgressControl extends Component {
|
||||
|
||||
/**
|
||||
* Creates an instance of this class.
|
||||
*
|
||||
* @param {Player} player
|
||||
* The `Player` that this class should be attached to.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* The key/value store of player options.
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.handleMouseMove = throttle(bind(this, this.handleMouseMove), 25);
|
||||
this.on(this.el_, 'mousemove', this.handleMouseMove);
|
||||
|
||||
this.throttledHandleMouseSeek = throttle(bind(this, this.handleMouseSeek), 25);
|
||||
this.on(['mousedown', 'touchstart'], this.handleMouseDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the `Component`'s DOM element
|
||||
*
|
||||
@@ -25,6 +44,98 @@ class ProgressControl extends Component {
|
||||
className: 'vjs-progress-control vjs-control'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When the mouse moves over the `ProgressControl`, the pointer position
|
||||
* gets passed down to the `MouseTimeDisplay` component.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `mousemove` event that caused this function to run.
|
||||
*
|
||||
* @listen mousemove
|
||||
*/
|
||||
handleMouseMove(event) {
|
||||
const seekBar = this.getChild('seekBar');
|
||||
const mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
|
||||
const seekBarEl = seekBar.el();
|
||||
const seekBarRect = Dom.getBoundingClientRect(seekBarEl);
|
||||
let seekBarPoint = Dom.getPointerPosition(seekBarEl, event).x;
|
||||
|
||||
// The default skin has a gap on either side of the `SeekBar`. This means
|
||||
// that it's possible to trigger this behavior outside the boundaries of
|
||||
// the `SeekBar`. This ensures we stay within it at all times.
|
||||
if (seekBarPoint > 1) {
|
||||
seekBarPoint = 1;
|
||||
} else if (seekBarPoint < 0) {
|
||||
seekBarPoint = 0;
|
||||
}
|
||||
|
||||
if (mouseTimeDisplay) {
|
||||
mouseTimeDisplay.update(seekBarRect, seekBarPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
|
||||
*
|
||||
* @method ProgressControl#throttledHandleMouseSeek
|
||||
* @param {EventTarget~Event} event
|
||||
* The `mousemove` event that caused this function to run.
|
||||
*
|
||||
* @listen mousemove
|
||||
* @listen touchmove
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handle `mousemove` or `touchmove` events on the `ProgressControl`.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* `mousedown` or `touchstart` event that triggered this function
|
||||
*
|
||||
* @listens mousemove
|
||||
* @listens touchmove
|
||||
*/
|
||||
handleMouseSeek(event) {
|
||||
const seekBar = this.getChild('seekBar');
|
||||
|
||||
seekBar.handleMouseMove(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle `mousedown` or `touchstart` events on the `ProgressControl`.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* `mousedown` or `touchstart` event that triggered this function
|
||||
*
|
||||
* @listens mousedown
|
||||
* @listens touchstart
|
||||
*/
|
||||
handleMouseDown(event) {
|
||||
const doc = this.el_.ownerDocument;
|
||||
|
||||
this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
|
||||
this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
|
||||
this.on(doc, 'mouseup', this.handleMouseUp);
|
||||
this.on(doc, 'touchend', this.handleMouseUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle `mouseup` or `touchend` events on the `ProgressControl`.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* `mouseup` or `touchend` event that triggered this function.
|
||||
*
|
||||
* @listens touchend
|
||||
* @listens mouseup
|
||||
*/
|
||||
handleMouseUp(event) {
|
||||
const doc = this.el_.ownerDocument;
|
||||
|
||||
this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
|
||||
this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
|
||||
this.off(doc, 'mouseup', this.handleMouseUp);
|
||||
this.off(doc, 'touchend', this.handleMouseUp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,16 +3,21 @@
|
||||
*/
|
||||
import Slider from '../../slider/slider.js';
|
||||
import Component from '../../component.js';
|
||||
import {IE_VERSION, IS_IOS, IS_ANDROID} from '../../utils/browser.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
import computedStyle from '../../utils/computed-style.js';
|
||||
|
||||
import './load-progress-bar.js';
|
||||
import './play-progress-bar.js';
|
||||
import './tooltip-progress-bar.js';
|
||||
import './mouse-time-display.js';
|
||||
|
||||
// The number of seconds the `step*` functions move the timeline.
|
||||
const STEP_SECONDS = 5;
|
||||
|
||||
/**
|
||||
* Seek Bar and holder for the progress bars
|
||||
* Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
|
||||
* as its `bar`.
|
||||
*
|
||||
* @extends Slider
|
||||
*/
|
||||
@@ -29,20 +34,8 @@ class SeekBar extends Slider {
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.on(player, 'timeupdate', this.updateProgress);
|
||||
this.on(player, 'ended', this.updateProgress);
|
||||
player.ready(Fn.bind(this, this.updateProgress));
|
||||
|
||||
if (options.playerOptions &&
|
||||
options.playerOptions.controlBar &&
|
||||
options.playerOptions.controlBar.progressControl &&
|
||||
options.playerOptions.controlBar.progressControl.keepTooltipsInside) {
|
||||
this.keepTooltipsInside = options.playerOptions.controlBar.progressControl.keepTooltipsInside;
|
||||
}
|
||||
|
||||
if (this.keepTooltipsInside) {
|
||||
this.tooltipProgressBar = this.addChild('TooltipProgressBar');
|
||||
}
|
||||
this.update = Fn.throttle(Fn.bind(this, this.update), 50);
|
||||
this.on(player, ['timeupdate', 'ended'], this.update);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,12 +48,12 @@ class SeekBar extends Slider {
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-progress-holder'
|
||||
}, {
|
||||
'aria-label': 'progress bar'
|
||||
'aria-label': this.localize('Progress Bar')
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the seek bars tooltip and width.
|
||||
* Update the seek bar's UI.
|
||||
*
|
||||
* @param {EventTarget~Event} [event]
|
||||
* The `timeupdate` or `ended` event that caused this to run.
|
||||
@@ -68,47 +61,45 @@ class SeekBar extends Slider {
|
||||
* @listens Player#timeupdate
|
||||
* @listens Player#ended
|
||||
*/
|
||||
updateProgress(event) {
|
||||
this.updateAriaAttributes(this.el_);
|
||||
update() {
|
||||
const percent = super.update();
|
||||
const duration = this.player_.duration();
|
||||
|
||||
if (this.keepTooltipsInside) {
|
||||
this.updateAriaAttributes(this.tooltipProgressBar.el_);
|
||||
this.tooltipProgressBar.el_.style.width = this.bar.el_.style.width;
|
||||
|
||||
const playerWidth = parseFloat(computedStyle(this.player().el(), 'width'));
|
||||
const tooltipWidth = parseFloat(computedStyle(this.tooltipProgressBar.tooltip, 'width'));
|
||||
const tooltipStyle = this.tooltipProgressBar.el().style;
|
||||
|
||||
tooltipStyle.maxWidth = Math.floor(playerWidth - (tooltipWidth / 2)) + 'px';
|
||||
tooltipStyle.minWidth = Math.ceil(tooltipWidth / 2) + 'px';
|
||||
tooltipStyle.right = `-${tooltipWidth / 2}px`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update ARIA accessibility attributes
|
||||
*
|
||||
* @param {Element} el
|
||||
* The element to update with aria accessibility attributes.
|
||||
*/
|
||||
updateAriaAttributes(el) {
|
||||
// Allows for smooth scrubbing, when player can't keep up.
|
||||
const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
const time = (this.player_.scrubbing()) ?
|
||||
this.player_.getCache().currentTime :
|
||||
this.player_.currentTime();
|
||||
|
||||
// machine readable value of progress bar (percentage complete)
|
||||
el.setAttribute('aria-valuenow', (this.getPercent() * 100).toFixed(2));
|
||||
this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
|
||||
|
||||
// human readable value of progress bar (time complete)
|
||||
el.setAttribute('aria-valuetext', formatTime(time, this.player_.duration()));
|
||||
this.el_.setAttribute('aria-valuetext',
|
||||
this.localize('progress bar timing: currentTime={1} duration={2}',
|
||||
[formatTime(time, duration),
|
||||
formatTime(duration, duration)],
|
||||
'{1} of {2}'));
|
||||
|
||||
// Update the `PlayProgressBar`.
|
||||
this.bar.update(Dom.getBoundingClientRect(this.el_), percent);
|
||||
|
||||
return percent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get percentage of video played
|
||||
* Get the percentage of media played so far.
|
||||
*
|
||||
* @return {number}
|
||||
* The percentage played
|
||||
* The percentage of media played so far (0 to 1).
|
||||
*/
|
||||
getPercent() {
|
||||
const percent = this.player_.currentTime() / this.player_.duration();
|
||||
|
||||
// Allows for smooth scrubbing, when player can't keep up.
|
||||
const time = (this.player_.scrubbing()) ?
|
||||
this.player_.getCache().currentTime :
|
||||
this.player_.currentTime();
|
||||
|
||||
const percent = time / this.player_.duration();
|
||||
|
||||
return percent >= 1 ? 1 : percent;
|
||||
}
|
||||
@@ -122,15 +113,12 @@ class SeekBar extends Slider {
|
||||
* @listens mousedown
|
||||
*/
|
||||
handleMouseDown(event) {
|
||||
super.handleMouseDown(event);
|
||||
|
||||
this.player_.scrubbing(true);
|
||||
|
||||
this.videoWasPlaying = !this.player_.paused();
|
||||
this.player_.pause();
|
||||
|
||||
this.pauseTimer_ = this.setTimeout(function() {
|
||||
this.player_.pause();
|
||||
}, 100);
|
||||
super.handleMouseDown(event);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,11 +139,6 @@ class SeekBar extends Slider {
|
||||
|
||||
// Set new time (tell player to seek to new time)
|
||||
this.player_.currentTime(newTime);
|
||||
|
||||
if (event.type === 'mousemove') {
|
||||
this.clearTimeout(this.pauseTimer_);
|
||||
this.player_.pause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,8 +152,6 @@ class SeekBar extends Slider {
|
||||
handleMouseUp(event) {
|
||||
super.handleMouseUp(event);
|
||||
|
||||
this.clearTimeout(this.pauseTimer_);
|
||||
|
||||
this.player_.scrubbing(false);
|
||||
if (this.videoWasPlaying) {
|
||||
this.player_.play();
|
||||
@@ -181,18 +162,53 @@ class SeekBar extends Slider {
|
||||
* Move more quickly fast forward for keyboard-only users
|
||||
*/
|
||||
stepForward() {
|
||||
// more quickly fast forward for keyboard-only users
|
||||
this.player_.currentTime(this.player_.currentTime() + 5);
|
||||
this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move more quickly rewind for keyboard-only users
|
||||
*/
|
||||
stepBack() {
|
||||
// more quickly rewind for keyboard-only users
|
||||
this.player_.currentTime(this.player_.currentTime() - 5);
|
||||
this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the playback state of the player
|
||||
* This gets called when enter or space is used on the seekbar
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown` event that caused this function to be called
|
||||
*
|
||||
*/
|
||||
handleAction(event) {
|
||||
if (this.player_.paused()) {
|
||||
this.player_.play();
|
||||
} else {
|
||||
this.player_.pause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this SeekBar has focus and a key gets pressed down. By
|
||||
* default it will call `this.handleAction` when the key is space or enter.
|
||||
*
|
||||
* @param {EventTarget~Event} event
|
||||
* The `keydown` event that caused this function to be called.
|
||||
*
|
||||
* @listens keydown
|
||||
*/
|
||||
handleKeyPress(event) {
|
||||
|
||||
// Support Space (32) or Enter (13) key operation to fire a click event
|
||||
if (event.which === 32 || event.which === 13) {
|
||||
event.preventDefault();
|
||||
this.handleAction(event);
|
||||
} else if (super.handleKeyPress) {
|
||||
|
||||
// Pass keypress handling up for unsupported keys
|
||||
super.handleKeyPress(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,12 +220,16 @@ class SeekBar extends Slider {
|
||||
SeekBar.prototype.options_ = {
|
||||
children: [
|
||||
'loadProgressBar',
|
||||
'mouseTimeDisplay',
|
||||
'playProgressBar'
|
||||
],
|
||||
barName: 'playProgressBar'
|
||||
};
|
||||
|
||||
// MouseTimeDisplay tooltips should not be added to a player on mobile devices or IE8
|
||||
if ((!IE_VERSION || IE_VERSION > 8) && !IS_IOS && !IS_ANDROID) {
|
||||
SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the update event for this Slider when this event happens on the player.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @file time-tooltip.js
|
||||
*/
|
||||
import Component from '../../component';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
|
||||
/**
|
||||
* Time tooltips display a time above the progress bar.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class TimeTooltip extends Component {
|
||||
|
||||
/**
|
||||
* Create the time tooltip DOM element
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created.
|
||||
*/
|
||||
createEl() {
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-time-tooltip'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the position of the time tooltip relative to the `SeekBar`.
|
||||
*
|
||||
* @param {Object} seekBarRect
|
||||
* The `ClientRect` for the {@link SeekBar} element.
|
||||
*
|
||||
* @param {number} seekBarPoint
|
||||
* A number from 0 to 1, representing a horizontal reference point
|
||||
* from the left edge of the {@link SeekBar}
|
||||
*/
|
||||
update(seekBarRect, seekBarPoint, content) {
|
||||
const tooltipRect = Dom.getBoundingClientRect(this.el_);
|
||||
const playerRect = Dom.getBoundingClientRect(this.player_.el());
|
||||
const seekBarPointPx = seekBarRect.width * seekBarPoint;
|
||||
|
||||
// do nothing if either rect isn't available
|
||||
// for example, if the player isn't in the DOM for testing
|
||||
if (!playerRect || !tooltipRect) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is the space left of the `seekBarPoint` available within the bounds
|
||||
// of the player. We calculate any gap between the left edge of the player
|
||||
// and the left edge of the `SeekBar` and add the number of pixels in the
|
||||
// `SeekBar` before hitting the `seekBarPoint`
|
||||
const spaceLeftOfPoint = (seekBarRect.left - playerRect.left) + seekBarPointPx;
|
||||
|
||||
// This is the space right of the `seekBarPoint` available within the bounds
|
||||
// of the player. We calculate the number of pixels from the `seekBarPoint`
|
||||
// to the right edge of the `SeekBar` and add to that any gap between the
|
||||
// right edge of the `SeekBar` and the player.
|
||||
const spaceRightOfPoint = (seekBarRect.width - seekBarPointPx) +
|
||||
(playerRect.right - seekBarRect.right);
|
||||
|
||||
// This is the number of pixels by which the tooltip will need to be pulled
|
||||
// further to the right to center it over the `seekBarPoint`.
|
||||
let pullTooltipBy = tooltipRect.width / 2;
|
||||
|
||||
// Adjust the `pullTooltipBy` distance to the left or right depending on
|
||||
// the results of the space calculations above.
|
||||
if (spaceLeftOfPoint < pullTooltipBy) {
|
||||
pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
|
||||
} else if (spaceRightOfPoint < pullTooltipBy) {
|
||||
pullTooltipBy = spaceRightOfPoint;
|
||||
}
|
||||
|
||||
// Due to the imprecision of decimal/ratio based calculations and varying
|
||||
// rounding behaviors, there are cases where the spacing adjustment is off
|
||||
// by a pixel or two. This adds insurance to these calculations.
|
||||
if (pullTooltipBy < 0) {
|
||||
pullTooltipBy = 0;
|
||||
} else if (pullTooltipBy > tooltipRect.width) {
|
||||
pullTooltipBy = tooltipRect.width;
|
||||
}
|
||||
|
||||
this.el_.style.right = `-${pullTooltipBy}px`;
|
||||
Dom.textContent(this.el_, content);
|
||||
}
|
||||
}
|
||||
|
||||
Component.registerComponent('TimeTooltip', TimeTooltip);
|
||||
export default TimeTooltip;
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* @file play-progress-bar.js
|
||||
*/
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
|
||||
/**
|
||||
* Shows play progress
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class TooltipProgressBar extends Component {
|
||||
|
||||
/**
|
||||
* Creates an instance of this class.
|
||||
*
|
||||
* @param {Player} player
|
||||
* The `Player` that this class should be attached to.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* The key/value store of player options.
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.updateDataAttr();
|
||||
this.on(player, 'timeupdate', this.updateDataAttr);
|
||||
player.ready(Fn.bind(this, this.updateDataAttr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the `Component`'s DOM element
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created.
|
||||
*/
|
||||
createEl() {
|
||||
const el = super.createEl('div', {
|
||||
className: 'vjs-tooltip-progress-bar vjs-slider-bar',
|
||||
innerHTML: `<div class="vjs-time-tooltip"></div>
|
||||
<span class="vjs-control-text"><span>${this.localize('Progress')}</span>: 0%</span>`
|
||||
});
|
||||
|
||||
this.tooltip = el.querySelector('.vjs-time-tooltip');
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updatet the data-current-time attribute for TooltipProgressBar
|
||||
*
|
||||
* @param {EventTarget~Event} [event]
|
||||
* The `timeupdate` event that caused this function to run.
|
||||
*
|
||||
* @listens Player#timeupdate
|
||||
*/
|
||||
updateDataAttr(event) {
|
||||
const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
const formattedTime = formatTime(time, this.player_.duration());
|
||||
|
||||
this.el_.setAttribute('data-current-time', formattedTime);
|
||||
this.tooltip.innerHTML = formattedTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component.registerComponent('TooltipProgressBar', TooltipProgressBar);
|
||||
export default TooltipProgressBar;
|
||||
@@ -33,6 +33,8 @@ class CaptionSettingsMenuItem extends TextTrackMenuItem {
|
||||
// CaptionSettingsMenuItem has no concept of 'selected'
|
||||
options.selectable = false;
|
||||
|
||||
options.name = 'CaptionSettingsMenuItem';
|
||||
|
||||
super(player, options);
|
||||
this.addClass('vjs-texttrack-settings');
|
||||
this.controlText(', opens ' + options.kind + ' settings dialog');
|
||||
@@ -50,10 +52,8 @@ class CaptionSettingsMenuItem extends TextTrackMenuItem {
|
||||
* @listens click
|
||||
*/
|
||||
handleClick(event) {
|
||||
this.player().getChild('textTrackSettings').show();
|
||||
this.player().getChild('textTrackSettings').el_.focus();
|
||||
this.player().getChild('textTrackSettings').open();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
|
||||
|
||||
@@ -26,7 +26,6 @@ class CaptionsButton extends TextTrackButton {
|
||||
*/
|
||||
constructor(player, options, ready) {
|
||||
super(player, options, ready);
|
||||
this.el_.setAttribute('aria-label', 'Captions Menu');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,31 +38,8 @@ class CaptionsButton extends TextTrackButton {
|
||||
return `vjs-captions-button ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update caption menu items
|
||||
*
|
||||
* @param {EventTarget~Event} [event]
|
||||
* The `addtrack` or `removetrack` event that caused this function to be
|
||||
* called.
|
||||
*
|
||||
* @listens TextTrackList#addtrack
|
||||
* @listens TextTrackList#removetrack
|
||||
*/
|
||||
update(event) {
|
||||
let threshold = 2;
|
||||
|
||||
super.update();
|
||||
|
||||
// if native, then threshold is 1 because no settings button
|
||||
if (this.player().tech_ && this.player().tech_.featuresNativeTextTracks) {
|
||||
threshold = 1;
|
||||
}
|
||||
|
||||
if (this.items && this.items.length > threshold) {
|
||||
this.show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
buildWrapperCSSClass() {
|
||||
return `vjs-captions-button ${super.buildWrapperCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,6 +53,8 @@ class CaptionsButton extends TextTrackButton {
|
||||
|
||||
if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) {
|
||||
items.push(new CaptionSettingsMenuItem(this.player_, {kind: this.kind_}));
|
||||
|
||||
this.hideThreshold_ += 1;
|
||||
}
|
||||
|
||||
return super.createItems(items);
|
||||
|
||||
@@ -29,7 +29,6 @@ class ChaptersButton extends TextTrackButton {
|
||||
*/
|
||||
constructor(player, options, ready) {
|
||||
super(player, options, ready);
|
||||
this.el_.setAttribute('aria-label', 'Chapters Menu');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,6 +41,10 @@ class ChaptersButton extends TextTrackButton {
|
||||
return `vjs-chapters-button ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
buildWrapperCSSClass() {
|
||||
return `vjs-chapters-button ${super.buildWrapperCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the menu based on the current state of its items.
|
||||
*
|
||||
|
||||
@@ -26,18 +26,14 @@ class DescriptionsButton extends TextTrackButton {
|
||||
*/
|
||||
constructor(player, options, ready) {
|
||||
super(player, options, ready);
|
||||
this.el_.setAttribute('aria-label', 'Descriptions Menu');
|
||||
|
||||
const tracks = player.textTracks();
|
||||
const changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
|
||||
if (tracks) {
|
||||
const changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
this.on('dispose', function() {
|
||||
tracks.removeEventListener('change', changeHandler);
|
||||
});
|
||||
}
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
this.on('dispose', function() {
|
||||
tracks.removeEventListener('change', changeHandler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,6 +76,9 @@ class DescriptionsButton extends TextTrackButton {
|
||||
return `vjs-descriptions-button ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
buildWrapperCSSClass() {
|
||||
return `vjs-descriptions-button ${super.buildWrapperCSSClass()}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,11 +26,21 @@ class OffTextTrackMenuItem extends TextTrackMenuItem {
|
||||
options.track = {
|
||||
player,
|
||||
kind: options.kind,
|
||||
label: options.kind + ' off',
|
||||
kinds: options.kinds,
|
||||
default: false,
|
||||
mode: 'disabled'
|
||||
};
|
||||
|
||||
if (!options.kinds) {
|
||||
options.kinds = [options.kind];
|
||||
}
|
||||
|
||||
if (options.label) {
|
||||
options.track.label = options.label;
|
||||
} else {
|
||||
options.track.label = options.kinds.join(' and ') + ' off';
|
||||
}
|
||||
|
||||
// MenuItem is selectable
|
||||
options.selectable = true;
|
||||
|
||||
@@ -51,7 +61,7 @@ class OffTextTrackMenuItem extends TextTrackMenuItem {
|
||||
for (let i = 0, l = tracks.length; i < l; i++) {
|
||||
const track = tracks[i];
|
||||
|
||||
if (track.kind === this.track.kind && track.mode === 'showing') {
|
||||
if ((this.options_.kinds.indexOf(track.kind) > -1) && track.mode === 'showing') {
|
||||
selected = false;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @file sub-caps-button.js
|
||||
*/
|
||||
import TextTrackButton from './text-track-button.js';
|
||||
import Component from '../../component.js';
|
||||
import CaptionSettingsMenuItem from './caption-settings-menu-item.js';
|
||||
import SubsCapsMenuItem from './subs-caps-menu-item.js';
|
||||
import toTitleCase from '../../utils/to-title-case.js';
|
||||
/**
|
||||
* The button component for toggling and selecting captions and/or subtitles
|
||||
*
|
||||
* @extends TextTrackButton
|
||||
*/
|
||||
class SubsCapsButton extends TextTrackButton {
|
||||
|
||||
constructor(player, options = {}) {
|
||||
super(player, options);
|
||||
|
||||
// Although North America uses "captions" in most cases for
|
||||
// "captions and subtitles" other locales use "subtitles"
|
||||
this.label_ = 'subtitles';
|
||||
if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(this.player_.language_) > -1) {
|
||||
this.label_ = 'captions';
|
||||
}
|
||||
this.menuButton_.controlText(toTitleCase(this.label_));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the default DOM `className`.
|
||||
*
|
||||
* @return {string}
|
||||
* The DOM `className` for this object.
|
||||
*/
|
||||
buildCSSClass() {
|
||||
return `vjs-subs-caps-button ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
buildWrapperCSSClass() {
|
||||
return `vjs-subs-caps-button ${super.buildWrapperCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create caption/subtitles menu items
|
||||
*
|
||||
* @return {CaptionSettingsMenuItem[]}
|
||||
* The array of current menu items.
|
||||
*/
|
||||
createItems() {
|
||||
let items = [];
|
||||
|
||||
if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) {
|
||||
items.push(new CaptionSettingsMenuItem(this.player_, {kind: this.label_}));
|
||||
|
||||
this.hideThreshold_ += 1;
|
||||
}
|
||||
|
||||
items = super.createItems(items, SubsCapsMenuItem);
|
||||
return items;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* `kind`s of TextTrack to look for to associate it with this menu.
|
||||
*
|
||||
* @type {array}
|
||||
* @private
|
||||
*/
|
||||
SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
|
||||
|
||||
/**
|
||||
* The text that should display over the `SubsCapsButton`s controls.
|
||||
*
|
||||
*
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
SubsCapsButton.prototype.controlText_ = 'Subtitles';
|
||||
|
||||
Component.registerComponent('SubsCapsButton', SubsCapsButton);
|
||||
export default SubsCapsButton;
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @file subs-caps-menu-item.js
|
||||
*/
|
||||
import TextTrackMenuItem from './text-track-menu-item.js';
|
||||
import Component from '../../component.js';
|
||||
import {assign} from '../../utils/obj';
|
||||
|
||||
/**
|
||||
* SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
|
||||
* in the SubsCapsMenu.
|
||||
*
|
||||
* @extends TextTrackMenuItem
|
||||
*/
|
||||
class SubsCapsMenuItem extends TextTrackMenuItem {
|
||||
|
||||
createEl(type, props, attrs) {
|
||||
let innerHTML = `<span class="vjs-menu-item-text">${this.localize(this.options_.label)}`;
|
||||
|
||||
if (this.options_.track.kind === 'captions') {
|
||||
innerHTML += `
|
||||
<span aria-hidden="true" class="vjs-icon-placeholder"></span>
|
||||
<span class="vjs-control-text"> ${this.localize('Captions')}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
innerHTML += '</span>';
|
||||
|
||||
const el = super.createEl(type, assign({
|
||||
innerHTML
|
||||
}, props), attrs);
|
||||
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
|
||||
export default SubsCapsMenuItem;
|
||||
@@ -25,7 +25,6 @@ class SubtitlesButton extends TextTrackButton {
|
||||
*/
|
||||
constructor(player, options, ready) {
|
||||
super(player, options, ready);
|
||||
this.el_.setAttribute('aria-label', 'Subtitles Menu');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,6 +37,9 @@ class SubtitlesButton extends TextTrackButton {
|
||||
return `vjs-subtitles-button ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
buildWrapperCSSClass() {
|
||||
return `vjs-subtitles-button ${super.buildWrapperCSSClass()}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,10 @@ class TextTrackButton extends TrackButton {
|
||||
options.tracks = player.textTracks();
|
||||
|
||||
super(player, options);
|
||||
|
||||
if (!Array.isArray(this.kinds_)) {
|
||||
this.kinds_ = [this.kind_];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,26 +41,39 @@ class TextTrackButton extends TrackButton {
|
||||
* @return {TextTrackMenuItem[]}
|
||||
* Array of menu items that were created
|
||||
*/
|
||||
createItems(items = []) {
|
||||
createItems(items = [], TrackMenuItem = TextTrackMenuItem) {
|
||||
|
||||
// Label is an overide for the [track] off label
|
||||
// USed to localise captions/subtitles
|
||||
let label;
|
||||
|
||||
if (this.label_) {
|
||||
label = `${this.label_} off`;
|
||||
}
|
||||
// Add an OFF menu item to turn all tracks off
|
||||
items.push(new OffTextTrackMenuItem(this.player_, {kind: this.kind_}));
|
||||
items.push(new OffTextTrackMenuItem(this.player_, {
|
||||
kinds: this.kinds_,
|
||||
kind: this.kind_,
|
||||
label
|
||||
}));
|
||||
|
||||
this.hideThreshold_ += 1;
|
||||
|
||||
const tracks = this.player_.textTracks();
|
||||
|
||||
if (!tracks) {
|
||||
return items;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
const track = tracks[i];
|
||||
|
||||
// only add tracks that are of the appropriate kind and have a label
|
||||
if (track.kind === this.kind_) {
|
||||
items.push(new TextTrackMenuItem(this.player_, {
|
||||
// only add tracks that are of an appropriate kind and have a label
|
||||
if (this.kinds_.indexOf(track.kind) > -1) {
|
||||
const item = new TrackMenuItem(this.player_, {
|
||||
track,
|
||||
// MenuItem is selectable
|
||||
selectable: true
|
||||
}));
|
||||
});
|
||||
|
||||
item.addClass(`vjs-${track.kind}-menu-item`);
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,15 +34,13 @@ class TextTrackMenuItem extends MenuItem {
|
||||
super(player, options);
|
||||
|
||||
this.track = track;
|
||||
const changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
|
||||
if (tracks) {
|
||||
const changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
this.on('dispose', function() {
|
||||
tracks.removeEventListener('change', changeHandler);
|
||||
});
|
||||
}
|
||||
player.on(['loadstart', 'texttrackchange'], changeHandler);
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
this.on('dispose', function() {
|
||||
tracks.removeEventListener('change', changeHandler);
|
||||
});
|
||||
|
||||
// iOS7 doesn't dispatch change events to TextTrackLists when an
|
||||
// associated track's mode changes. Without something like
|
||||
@@ -50,7 +48,7 @@ class TextTrackMenuItem extends MenuItem {
|
||||
// possible to detect changes to the mode attribute and polyfill
|
||||
// the change event. As a poor substitute, we manually dispatch
|
||||
// change events whenever the controls modify the mode.
|
||||
if (tracks && tracks.onchange === undefined) {
|
||||
if (tracks.onchange === undefined) {
|
||||
let event;
|
||||
|
||||
this.on(['tap', 'click'], function() {
|
||||
@@ -86,8 +84,13 @@ class TextTrackMenuItem extends MenuItem {
|
||||
*/
|
||||
handleClick(event) {
|
||||
const kind = this.track.kind;
|
||||
let kinds = this.track.kinds;
|
||||
const tracks = this.player_.textTracks();
|
||||
|
||||
if (!kinds) {
|
||||
kinds = [kind];
|
||||
}
|
||||
|
||||
super.handleClick(event);
|
||||
|
||||
if (!tracks) {
|
||||
@@ -97,11 +100,7 @@ class TextTrackMenuItem extends MenuItem {
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
const track = tracks[i];
|
||||
|
||||
if (track.kind !== kind) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (track === this.track) {
|
||||
if (track === this.track && (kinds.indexOf(track.kind) > -1)) {
|
||||
track.mode = 'showing';
|
||||
} else {
|
||||
track.mode = 'disabled';
|
||||
|
||||
@@ -38,6 +38,7 @@ class TrackButton extends MenuButton {
|
||||
|
||||
tracks.addEventListener('removetrack', updateHandler);
|
||||
tracks.addEventListener('addtrack', updateHandler);
|
||||
this.player_.on('ready', updateHandler);
|
||||
|
||||
this.player_.on('dispose', function() {
|
||||
tracks.removeEventListener('removetrack', updateHandler);
|
||||
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário