Comparar commits
83 Commits
v5.14.1
...
v6.0.0-RC.1
| Autor | SHA1 | Data | |
|---|---|---|---|
| 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 | |||
| 4fd902229f | |||
| 42507f8c11 | |||
| e92db4f407 | |||
| 22cf3dd935 | |||
| eb389c57c0 | |||
| 26789e7470 | |||
| 642ad4b5cf | |||
| 0493f54d6f | |||
| d20e9ce128 | |||
| 74530d8b3a | |||
| a7ffa34b7b | |||
| 3f724f9349 | |||
| 559297a376 | |||
| 87cd26d958 | |||
| 98b80df0d0 | |||
| 83cbeec424 |
+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"]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ Include a [reduced test case](https://css-tricks.com/reduced-test-cases/), we ha
|
||||
|
||||
## Steps to reproduce
|
||||
Explain in detail the exact steps necessary to reproduce the issue.
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
@@ -3,7 +3,6 @@ Please describe the change as necessary.
|
||||
If it's a feature or enhancement please be as detailed as possible.
|
||||
If it's a bug fix, please link the issue that it fixes or describe the bug in as much detail.
|
||||
|
||||
|
||||
## Specific Changes proposed
|
||||
Please list the specific changes involved in this pull request.
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
CHANGELOG.md
|
||||
@@ -0,0 +1,84 @@
|
||||
var remarkrc = {
|
||||
settings: {
|
||||
bullet: '*',
|
||||
fence: '`',
|
||||
strong: '*',
|
||||
emphasis: '_',
|
||||
listItemIndent: 1,
|
||||
incrementListMarker: false
|
||||
},
|
||||
plugins: {
|
||||
'toc': {
|
||||
tight: true
|
||||
},
|
||||
}
|
||||
};
|
||||
var args = process.argv;
|
||||
|
||||
// only lint in non-output mode
|
||||
if (args.indexOf('-o') === -1 && args.indexOf('--output') === -1) {
|
||||
remarkrc['validate-links'] = {};
|
||||
remarkrc.plugins.lint = {
|
||||
'blockquote-indentation': ['error', 2],
|
||||
'checkbox-character-style': ['warn'],
|
||||
'checkbox-content-indent': ['error'],
|
||||
'code-block-style': ['error', 'fenced'],
|
||||
'definition-case': ['off'],
|
||||
'definition-spacing': ['error'],
|
||||
'emphasis-marker': ['error', '_'],
|
||||
'fenced-code-flag': ['error'],
|
||||
'fenced-code-marker': ['error', '`'],
|
||||
'file-extension': ['error'],
|
||||
'final-definition': ['error'],
|
||||
'final-newline': ['off'],
|
||||
'first-heading-level': ['warn', 1],
|
||||
'hard-break-spaces': ['off'],
|
||||
'heading-increment': ['error'],
|
||||
'heading-style': ['error', 'atx'],
|
||||
'link-title-style': ['warn', '"'],
|
||||
'list-item-bullet-indent': ['error'],
|
||||
'list-item-content-indent': ['warn'],
|
||||
'list-item-indent': ['error', 'space'],
|
||||
'list-item-spacing': ['off'],
|
||||
'maximum-heading-length': ['off'],
|
||||
'maximum-line-length': ['off'],
|
||||
'no-auto-link-without-protocol': ['error'],
|
||||
'no-blockquote-without-caret': ['error'],
|
||||
'no-consecutive-blank-lines': ['error'],
|
||||
'no-duplicate-definitions': ['error'],
|
||||
'no-duplicate-headings-in-section': ['error'],
|
||||
'no-duplicate-headings': ['off'],
|
||||
'no-emphasis-as-heading': ['error'],
|
||||
'no-file-name-articles': ['off'],
|
||||
'no-file-name-consecutive-dashes': ['off'],
|
||||
'no-file-name-irregular-characters': ['warn', '\\.a-zA-Z0-9-_'],
|
||||
'no-file-name-mixed-case': ['error'],
|
||||
'no-file-name-outer-dashes': ['error'],
|
||||
'no-heading-content-indent': ['error'],
|
||||
'no-heading-indent': ['error'],
|
||||
'no-heading-punctuation': ['off'],
|
||||
'no-html': ['off'],
|
||||
'no-inline-padding': ['error'],
|
||||
'no-literal-urls': ['off'],
|
||||
'no-missing-blank-lines': ['off'],
|
||||
'no-multiple-toplevel-headings': ['error'],
|
||||
'no-reference-like-url': ['error'],
|
||||
'no-shell-dollars': ['error'],
|
||||
'no-shortcut-reference-iamge': ['off'],
|
||||
'no-shortcut-reference-link': ['off'],
|
||||
'no-table-indentation': ['error'],
|
||||
'no-tabs': ['error'],
|
||||
'no-undefined-references': ['error'],
|
||||
'no-unused-definitions': ['error'],
|
||||
'ordered-list-marker-style': ['error', '.'],
|
||||
'ordered-list-marker-value': ['error', 'one'],
|
||||
'rule-style': ['error', '***'],
|
||||
'strong-marker': ['error', '*'],
|
||||
'table-cell-padding': ['warn', 'padded'],
|
||||
'table-cell-alignment': ['warn'],
|
||||
'table-pipes': ['warn'],
|
||||
'unordered-list-marker-style': ['warn', '*']
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = remarkrc;
|
||||
@@ -1,3 +1,76 @@
|
||||
<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)
|
||||
|
||||
### Features
|
||||
|
||||
* **player:** ingest a player div for videojs ([#3856](https://github.com/videojs/video.js/issues/3856)) ([74530d8](https://github.com/videojs/video.js/commit/74530d8))
|
||||
* deprecate the use of `starttime` in player.js ([#3838](https://github.com/videojs/video.js/issues/3838)) ([22cf3dd](https://github.com/videojs/video.js/commit/22cf3dd))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **html5:** (un)patchCanPlayType could set native canPlayType to null ([#3863](https://github.com/videojs/video.js/issues/3863)) ([559297a](https://github.com/videojs/video.js/commit/559297a))
|
||||
* **seeking:** don't always pause in mouse down ([#3886](https://github.com/videojs/video.js/issues/3886)) ([e92db4f](https://github.com/videojs/video.js/commit/e92db4f)), closes [#3839](https://github.com/videojs/video.js/issues/3839)
|
||||
* don't emit tap events on tech when using native controls ([#3873](https://github.com/videojs/video.js/issues/3873)) ([42507f8](https://github.com/videojs/video.js/commit/42507f8))
|
||||
* remote text track deprecation warnings ([#3864](https://github.com/videojs/video.js/issues/3864)) ([a7ffa34](https://github.com/videojs/video.js/commit/a7ffa34))
|
||||
* remove vjs-seeking on src change ([#3846](https://github.com/videojs/video.js/issues/3846)) ([83cbeec](https://github.com/videojs/video.js/commit/83cbeec)), closes [#3765](https://github.com/videojs/video.js/issues/3765)
|
||||
|
||||
### Chores
|
||||
|
||||
* **docs:** Documentation Linting and TOC generation ([#3841](https://github.com/videojs/video.js/issues/3841)) ([0493f54](https://github.com/videojs/video.js/commit/0493f54))
|
||||
* **faq:** move FAQ and troubleshooting guide to docs/ ([#3883](https://github.com/videojs/video.js/issues/3883)) ([26789e7](https://github.com/videojs/video.js/commit/26789e7))
|
||||
* **package:** update dependencies (enable Greenkeeper) 🌴 ([#3777](https://github.com/videojs/video.js/issues/3777)) ([d20e9ce](https://github.com/videojs/video.js/commit/d20e9ce))
|
||||
* **videojs-standard:** update to version 6.0.1 ([#3884](https://github.com/videojs/video.js/issues/3884)) ([eb389c5](https://github.com/videojs/video.js/commit/eb389c5))
|
||||
|
||||
### Documentation
|
||||
|
||||
* move examples out of code into docs ([642ad4b](https://github.com/videojs/video.js/commit/642ad4b))
|
||||
|
||||
### Tests
|
||||
|
||||
* **hooks:** move vjs hooks QUnit module into separate file ([#3862](https://github.com/videojs/video.js/issues/3862)) ([87cd26d](https://github.com/videojs/video.js/commit/87cd26d))
|
||||
* **hooks:** remove errors logged in tests ([#3865](https://github.com/videojs/video.js/issues/3865)) ([3f724f9](https://github.com/videojs/video.js/commit/3f724f9))
|
||||
|
||||
<a name="5.14.1"></a>
|
||||
## [5.14.1](https://github.com/videojs/video.js/compare/v5.14.0...v5.14.1) (2016-12-05)
|
||||
|
||||
|
||||
+57
-55
@@ -1,32 +1,28 @@
|
||||
# Collaborator Guide
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
**Table of Contents**
|
||||
## Table of Contents
|
||||
|
||||
- [Issues and Pull Requests](#issues-and-pull-requests)
|
||||
- [Accepting changes](#accepting-changes)
|
||||
- [Involving the TSC](#involving-the-tsc)
|
||||
- [Landing a PR](#landing-a-pr)
|
||||
- [Landing a PR manually](#landing-a-pr-manually)
|
||||
- [Landing a PR manually with several changes](#landing-a-pr-manually-with-several-changes)
|
||||
- [I just made a mistake](#i-just-made-a-mistake)
|
||||
- [I accidentally pushed a broken commit or incorrect commit to master](#i-accidentally-pushed-a-broken-commit-or-incorrect-commit-to-master)
|
||||
- [I lost changes](#i-lost-changes)
|
||||
- [I accidentally committed a broken change to master](#i-accidentally-committed-a-broken-change-to-master)
|
||||
- [video.js releases](#videojs-releases)
|
||||
- [Getting dependencies](#getting-dependencies)
|
||||
- [Install contrib](#install-contrib)
|
||||
- [npm access](#npm-access)
|
||||
- [GitHub personal access token](#github-personal-access-token)
|
||||
- [Doing a release](#doing-a-release)
|
||||
- [Doc credit](#doc-credit)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
* [Issues and Pull Requests](#issues-and-pull-requests)
|
||||
* [Accepting changes](#accepting-changes)
|
||||
* [Involving the TSC](#involving-the-tsc)
|
||||
* [Landing a PR](#landing-a-pr)
|
||||
* [Landing a PR manually](#landing-a-pr-manually)
|
||||
* [Landing a PR manually with several changes](#landing-a-pr-manually-with-several-changes)
|
||||
* [I just made a mistake](#i-just-made-a-mistake)
|
||||
* [I accidentally pushed a broken commit or incorrect commit to master](#i-accidentally-pushed-a-broken-commit-or-incorrect-commit-to-master)
|
||||
* [I lost changes](#i-lost-changes)
|
||||
* [I accidentally committed a broken change to master](#i-accidentally-committed-a-broken-change-to-master)
|
||||
* [video.js releases](#videojs-releases)
|
||||
* [Getting dependencies](#getting-dependencies)
|
||||
* [Install contrib](#install-contrib)
|
||||
* [npm access](#npm-access)
|
||||
* [GitHub personal access token](#github-personal-access-token)
|
||||
* [Doing a release](#doing-a-release)
|
||||
* [Doc credit](#doc-credit)
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -77,7 +73,7 @@ The footer should contain things like whether this is a breaking change or what
|
||||
|
||||
Here's an example:
|
||||
|
||||
```
|
||||
```commit
|
||||
fix(html5): a regression with html5 tech
|
||||
|
||||
This is where you'd explain what the regression is.
|
||||
@@ -90,87 +86,91 @@ Fixes #123
|
||||
_Optional:_ ensure you're not in a weird rebase or merge state:
|
||||
|
||||
```sh
|
||||
$ git am --abort
|
||||
$ git rebase --abort
|
||||
git am --abort
|
||||
git rebase --abort
|
||||
```
|
||||
|
||||
Checkout and update the master branch:
|
||||
|
||||
```sh
|
||||
$ git checkout master
|
||||
$ git remote update
|
||||
$ git rebase upstream/master
|
||||
git checkout master
|
||||
git remote update
|
||||
git rebase upstream/master
|
||||
```
|
||||
|
||||
Check out the PR:
|
||||
|
||||
```sh
|
||||
$ git fetch upstream pull/{{PR Number}}/head:{{name of branch}}
|
||||
$ git checkout -t {{name of branch}}
|
||||
git fetch upstream pull/{{PR Number}}/head:{{name of branch}}
|
||||
git checkout -t {{name of branch}}
|
||||
```
|
||||
|
||||
> For example:
|
||||
>
|
||||
> ```sh
|
||||
> $ git fetch upstream pull/123/head:gkatsev-html5-fix
|
||||
> $ git checkout -t gkatsev-html5-fix
|
||||
> git fetch upstream pull/123/head:gkatsev-html5-fix
|
||||
> git checkout -t gkatsev-html5-fix
|
||||
> ```
|
||||
|
||||
_Optional:_ If necessary, rebase against master. If you have multiple features in the PR, [landing a PR manually with several changes](#landing-a-pr-manually-with-several-changes)
|
||||
|
||||
```sh
|
||||
$ git rebase master
|
||||
git rebase master
|
||||
```
|
||||
|
||||
Fix up any issues that arise from the rebase, change back to the master branch and squash merge:
|
||||
|
||||
```sh
|
||||
$ git checkout master
|
||||
$ git merge --squash --no-commit gkatsev-html5-fix
|
||||
git checkout master
|
||||
git merge --squash --no-commit gkatsev-html5-fix
|
||||
```
|
||||
|
||||
The `--no-commit` tells git not to make a commit on your behalf. It does stage everything for you, so, you can instead it:
|
||||
|
||||
```sh
|
||||
$ git diff --cached
|
||||
git diff --cached
|
||||
```
|
||||
|
||||
Now get the author from the original commit:
|
||||
|
||||
```sh
|
||||
$ git log -n 1 --pretty=short gkatsev-html5-fix
|
||||
git log -n 1 --pretty=short gkatsev-html5-fix
|
||||
```
|
||||
Which shows:
|
||||
```
|
||||
commit 433c58224f5be34480c8e067ca6c5406ba1c1e9c
|
||||
Author: Gary Katsevman <git@gkatsev.com>
|
||||
|
||||
Update TOC
|
||||
Which shows:
|
||||
|
||||
```txt
|
||||
commit 433c58224f5be34480c8e067ca6c5406ba1c1e9c
|
||||
Author: Gary Katsevman <git@gkatsev.com>
|
||||
|
||||
Update TOC
|
||||
```
|
||||
|
||||
Now you can commit the change the change with the author, following our commit guidelines
|
||||
|
||||
```sh
|
||||
$ git commit --author "Gary Katsevman <git@gkatsev.com>"
|
||||
git commit --author "Gary Katsevman <git@gkatsev.com>"
|
||||
```
|
||||
|
||||
Now that it's committed, push to master
|
||||
|
||||
```sh
|
||||
$ git push upstream master
|
||||
git push upstream master
|
||||
```
|
||||
|
||||
Congratulate yourself for a job well done and the contributor for having his change landed in master.
|
||||
|
||||
#### Landing a PR manually with several changes
|
||||
|
||||
Follow the same steps as before but when you rebase against master, you want to do an interactive rebase and then squash the changes into just a few commits.
|
||||
|
||||
```sh
|
||||
$ git rebase -i master
|
||||
git rebase -i master
|
||||
```
|
||||
|
||||
This will give you an output like the following:
|
||||
|
||||
```
|
||||
```txt
|
||||
pick b4dc15d Update CONTRIBUTING.md with latest info
|
||||
pick 8592149 Add Dev certificate of origin
|
||||
pick 259dee6 Add grunt and doctoc npm scripts
|
||||
@@ -201,10 +201,10 @@ pick 433c582 Update TOC
|
||||
Replace `pick` to `fixup` or `edit` depending on how you want the output to look. You can also re-order the commits, if necessary.
|
||||
|
||||
> `fixup` will squash the commit it's infront of up into the commit above it
|
||||
|
||||
>
|
||||
> `edit` will allow you to edit the commit message before continuing
|
||||
|
||||
```
|
||||
```txt
|
||||
edit b4dc15d Update CONTRIBUTING.md with latest info
|
||||
fixup 8592149 Add Dev certificate of origin
|
||||
fixup f12af12 Add conventional-changelog-videojs link
|
||||
@@ -216,17 +216,18 @@ edit 259dee6 Add grunt and doctoc npm scripts
|
||||
When you get to the edit commits, git will give more information, but you'd want to run ammend the current commit while following our commit guidelines
|
||||
|
||||
```sh
|
||||
$ git commit --amend
|
||||
git commit --amend
|
||||
```
|
||||
|
||||
After going through and making the commits you want, you want to change back to master and then rebase the branch onto master so we get a clean history
|
||||
|
||||
```sh
|
||||
$ git rebase gkatsev-html5-fix
|
||||
git rebase gkatsev-html5-fix
|
||||
```
|
||||
|
||||
This will put our two commits into master:
|
||||
```
|
||||
|
||||
```txt
|
||||
b4dc15d chore(contributing.md): Update CONTRIBUTING.md with latest info <Gary Katsevman>
|
||||
259dee6 chore(package.json): Add grunt and doctoc npm scripts <Gary Katsevman>
|
||||
9e20386 v5.12.6 <Gary Katsevman>
|
||||
@@ -253,7 +254,7 @@ This is a great time to discover that something is broken. Because it hasn't bee
|
||||
To do so, just reset the branch against master.
|
||||
|
||||
```sh
|
||||
$ git reset --hard upstream/master
|
||||
git reset --hard upstream/master
|
||||
```
|
||||
|
||||
## video.js releases
|
||||
@@ -295,7 +296,7 @@ After generating one, make sure to keep it safe because GitHub will not show the
|
||||
To do a release, check out the master branch
|
||||
|
||||
```sh
|
||||
$ git checkout master
|
||||
git checkout master
|
||||
```
|
||||
|
||||
Then run the contrib command to do the next release. Don't forget to provide your GitHub token so the GitHub release goes through.
|
||||
@@ -312,7 +313,8 @@ After it's done, verify that the GitHub release has the correct changelog output
|
||||
|
||||
This collaborator guide was heavily inspired by [node.js's guide](https://github.com/nodejs/node/blob/master/COLLABORATOR_GUIDE.md)
|
||||
|
||||
|
||||
[issue template]: /.github/ISSUE_TEMPLATE.md
|
||||
|
||||
[pr template]: /.github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
[conventions]: https://github.com/videojs/conventional-changelog-videojs/blob/master/convention.md
|
||||
|
||||
+87
-88
@@ -2,56 +2,53 @@
|
||||
|
||||
So you want to help out? Great! There's a number of ways you can get involved.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
**Table of Contents**
|
||||
## Table of Contents
|
||||
|
||||
- [Other repositories where issues could be filed](#other-repositories-where-issues-could-be-filed)
|
||||
- [Filing issues](#filing-issues)
|
||||
- [Reporting a Bug](#reporting-a-bug)
|
||||
- [Requesting a Feature](#requesting-a-feature)
|
||||
- [Contributing code](#contributing-code)
|
||||
- [Building video.js locally](#building-videojs-locally)
|
||||
- [Forking and cloning the repository](#forking-and-cloning-the-repository)
|
||||
- [Installing local dependencies](#installing-local-dependencies)
|
||||
- [Running tests](#running-tests)
|
||||
- [Building videojs](#building-videojs)
|
||||
- [Testing Locally](#testing-locally)
|
||||
- [Sandbox test directory](#sandbox-test-directory)
|
||||
- [Running a local web server](#running-a-local-web-server)
|
||||
- [Watching source and test changes](#watching-source-and-test-changes)
|
||||
- [Making Changes](#making-changes)
|
||||
- [Step 1: Verify](#step-1-verify)
|
||||
- [Step 2: Update remote](#step-2-update-remote)
|
||||
- [Step 3: Branch](#step-3-branch)
|
||||
- [Step 4: Commit](#step-4-commit)
|
||||
- [Step 5: Test](#step-5-test)
|
||||
- [Step 6: Push](#step-6-push)
|
||||
- [Code Style Guide](#code-style-guide)
|
||||
- [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin-11)
|
||||
- [Doc Credit](#doc-credit)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
* [Other repositories where issues could be filed](#other-repositories-where-issues-could-be-filed)
|
||||
* [Filing issues](#filing-issues)
|
||||
* [Reporting a Bug](#reporting-a-bug)
|
||||
* [Requesting a Feature](#requesting-a-feature)
|
||||
* [Contributing code](#contributing-code)
|
||||
* [Building video.js locally](#building-videojs-locally)
|
||||
* [Forking and cloning the repository](#forking-and-cloning-the-repository)
|
||||
* [Installing local dependencies](#installing-local-dependencies)
|
||||
* [Running tests](#running-tests)
|
||||
* [Building videojs](#building-videojs)
|
||||
* [Testing Locally](#testing-locally)
|
||||
* [Sandbox test directory](#sandbox-test-directory)
|
||||
* [Running a local web server](#running-a-local-web-server)
|
||||
* [Watching source and test changes](#watching-source-and-test-changes)
|
||||
* [Making Changes](#making-changes)
|
||||
* [Step 1: Verify](#step-1-verify)
|
||||
* [Step 2: Update remote](#step-2-update-remote)
|
||||
* [Step 3: Branch](#step-3-branch)
|
||||
* [Step 4: Commit](#step-4-commit)
|
||||
* [Step 5: Test](#step-5-test)
|
||||
* [Step 6: Push](#step-6-push)
|
||||
* [Code Style Guide](#code-style-guide)
|
||||
* [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin-11)
|
||||
* [Doc Credit](#doc-credit)
|
||||
|
||||
## Other repositories where issues could be filed
|
||||
|
||||
There's also other Video.js projects where you can help. (check the [video.js org](https://github.com/videojs) for an up-to-date list of projects)
|
||||
|
||||
* [Videojs.com](https://github.com/videojs/videojs.com)
|
||||
* [Video.js flash player](https://github.com/videojs/videojs-flash)
|
||||
* [HLS](https://github.com/videojs/videojs-contrib-hls)
|
||||
* [DASH](https://github.com/videojs/videojs-contrib-dash)
|
||||
* [Youtube Tech](https://github.com/videojs/videojs-youtube)
|
||||
* [Vimeo Tech](https://github.com/videojs/videojs-vimeo)
|
||||
* [Ads](https://github.com/videojs/videojs-contrib-ads)
|
||||
* [Plugin generator](https://github.com/videojs/generator-videojs-plugin)
|
||||
* [Linter][linter]
|
||||
* [Videojs.com](https://github.com/videojs/videojs.com)
|
||||
* [Video.js flash player](https://github.com/videojs/videojs-flash)
|
||||
* [HLS](https://github.com/videojs/videojs-contrib-hls)
|
||||
* [DASH](https://github.com/videojs/videojs-contrib-dash)
|
||||
* [Youtube Tech](https://github.com/videojs/videojs-youtube)
|
||||
* [Vimeo Tech](https://github.com/videojs/videojs-vimeo)
|
||||
* [Ads](https://github.com/videojs/videojs-contrib-ads)
|
||||
* [Plugin generator](https://github.com/videojs/generator-videojs-plugin)
|
||||
* [Linter][linter]
|
||||
|
||||
## Filing issues
|
||||
|
||||
[GitHub Issues](https://github.com/videojs/video.js/issues) are used for all discussions around the codebase, including **bugs**, **features**, and other **enhancements**.
|
||||
|
||||
When filling out an issue, make sure to fill out the questions in the
|
||||
When filling out an issue, make sure to fill out the questions in the
|
||||
|
||||
### Reporting a Bug
|
||||
|
||||
**A bug is a demonstrable problem** that is caused by the code in the repository. Good bug reports are extremely helpful. Thank You!
|
||||
@@ -59,16 +56,11 @@ When filling out an issue, make sure to fill out the questions in the
|
||||
Guidelines for bug reports:
|
||||
|
||||
1. If your issue is with a particular video.js plugin or subproject, please open an issue against that project. See [list of some potential other projects above](#other-repositories-where-issues-could-be-filed)
|
||||
|
||||
1. Use the [GitHub issue search](https://github.com/videojs/video.js/issues) — check if the issue has already been reported.
|
||||
|
||||
1. Check if the issue has already been fixed — try to reproduce it using the latest `master` branch in the repository.
|
||||
|
||||
1. Isolate the problem — **create a [reduced test case](https://css-tricks.com/reduced-test-cases/)** with a live example. You can possibly use [this JSBin example](http://jsbin.com/axedog/edit) as a starting point -- don't forget to update it to the videojs version you use.
|
||||
|
||||
1. Use the [GitHub issue search](https://github.com/videojs/video.js/issues) — check if the issue has already been reported.
|
||||
1. Check if the issue has already been fixed — try to reproduce it using the latest `master` branch in the repository.
|
||||
1. Isolate the problem — **create a [reduced test case](https://css-tricks.com/reduced-test-cases/)** with a live example. You can possibly use [this JSBin example](http://jsbin.com/axedog/edit) as a starting point -- don't forget to update it to the videojs version you use.
|
||||
1. Answer all questions in the [issue template][]. The questions in the issue template are designed to try and provide the maintainers with as much information possible to minimize back-and-forth to get the issue resolved.
|
||||
|
||||
|
||||
A good bug report should be as detailed as possible, so that others won't have to follow up for the essential details.
|
||||
|
||||
**[File a bug report](https://github.com/videojs/video.js/issues/new)**
|
||||
@@ -76,12 +68,9 @@ A good bug report should be as detailed as possible, so that others won't have t
|
||||
### Requesting a Feature
|
||||
|
||||
1. [Check the plugin list](http://videojs.com/plugins/) for any plugins that may already support the feature.
|
||||
|
||||
2. [Search the issues](https://github.com/videojs/video.js/issues) for any previous requests for the same feature, and give a thumbs up or +1 on existing requests.
|
||||
|
||||
2. If no previous requests exist, create a new issue. Please be as clear as possible about why the feautre is needed and the intended use case.
|
||||
|
||||
3. Once again, be as details as possible and follow the [issue template][]
|
||||
1. [Search the issues](https://github.com/videojs/video.js/issues) for any previous requests for the same feature, and give a thumbs up or +1 on existing requests.
|
||||
1. If no previous requests exist, create a new issue. Please be as clear as possible about why the feautre is needed and the intended use case.
|
||||
1. Once again, be as details as possible and follow the [issue template][]
|
||||
|
||||
**[Request a feature](https://github.com/videojs/video.js/issues/new)**
|
||||
|
||||
@@ -89,17 +78,18 @@ A good bug report should be as detailed as possible, so that others won't have t
|
||||
|
||||
To contibute code you'll need to be able to build a copy of Video.js and run tests locally. There are a few requirements before getting started.
|
||||
|
||||
- Node.js
|
||||
Video.js uses Node for build and test automation. Node is available for Windows, Mac OS X, Linux, and SunOS, as well as source code if that doesn't scare you. [Download and install Node.js](http://nodejs.org/download/)
|
||||
* Node.js
|
||||
Video.js uses Node for build and test automation. Node is available for Windows, Mac OS X, Linux, and SunOS, as well as source code if that doesn't scare you. [Download and install Node.js](http://nodejs.org/download/)
|
||||
|
||||
* `grunt-cli`
|
||||
Optionally, install `grunt-cli` globally to use grunt directly. It can always be run via an npm script:
|
||||
|
||||
- `grunt-cli`
|
||||
Optionally, install `grunt-cli` globally to use grunt directly. It can always be run via an npm script:
|
||||
```sh
|
||||
$ npm run grunt
|
||||
npm run grunt
|
||||
```
|
||||
|
||||
```sh
|
||||
$ npm install -g grunt-cli
|
||||
npm install -g grunt-cli
|
||||
```
|
||||
|
||||
Depending on how you have node and npm set up, you may need to run the global install (`-g`) as a superuser by prepending `sudo`.
|
||||
@@ -113,27 +103,27 @@ First, [fork](http://help.github.com/fork-a-repo/) the video.js git repository.
|
||||
Clone your fork of the repo into your code directory
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/<your-username>/video.js.git
|
||||
git clone https://github.com/<your-username>/video.js.git
|
||||
```
|
||||
|
||||
Navigate to the newly cloned directory
|
||||
|
||||
```sh
|
||||
$ cd video.js
|
||||
cd video.js
|
||||
```
|
||||
|
||||
Assign the original repo to a remote called "upstream"
|
||||
|
||||
```
|
||||
```sh
|
||||
git remote add upstream https://github.com/videojs/video.js.git
|
||||
```
|
||||
|
||||
> In the future, if you want to pull in updates to video.js that happened after you cloned the main repo, you can run:
|
||||
>
|
||||
> ```sh
|
||||
> $ git remote update
|
||||
> $ git checkout master
|
||||
> $ git pull upstream master
|
||||
> git remote update
|
||||
> git checkout master
|
||||
> git pull upstream master
|
||||
> ```
|
||||
|
||||
#### Installing local dependencies
|
||||
@@ -141,7 +131,7 @@ git remote add upstream https://github.com/videojs/video.js.git
|
||||
Install the required node.js modules using node package manager
|
||||
|
||||
```sh
|
||||
$ npm install
|
||||
npm install
|
||||
```
|
||||
|
||||
> A note to Windows developers: If you run npm commands, and you find that your command prompt colors have suddenly reversed, you can configure npm to set color to false to prevent this from happening.
|
||||
@@ -153,7 +143,7 @@ $ npm install
|
||||
To run the tests all you need to do is run
|
||||
|
||||
```sh
|
||||
$ npm test
|
||||
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.
|
||||
@@ -163,12 +153,11 @@ This will build video.js locally and run the tests using [Karma](https://karma-r
|
||||
To build video.js, simply run
|
||||
|
||||
```sh
|
||||
$ npm run build
|
||||
npm run build
|
||||
```
|
||||
|
||||
This outputs an `es5/` and `dist/` folder. The `es5/` folder is used by bundling tools like browserify and webpack to package video.js into projects. The `dist/` folder has pre-compiled versions of video.js, including a minified version and the CSS file. This file can be included in page via a `<script></script>` tag.
|
||||
|
||||
|
||||
#### Testing Locally
|
||||
|
||||
Besides running automated tests, you often want to run video.js manually and play around with things as you're developing. A few things are provided to make it easier.
|
||||
@@ -178,7 +167,7 @@ Besides running automated tests, you often want to run video.js manually and pla
|
||||
There's a sandbox directory where you can add any file and it won't get tracked in git. To start you can copy the example index file.
|
||||
|
||||
```sh
|
||||
$ cp sandbox/index.html.example sandbox/index.html
|
||||
cp sandbox/index.html.example sandbox/index.html
|
||||
```
|
||||
|
||||
See [the following section](#running-a-local-web-server) for how to open the page in a browser.
|
||||
@@ -193,22 +182,27 @@ This ties in nicely with the sandbox directory. You can always open the `sandbox
|
||||
To run the local webserver, you can run it in a couple of ways.
|
||||
|
||||
```sh
|
||||
$ grunt connect
|
||||
$ open http://localhost:9999/sandbox/index.html
|
||||
grunt connect
|
||||
open http://localhost:9999/sandbox/index.html
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
$ npm start
|
||||
npm start
|
||||
```
|
||||
The latter does some extra work which will be described in the [next section]
|
||||
|
||||
The latter does some extra work which will be described in the next section.
|
||||
|
||||
#### Watching source and test changes
|
||||
|
||||
As you're developing, you want the build to re-run and update itself, and potentially re-run the tests. In addition, you want to launch a local web-server that you can open the `sandbox` directory in.
|
||||
To do so, you just need to run
|
||||
|
||||
```sh
|
||||
$ npm start
|
||||
npm start
|
||||
```
|
||||
|
||||
This sets up the local webserver using connect and then watches source files, test files, and CSS files for you and rebuilds things as they happen.
|
||||
|
||||
### Making Changes
|
||||
@@ -222,9 +216,9 @@ Whether you're adding something new, making something better, or fixing a bug, y
|
||||
Before starting work, you want to update your local repository to have all the latest changes.
|
||||
|
||||
```sh
|
||||
$ git remote update
|
||||
$ git checkout master
|
||||
$ git rebase upstream/master
|
||||
git remote update
|
||||
git checkout master
|
||||
git rebase upstream/master
|
||||
```
|
||||
|
||||
#### Step 3: Branch
|
||||
@@ -232,10 +226,11 @@ $ git rebase upstream/master
|
||||
You want to do your work in a separate branch.
|
||||
|
||||
```sh
|
||||
$ git checkout -b my-branch
|
||||
git checkout -b my-branch
|
||||
```
|
||||
|
||||
#### Step 4: Commit
|
||||
|
||||
Commit changes as you go. Write thorough descriptions of your changes in your commit messages.
|
||||
For more information see our [conventional changelog guidelines for video.js](https://github.com/videojs/conventional-changelog-videojs/blob/master/convention.md)
|
||||
Follow these guidelines:
|
||||
@@ -245,13 +240,16 @@ Follow these guidelines:
|
||||
1. A footer can be added if this fixes a particular issue on GitHub.
|
||||
|
||||
```sh
|
||||
$ git add src/js/player.js
|
||||
$ git commit
|
||||
git add src/js/player.js
|
||||
git commit
|
||||
```
|
||||
|
||||
An example commit message:
|
||||
An example of the first line of a commit message: `fix: changed the footer to correctly display foo`
|
||||
|
||||
```
|
||||
In the body of the commit message, we can talk about why we made the change. What the change entails.
|
||||
Any testing considerations or things to think about when looking at the commit. For Example:
|
||||
|
||||
```txt
|
||||
fix: one line commit explanation
|
||||
|
||||
In the body of the commit message, we can talk about why we made the change. What the change entails.
|
||||
@@ -264,8 +262,8 @@ Fixes #123. The footer can contain Fixes messages.
|
||||
> Make sure that git knows your name and email:
|
||||
>
|
||||
> ```sh
|
||||
> $ git config --global user.name "Random User"
|
||||
> $ git config --global user.email "random.user@example.com"
|
||||
> git config --global user.name "Random User"
|
||||
> git config --global user.email "random.user@example.com"
|
||||
> ```
|
||||
|
||||
#### Step 5: Test
|
||||
@@ -274,13 +272,13 @@ Any code change should come with corresponding test changes. Especially bug fixe
|
||||
Tests attached to bug fixes should fail before the change and succeed with it.
|
||||
|
||||
```sh
|
||||
$ npm test
|
||||
npm test
|
||||
```
|
||||
|
||||
#### Step 6: Push
|
||||
|
||||
```sh
|
||||
$ git push origin my-branch
|
||||
git push origin my-branch
|
||||
```
|
||||
|
||||
Then go to the [repo page](http://github.com/videojs/video.js) and click the "Pull Request" button and fill out the [pull request template](/.github/PULL_REQUEST_TEMPLATE.md)
|
||||
@@ -315,8 +313,8 @@ By making a contribution to this project, I certify that:
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
|
||||
|
||||
## Doc Credit
|
||||
|
||||
This doc was inspired by some great contribution guide examples including [contribute.md template](https://github.com/contribute-md/contribute-md-template),
|
||||
[grunt](https://github.com/gruntjs/grunt/wiki/Contributing),
|
||||
[html5 boilerplate](https://github.com/h5bp/html5-boilerplate/blob/master/CONTRIBUTING.md),
|
||||
@@ -324,4 +322,5 @@ This doc was inspired by some great contribution guide examples including [contr
|
||||
and [node.js](https://github.com/nodejs/node/blob/master/CONTRIBUTING.md).
|
||||
|
||||
[issue template]: /.github/ISSUE_TEMPLATE.md
|
||||
|
||||
[linter]: https://github.com/videojs/standard
|
||||
|
||||
+20
-5
@@ -83,25 +83,40 @@ _Video.js uses [BrowserStack][browserstack] for compatibility testing._
|
||||
|
||||
Video.js is [licensed][license] under the Apache License, Version 2.0.
|
||||
|
||||
|
||||
[browserstack]: https://browserstack.com
|
||||
[buildwith]: https://trends.builtwith.com/media/VideoJS
|
||||
|
||||
[builtwith]: https://trends.builtwith.com/media/VideoJS
|
||||
|
||||
[contributing]: CONTRIBUTING.md
|
||||
[contributing-building]: CONTRIBUTING.md#building-your-own-copy-of-videojs
|
||||
[contributing-code]: CONTRIBUTING.md#contributing-code
|
||||
|
||||
[coveralls-icon]: https://coveralls.io/repos/github/videojs/video.js/badge.svg?branch=master
|
||||
|
||||
[coveralls-link]: https://coveralls.io/github/videojs/video.js?branch=master
|
||||
|
||||
[docs]: http://docs.videojs.com
|
||||
|
||||
[fastly]: http://www.fastly.com/
|
||||
|
||||
[getting-started]: http://videojs.com/getting-started/
|
||||
|
||||
[license]: LICENSE
|
||||
|
||||
[logo]: http://videojs.com/img/logo.png
|
||||
|
||||
[npm-icon]: https://nodei.co/npm/video.js.png?downloads=true&downloadRank=true
|
||||
|
||||
[npm-link]: https://nodei.co/npm/video.js/
|
||||
[options]: docs/options.md
|
||||
|
||||
[options]: docs/guides/options.md
|
||||
|
||||
[plugins]: http://videojs.com/plugins/
|
||||
|
||||
[slack-icon]: http://slack.videojs.com/badge.svg
|
||||
|
||||
[slack-link]: http://slack.videojs.com
|
||||
|
||||
[travis-icon]: https://travis-ci.org/videojs/video.js.svg?branch=master
|
||||
|
||||
[travis-link]: https://travis-ci.org/videojs/video.js
|
||||
|
||||
[vjs]: http://videojs.com
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+75
-60
@@ -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,14 +145,6 @@ 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'
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
dev: {
|
||||
@@ -265,25 +263,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 +318,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 +389,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 +399,23 @@ 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:dist'
|
||||
],
|
||||
// Run multiple watch tasks in parallel
|
||||
// Needed so watchify can cache intelligently
|
||||
watchAll: [
|
||||
'watch',
|
||||
'browserify:watch',
|
||||
'browserify:watchnovtt',
|
||||
'browserify:tests',
|
||||
'karma:watch'
|
||||
],
|
||||
@@ -437,6 +443,12 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
shell: {
|
||||
babel: {
|
||||
command: 'npm run babel -- --watch',
|
||||
options: {
|
||||
preferLocal: true
|
||||
}
|
||||
},
|
||||
lint: {
|
||||
command: 'npm run lint',
|
||||
options: {
|
||||
@@ -486,17 +498,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 +519,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 +535,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
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"version": "5.14.1",
|
||||
"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" ],
|
||||
|
||||
@@ -1,12 +1,37 @@
|
||||
# Audio Tracks
|
||||
Audio tracks are a feature of HTML5 video for providing alternate audio track selections to the user, so that a track other than the main track can be played. Video.js offers a cross-browser implementation of audio tracks.
|
||||
|
||||
Audio tracks are a feature of HTML5 video for providing alternate audio track selections
|
||||
to the user, so that a track other than the main track can be played. Video.js offers a
|
||||
cross-browser implementation of audio tracks.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Caveats](#caveats)
|
||||
* [Working with Audio Tracks](#working-with-audio-tracks)
|
||||
* [Add an Audio Track to the Player](#add-an-audio-track-to-the-player)
|
||||
* [Listen for a Video Track Becoming Enabled](#listen-for-a-video-track-becoming-enabled)
|
||||
* [Removing an Audio Track from the Player](#removing-an-audio-track-from-the-player)
|
||||
* [API](#api)
|
||||
* [videojs.AudioTrack](#videojsaudiotrack)
|
||||
* [id](#id)
|
||||
* [kind](#kind)
|
||||
* [label](#label)
|
||||
* [language](#language)
|
||||
* [enabled](#enabled)
|
||||
|
||||
## Caveats
|
||||
- It is not possible to add audio tracks through HTML like you can with text 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 handles switching audio tracks to support track selection through the UI.
|
||||
|
||||
* It is not possible to add audio tracks through HTML like you can with text 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
|
||||
audio tracks to support track selection through the UI.
|
||||
|
||||
## Working with Audio Tracks
|
||||
|
||||
### Add an Audio Track to the Player
|
||||
|
||||
```js
|
||||
// Create a player.
|
||||
var player = videojs('my-player');
|
||||
@@ -24,9 +49,12 @@ player.audioTracks().addTrack(track);
|
||||
```
|
||||
|
||||
### Listen for a Video Track Becoming Enabled
|
||||
When a track is enabled or disabled on an `AudioTrackList`, a `change` event will be fired. You can listen for that event and do something with it.
|
||||
|
||||
> NOTE: The initial `AudioTrack` selection (usually the main track that is selected) should not fire a `change` event.
|
||||
When a track is enabled or disabled on an `AudioTrackList`, a `change` event will be
|
||||
fired. You can listen for that event and do something with it.
|
||||
|
||||
> NOTE: The initial `AudioTrack` selection (usually the main track that is selected)
|
||||
> should not fire a `change` event.
|
||||
|
||||
```js
|
||||
// Get the current player's AudioTrackList object.
|
||||
@@ -48,7 +76,9 @@ audioTrackList.addEventListener('change', function() {
|
||||
```
|
||||
|
||||
### Removing an Audio Track from the Player
|
||||
Assuming a player already exists and has an audio track that you want to remove, you might do something like the following:
|
||||
|
||||
Assuming a player already exists and has an audio track that you want to remove, you
|
||||
might do something like the following:
|
||||
|
||||
```js
|
||||
// Get the track we created in an earlier example.
|
||||
@@ -59,53 +89,69 @@ 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:
|
||||
|
||||
- `Player#audioTracks`
|
||||
- `AudioTrackList`
|
||||
- `AudioTrack`
|
||||
For more complete information, refer to the
|
||||
[Video.js API docs](http://docs.videojs.com/docs/api/index.html), specifically:
|
||||
|
||||
* `Player#audioTracks`
|
||||
* `AudioTrackList`
|
||||
* `AudioTrack`
|
||||
|
||||
### `videojs.AudioTrack`
|
||||
This class is based on [the `AudioTrack` standard][spec-audiotrack] and can be used to create new audio track objects.
|
||||
|
||||
This class is based on [the `AudioTrack` standard][spec-audiotrack] and can be used to
|
||||
create new audio track objects.
|
||||
|
||||
Each property below is available as an option to the `AudioTrack` constructor.
|
||||
|
||||
#### `id`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-id)
|
||||
|
||||
A unique identifier for this track. Video.js will generate one if not given.
|
||||
|
||||
#### `kind`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind)
|
||||
|
||||
Video.js supports standard `kind` values for `AudioTracks`:
|
||||
|
||||
- `"alternative"`: A possible alternative to the main track.
|
||||
- `"descriptions"`: An audio description of a video track.
|
||||
- `"main"`: The primary audio track for this video.
|
||||
- `"main-desc"`: The primary audio track, mixed with audio descriptions.
|
||||
- `"translation"`: A translated version of the main audio track.
|
||||
- `"commentary"`: Commentary on the primary audio track, e.g. a director's commentary.
|
||||
- `""` (default): No explicit kind, or the kind given by the track's metadata is not recognized by the user agent.
|
||||
* `"alternative"`: A possible alternative to the main track.
|
||||
* `"descriptions"`: An audio description of a video track.
|
||||
* `"main"`: The primary audio track for this video.
|
||||
* `"main-desc"`: The primary audio track, mixed with audio descriptions.
|
||||
* `"translation"`: A translated version of the main audio track.
|
||||
* `"commentary"`: Commentary on the primary audio track, e.g. a director's commentary.
|
||||
* `""` (default): No explicit kind, or the kind given by the track's metadata is not
|
||||
recognized by the user agent.
|
||||
|
||||
#### `label`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-label)
|
||||
|
||||
The label for the track that will be shown to the user. For example, in a menu that lists the different languages available as alternate audio tracks.
|
||||
The label for the track that will be shown to the user. For example, in a menu that lists
|
||||
the different languages available as alternate audio tracks.
|
||||
|
||||
#### `language`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-language)
|
||||
|
||||
The valid [BCP 47](https://tools.ietf.org/html/bcp47) code for the language of the audio track, e.g. `"en"` for English or `"es"` for Spanish.
|
||||
The valid [BCP 47](https://tools.ietf.org/html/bcp47) code for the language of the audio
|
||||
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)
|
||||
located in the Video.js root and refer to the [languages guide](languages.md) for more
|
||||
information on languages in Video.js.
|
||||
|
||||
#### `enabled`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-enabled)
|
||||
|
||||
Whether or not this track should be playing.
|
||||
|
||||
In Video.js, we only allow one track to be enabled at a time; so, if you enable more than one, the last one to be enabled will end up being the only one. While the spec allows for more than one track to be enabled, Safari and most implementations only allow one audio track to be enabled at a time.
|
||||
|
||||
In Video.js, we only allow one track to be enabled at a time; so, if you enable more
|
||||
than one, the last one to be enabled will end up being the only one. While the spec
|
||||
allows for more than one track to be enabled, Safari and most implementations only allow
|
||||
one audio track to be enabled at a time.
|
||||
|
||||
[spec-audiotrack]: https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack
|
||||
|
||||
+241
-17
@@ -1,29 +1,84 @@
|
||||
# Components
|
||||
|
||||
The architecture of the Video.js player is centered around components. The `Player` class and all classes representing player controls and other UI elements inherit from the `Component` class. This architecture makes it easy to construct the user interface of the Video.js player in a tree-like structure that mirrors the DOM.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [What is a Component?](#what-is-a-component)
|
||||
* [Creating a Component](#creating-a-component)
|
||||
* [Component Children](#component-children)
|
||||
* [Basic Example](#basic-example)
|
||||
* [Using Options](#using-options)
|
||||
* [Event Listening](#event-listening)
|
||||
* [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)
|
||||
* [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 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.
|
||||
* An associated DOM element.
|
||||
* 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).
|
||||
|
||||
## Creating a Component
|
||||
|
||||
Video.js components can be inherited and registered with Video.js to add new features and UI to the player.
|
||||
|
||||
For a working example, [we have a JSBin](http://jsbin.com/vobacas/edit?html,css,js,output) demonstrating the creation of a component for displaying a title across the top of the player.
|
||||
|
||||
In addition, there are a couple methods worth recognizing:
|
||||
|
||||
- `videojs.getComponent(String name)`: Retrieves component constructors from Video.js.
|
||||
- `videojs.registerComponent(String name, Function Comp)`: Registers component constructors with Video.js.
|
||||
- `videojs.extend(Function component, Object properties)`: Provides prototype inheritance. Can be used to extend a component's constructor, returning a new constructor with the given properties.
|
||||
* `videojs.getComponent(String name)`: Retrieves component constructors from Video.js.
|
||||
* `videojs.registerComponent(String name, Function Comp)`: Registers component constructors with Video.js.
|
||||
* `videojs.extend(Function component, Object properties)`: Provides prototype inheritance. Can be used to extend a component's constructor, returning a new constructor with the given properties.
|
||||
|
||||
Creation:
|
||||
|
||||
```js
|
||||
// adding a button to the player
|
||||
var player = videojs('some-video-id');
|
||||
var Component = videojs.getComponent('Component');
|
||||
var button = new Component(player);
|
||||
|
||||
console.log(button.el());
|
||||
```
|
||||
|
||||
The above code will output
|
||||
|
||||
```html
|
||||
<div class="video-js">
|
||||
<div class="vjs-button">Button</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Adding the new button to the player
|
||||
|
||||
```js
|
||||
// adding a button to the player
|
||||
var player = videojs('some-video-id');
|
||||
var button = player.addChild('button');
|
||||
|
||||
console.log(button.el());
|
||||
// will have the same html result as the previous example
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
### Basic Example
|
||||
|
||||
When child component is added to a parent component, Video.js inserts the element of the child into the element of the parent. For example, adding a component like this:
|
||||
|
||||
```js
|
||||
@@ -55,12 +110,180 @@ Results in a DOM that looks like this:
|
||||
</div>
|
||||
```
|
||||
|
||||
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.
|
||||
### Using Options
|
||||
|
||||
Pass in options for child constructors and options for children of the child.
|
||||
|
||||
```js
|
||||
var player = videojs('some-vid-id');
|
||||
var Component = videojs.getComponent('Component');
|
||||
var myComponent = new Component(player);
|
||||
var myButton = myComponent.addChild('MyButton', {
|
||||
text: 'Press Me',
|
||||
buttonChildExample: {
|
||||
buttonChildOption: true
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Children can also be added via options when a component is initialized.
|
||||
|
||||
> Note: Include a 'name' key which will be used if two child components of the same
|
||||
> type that need different options.
|
||||
|
||||
```js
|
||||
// MyComponent is from the above example
|
||||
var myComp = new MyComponent(player, {
|
||||
children: ['button', {
|
||||
name: 'button',
|
||||
someOtherOption: true
|
||||
}, {
|
||||
name: 'button',
|
||||
someOtherOption: false
|
||||
}]
|
||||
});
|
||||
```
|
||||
|
||||
## Event Listening
|
||||
|
||||
### Using `on`
|
||||
|
||||
```js
|
||||
var player = videojs('some-player-id');
|
||||
var Component = videojs.getComponent('Component');
|
||||
var myComponent = new Component(player);
|
||||
var myFunc = function() {
|
||||
var myComponent = this;
|
||||
console.log('myFunc called');
|
||||
};
|
||||
|
||||
myComponent.on('eventType', myFunc);
|
||||
myComponent.trigger('eventType');
|
||||
// logs 'myFunc called'
|
||||
```
|
||||
|
||||
The context of `myFunc` will be `myComponent` unless it is bound. You can add
|
||||
a listener to another element or component.
|
||||
|
||||
```js
|
||||
var otherComponent = new Component(player);
|
||||
|
||||
// myComponent/myFunc is from the above example
|
||||
myComponent.on(otherComponent.el(), 'eventName', myFunc);
|
||||
myComponent.on(otherComponent, 'eventName', myFunc);
|
||||
|
||||
otherComponent.trigger('eventName');
|
||||
// logs 'myFunc called' twice
|
||||
```
|
||||
|
||||
### Using `off`
|
||||
|
||||
```js
|
||||
var player = videojs('some-player-id');
|
||||
var Component = videojs.getComponent('Component');
|
||||
var myComponent = new Component(player);
|
||||
var myFunc = function() {
|
||||
var myComponent = this;
|
||||
console.log('myFunc called');
|
||||
};
|
||||
myComponent.on('eventType', myFunc);
|
||||
myComponent.trigger('eventType');
|
||||
// logs 'myFunc called'
|
||||
|
||||
myComponent.off('eventType', myFunc);
|
||||
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.
|
||||
You can use `off` to remove listeners that get added to other elements or
|
||||
components using:
|
||||
|
||||
`myComponent.on(otherComponent...`
|
||||
|
||||
In this case both the event type and listener function are **REQUIRED**.
|
||||
|
||||
```js
|
||||
var otherComponent = new Component(player);
|
||||
|
||||
// myComponent/myFunc is from the above example
|
||||
myComponent.on(otherComponent.el(), 'eventName', myFunc);
|
||||
myComponent.on(otherComponent, 'eventName', myFunc);
|
||||
|
||||
otherComponent.trigger('eventName');
|
||||
// logs 'myFunc called' twice
|
||||
myComponent.off(ootherComponent.el(), 'eventName', myFunc);
|
||||
myComponent.off(otherComponent, 'eventName', myFunc);
|
||||
otherComponent.trigger('eventName');
|
||||
// does nothing
|
||||
```
|
||||
|
||||
### Using `one`
|
||||
|
||||
```js
|
||||
var player = videojs('some-player-id');
|
||||
var Component = videojs.getComponent('Component');
|
||||
var myComponent = new Component(player);
|
||||
var myFunc = function() {
|
||||
var myComponent = this;
|
||||
console.log('myFunc called');
|
||||
};
|
||||
myComponent.one('eventName', myFunc);
|
||||
myComponent.trigger('eventName');
|
||||
// logs 'myFunc called'
|
||||
|
||||
myComponent.trigger('eventName');
|
||||
// does nothing
|
||||
```
|
||||
|
||||
You can also add a listener to another element or component that will get
|
||||
triggered only once.
|
||||
|
||||
```js
|
||||
var otherComponent = new Component(player);
|
||||
|
||||
// myComponent/myFunc is from the above example
|
||||
myComponent.one(otherComponent.el(), 'eventName', myFunc);
|
||||
myComponent.one(otherComponent, 'eventName', myFunc);
|
||||
|
||||
otherComponent.trigger('eventName');
|
||||
// logs 'myFunc called' twice
|
||||
|
||||
otherComponent.trigger('eventName');
|
||||
// does nothing
|
||||
```
|
||||
|
||||
### Using `trigger`
|
||||
|
||||
```js
|
||||
var player = videojs('some-player-id');
|
||||
var Component = videojs.getComponent('Component');
|
||||
var myComponent = new Component(player);
|
||||
var myFunc = function(data) {
|
||||
var myComponent = this;
|
||||
console.log('myFunc called');
|
||||
console.log(data);
|
||||
};
|
||||
myComponent.one('eventName', myFunc);
|
||||
myComponent.trigger('eventName');
|
||||
// logs 'myFunc called' and 'undefined'
|
||||
|
||||
myComponent.trigger({'type':'eventName'});
|
||||
// logs 'myFunc called' and 'undefined'
|
||||
|
||||
myComponent.trigger('eventName', {data: 'some data'});
|
||||
// logs 'myFunc called' and "{data: 'some data'}"
|
||||
|
||||
myComponent.trigger({'type':'eventName'}, {data: 'some data'});
|
||||
// logs 'myFunc called' and "{data: 'some data'}"
|
||||
```
|
||||
|
||||
## Default Component Tree
|
||||
|
||||
The default component structure of the Video.js player looks something like this:
|
||||
|
||||
```
|
||||
```tree
|
||||
Player
|
||||
├── PosterImage
|
||||
├── TextTrackDisplay
|
||||
@@ -92,22 +315,23 @@ Player
|
||||
```
|
||||
|
||||
## Specific Component Details
|
||||
### Progress Control
|
||||
The progress control has a grandchild component, the mouse time display, which shows a time tooltip that follows the mouse cursor.
|
||||
|
||||
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.
|
||||
### Volume Panel
|
||||
|
||||
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}`.
|
||||
|
||||
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.
|
||||
|
||||
@@ -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/docs/api/index.html
|
||||
|
||||
[console]: https://developer.mozilla.org/en-US/docs/Web/API/Console
|
||||
@@ -0,0 +1,118 @@
|
||||
# Event Target
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Overview](#overview)
|
||||
* [on() and addEventListener()](#on-and-addeventlistener)
|
||||
* [off() and removeEventListener()](#off-and-removeeventlistener)
|
||||
* [one()](#one)
|
||||
* [trigger() and dispatchEvent()](#trigger-and-dispatchevent)
|
||||
|
||||
## Overview
|
||||
|
||||
Events in video.js are setup so that they mimic the DOM API that is used on object, but also have helpful shorthand functions with the same functionality.
|
||||
|
||||
## `on()` and `addEventListener()`
|
||||
|
||||
This function is used to add an event listener to an EventTarget.
|
||||
|
||||
```js
|
||||
var foo = new EventTarget();
|
||||
var handleBar = function() {
|
||||
console.log('bar was triggered');
|
||||
};
|
||||
|
||||
foo.on('bar', handleBar);
|
||||
|
||||
// This causes any `event listeners` for the `bar` event to get called
|
||||
// see {@link EventTarget#trigger} for more information
|
||||
foo.trigger('bar');
|
||||
// logs 'bar was triggered'
|
||||
```
|
||||
|
||||
## `off()` and `removeEventListener()`
|
||||
|
||||
This function is used to remove an listener function from an EventTarget.
|
||||
|
||||
```js
|
||||
var foo = new EventTarget();
|
||||
var handleBar = function() {
|
||||
console.log('bar was triggered');
|
||||
};
|
||||
|
||||
// adds an `event listener` for the `bar` event
|
||||
// see {@link EventTarget#on} for more info
|
||||
foo.on('bar', handleBar);
|
||||
|
||||
// runs all `event listeners` for the `bar` event
|
||||
// see {@link EventTarget#trigger} for more info
|
||||
foo.trigger('bar');
|
||||
// logs 'bar was triggered'
|
||||
|
||||
foo.off('bar', handleBar);
|
||||
foo.trigger('bar');
|
||||
// does nothing
|
||||
```
|
||||
|
||||
## `one()`
|
||||
|
||||
This function is used to only have an event listener called once and never again.
|
||||
|
||||
Using `on()` and `off()` to mimic `one()` (not recommended)
|
||||
|
||||
```js
|
||||
var foo = new EventTarget();
|
||||
var handleBar = function() {
|
||||
console.log('bar was triggered');
|
||||
// after the first trigger remove this handler
|
||||
foo.off('bar', handleBar);
|
||||
};
|
||||
|
||||
foo.on('bar', handleBar);
|
||||
foo.trigger('bar');
|
||||
// logs 'bar was triggered'
|
||||
|
||||
foo.trigger('bar');
|
||||
// does nothing
|
||||
```
|
||||
|
||||
Using `one()`
|
||||
|
||||
```js
|
||||
var foo = new EventTarget();
|
||||
var handleBar = function() {
|
||||
console.log('bar was triggered');
|
||||
};
|
||||
|
||||
// removed after the first trigger
|
||||
foo.one('bar', handleBar);
|
||||
foo.trigger('bar');
|
||||
// logs 'bar was triggered'
|
||||
|
||||
foo.trigger('bar');
|
||||
// does nothing
|
||||
```
|
||||
|
||||
## `trigger()` and `dispatchEvent()`
|
||||
|
||||
This function is used to trigger an event on an EventTarget which will cause all listeners to run.
|
||||
|
||||
> Note: if 'click' is in `EventTarget.allowedEvents_`, trigger will attempt to call the
|
||||
> `onClick` function if it exists.
|
||||
|
||||
```js
|
||||
var foo = new EventTarget();
|
||||
var handleBar = function() {
|
||||
console.log('bar was triggered');
|
||||
};
|
||||
|
||||
foo.on('bar', handleBar);
|
||||
foo.trigger('bar');
|
||||
// logs 'bar was triggered'
|
||||
|
||||
foo.trigger('bar');
|
||||
// logs 'bar was triggered'
|
||||
|
||||
foo.trigger('foo');
|
||||
// does nothing
|
||||
```
|
||||
+178
-28
@@ -1,85 +1,194 @@
|
||||
# FAQ
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Q: What is video.js?](#q-what-is-videojs)
|
||||
* [Q: How do I install video.js?](#q-how-do-i-install-videojs)
|
||||
* [Q: Is video.js on bower?](#q-is-videojs-on-bower)
|
||||
* [Q: What do video.js version numbers mean?](#q-what-do-videojs-version-numbers-mean)
|
||||
* [Q: How can I troubleshoot playback issues?](#q-how-can-i-troubleshoot-playback-issues)
|
||||
* [Q: A video does not play in a specific browser. Why?](#q-a-video-does-not-play-in-a-specific-browser-why)
|
||||
* [Q: Why does the entire video download before playback? Why does the video load for a long time?](#q-why-does-the-entire-video-download-before-playback-why-does-the-video-load-for-a-long-time)
|
||||
* [Q: I see an error thrown that mentions vdata12345. What is that?](#q-i-see-an-error-thrown-that-mentions-vdata12345-what-is-that)
|
||||
* [Q: I think I found a bug with video.js or I want to add a feature. What should I do?](#q-i-think-i-found-a-bug-with-videojs-or-i-want-to-add-a-feature-what-should-i-do)
|
||||
* [if you think that you can fix the issue or add the feature](#if-you-think-that-you-can-fix-the-issue-or-add-the-feature)
|
||||
* [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)
|
||||
* [Q: How can I get my plugin listed on the website?](#q-how-can-i-get-my-plugin-listed-on-the-website)
|
||||
* [Q: Where can I find a list of video.js skins?](#q-where-can-i-find-a-list-of-videojs-skins)
|
||||
* [Q: Does video.js work as an audio only player?](#q-does-videojs-work-as-an-audio-only-player)
|
||||
* [Q: Does video.js support audio tracks?](#q-does-videojs-support-audio-tracks)
|
||||
* [Q: Does video.js support video tracks?](#q-does-videojs-support-video-tracks)
|
||||
* [Q: Does video.js support text tracks (captions, subtitles, etc)?](#q-does-videojs-support-text-tracks-captions-subtitles-etc)
|
||||
* [Q: Does video.js support HLS (HTTP Live streaming) video?](#q-does-videojs-support-hls-http-live-streaming-video)
|
||||
* [Q: Does video.js support MPEG Dash video?](#q-does-videojs-support-mpeg-dash-video)
|
||||
* [Q: Does video.js support live video?](#q-does-videojs-support-live-video)
|
||||
* [Q: Can video.js wrap around YouTube videos?](#q-can-videojs-wrap-around-youtube-videos)
|
||||
* [Q: Can video.js wrap around Vimeo videos?](#q-can-videojs-wrap-around-vimeo-videos)
|
||||
* [Q: Does video.js support DRM video?](#q-does-videojs-support-drm-video)
|
||||
* [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).
|
||||
* Unifies the native video api across browsers (polyfilling support for features
|
||||
if necessary)
|
||||
* Offers an extendable and themable UI
|
||||
* Takes care of accessibility for the user (in-progress)
|
||||
* Has a set of core plugins that offer support for tons of additional video formats.
|
||||
* [videojs-contrib-hls][hls]
|
||||
* [videojs-contrib-dash][dash]
|
||||
* Support for DRM video via a core plugin
|
||||
* [videojs-contrib-eme][eme]
|
||||
* Lots of plugins which offer support for all kinds of features. See the [plugin list on videojs.com][plugin-list]
|
||||
|
||||
* 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).
|
||||
* Unifies the native video api across browsers (polyfilling support for features
|
||||
if necessary)
|
||||
* Offers an extendable and themable UI
|
||||
* Takes care of accessibility for the user (in-progress)
|
||||
* Has a set of core plugins that offer support for tons of additional video formats.
|
||||
* [videojs-contrib-hls][hls]
|
||||
* [videojs-contrib-dash][dash]
|
||||
* Support for DRM video via a core plugin
|
||||
* [videojs-contrib-eme][eme]
|
||||
* Lots of plugins which offer support for all kinds of features. See the [plugin list on videojs.com][plugin-list]
|
||||
|
||||
## Q: How do I install video.js?
|
||||
|
||||
Currently video.js can be installed using bower, npm, serving a release file from
|
||||
a github tag, or even using a CDN hosted version. For information on doing any of those
|
||||
see the [install guide][install-guide].
|
||||
|
||||
## Q: Is video.js on bower?
|
||||
|
||||
Yes! See the [install guide][install-guide] for more information.
|
||||
|
||||
## Q: What do video.js version numbers mean?
|
||||
|
||||
video.js follows [semver][semver] which means that the API should not change
|
||||
out from under a user unless there is a major version increase.
|
||||
|
||||
## Q: How can I troubleshoot playback issues?
|
||||
|
||||
See the [troubleshooting guide][troubleshooting]. If troubleshooting does not
|
||||
solve your issue, please submit a [pull request or an issue][pr-issue-question].
|
||||
|
||||
## Q: A video does not play in a specific browser. Why?
|
||||
|
||||
See the [troubleshooting guide][troubleshooting]. If troubleshooting does not
|
||||
solve your issue, please submit a [pull request or an issue][pr-issue-question].
|
||||
|
||||
## Q: Why does the entire video download before playback? Why does the video load for a long time?
|
||||
|
||||
See the [troubleshooting guide][troubleshooting]. If troubleshooting does not
|
||||
solve your issue, please submit a [pull request or an issue][pr-issue-question].
|
||||
|
||||
## Q: I see an error thrown that mentions `vdata12345`. What is that?
|
||||
|
||||
See the [troubleshooting guide][troubleshooting]. If troubleshooting does not
|
||||
solve your issue, please submit a [pull request or an issue][pr-issue-question].
|
||||
|
||||
## Q: I think I found a bug with video.js or I want to add a feature. What should I do?
|
||||
|
||||
### if you think that you can fix the issue or add the feature
|
||||
|
||||
Submit a pull request to the [video.js repo][vjs-prs].
|
||||
Make sure to follow the [contributing guide][contributing-prs] and
|
||||
the [pull request template][pr-template].
|
||||
|
||||
### If you don't think you can fix the issue or add the feature
|
||||
|
||||
Open an [issue on the video.js repo][vjs-issues]. Make
|
||||
sure that you follow the [issue template][issue-template] and the
|
||||
[contributing guide][contributing-issues] so that we can better assist you
|
||||
with your issue.
|
||||
|
||||
## Q: What is a reduced test case?
|
||||
|
||||
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: https://css-tricks.com/reduced-test-cases/
|
||||
about reduced test cases visit [css-tricks](https://css-tricks.com/reduced-test-cases/)
|
||||
|
||||
## 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].
|
||||
|
||||
## 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. 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?
|
||||
|
||||
It's impossible to hide the network requests a browser makes and difficult to
|
||||
sufficiently obfuscate URLs in the source. Techniques such as token authentication may
|
||||
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
|
||||
a button to video.js that makes the video replay 10 times in a row before it stops playback for good. If such
|
||||
a plugin existed and was published users could include it on their page to share that functionality.
|
||||
|
||||
## Q: How do I make a plugin for video.js?
|
||||
|
||||
See the [plugin guide][plugin-guide] for information on making a plugin for video.js.
|
||||
|
||||
<!-- TODO: Once these is a button guide, add this back in
|
||||
@@ -88,92 +197,133 @@ See the [button guide][button-guide] for information on adding a button to video
|
||||
-->
|
||||
|
||||
## Q: Where can I find a list of video.js plugins?
|
||||
|
||||
The official [list of plugins on videojs.com][plugin-list].
|
||||
|
||||
## Q: How can I get my plugin listed on the website?
|
||||
|
||||
Add the 'videojs-plugin' [keyword to your array in package.json][npm-keywords]
|
||||
and publish your package to npm. If you use the [plugin generator][generator] this will be done automatically for you. See
|
||||
the [plugins guide][plugin-guide] for more information.
|
||||
|
||||
## Q: Where can I find a list of video.js skins?
|
||||
|
||||
See the [video.js github wiki][skins-list].
|
||||
|
||||
## Q: Does video.js work as an audio only player?
|
||||
|
||||
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.
|
||||
|
||||
## Q: Does video.js support audio tracks?
|
||||
|
||||
Yes! See the [audio tracks guide][audio-tracks] for information on using audio tracks.
|
||||
|
||||
## Q: Does video.js support video tracks?
|
||||
|
||||
The code for video tracks exists but it has not been tested. See the [video tracks guide][video-tracks]
|
||||
for more information on using video tracks.
|
||||
|
||||
## Q: Does video.js support text tracks (captions, subtitles, etc)?
|
||||
|
||||
Yes! See the [text tracks guide][text-tracks] for information on using text tracks.
|
||||
|
||||
## Q: Does video.js support HLS (HTTP Live streaming) video?
|
||||
|
||||
video.js supports HLS video if the native HTML5 element supports HLS (e.g. Safari, Edge,
|
||||
Chrome for Android, and iOS). For browsers without native support see the [videojs-contrib-hls][hls]
|
||||
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]
|
||||
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
|
||||
if you add it to your page with video.js.
|
||||
|
||||
## Q: Can video.js wrap around YouTube videos?
|
||||
|
||||
No. There is an official plugin that adds support. It is called [videojs-youtube][youtube].
|
||||
|
||||
## Q: Can video.js wrap around Vimeo videos?
|
||||
|
||||
No. There is an official plugin that adds support. It is called [videojs-vimeo][vimeo].
|
||||
|
||||
## Q: Does video.js support DRM video?
|
||||
|
||||
No. There is an official plugin that adds support. It is called [videojs-contrib-eme][eme].
|
||||
|
||||
## Q: Does video.js have any support for advertisement integrations?
|
||||
|
||||
No. There is an official plugin that adds support. It is called [videojs-contrib-ads][ads].
|
||||
|
||||
## Q: Can video.js be required in node.js?
|
||||
|
||||
Yes! Please [submit an issue or open a pull request][pr-issue-question] if this does not work.
|
||||
|
||||
## Q: Does video.js work with webpack?
|
||||
|
||||
Yes! Please [submit an issue or open a pull request][pr-issue-question] if this does not work.
|
||||
|
||||
<!-- guides -->
|
||||
[plugin-guide]: /docs/guides/plugins.md
|
||||
[install-guide]: http://videojs.com/getting-started/
|
||||
[troubleshooting]: /docs/guides/troubleshooting.md
|
||||
[video-tracks]: /docs/guides/video-tracks.md
|
||||
[audio-tracks]: /docs/guides/audio-tracks.md
|
||||
[text-tracks]: /docs/guides/text-tracks.md
|
||||
[pr-issue-question]: /docs/guides/faq.md#q-i-think-i-found-a-bug-with-videojs-or-i-want-to-add-a-feature-what-should-i-do
|
||||
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](./guides/react.md).
|
||||
|
||||
[plugin-guide]: plugins.md
|
||||
|
||||
[install-guide]: http://videojs.com/getting-started/
|
||||
|
||||
[troubleshooting]: troubleshooting.md
|
||||
|
||||
[video-tracks]: video-tracks.md
|
||||
|
||||
[audio-tracks]: audio-tracks.md
|
||||
|
||||
[text-tracks]: text-tracks.md
|
||||
|
||||
[pr-issue-question]: #q-i-think-i-found-a-bug-with-videojs-or-i-want-to-add-a-feature-what-should-i-do
|
||||
|
||||
<!-- official projects -->
|
||||
[hls]: http://github.com/videojs/videojs-contrib-hls
|
||||
|
||||
[dash]: http://github.com/videojs/videojs-contrib-dash
|
||||
|
||||
[eme]: https://github.com/videojs/videojs-contrib-eme
|
||||
|
||||
[generator]: https://github.com/videojs/generator-videojs-plugin
|
||||
|
||||
[youtube]: https://github.com/videojs/videojs-youtube
|
||||
|
||||
[vimeo]: https://github.com/videojs/videojs-vimeo
|
||||
|
||||
[ads]: https://github.com/videojs/videojs-contrib-ads
|
||||
|
||||
<!-- website/github -->
|
||||
[pr-template]: /.github/PULL_REQUEST_TEMPATLE.md
|
||||
[issue-template]: /.github/ISSUE_TEMPLATE.md
|
||||
[pr-template]: http://github.com/videojs/video.js/blob/master/.github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
[issue-template]: http://github.com/videojs/video.js/blob/master/.github/ISSUE_TEMPLATE.md
|
||||
|
||||
[plugin-list]: http://videojs.com/plugins
|
||||
|
||||
[skins-list]: https://github.com/videojs/video.js/wiki/Skins
|
||||
[contributing-issues]: /CONTRIBUTING.md#filing-issues
|
||||
[contributing-prs]: /CONTRIBUTING.md#contributing-code
|
||||
|
||||
[contributing-issues]: http://github.com/videojs/video.js/blob/master/CONTRIBUTING.md#filing-issues
|
||||
|
||||
[contributing-prs]: http://github.com/videojs/video.js/blob/master/CONTRIBUTING.md#contributing-code
|
||||
|
||||
[vjs-issues]: https://github.com/videojs/video.js/issues
|
||||
|
||||
[vjs-prs]: https://github.com/videojs/video.js/pulls
|
||||
|
||||
<!-- external -->
|
||||
[npm-keywords]: https://docs.npmjs.com/files/package.json#keywords
|
||||
|
||||
[semver]: http://semver.org/
|
||||
|
||||
[starter-example]: http://jsbin.com/axedog/edit?html,output
|
||||
|
||||
[debug-guide]: ./guides/debug.md
|
||||
|
||||
+66
-40
@@ -1,55 +1,74 @@
|
||||
# Hooks
|
||||
|
||||
Hooks exist so that users can "hook" on to certain video.js player lifecycle
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Current Hooks](#current-hooks)
|
||||
* [beforesetup](#beforesetup)
|
||||
* [setup](#setup)
|
||||
* [Usage](#usage)
|
||||
* [Adding](#adding)
|
||||
* [Getting](#getting)
|
||||
* [Removing](#removing)
|
||||
|
||||
## Current Hooks
|
||||
|
||||
Currently, the following hooks are avialable:
|
||||
|
||||
### beforesetup
|
||||
|
||||
`beforesetup` is called just before the player is created. This allows:
|
||||
|
||||
* modification of the options passed to the video.js function (`videojs('some-id, options)`)
|
||||
* modification of the dom video element that will be used for the player
|
||||
|
||||
`beforesetup` hook functions should:
|
||||
|
||||
* take two arguments
|
||||
1. videoEl: dom video element that video.js is going to use to create a player
|
||||
2. options: options that video.js was intialized with and will later pass to the player during creation
|
||||
1. videoEl: dom video element that video.js is going to use to create a player
|
||||
1. options: options that video.js was intialized with and will later pass to the player during creation
|
||||
* return options that will merge and override options that video.js with intialized with
|
||||
|
||||
Example: adding beforesetup hook
|
||||
|
||||
```js
|
||||
var beforeSetup = function(videoEl, options) {
|
||||
// videoEl.id will be some-id here, since that is what video.js
|
||||
// was created with
|
||||
var beforeSetup = function(videoEl, options) {
|
||||
// videoEl.id will be some-id here, since that is what video.js
|
||||
// was created with
|
||||
|
||||
videoEl.className += ' some-super-class';
|
||||
videoEl.className += ' some-super-class';
|
||||
|
||||
// autoplay will be true here, since we passed in as such
|
||||
(options.autoplay) {
|
||||
options.autoplay = false
|
||||
}
|
||||
// autoplay will be true here, since we passed in as such
|
||||
(options.autoplay) {
|
||||
options.autoplay = false
|
||||
}
|
||||
|
||||
// options that are returned here will be merged with old options
|
||||
// in this example options will now be
|
||||
// {autoplay: false, controls: true}
|
||||
return options;
|
||||
};
|
||||
// options that are returned here will be merged with old options
|
||||
// in this example options will now be
|
||||
// {autoplay: false, controls: true}
|
||||
return options;
|
||||
};
|
||||
|
||||
videojs.hook('beforesetup', beforeSetup);
|
||||
videojs('some-id', {autoplay: true, controls: true});
|
||||
videojs.hook('beforesetup', beforeSetup);
|
||||
videojs('some-id', {autoplay: true, controls: true});
|
||||
```
|
||||
|
||||
### setup
|
||||
|
||||
`setup` is called just after the player is created. This allows:
|
||||
|
||||
* plugin or custom functionalify to intialize on the player
|
||||
* changes to the player object itself
|
||||
|
||||
`setup` hook functions:
|
||||
|
||||
* Take one argument
|
||||
* player: the player that video.js created
|
||||
* player: the player that video.js created
|
||||
* Don't have to return anything
|
||||
|
||||
Example: adding setup hook
|
||||
|
||||
```js
|
||||
var setup = function(player) {
|
||||
// initialize the foo plugin
|
||||
@@ -65,56 +84,63 @@ Example: adding setup hook
|
||||
## Usage
|
||||
|
||||
### Adding
|
||||
|
||||
In order to use hooks you must first include video.js in the page or script that you are using. Then you add hooks using `videojs.hook(<name>, function)` before running the `videojs()` function.
|
||||
|
||||
Example: adding hooks
|
||||
|
||||
```js
|
||||
videojs.hook('beforesetup', function(videoEl, options) {
|
||||
// videoEl will be the element with id=vid1
|
||||
// options will contain {autoplay: false}
|
||||
});
|
||||
videojs.hook('setup', function(player) {
|
||||
// player will be the same player that is defined below
|
||||
// as `var player`
|
||||
});
|
||||
var player = videojs('vid1', {autoplay: false});
|
||||
videojs.hook('beforesetup', function(videoEl, options) {
|
||||
// videoEl will be the element with id=vid1
|
||||
// options will contain {autoplay: false}
|
||||
});
|
||||
videojs.hook('setup', function(player) {
|
||||
// player will be the same player that is defined below
|
||||
// as `var player`
|
||||
});
|
||||
|
||||
var player = videojs('vid1', {autoplay: false});
|
||||
```
|
||||
|
||||
After adding your hooks they will automatically be run at the correct time in the video.js lifecycle.
|
||||
|
||||
### Getting
|
||||
|
||||
To access the array of hooks that currently exists and will be run on the video.js object you can use the `videojs.hooks` function.
|
||||
|
||||
Example: getting all hooks attached to video.js
|
||||
|
||||
```js
|
||||
var beforeSetupHooks = videojs.hooks('beforesetup');
|
||||
var setupHooks = videojs.hooks('setup');
|
||||
var beforeSetupHooks = videojs.hooks('beforesetup');
|
||||
var setupHooks = videojs.hooks('setup');
|
||||
```
|
||||
|
||||
### Removing
|
||||
|
||||
To stop hooks from being executed during the video.js lifecycle you will remove them using `videojs.removeHook`.
|
||||
|
||||
Example: remove a hook that was defined by you
|
||||
|
||||
```js
|
||||
var beforeSetup = function(videoEl, options) {};
|
||||
var beforeSetup = function(videoEl, options) {};
|
||||
|
||||
// add the hook
|
||||
videojs.hook('beforesetup', beforeSetup);
|
||||
// add the hook
|
||||
videojs.hook('beforesetup', beforeSetup);
|
||||
|
||||
// remove that same hook
|
||||
videojs.removeHook('beforesetup', beforeSetup);
|
||||
// remove that same hook
|
||||
videojs.removeHook('beforesetup', beforeSetup);
|
||||
```
|
||||
|
||||
You can also use `videojs.hooks` in conjunction with `videojs.removeHook` but it may have unexpected results if used during an asynchronous callbacks as other plugins/functionality may have added hooks.
|
||||
|
||||
Example: using `videojs.hooks` and `videojs.removeHook` to remove a hook
|
||||
|
||||
```js
|
||||
// add the hook
|
||||
videojs.hook('setup', function(videoEl, options) {});
|
||||
// add the hook
|
||||
videojs.hook('setup', function(videoEl, options) {});
|
||||
|
||||
var setupHooks = videojs.hooks('setup');
|
||||
var setupHooks = videojs.hooks('setup');
|
||||
|
||||
// remove the hook you just added
|
||||
videojs.removeHook('setup', setupHooks[setupHooks.length - 1]);
|
||||
// remove the hook you just added
|
||||
videojs.removeHook('setup', setupHooks[setupHooks.length - 1]);
|
||||
```
|
||||
|
||||
|
||||
@@ -1,20 +1,40 @@
|
||||
# Languages
|
||||
|
||||
Multiple language support allows for users of non-English locales to natively interact with the Video.js player.
|
||||
|
||||
For an up-to-date list of the languages Video.js supports, see the [languages folder (`lang`)][lang-supported]. These JSON files are converted to JavaScript during the Video.js build process.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Using Video.js Languages](#using-videojs-languages)
|
||||
* [Contributing to Video.js Translations](#contributing-to-videojs-translations)
|
||||
* [JSON Format](#json-format)
|
||||
* [File Naming](#file-naming)
|
||||
* [Updating an Existing Translation](#updating-an-existing-translation)
|
||||
* [Writing a New Translation](#writing-a-new-translation)
|
||||
* [Advanced Language Usage](#advanced-language-usage)
|
||||
* [Adding Languages via the API](#adding-languages-via-the-api)
|
||||
* [Per-Player Languages](#per-player-languages)
|
||||
* [Setting Default Player Language](#setting-default-player-language)
|
||||
* [Determining Player Language](#determining-player-language)
|
||||
* [Internal Language Selection](#internal-language-selection)
|
||||
* [References](#references)
|
||||
|
||||
## Using Video.js Languages
|
||||
|
||||
Video.js ships with multiple translations (in `dist/lang/`) in JavaScript files. Each of these files can be included in a web page to provide support for that language in _all_ Video.js players:
|
||||
|
||||
```
|
||||
```html
|
||||
<script src="//example.com/path/to/video.min.js"></script>
|
||||
<script src="//example.com/path/to/lang/es.js"></script>
|
||||
```
|
||||
|
||||
## Contributing to Video.js Translations
|
||||
|
||||
We welcome new translations and improvements to existing ones! Please see the [contributing document](../../CONTRIBUTING.md) to get started contributing to Video.js and continue reading for specifics on how to contribute to translations of Video.js...
|
||||
|
||||
### JSON Format
|
||||
|
||||
Video.js uses a JSON object to describe a language, where the keys are English and the values are the target language. For example, a Spanish translation might look like this:
|
||||
|
||||
```JSON
|
||||
@@ -29,6 +49,7 @@ Video.js uses a JSON object to describe a language, where the keys are English a
|
||||
```
|
||||
|
||||
### File Naming
|
||||
|
||||
Translations are always found in the `lang/` directory.
|
||||
|
||||
Each file's name should be the [standard language code][lang-codes] that is most appropriate. For example, "es" for Spanish or "zh-CN" for Chinese.
|
||||
@@ -46,6 +67,7 @@ If there is a [missing translation](../translations-needed.md), mistake, or room
|
||||
1. Commit and open a pull request on GitHub.
|
||||
|
||||
### Writing a New Translation
|
||||
|
||||
The process for writing an entirely new translation is virtually identical to the process for [updating an existing translation](#updating-an-existing-translation) except that the new translation JSON file needs to be created.
|
||||
|
||||
The template for new language files is the English file ([lang/en.json][lang-en]). This file is always up-to-date with strings that need translations.
|
||||
@@ -53,17 +75,19 @@ The template for new language files is the English file ([lang/en.json][lang-en]
|
||||
The first step to writing a new translation is to copy the English file:
|
||||
|
||||
```sh
|
||||
$ cp lang/en.json lang/${NEW_LANG_CODE}.json
|
||||
cp lang/en.json lang/${NEW_LANG_CODE}.json
|
||||
```
|
||||
|
||||
Otherwise, the process is the same as [updating an existing translation](#updating-an-existing-translation).
|
||||
|
||||
## Advanced Language Usage
|
||||
|
||||
The instructions above for [using Video.js languages](#using-videojs-languages) should be sufficient for the majority of use-cases. However, languages can be provided at runtime.
|
||||
|
||||
In each case, these custom language definitions _take precedence over any Video.js-provided languages!_
|
||||
|
||||
### Adding Languages via the API
|
||||
|
||||
In addition to the stand-alone scripts provided by Video.js, the API supports manual definition of new languages via the `addLanguage` method. It takes two arguments: the [standard language code][lang-codes] and a [language definition object](#json-format).
|
||||
|
||||
```js
|
||||
@@ -78,6 +102,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):
|
||||
|
||||
```js
|
||||
@@ -106,14 +131,15 @@ Additionally, the `language` method of the player can be used to set the languag
|
||||
|
||||
The player language is set to one of the following in descending priority:
|
||||
|
||||
- The language [specified in options](#setting-default-player-language)
|
||||
- The language specified by the closest element with a `lang` attribute. This could be the player itself or a parent element. Usually, the document language is specified on the `<html>` tag.
|
||||
- Browser language preference; the first language if more than one is configured
|
||||
- English
|
||||
* The language [specified in options](#setting-default-player-language)
|
||||
* The language specified by the closest element with a `lang` attribute. This could be the player itself or a parent element. Usually, the document language is specified on the `<html>` tag.
|
||||
* Browser language preference; the first language if more than one is configured
|
||||
* English
|
||||
|
||||
#### Internal Language Selection
|
||||
- Language codes are considered case-insensitively (e.g. `en-US` == `en-us`).
|
||||
- If there is no match for a language code with a subcode (e.g. `en-us`), a match for the primary code (e.g. `en`) is used if available.
|
||||
|
||||
* Language codes are considered case-insensitively (e.g. `en-US` == `en-us`).
|
||||
* If there is no match for a language code with a subcode (e.g. `en-us`), a match for the primary code (e.g. `en`) is used if available.
|
||||
|
||||
## References
|
||||
|
||||
@@ -123,7 +149,8 @@ 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-supported]: https://github.com/videojs/video.js/tree/master/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 the ModalDialog](#creating-the-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/docs/api/modal-dialog.html
|
||||
|
||||
[creating-component]: ./components.md#creating-a-component
|
||||
+79
-42
@@ -2,44 +2,49 @@
|
||||
|
||||
> **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).
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table of Contents
|
||||
|
||||
|
||||
- [Standard `<video>` Element Options](#standard-video-element-options)
|
||||
- [`autoplay`](#autoplay)
|
||||
- [`controls`](#controls)
|
||||
- [`height`](#height)
|
||||
- [`loop`](#loop)
|
||||
- [`muted`](#muted)
|
||||
- [`poster`](#poster)
|
||||
- [`preload`](#preload)
|
||||
- [`src`](#src)
|
||||
- [`width`](#width)
|
||||
- [Video.js-specific Options](#videojs-specific-options)
|
||||
- [`aspectRatio`](#aspectratio)
|
||||
- [`children`](#children)
|
||||
- [`fluid`](#fluid)
|
||||
- [`inactivityTimeout`](#inactivitytimeout)
|
||||
- [`language`](#language)
|
||||
- [`languages`](#languages)
|
||||
- [`notSupportedMessage`](#notsupportedmessage)
|
||||
- [`plugins`](#plugins)
|
||||
- [`sourceOrder`](#sourceorder)
|
||||
- [`sources`](#sources)
|
||||
- [`techOrder`](#techorder)
|
||||
- [Component Options](#component-options)
|
||||
- [`children`](#children-1)
|
||||
- [`${componentName}`](#componentname)
|
||||
- [Tech Options](#tech-options)
|
||||
- [`${techName}`](#techname)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
* [Standard <video> Element Options](#standard-video-element-options)
|
||||
* [autoplay](#autoplay)
|
||||
* [controls](#controls)
|
||||
* [height](#height)
|
||||
* [loop](#loop)
|
||||
* [muted](#muted)
|
||||
* [poster](#poster)
|
||||
* [preload](#preload)
|
||||
* ['auto'](#auto)
|
||||
* ['metadata'](#metadata)
|
||||
* ['none'](#none)
|
||||
* [src](#src)
|
||||
* [width](#width)
|
||||
* [Video.js-specific Options](#videojs-specific-options)
|
||||
* [aspectRatio](#aspectratio)
|
||||
* [children](#children)
|
||||
* [fluid](#fluid)
|
||||
* [inactivityTimeout](#inactivitytimeout)
|
||||
* [language](#language)
|
||||
* [languages](#languages)
|
||||
* [nativeControlsForTouch](#nativecontrolsfortouch)
|
||||
* [notSupportedMessage](#notsupportedmessage)
|
||||
* [plugins](#plugins)
|
||||
* [sourceOrder](#sourceorder)
|
||||
* [sources](#sources)
|
||||
* [techOrder](#techorder)
|
||||
* [vtt.js](#vttjs)
|
||||
* [Component Options](#component-options)
|
||||
* [children](#children-1)
|
||||
* [${componentName}](#componentname)
|
||||
* [Tech Options](#tech-options)
|
||||
* [${techName}](#techname)
|
||||
* [nativeControlsForTouch](#nativecontrolsfortouch-1)
|
||||
* [nativeTextTracks](#nativetexttracks)
|
||||
|
||||
## 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 above](#setting-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](setup.md#options). Typically, defaults are not listed as this is left to browser vendors.
|
||||
|
||||
### `autoplay`
|
||||
|
||||
> Type: `boolean`
|
||||
|
||||
If `true`/present as an attribute, begins playback when the player is ready.
|
||||
@@ -47,70 +52,85 @@ If `true`/present as an attribute, begins playback when the player is ready.
|
||||
> **Note:** As of iOS 10, Apple offers `autoplay` support in Safari. For details, refer to ["New <video> Policies for iOS"][ios-10-updates].
|
||||
|
||||
### `controls`
|
||||
|
||||
> Type: `boolean`
|
||||
|
||||
Determines whether or not the player has controls that the user can interact with. Without controls the only way to start the video playing is with the `autoplay` attribute or through the Player API.
|
||||
|
||||
### `height`
|
||||
|
||||
> Type: `string|number`
|
||||
|
||||
Sets the display height of the video player in pixels.
|
||||
|
||||
### `loop`
|
||||
|
||||
> Type: `boolean`
|
||||
|
||||
Causes the video to start over as soon as it ends.
|
||||
|
||||
### `muted`
|
||||
|
||||
> Type: `boolean`
|
||||
|
||||
Will silence any audio by default.
|
||||
|
||||
### `poster`
|
||||
|
||||
> Type: `string`
|
||||
|
||||
A URL to an image that displays before the video begins playing. This is often a frame of the video or a custom title screen. As soon as the user hits "play" the image will go away.
|
||||
|
||||
### `preload`
|
||||
|
||||
> Type: `string`
|
||||
|
||||
Suggests to the browser whether or not the video data should begin downloading as soon as the `<video>` element is loaded. Supported values are:
|
||||
|
||||
#### `'auto'`
|
||||
|
||||
Start loading the video immediately (if the browser supports it). Some mobile devices will not preload the video in order to protect their users' bandwidth/data usage. This is why the value is called 'auto' and not something more conclusive like `'true'`.
|
||||
|
||||
_This tends to be the most common and recommended value as it allows the browser to choose the best behavior._
|
||||
|
||||
#### `'metadata'`
|
||||
|
||||
Load only the meta data of the video, which includes information like the duration and dimensions of the video. Sometimes, the meta data will be loaded by downloading a few frames of video.
|
||||
|
||||
#### `'none'`
|
||||
|
||||
Don't preload any data. The browser will wait until the user hits "play" to begin downloading.
|
||||
|
||||
### `src`
|
||||
|
||||
> Type: `string`
|
||||
|
||||
The source URL to a video source to embed.
|
||||
|
||||
### `width`
|
||||
|
||||
> Type: `string|number`
|
||||
|
||||
Sets the display height of the video player in pixels.
|
||||
|
||||
## Video.js-specific Options
|
||||
|
||||
Each option is `undefined` by default unless otherwise specified.
|
||||
|
||||
### `aspectRatio`
|
||||
|
||||
> Type: `string`
|
||||
|
||||
Puts the player in [fluid](#fluid) mode and the value is used when calculating the dynamic size of the player. The value should represent a ratio - two numbers separated by a colon (e.g. `"16:9"` or `"4:3"`).
|
||||
|
||||
### `children`
|
||||
|
||||
> Type: `Array|Object`
|
||||
|
||||
This option is inherited from the [`Component` base class](#component-options).
|
||||
|
||||
### `fluid`
|
||||
|
||||
> Type: `boolean`
|
||||
|
||||
When `true`, the Video.js player will have a fluid size. In other words, it will scale to fit its container.
|
||||
@@ -118,6 +138,7 @@ When `true`, the Video.js player will have a fluid size. In other words, it will
|
||||
Also, if the `<video>` element has the `"vjs-fluid"`, this option is automatically set to `true`.
|
||||
|
||||
### `inactivityTimeout`
|
||||
|
||||
> Type: `number`
|
||||
|
||||
Video.js indicates that the user is interacting with the player by way of the `"vjs-user-active"` and `"vjs-user-inactive"` classes and the `"useractive"` event.
|
||||
@@ -125,6 +146,7 @@ Video.js indicates that the user is interacting with the player by way of the `"
|
||||
The `inactivityTimeout` determines how many milliseconds of inactivity is required before declaring the user inactive. A value of `0` indicates that there is no `inactivityTimeout` and the user will never be considered inactive.
|
||||
|
||||
### `language`
|
||||
|
||||
> Type: `string`, Default: browser default or `'en'`
|
||||
|
||||
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.
|
||||
@@ -132,6 +154,7 @@ A [language code][lang-codes] matching one of the available languages in the pla
|
||||
Learn more about [languages in Video.js](languages.md).
|
||||
|
||||
### `languages`
|
||||
|
||||
> Type: `Object`
|
||||
|
||||
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.
|
||||
@@ -141,16 +164,19 @@ Learn more about [languages in Video.js](languages.md).
|
||||
> **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!
|
||||
|
||||
### `nativeControlsForTouch`
|
||||
|
||||
> Type: `boolean`
|
||||
|
||||
Explicitly set a default value for [the associated tech option](#nativecontrolsfortouch).
|
||||
|
||||
### `notSupportedMessage`
|
||||
|
||||
> Type: `string`
|
||||
|
||||
Allows overriding the default message that is displayed when Video.js cannot play back a media source.
|
||||
|
||||
### `plugins`
|
||||
|
||||
> Type: `Object`
|
||||
|
||||
This supports having plugins be initialized automatically with custom options when the player is initialized - rather than requiring you to initialize them manually.
|
||||
@@ -178,11 +204,12 @@ Although, since the `plugins` option is an object, the order of initialization i
|
||||
See [the plugins guide](plugins.md) for more information on Video.js plugins.
|
||||
|
||||
### `sourceOrder`
|
||||
> Type: `boolean`, Default: `false`
|
||||
|
||||
> Type: `boolean`, Default: `false`
|
||||
>
|
||||
> **Note:** In video.js 6.0, this option will default to `true`.
|
||||
|
||||
Tells Video.js to prefer the order of [`sources`](#sources) over [`techOrder`](#techOrder) in selecting a source and playback tech.
|
||||
Tells Video.js to prefer the order of [`sources`](#sources) over [`techOrder`](#techorder) in selecting a source and playback tech.
|
||||
|
||||
Given the following example:
|
||||
|
||||
@@ -209,17 +236,18 @@ However, because the `sourceOrder` is `true`, Video.js flips that process around
|
||||
|
||||
In summary, the default algorithm is:
|
||||
|
||||
- for each tech:
|
||||
- for each source:
|
||||
- if tech can play source, use this tech/source combo
|
||||
* for each tech:
|
||||
* for each source:
|
||||
* if tech can play source, use this tech/source combo
|
||||
|
||||
With `sourceOrder: true`, the algorithm becomes:
|
||||
|
||||
- for each source:
|
||||
- for each tech:
|
||||
- if tech can play source, use this tech/source combo
|
||||
* for each source:
|
||||
* for each tech:
|
||||
* if tech can play source, use this tech/source combo
|
||||
|
||||
### `sources`
|
||||
|
||||
> Type: `Array`
|
||||
|
||||
An array of objects that mirror the native `<video>` element's capability to have a series of child `<source>` elements. This should be an array of objects with the `src` and `type` properties. For example:
|
||||
@@ -246,11 +274,13 @@ Using `<source>` elements will have the same effect:
|
||||
```
|
||||
|
||||
### `techOrder`
|
||||
|
||||
> Type: `Array`, Default: `['html5', 'flash']`
|
||||
|
||||
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.
|
||||
|
||||
### `vtt.js`
|
||||
|
||||
> Type: `string`
|
||||
|
||||
Allows overriding the default URL to vtt.js, which may be loaded asynchronously to polyfill support for `WebVTT`.
|
||||
@@ -264,6 +294,7 @@ The Video.js player is a component. Like all components, you can define what chi
|
||||
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).
|
||||
|
||||
### `children`
|
||||
|
||||
> Type: `Array|Object`
|
||||
|
||||
If an `Array` - which is the default - this is used to determine which children (by component name) and in which order they are created on a player (or other component):
|
||||
@@ -294,6 +325,7 @@ videojs('my-player', {
|
||||
```
|
||||
|
||||
### `${componentName}`
|
||||
|
||||
> Type: `Object`
|
||||
|
||||
Components can be given custom options via the _lower-camel-case variant of the component name_ (e.g. `controlBar` for `ControlBar`). These can be nested in a representation of grandchild relationships. For example, to disable the fullscreen control:
|
||||
@@ -307,7 +339,9 @@ videojs('my-player', {
|
||||
```
|
||||
|
||||
## Tech Options
|
||||
|
||||
### `${techName}`
|
||||
|
||||
> Type: `Object`
|
||||
|
||||
Video.js playback technologies (i.e. "techs") can be given custom options as part of the options passed to the `videojs` function. They should be passed under the _lower-case variant of the tech name_ (e.g. `"flash"` or `"html5"`).
|
||||
@@ -329,16 +363,19 @@ videojs.options.flash.swf = '//path/to/videojs.swf'
|
||||
```
|
||||
|
||||
#### `nativeControlsForTouch`
|
||||
|
||||
> Type: `boolean`
|
||||
|
||||
Only supported by the `Html5` tech, this option can be set to `true` to force native controls for touch devices.
|
||||
|
||||
#### `nativeTextTracks`
|
||||
|
||||
> Type: `boolean`
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
[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
|
||||
|
||||
[video-attrs]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#Attributes
|
||||
|
||||
@@ -1,10 +1,38 @@
|
||||
# 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!
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Accessing a player that has already been created on a page](#accessing-a-player-that-has-already-been-created-on-a-page)
|
||||
* [Removing Players](#removing-players)
|
||||
* [dispose()](#dispose)
|
||||
* [Signs of an Undisposed Player](#signs-of-an-undisposed-player)
|
||||
* [Showing and Hiding a Player](#showing-and-hiding-a-player)
|
||||
* [Changing the volume of a player](#changing-the-volume-of-a-player)
|
||||
* [Making the player fullscreen](#making-the-player-fullscreen)
|
||||
* [Using Playback information functions](#using-playback-information-functions)
|
||||
* [Dealing with the source or the poster on the player](#dealing-with-the-source-or-the-poster-on-the-player)
|
||||
* [Accesing the Tech on the player](#accesing-the-tech-on-the-player)
|
||||
* [Using Video.js with...](#using-videojs-with)
|
||||
* [jQuery](#jquery)
|
||||
* [React](#react)
|
||||
* [Ember](#ember)
|
||||
* [Angular](#angular)
|
||||
|
||||
## Accessing a player that has already been created on a page
|
||||
|
||||
After an instance has been created it can be accessed globally in two ways:
|
||||
|
||||
1. By calling `videojs('example_video_id');`
|
||||
1. By using it directly via `videojs.players.example_video_id;`
|
||||
|
||||
## Removing Players
|
||||
|
||||
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)
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
@@ -26,15 +54,16 @@ Additionally, these actions are recursively applied to _all_ the player's child
|
||||
> **Note**: Do _not_ remove players via standard DOM removal methods: this will leave listeners and other objects in memory that you might not be able to clean up!
|
||||
|
||||
### Signs of an Undisposed Player
|
||||
|
||||
Seeing an error such as:
|
||||
|
||||
```
|
||||
```console
|
||||
TypeError: this.el_.vjs_getProperty is not a function
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
```console
|
||||
TypeError: Cannot read property 'vdata1234567890' of null
|
||||
```
|
||||
|
||||
@@ -61,10 +90,285 @@ modal.on('hide', function() {
|
||||
});
|
||||
```
|
||||
|
||||
## Changing the volume of a player
|
||||
|
||||
Volume for a player can be changed through the `volume` function on a player. The volume function accepts a number from 0-1. Calling it without an argument will return the current volume.
|
||||
|
||||
Example
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
// get
|
||||
var howLoudIsIt = myPlayer.volume();
|
||||
// set
|
||||
myPlayer.volume(0.5); // Set volume to half
|
||||
});
|
||||
```
|
||||
|
||||
Volume can also be muted (without actually changing the volume value) using the `muted` function. Calling it without an argument will return the current status of muted on the player.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
// get, should be false
|
||||
console.log(myPlayer.muted());
|
||||
// set to true
|
||||
myPlayer.muted(true);
|
||||
// get should be true
|
||||
console.log(myPlayer.muted());
|
||||
});
|
||||
```
|
||||
|
||||
## Making the player fullscreen
|
||||
|
||||
To check if the player is currently fullscreen call the `isFullscreen` function on a player like so.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
// get, should be false
|
||||
console.log(myPlayer.isFullscreen());
|
||||
|
||||
// set, tell the player it's in fullscreen
|
||||
myPlayer.isFullscreen(true);
|
||||
|
||||
// get, should be true
|
||||
console.log(myPlayer.isFullscreen());
|
||||
});
|
||||
```
|
||||
|
||||
To request that the player enter fullscreen call `requestFullscreen`.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
myPlayer.requestFullscreen();
|
||||
});
|
||||
```
|
||||
|
||||
To exit fullscreen call `exitFullscreen`
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
myPlayer.requestFullscreen();
|
||||
myPlayer.exitFullscreen();
|
||||
});
|
||||
```
|
||||
|
||||
## Using Playback information functions
|
||||
|
||||
`play` can be used to start playback on a player that has a source.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
myPlayer.play();
|
||||
});
|
||||
```
|
||||
|
||||
`pause` can be used to pause playback on a player that is playing.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
myPlayer.play();
|
||||
myPlayer.pause();
|
||||
});
|
||||
```
|
||||
|
||||
`paused` can be used to determine if a player is currently paused.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
|
||||
myPlayer.ready(function() {
|
||||
// true
|
||||
console.log(myPlayer.paused());
|
||||
// false
|
||||
console.log(!myPlayer.paused());
|
||||
|
||||
myPlayer.play();
|
||||
// false
|
||||
console.log(myPlayer.paused());
|
||||
// true
|
||||
console.log(!myPlayer.paused());
|
||||
|
||||
myPlayer.pause();
|
||||
// true
|
||||
console.log(myPlayer.paused());
|
||||
// false
|
||||
console.log(!myPlayer.paused());
|
||||
});
|
||||
```
|
||||
|
||||
`currentTime` will give you the currentTime (in seconds) that playback is currently occuring at.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
// set current time to 2 minutes into the video
|
||||
myPlayer.currentTime(120);
|
||||
|
||||
// get the current time, should be 120 seconds
|
||||
var whereYouAt = myPlayer.currentTime();
|
||||
});
|
||||
```
|
||||
|
||||
`duration` will give you the total duration of the video that is playing
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
var lengthOfVideo = myPlayer.duration();
|
||||
});
|
||||
```
|
||||
|
||||
`remainingTime` will give you the seconds that are remaing in the video.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
myPlayer.currentTime(10);
|
||||
|
||||
// should be 10 seconds less than duration
|
||||
console.log(myPlayer.remainingTime());
|
||||
});
|
||||
```
|
||||
|
||||
`buffered` will give you a timeRange object representing the current ranges of time that are ready to be played at a future time.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
var bufferedTimeRange = myPlayer.buffered();
|
||||
|
||||
// number of different ranges of time have been buffered.
|
||||
// Usually 1
|
||||
var numberOfRanges = bufferedTimeRange.length,
|
||||
|
||||
// Time in seconds when the first range starts.
|
||||
// Usually 0
|
||||
var firstRangeStart = bufferedTimeRange.start(0),
|
||||
|
||||
// Time in seconds when the first range ends
|
||||
var firstRangeEnd = bufferedTimeRange.end(0),
|
||||
|
||||
// Length in seconds of the first time range
|
||||
var firstRangeLength = firstRangeEnd - firstRangeStart;
|
||||
});
|
||||
```
|
||||
|
||||
`bufferedPercent` will give you the the current percentage of the video that is buffered.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
// example 0.11 aka 11%
|
||||
var howMuchIsDownloaded = myPlayer.bufferedPercent();
|
||||
});
|
||||
```
|
||||
|
||||
## Dealing with the source or the poster on the player
|
||||
|
||||
Passing a source to the player via the API. (this can also be done using options)
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
```
|
||||
|
||||
**Source Object (or element):** A javascript object containing information
|
||||
about the source file. Use this method if you want the player to determine if
|
||||
it can support the file using the type information.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src({type: "video/mp4", src: "http://www.example.com/path/to/video.mp4"});
|
||||
```
|
||||
|
||||
**Array of Source Objects:** To provide multiple versions of the source so
|
||||
that it can be played using HTML5 across browsers you can use an array of
|
||||
source objects. Video.js will detect which version is supported and load that
|
||||
file.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src([
|
||||
{type: "video/mp4", src: "http://www.example.com/path/to/video.mp4"},
|
||||
{type: "video/webm", src: "http://www.example.com/path/to/video.webm"},
|
||||
{type: "video/ogg", src: "http://www.example.com/path/to/video.ogv"}
|
||||
]);
|
||||
```
|
||||
|
||||
Changing or setting the poster via the API. (this can also be done with options)
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('example_video_1');
|
||||
|
||||
// set
|
||||
myPlayer.poster('http://example.com/myImage.jpg');
|
||||
|
||||
// get
|
||||
console.log(myPlayer.poster());
|
||||
// 'http://example.com/myImage.jpg'
|
||||
```
|
||||
|
||||
## Accesing the Tech on the player
|
||||
|
||||
The tech on the player can only be accesed by pasing `{IWillNotUseThisInPlugins: true}` into the `tech()`
|
||||
function on the player.
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('some-player-id');
|
||||
|
||||
myPlayer.src("http://www.example.com/path/to/video.mp4");
|
||||
myPlayer.ready(function() {
|
||||
// function call throws an error if we
|
||||
// dont add {IWillNotUseThisInPlugins: true}
|
||||
var tech = myPlayer.tech({IWillNotUseThisInPlugins: true});
|
||||
});
|
||||
```
|
||||
|
||||
## Using Video.js with...
|
||||
|
||||
Coming soon...
|
||||
|
||||
### jQuery
|
||||
|
||||
### React
|
||||
|
||||
See [ReactJS integration example](./react.md)
|
||||
|
||||
### Ember
|
||||
|
||||
### Angular
|
||||
|
||||
+303
-35
@@ -1,59 +1,327 @@
|
||||
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:
|
||||
* [Grunt](http://gruntjs.com) for build management
|
||||
* [npm](https://www.npmjs.org) for dependency management
|
||||
* [QUnit](http://qunitjs.com) for testing
|
||||
# Video.js Plugins
|
||||
|
||||
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.md)!
|
||||
|
||||
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
|
||||
-----------------------------
|
||||
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:
|
||||
* [generator-videojs-plugin][generator]
|
||||
|
||||
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!');
|
||||
});
|
||||
};
|
||||
function examplePlugin(options) {
|
||||
|
||||
if (options.customClass) {
|
||||
this.addClass(options.customClass);
|
||||
}
|
||||
|
||||
this.on('playing', function() {
|
||||
videojs.log('playback began!');
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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:
|
||||
> **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.plugin('examplePlugin', examplePlugin);
|
||||
videojs.registerPlugin('examplePlugin', examplePlugin);
|
||||
```
|
||||
|
||||
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...
|
||||
After that, any player will automatically have an `examplePlugin` method on its prototype!
|
||||
|
||||
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:
|
||||
> **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.md), 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
|
||||
videojs('vidId', {
|
||||
plugins: {
|
||||
examplePlugin: {
|
||||
exampleOption: true
|
||||
}
|
||||
}
|
||||
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!');
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you've already initialized your video tag, you can activate a plugin at any time by calling its setup function directly:
|
||||
Or with ES5:
|
||||
|
||||
```js
|
||||
var video = videojs('cool-vid');
|
||||
video.examplePlugin({ exampleOption: true });
|
||||
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!');
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
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.
|
||||
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!
|
||||
|
||||
## How should I use the Video.js icons in my plugin?
|
||||
### Register a Advanced Plugin
|
||||
|
||||
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.
|
||||
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 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
|
||||
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'});
|
||||
```
|
||||
|
||||
#### Lifecycle
|
||||
|
||||
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
|
||||
// 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: {
|
||||
customClass: 'example-class'
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Otherwise, a plugin can be manually set up:
|
||||
|
||||
```js
|
||||
var player = videojs('example-player');
|
||||
player.examplePlugin({customClass: 'example-class'});
|
||||
```
|
||||
|
||||
These two methods are functionally identical - use whichever you prefer!
|
||||
|
||||
## References
|
||||
|
||||
* [Player API][api-player]
|
||||
* [Plugin API][api-plugin]
|
||||
* [Plugin Generator][generator]
|
||||
* [Plugin Conventions][standards]
|
||||
|
||||
[api-player]: http://docs.videojs.com/docs/api/player.html
|
||||
|
||||
[api-plugin]: http://docs.videojs.com/docs/api/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,55 @@
|
||||
# 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:
|
||||
|
||||
```jsx
|
||||
// see https://github.com/videojs/video.js/blob/master/docs/guides/options.md
|
||||
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`.
|
||||
+24
-13
@@ -1,20 +1,19 @@
|
||||
# Video.js Setup
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Getting Video.js](#getting-videojs)
|
||||
- [Creating a Player](#creating-a-player)
|
||||
- [Automatic Setup](#automatic-setup)
|
||||
- [Manual Setup](#manual-setup)
|
||||
- [Options](#options)
|
||||
- [Global Defaults](#global-defaults)
|
||||
- [A Note on `<video>` Tag Attributes](#a-note-on-video-tag-attributes)
|
||||
- [Player Readiness](#player-readiness)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
* [Getting Video.js](#getting-videojs)
|
||||
* [Creating a Player](#creating-a-player)
|
||||
* [Automatic Setup](#automatic-setup)
|
||||
* [Manual Setup](#manual-setup)
|
||||
* [Options](#options)
|
||||
* [Global Defaults](#global-defaults)
|
||||
* [A Note on <video> Tag Attributes](#a-note-on-video-tag-attributes)
|
||||
* [Player Readiness](#player-readiness)
|
||||
* [Advanced Player Workflows](#advanced-player-workflows)
|
||||
|
||||
## Getting Video.js
|
||||
|
||||
Video.js is officially available via CDN, npm, and Bower.
|
||||
|
||||
Video.js works out of the box with not only HTML `<script>` and `<link>` tags, but also all major bundlers/packagers/builders, such as Browserify, Node, WebPack, etc.
|
||||
@@ -22,6 +21,7 @@ Video.js works out of the box with not only HTML `<script>` and `<link>` tags, b
|
||||
Please refer to the [Getting Started][getting-started] document for details.
|
||||
|
||||
## Creating a Player
|
||||
|
||||
> **Note:** Video.js works with `<video>` _and_ `<audio>` elements, but for simplicity we'll refer only to `<video>` elements going forward.
|
||||
|
||||
Once you have Video.js [loaded on your page][getting-started], you're ready to create a player!
|
||||
@@ -38,6 +38,7 @@ Video.js supports all attributes of the `<video>` element (such as `controls`, `
|
||||
```
|
||||
|
||||
### Automatic Setup
|
||||
|
||||
By default, when your web page finishes loading, Video.js will scan for media elements that have the `data-setup` attribute. The `data-setup` attribute is used to pass options to Video.js. A minimal example looks like this:
|
||||
|
||||
```html
|
||||
@@ -50,6 +51,7 @@ By default, when your web page finishes loading, Video.js will scan for media el
|
||||
> **Note:** You _must_ use single-quotes with `data-setup` as it is expected to contain JSON.
|
||||
|
||||
### Manual Setup
|
||||
|
||||
On the modern web, a `<video>` element often does not exist when the page finishes loading. In these cases, automatic setup is not possible, but manual setup is available via [the `videojs` function][videojs].
|
||||
|
||||
One way to call this function is by providing it a string matching a `<video>` element's `id` attribute:
|
||||
@@ -79,6 +81,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).
|
||||
|
||||
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]:
|
||||
@@ -104,6 +107,7 @@ videojs('my-player', {
|
||||
```
|
||||
|
||||
### Global Defaults
|
||||
|
||||
Default options for all players can be found at `videojs.options` and can be changed directly. For example, to set `{autoplay: true}` for all future players:
|
||||
|
||||
```js
|
||||
@@ -133,6 +137,7 @@ These are correct:
|
||||
```
|
||||
|
||||
## Player Readiness
|
||||
|
||||
Because Video.js techs have the potential to be loaded asynchronously, it isn't always safe to interact with a player immediately upon setup. For this reason, Video.js players have a concept of "readiness" which will be familiar to anyone who has used jQuery before.
|
||||
|
||||
Essentially, any number of ready callbacks can be defined for a Video.js player. There are three ways to pass these callbacks. In each example, we'll add an identical class to the player:
|
||||
@@ -169,13 +174,19 @@ player.on('ready', function() {
|
||||
In each case, the callback is called asynchronously - _even if the player is already ready!_
|
||||
|
||||
## Advanced Player Workflows
|
||||
|
||||
For a discussion of more advanced player workflows, see the [player workflows guide](player-workflows.md).
|
||||
|
||||
|
||||
[boolean-attrs]: https://www.w3.org/TR/2011/WD-html5-20110525/common-microsyntaxes.html#boolean-attributes
|
||||
|
||||
[getting-started]: http://videojs.com/getting-started/
|
||||
|
||||
[json]: http://json.org/example.html
|
||||
|
||||
[video-attrs]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#Attributes
|
||||
|
||||
[videojs]: http://docs.videojs.com/docs/api/video.html
|
||||
|
||||
[w3c-media-events]: https://www.w3.org/2010/05/video/mediaevents.html
|
||||
|
||||
[w3c-video]: http://www.w3.org/TR/html5/embedded-content-0.html#the-video-element
|
||||
|
||||
+22
-2
@@ -1,30 +1,49 @@
|
||||
# Skins
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Default Skin](#default-skin)
|
||||
* [Additional <style> Elements](#additional-style-elements)
|
||||
* [Disabling Additional <style> Elements](#disabling-additional-style-elements)
|
||||
* [Effect on Player#width() and Player#height()](#effect-on-playerwidth-and-playerheight)
|
||||
* [Icons](#icons)
|
||||
* [Creating a Skin](#creating-a-skin)
|
||||
* [Add a Custom Class to the Player](#add-a-custom-class-to-the-player)
|
||||
* [Customize Styles](#customize-styles)
|
||||
|
||||
## Default Skin
|
||||
|
||||
When you include the Video.js CSS file (`video-js.min.css`), the default Video.js skin is applied. That means that customizing the look of a Video.js player is a matter of taking advantage of the cascading aspect of CSS to override styles.
|
||||
|
||||
## Additional `<style>` Elements
|
||||
|
||||
In addition to the Video.js CSS file, there are some styles generated automatically by JavaScript and included in the `<head>` as `<style>` elements.
|
||||
|
||||
- The `"vjs-styles-defaults"` element sets default dimensions for all Video.js players on the page.
|
||||
- A `"vjs-styles-dimensions"` element is created for _each_ player on the page and is used to adjust its size. This styling is handled in this manner to allow you to override it with custom CSS without relying on scripting or `!important` to overcome inline styles.
|
||||
* The `"vjs-styles-defaults"` element sets default dimensions for all Video.js players on the page.
|
||||
* A `"vjs-styles-dimensions"` element is created for _each_ player on the page and is used to adjust its size. This styling is handled in this manner to allow you to override it with custom CSS without relying on scripting or `!important` to overcome inline styles.
|
||||
|
||||
### Disabling Additional `<style>` Elements
|
||||
|
||||
In some cases, particularly with web applications using frameworks that may manage the `<head>` element (e.g. React, Ember, Angular, etc), these `<style>` elements are not desirable. They can be suppressed by setting `window.VIDEOJS_NO_DYNAMIC_STYLE = true` before including Video.js.
|
||||
|
||||
_This disables all CSS-based player sizing. Players will have no `height` or `width` by default!_ Even dimensional attributes, such as `width="600" height="300"` will be _ignored_. When using this global, you will need to set their own dimensions in a way that makes sense for their website or web app.
|
||||
|
||||
#### Effect on `Player#width()` and `Player#height()`
|
||||
|
||||
When `VIDEOJS_NO_DYNAMIC_STYLE` is set, `Player#width()` and `Player#height()` will apply any width and height that is set directly to the `<video>` element (or whichever element the current tech uses).
|
||||
|
||||
## Icons
|
||||
|
||||
Video.js ships with a number of icons built into the skin via an icon font.
|
||||
|
||||
You can view all of the icons available in the default skin by renaming [`sandbox/icons.html.example`](https://github.com/videojs/video.js/blob/master/sandbox/icons.html.example) to `sandbox/icons.html`, building Video.js with `npm run build`, and opening `sandbox/icons.html` in your browser of choice.
|
||||
|
||||
## Creating a Skin
|
||||
|
||||
The recommended process for creating a skin is to override the styles provided by the default skin. In this way, you don't need to start from scratch.
|
||||
|
||||
### Add a Custom Class to the Player
|
||||
|
||||
The most convenient way to create a hook in the player for your skin is to add a class to it. You can do this by adding a class to the initial `<video>` element:
|
||||
|
||||
```html
|
||||
@@ -42,6 +61,7 @@ player.addClass('vjs-matrix');
|
||||
> **Note:** The `vjs-` prefix is a convention for all classes that are contained in a Video.js player.
|
||||
|
||||
### Customize Styles
|
||||
|
||||
The first step in overriding default styles with custom ones is to determine which selectors and properties need overriding. As an example, let's say we don't like the default color of controls (white) and we want to change them to a bright green (say, `#00ff00`).
|
||||
|
||||
To do this, we'll use your browser's developer tools to inspect the player and figure out which selectors we need to use to adjust those styles - and we'll add our custom `.vjs-matrix` selector to ensure our final selectors are specific enough to override the default skin.
|
||||
|
||||
+52
-37
@@ -1,15 +1,15 @@
|
||||
Playback Technology ("Tech")
|
||||
============================
|
||||
# Playback Technology ("Tech")
|
||||
|
||||
Playback Technology refers to the specific browser or plugin technology used to play the video or audio. When using HTML5, the playback technology is the video or audio element. When using Flash, the playback technology is the specific Flash player used, e.g. Flowplayer, YouTube Player, video-js.swf, etc. (not just "Flash"). This could also include Silverlight, Quicktime, or any other plugin that will play back video in the browser, as long as there is an API wrapper written for it.
|
||||
|
||||
Essentially we're using HTML5 and plugins only as video decoders, and using HTML and JavaScript to create a consistent API and skinning experience across all of them.
|
||||
|
||||
Building an API Wrapper
|
||||
-----------------------
|
||||
## Building an API Wrapper
|
||||
|
||||
We'll write a more complete guide on writing a wrapper soon, but for now the best resource is the [Video.js](https://github.com/zencoder/video-js/tree/master/src) source where you can see how both the HTML5 and video-js.swf API wrappers were created.
|
||||
|
||||
Required Methods
|
||||
----------------
|
||||
## Required Methods
|
||||
|
||||
canPlayType
|
||||
play
|
||||
pause
|
||||
@@ -19,8 +19,8 @@ duration
|
||||
buffered
|
||||
supportsFullScreen
|
||||
|
||||
Required Events
|
||||
---------------
|
||||
## Required Events
|
||||
|
||||
loadstart
|
||||
play
|
||||
pause
|
||||
@@ -30,37 +30,45 @@ volumechange
|
||||
durationchange
|
||||
error
|
||||
|
||||
Optional Events (include if supported)
|
||||
--------------------------------------
|
||||
## Optional Events (include if supported)
|
||||
|
||||
timeupdate
|
||||
progress
|
||||
enterFullScreen
|
||||
exitFullScreen
|
||||
|
||||
Adding Playback Technology
|
||||
==================
|
||||
## Adding Playback Technology
|
||||
|
||||
When adding additional Tech to a video player, make sure to add the supported tech to the video object.
|
||||
|
||||
### Tag Method: ###
|
||||
<video data-setup='{"techOrder": ["html5", "flash", "other supported tech"]}'
|
||||
### Tag Method:
|
||||
|
||||
### Object Method: ###
|
||||
videojs("videoID", {
|
||||
techOrder: ["html5", "flash", "other supported tech"]
|
||||
});
|
||||
```html
|
||||
<video data-setup='{"techOrder": ["html5", "flash", "other supported tech"]}'>
|
||||
```
|
||||
|
||||
### Object Method:
|
||||
|
||||
```js
|
||||
videojs("videoID", {
|
||||
techOrder: ["html5", "flash", "other supported tech"]
|
||||
});
|
||||
```
|
||||
|
||||
## Technology Ordering
|
||||
|
||||
Technology Ordering
|
||||
==================
|
||||
By default Video.js performs "Tech-first" ordering when it searches for a source/tech combination to play videos. This means that if you have two sources and two techs, video.js will try to play each video with the first tech in the `techOrder` option property before moving on to try the next playback technology.
|
||||
|
||||
Tech-first ordering can present a problem if you have a `sourceHandler` that supports both `Html5` and `Flash` techs such as videojs-contrib-hls.
|
||||
|
||||
For example, given the following video element:
|
||||
|
||||
<video data-setup='{"techOrder": ["html5", "flash"]}'>
|
||||
<source src="http://your.static.provider.net/path/to/video.m3u8" type="application/x-mpegURL">
|
||||
<source src="http://your.static.provider.net/path/to/video.mp4" type="video/mp4">
|
||||
</video>
|
||||
```html
|
||||
<video data-setup='{"techOrder": ["html5", "flash"]}'>
|
||||
<source src="http://your.static.provider.net/path/to/video.m3u8" type="application/x-mpegURL">
|
||||
<source src="http://your.static.provider.net/path/to/video.mp4" type="video/mp4">
|
||||
</video>
|
||||
```
|
||||
|
||||
There is a good chance that the mp4 source will be selected on platforms that do not have media source extensions. Video.js will try all sources against the first playback technology, in this case `Html5`, and select the first source that can play - in this case MP4.
|
||||
|
||||
@@ -72,10 +80,12 @@ Video.js now provides another method of selecting the source - "Source-first" or
|
||||
|
||||
With a player setup as follows:
|
||||
|
||||
<video data-setup='{"techOrder": ["html5", "flash"], "sourceOrder": true}'>
|
||||
<source src="http://your.static.provider.net/path/to/video.m3u8" type="application/x-mpegURL">
|
||||
<source src="http://your.static.provider.net/path/to/video.mp4" type="video/mp4">
|
||||
</video>
|
||||
```html
|
||||
<video data-setup='{"techOrder": ["html5", "flash"], "sourceOrder": true}'>
|
||||
<source src="http://your.static.provider.net/path/to/video.m3u8" type="application/x-mpegURL">
|
||||
<source src="http://your.static.provider.net/path/to/video.mp4" type="video/mp4">
|
||||
</video>
|
||||
```
|
||||
|
||||
The Flash-based HLS support will be tried before falling back to the MP4 source.
|
||||
|
||||
@@ -83,23 +93,28 @@ In "Source-first" mode, the tests run something like this:
|
||||
Can video.m3u8 play with Html5? No...
|
||||
Can video.m3u8 play with Flash? Yes! Use the first source.
|
||||
|
||||
Flash Technology
|
||||
==================
|
||||
## Flash Technology
|
||||
|
||||
The Flash playback tech is a part of the default `techOrder`. You may notice undesirable playback behavior in browsers that are subject to using this playback tech, in particular when scrubbing and seeking within a video. This behavior is a result of Flash's progressive video playback.
|
||||
|
||||
Enabling Streaming Playback
|
||||
--------------------------------
|
||||
### Enabling Streaming Playback
|
||||
|
||||
In order to force the Flash tech to choose streaming playback, you need to provide a valid streaming source **before other valid Flash video sources**. This is necessary because of the source selection algorithm, where playback tech chooses the first possible source object with a valid type. Valid streaming `type` values include `rtmp/mp4` and `rtmp/flv`. The streaming `src` value requires valid connection and stream strings, separated by an `&`. An example of supplying a streaming source through your HTML markup might look like:
|
||||
|
||||
<source src="rtmp://your.streaming.provider.net/cfx/st/&mp4:path/to/video.mp4" type="rtmp/mp4">
|
||||
<source src="http://your.static.provider.net/path/to/video.mp4" type="video/mp4">
|
||||
<source src="http://your.static.provider.net/path/to/video.webm" type="video/webm">
|
||||
```html
|
||||
<source src="rtmp://your.streaming.provider.net/cfx/st/&mp4:path/to/video.mp4" type="rtmp/mp4">
|
||||
<source src="http://your.static.provider.net/path/to/video.mp4" type="video/mp4">
|
||||
<source src="http://your.static.provider.net/path/to/video.webm" type="video/webm">
|
||||
```
|
||||
|
||||
You may optionally use the last `/` as the separator between connection and stream strings, for example:
|
||||
|
||||
<source src="rtmp://your.streaming.provider.net/cfx/st/mp4:video.mp4" type="rtmp/mp4">
|
||||
```html
|
||||
<source src="rtmp://your.streaming.provider.net/cfx/st/mp4:video.mp4" type="rtmp/mp4">
|
||||
```
|
||||
|
||||
All four RTMP protocols are valid in the `src` (RTMP, RTMPT, RTMPE, and RTMPS).
|
||||
|
||||
### A note on sandboxing and security
|
||||
#### A note on sandboxing and security
|
||||
|
||||
In some environments, such as Electron and NW.js apps, stricter policies are enforced, and `.swf` files won’t be able to communicate with the outside world out of the box. To stream media, you have to add them to a special manifest of trusted files. [nw-flash-trust](https://github.com/szwacz/nw-flash-trust) makes this job easy.
|
||||
|
||||
@@ -1,12 +1,36 @@
|
||||
# Text Tracks
|
||||
|
||||
Text tracks are a feature of HTML5 video for displaying time-triggered text to the viewer. Video.js offers a cross-browser implementation of text tracks.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [A Note on "Remote" Text Tracks](#a-note-on-remote-text-tracks)
|
||||
* [Creating the Text File](#creating-the-text-file)
|
||||
* [Adding Text Tracks to Video.js](#adding-text-tracks-to-videojs)
|
||||
* [track Attributes](#track-attributes)
|
||||
* [kind](#kind)
|
||||
* [label](#label)
|
||||
* [default](#default)
|
||||
* [srclang](#srclang)
|
||||
* [Text Tracks from Another Domain](#text-tracks-from-another-domain)
|
||||
* [Working with Text Tracks](#working-with-text-tracks)
|
||||
* [Showing Tracks Programmatically](#showing-tracks-programmatically)
|
||||
* [Listen for a Cue Becoming Active](#listen-for-a-cue-becoming-active)
|
||||
* [Emulated Text Tracks](#emulated-text-tracks)
|
||||
* [Text Track Settings](#text-track-settings)
|
||||
* [Text Track Precedence](#text-track-precedence)
|
||||
* [API](#api)
|
||||
* [Remote Text Tracks](#remote-text-tracks)
|
||||
* [Text Tracks](#text-tracks-1)
|
||||
|
||||
## A Note on "Remote" Text Tracks
|
||||
|
||||
Video.js refers to so-called "remote" text tracks. This is a convenient term for tracks that have an associated `<track>` element rather than those that do not.
|
||||
|
||||
Either can be created programmatically, but _only remote text tracks can be removed from a player._ For that reason, we recommend _only_ using remote text tracks.
|
||||
|
||||
## Creating the Text File
|
||||
|
||||
Timed text requires a text file in [WebVTT](http://dev.w3.org/html5/webvtt/) format. This format defines a list of "cues" that have a start time, an end time, and text to display. [Microsoft has a builder](https://dev.modern.ie/testdrive/demos/captionmaker/) that can help you get started on the file.
|
||||
|
||||
> **Note:** When creating captions, there are additional [caption formatting techniques](http://www.theneitherworld.com/mcpoodle/SCC_TOOLS/DOCS/SCC_FORMAT.HTML#style) to make captions more meaningful, like brackets around sound effects (e.g. `[ birds chirping ]`).
|
||||
@@ -14,6 +38,7 @@ Timed text requires a text file in [WebVTT](http://dev.w3.org/html5/webvtt/) for
|
||||
> For a more in depth style guide for captioning, see the [Captioning Key](http://www.dcmp.org/captioningkey/), but keep in mind not all features are supported by WebVTT or (more likely) the Video.js WebVTT implementation.
|
||||
|
||||
## Adding Text Tracks to Video.js
|
||||
|
||||
Once you have your WebVTT files created, you can add them to your `video` element using the `track` tag. Similar to `source` elements, `track` elements should be added as children of the `video` element:
|
||||
|
||||
```html
|
||||
@@ -33,23 +58,27 @@ Once you have your WebVTT files created, you can add them to your `video` elemen
|
||||
Video.js will automatically read `track` elements from the `video` element. Tracks (remote and non-remote) can also be added programmatically.
|
||||
|
||||
### `track` Attributes
|
||||
|
||||
#### `kind`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#text-track-kind)
|
||||
|
||||
One of the track types supported by Video.js:
|
||||
|
||||
- `"subtitles"` (default): Translations of the dialogue in the video for when audio is available but not understood. Subtitles are shown over the video.
|
||||
- `"captions"`: Transcription of the dialogue, sound effects, musical cues, and other audio information for viewer who are deaf/hard of hearing, or the video is muted. Captions are also shown over the video.
|
||||
- `"chapters"`: Chapter titles that are used to create navigation within the video. Typically, these are in the form of a list of chapters that the viewer can use to navigate the video.
|
||||
- `"descriptions"`: Text descriptions of the action in the content for when the video portion isn't available or because the viewer is blind or not using a screen. Descriptions are read by a screen reader or turned into a separate audio track.
|
||||
- `"metadata"`: Tracks that have data meant for JavaScript to parse and do something with. These aren't shown to the user.
|
||||
* `"subtitles"` (default): Translations of the dialogue in the video for when audio is available but not understood. Subtitles are shown over the video.
|
||||
* `"captions"`: Transcription of the dialogue, sound effects, musical cues, and other audio information for viewer who are deaf/hard of hearing, or the video is muted. Captions are also shown over the video.
|
||||
* `"chapters"`: Chapter titles that are used to create navigation within the video. Typically, these are in the form of a list of chapters that the viewer can use to navigate the video.
|
||||
* `"descriptions"`: Text descriptions of the action in the content for when the video portion isn't available or because the viewer is blind or not using a screen. Descriptions are read by a screen reader or turned into a separate audio track.
|
||||
* `"metadata"`: Tracks that have data meant for JavaScript to parse and do something with. These aren't shown to the user.
|
||||
|
||||
#### `label`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#text-track-label)
|
||||
|
||||
Short descriptive text for the track that will used in the user interface. For example, in a menu for selecting a captions language.
|
||||
|
||||
#### `default`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-track-default)
|
||||
|
||||
The boolean `default` attribute can be used to indicate that a track's mode should start as `"showing"`. Otherwise, the viewer would need to select their language from a captions or subtitles menu.
|
||||
@@ -57,6 +86,7 @@ The boolean `default` attribute can be used to indicate that a track's mode shou
|
||||
> **Note:** For chapters, `default` is required if you want the chapters menu to show.
|
||||
|
||||
#### `srclang`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-track-srclang)
|
||||
|
||||
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.
|
||||
@@ -64,6 +94,7 @@ The valid [BCP 47](https://tools.ietf.org/html/bcp47) code for the language of t
|
||||
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.
|
||||
|
||||
### Text Tracks from Another Domain
|
||||
|
||||
Because Video.js loads the text track file via JavaScript, the [same-origin policy](http://en.wikipedia.org/wiki/Same_origin_policy) applies. If you'd like to have a player served from one domain, but the text track served from another, you'll need to [enable CORS](http://enable-cors.org/) on the server that is serving your text tracks.
|
||||
|
||||
In addition to enabling CORS, you will need to add the [`crossorigin` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) to the video element itself. This attribute has two possible values `"anonymous"` and `"use-credentials"`. Most users will want to use `"anonymous"` with cross-origin tracks:
|
||||
@@ -78,7 +109,9 @@ In addition to enabling CORS, you will need to add the [`crossorigin` attribute]
|
||||
One thing to be aware of is that the video files themselves will _also_ need CORS headers. This is because some browsers apply the `crossorigin` attribute to the video source itself and not just the tracks. This is considered a [security concern by the spec](https://html.spec.whatwg.org/multipage/embedded-content.html#security-and-privacy-considerations).
|
||||
|
||||
## Working with Text Tracks
|
||||
|
||||
### Showing Tracks Programmatically
|
||||
|
||||
Certain use cases call for turning captions on and off programmatically rather than forcing the user to do so themselves. This can be easily achieved by modifying the `mode` property of the text tracks.
|
||||
|
||||
The `mode` can be one of three values `"disabled"`, `"hidden"`, and `"showing"`. When a text track's `mode` is `"disabled"`, the track does not show on screen as the video is playing.
|
||||
@@ -100,6 +133,7 @@ for (var i = 0; i < tracks.length; i++) {
|
||||
```
|
||||
|
||||
### Listen for a Cue Becoming Active
|
||||
|
||||
One of the supported values for `mode` is `"hidden"`. This `mode` means that the track will update as the video is playing, but it won't be visible to the viewer. This is most useful for tracks where `kind="metadata"`.
|
||||
|
||||
A common use case for metadata text tracks is to use them to trigger behaviors when their cues become active. For this purpose, tracks emit a `"cuechange"` event.
|
||||
@@ -128,6 +162,7 @@ metadataTrack.addEventListener('cuechange', function() {
|
||||
```
|
||||
|
||||
## Emulated Text Tracks
|
||||
|
||||
By default, Video.js will use native text tracks and fall back to emulated text tracks if the native functionality is broken, incomplete, or non-existent. The Flash tech will always use the emulated text track functionality.
|
||||
|
||||
The Video.js API and TextTrack objects were modeled after the W3C specification. Video.js uses [Mozilla's vtt.js](https://github.com/mozilla/vtt.js) library to parse and display emulated text tracks.
|
||||
@@ -144,6 +179,7 @@ var player = videojs('myvideo', {
|
||||
```
|
||||
|
||||
### Text Track Settings
|
||||
|
||||
When using emulated text tracks, captions will have an additional item in the menu called "Caption Settings". This allows the user to alter how captions are styled on screen.
|
||||
|
||||
This feature can be disabled by turning off the `TextTrackSettings` component and hiding the menu item.
|
||||
@@ -163,41 +199,45 @@ var player = videojs('myvideo', {
|
||||
```
|
||||
|
||||
## Text Track Precedence
|
||||
|
||||
In general, `"descriptions"` tracks are of lower precedence than `"captions"` and `"subtitles"`. What this mean for developers using Video.js?
|
||||
|
||||
- If you are using the `default` attribute, Video.js will choose the first track that is marked as `default` and turn it on. If there are multiple tracks marked `default`, it will turn on the first `"captions"` or `"subtitles"` track _before_ any `"descriptions"` tracks.
|
||||
- This only applied to the emulated text track support, native text tracks behavior will change depending on the browser.
|
||||
- If a track is selected from the menu, Video.js will turn off all the other tracks of the same kind. While this suggests Video.js supports both `"subtitles"` and `"captions"` being turned on simultaneously, this is currently not the case; Video.js only supports one track being displayed at a time.
|
||||
- This means that for emulated text tracks, Video.js will display the first enabled `"subtitles"` or `"captions"` track.
|
||||
- When native text tracks are supported, other tracks of the same kind will still be disabled, but it is possible that multiple text tracks are shown.
|
||||
- If a `"descriptions"` track is selected and subsequently a `"subtitles"` or `"captions"` track is selected, the `"descriptions"` track is disabled and its menu button is also disabled.
|
||||
- When enabling a track programmatically, Video.js performs minimal enforcement.
|
||||
- For emulated text tracks, Video.js chooses the first track that's `"showing"` - again choosing `"subtitles"` or `"captions"` over `"descriptions"`.
|
||||
- For native text tracks, this behavior depends on the browser. Some browsers will allow multiple text tracks, but others will disable all other tracks when a new one is selected.
|
||||
* If you are using the `default` attribute, Video.js will choose the first track that is marked as `default` and turn it on. If there are multiple tracks marked `default`, it will turn on the first `"captions"` or `"subtitles"` track _before_ any `"descriptions"` tracks.
|
||||
* This only applied to the emulated text track support, native text tracks behavior will change depending on the browser.
|
||||
* If a track is selected from the menu, Video.js will turn off all the other tracks of the same kind. While this suggests Video.js supports both `"subtitles"` and `"captions"` being turned on simultaneously, this is currently not the case; Video.js only supports one track being displayed at a time.
|
||||
* This means that for emulated text tracks, Video.js will display the first enabled `"subtitles"` or `"captions"` track.
|
||||
* When native text tracks are supported, other tracks of the same kind will still be disabled, but it is possible that multiple text tracks are shown.
|
||||
* If a `"descriptions"` track is selected and subsequently a `"subtitles"` or `"captions"` track is selected, the `"descriptions"` track is disabled and its menu button is also disabled.
|
||||
* When enabling a track programmatically, Video.js performs minimal enforcement.
|
||||
* For emulated text tracks, Video.js chooses the first track that's `"showing"` - again choosing `"subtitles"` or `"captions"` over `"descriptions"`.
|
||||
* For native text tracks, this behavior depends on the browser. Some browsers will allow multiple text tracks, but others will disable all other tracks when a new one is selected.
|
||||
|
||||
## API
|
||||
|
||||
For more complete information, refer to the [Video.js API docs](http://docs.videojs.com/docs/api/index.html).
|
||||
|
||||
### Remote Text Tracks
|
||||
|
||||
As mentioned [above](#a-note-on-remote-text-tracks), remote text tracks represent the recommended API offered by Video.js as they can be removed.
|
||||
|
||||
- `Player#remoteTextTracks()`
|
||||
- `Player#remoteTextTrackEls()`
|
||||
- `Player#addRemoteTextTrack(Object options)`
|
||||
* `Player#remoteTextTracks()`
|
||||
* `Player#remoteTextTrackEls()`
|
||||
* `Player#addRemoteTextTrack(Object options)`
|
||||
|
||||
Available options are the same as the [available `track` attributes](#track-attributes). And `language` is a supported option as an alias for the `srclang` attribute - either works here.
|
||||
|
||||
- `Player#removeRemoteTextTrack(HTMLTrackElement|TextTrack)`
|
||||
* `Player#removeRemoteTextTrack(HTMLTrackElement|TextTrack)`
|
||||
|
||||
### Text Tracks
|
||||
|
||||
It is generally recommended that you use _remote_ text tracks rather than these purely programmatic text tracks for the majority of use-cases.
|
||||
|
||||
- `Player#textTracks()`
|
||||
- `Player#addTextTrack(String kind, [String label [, String language]])`
|
||||
* `Player#textTracks()`
|
||||
* `Player#addTextTrack(String kind, [String label [, String language]])`
|
||||
|
||||
> **Note:** Non-remote text tracks are intended for _purely programmatic usage_ of tracks and have the important limitation that they _cannot be removed once created_.
|
||||
>
|
||||
> The standard `addTextTrack` does **not** have a corresponding `removeTextTrack` method; so, we actually discourage the use of this method!
|
||||
|
||||
- `TextTrackList()`
|
||||
- `TextTrack()`
|
||||
* `TextTrackList()`
|
||||
* `TextTrack()`
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
# Tracks
|
||||
|
||||
There are currently three types of tracks:
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Audio Tracks](#audio-tracks)
|
||||
* [Video Tracks](#video-tracks)
|
||||
* [Text Tracks](#text-tracks)
|
||||
|
||||
## [Audio Tracks](./audio-tracks.md)
|
||||
|
||||
Audio tracks allow the selection of alternate audio for a video.
|
||||
|
||||
## [Video Tracks](./video-tracks.md)
|
||||
|
||||
Video tracks allow the selection of alternate video content.
|
||||
|
||||
## [Text Tracks](./text-tracks.md)
|
||||
|
||||
Text tracks are used to display subtitles and captions and add a menu for navigating between chapters in a video.
|
||||
|
||||
@@ -1,37 +1,59 @@
|
||||
Troubleshooting
|
||||
# Troubleshooting
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Problems with media formats](#problems-with-media-formats)
|
||||
* [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 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 playback](#problems-with-playback)
|
||||
* [Video.js Errors](#videojs-errors)
|
||||
* [vdata123456 errors](#vdata123456-errors)
|
||||
|
||||
## Problems with media formats
|
||||
|
||||
### Choosing a video format
|
||||
|
||||
#### 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
|
||||
|
||||
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 used in the file container is supported:
|
||||
|
||||
### Make sure that the codec for that is being 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.
|
||||
|
||||
### 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
|
||||
|
||||
## Problems when hosting media
|
||||
|
||||
* Your server must support byte-range requests as Chrome and Safari rely on them:
|
||||
* Most servers support this by default.
|
||||
* If you are proxying the media files via a server side script (PHP), this script must implement ranges. PHP does not do this by default.
|
||||
@@ -41,21 +63,25 @@ 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`
|
||||
* `webkitallowfullscreen`
|
||||
* `mozallowfullscreen`
|
||||
|
||||
## 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
|
||||
|
||||
This error is thrown when an element that is associated with a component is removed
|
||||
from the DOM but the event handlers associated with the element are not removed. This
|
||||
is almost always due to event listeners not being disposed when dispose is called on
|
||||
@@ -63,18 +89,18 @@ a component.
|
||||
|
||||
To fix this issue please make sure that all event listeners are cleaned up on dispose.
|
||||
|
||||
<!-- same-page -->
|
||||
[hosting-problems]: #problems-when-hosting-media
|
||||
[hosting-media]: #problems-when-hosting-media
|
||||
|
||||
<!-- guides -->
|
||||
[text-tracks]: /docs/guides/text-tracks.md
|
||||
[text-tracks]: text-tracks.md
|
||||
|
||||
<!-- official projects -->
|
||||
[hls]: https://github.com/videojs/videojs-contrib-hls
|
||||
|
||||
[dash]: https://github.com/videojs/videojs-contrib-dash
|
||||
|
||||
[youtube]: https://github.com/videojs/videojs-youtube
|
||||
|
||||
[flash]: https://github.com/videojs/videojs-flash
|
||||
|
||||
<!-- External links -->
|
||||
[media-types]: http://www.iana.org/assignments/media-types/media-types.xhtml#video
|
||||
|
||||
[cors]: http://enable-cors.org/
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
# Video Tracks
|
||||
|
||||
> **Note:** While video tracks [are a standard][spec-videotrack], there are no compatible implementations at this time. This is an experimental technology!
|
||||
|
||||
Video tracks are a feature of HTML5 video for providing alternate video tracks to the user, so they can change the type of video they want to watch. Video.js offers a cross-browser implementation of video tracks.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Caveats](#caveats)
|
||||
* [Working with Video Tracks](#working-with-video-tracks)
|
||||
* [Add a Video Track to the Player](#add-a-video-track-to-the-player)
|
||||
* [Listen for a Video Track Becoming Enabled](#listen-for-a-video-track-becoming-enabled)
|
||||
* [Removing an Video Track from the Player](#removing-an-video-track-from-the-player)
|
||||
* [API](#api)
|
||||
* [videojs.VideoTrack](#videojsvideotrack)
|
||||
* [id](#id)
|
||||
* [kind](#kind)
|
||||
* [label](#label)
|
||||
* [language](#language)
|
||||
* [selected](#selected)
|
||||
|
||||
## Caveats
|
||||
- It is not possible to add video tracks through HTML like you can with text tracks. They must be added programmatically.
|
||||
- Video.js only stores track representations. Switching video tracks for playback is _not handled by Video.js_ and must be handled elsewhere.
|
||||
|
||||
* It is not possible to add video tracks through HTML like you can with text tracks. They must be added programmatically.
|
||||
* Video.js only stores track representations. Switching video tracks for playback is _not handled by Video.js_ and must be handled elsewhere.
|
||||
|
||||
## Working with Video Tracks
|
||||
|
||||
### Add a Video Track to the Player
|
||||
|
||||
```js
|
||||
// Create a player.
|
||||
var player = videojs('my-player');
|
||||
@@ -26,6 +45,7 @@ player.videoTracks().addTrack(track);
|
||||
```
|
||||
|
||||
### Listen for a Video Track Becoming Enabled
|
||||
|
||||
When a track is enabled or disabled on an `VideoTrackList`, a `change` event will be fired. You can listen for that event and do something with it.
|
||||
|
||||
> NOTE: The initial `VideoTrack` selection (usually the main track that is selected) should not fire a `change` event.
|
||||
@@ -50,6 +70,7 @@ videoTrackList.addEventListener('change', function() {
|
||||
```
|
||||
|
||||
### Removing an Video Track from the Player
|
||||
|
||||
Assuming a player already exists and has an video track that you want to remove, you might do something like the following:
|
||||
|
||||
```js
|
||||
@@ -61,41 +82,47 @@ 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:
|
||||
|
||||
- `Player#videoTracks`
|
||||
- `VideoTrackList`
|
||||
- `VideoTrack`
|
||||
* `Player#videoTracks`
|
||||
* `VideoTrackList`
|
||||
* `VideoTrack`
|
||||
|
||||
### `videojs.VideoTrack`
|
||||
|
||||
This class is based on [the `VideoTrack` standard][spec-videotrack] and can be used to create new video track objects.
|
||||
|
||||
Each property below is available as an option to the `VideoTrack` constructor.
|
||||
|
||||
#### `id`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-id)
|
||||
|
||||
A unique identifier for this track. Video.js will generate one if not given.
|
||||
|
||||
#### `kind`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind)
|
||||
|
||||
Video.js supports standard `kind` values for `VideoTracks`:
|
||||
|
||||
- `"alternative"`: A possible alternative to the main track.
|
||||
- `"captions"`: The main video track with burned in captions
|
||||
- `"main"`: The main video track.
|
||||
- `"sign"`: The main video track with added sign language overlay.
|
||||
- `"subtitles"`: The main video track with burned in subtitles.
|
||||
- `"commentary"`: The main video track with burned in commentary.
|
||||
- `""` (default): No explicit kind, or the kind given by the track's metadata is not recognized by the user agent.
|
||||
* `"alternative"`: A possible alternative to the main track.
|
||||
* `"captions"`: The main video track with burned in captions
|
||||
* `"main"`: The main video track.
|
||||
* `"sign"`: The main video track with added sign language overlay.
|
||||
* `"subtitles"`: The main video track with burned in subtitles.
|
||||
* `"commentary"`: The main video track with burned in commentary.
|
||||
* `""` (default): No explicit kind, or the kind given by the track's metadata is not recognized by the user agent.
|
||||
|
||||
#### `label`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-label)
|
||||
|
||||
The label for the track that will be shown to the user. For example, in a menu that lists the different captions available as alternate video tracks.
|
||||
|
||||
#### `language`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-language)
|
||||
|
||||
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.
|
||||
@@ -103,9 +130,9 @@ The valid [BCP 47](https://tools.ietf.org/html/bcp47) code for the language of t
|
||||
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.
|
||||
|
||||
#### `selected`
|
||||
|
||||
> [standard definition](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-selected)
|
||||
|
||||
Whether or not this track should be playing. Only one video track may be selected at a time.
|
||||
|
||||
|
||||
[spec-videotrack]: https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
# Usage examples for the functions on videojs
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [videojs()](#videojs)
|
||||
* [options](#options)
|
||||
* [getComponent()](#getcomponent)
|
||||
* [registerComponent()](#registercomponent)
|
||||
* [getTech()](#gettech)
|
||||
* [registerTech()](#registertech)
|
||||
* [extend()](#extend)
|
||||
* [mergeOptions()](#mergeoptions)
|
||||
* [bind()](#bind)
|
||||
* [plugin()](#plugin)
|
||||
* [xhr()](#xhr)
|
||||
|
||||
## `videojs()`
|
||||
|
||||
```js
|
||||
var myPlayer = videojs('my_video_id');
|
||||
```
|
||||
|
||||
## `options`
|
||||
|
||||
```js
|
||||
videojs.options.autoplay = true
|
||||
// -> all players will autoplay by default
|
||||
```
|
||||
|
||||
## `getComponent()`
|
||||
|
||||
```js
|
||||
var VjsButton = videojs.getComponent('Button');
|
||||
// Create a new instance of the component
|
||||
var myButton = new VjsButton(myPlayer);
|
||||
```
|
||||
|
||||
## `registerComponent()`
|
||||
|
||||
```js
|
||||
// Get a component to subclass
|
||||
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);
|
||||
// (optionally) add the new component as a default player child
|
||||
myPlayer.addChild('MySepcialButton');
|
||||
```
|
||||
|
||||
## `getTech()`
|
||||
|
||||
```js
|
||||
var Html5 = videojs.getTech('Html5');
|
||||
// Create a new instance of the component
|
||||
var html5 = new Html5(options);
|
||||
```
|
||||
|
||||
## `registerTech()`
|
||||
|
||||
```js
|
||||
// get the Html5 Tech
|
||||
var Html5 = videojs.getTech('Html5');
|
||||
var MyTech = videojs.extend(Html5, {});
|
||||
// Register the new Tech
|
||||
VjsButton.registerTech('Tech', MyTech);
|
||||
var player = videojs('myplayer', {
|
||||
techOrder: ['myTech', 'html5']
|
||||
});
|
||||
```
|
||||
|
||||
## `extend()`
|
||||
|
||||
```js
|
||||
// Create a basic javascript 'class'
|
||||
function MyClass(name) {
|
||||
// Set a property at initialization
|
||||
this.myName = name;
|
||||
}
|
||||
// Create an instance method
|
||||
MyClass.prototype.sayMyName = function() {
|
||||
alert(this.myName);
|
||||
};
|
||||
// Subclass the exisitng class and change the name
|
||||
// when initializing
|
||||
var MySubClass = videojs.extend(MyClass, {
|
||||
constructor: function(name) {
|
||||
// Call the super class constructor for the subclass
|
||||
MyClass.call(this, name)
|
||||
}
|
||||
});
|
||||
// Create an instance of the new sub class
|
||||
var myInstance = new MySubClass('John');
|
||||
myInstance.sayMyName(); // -> should alert "John"
|
||||
```
|
||||
|
||||
## `mergeOptions()`
|
||||
|
||||
```js
|
||||
var defaultOptions = {
|
||||
foo: true,
|
||||
bar: {
|
||||
a: true,
|
||||
b: [1,2,3]
|
||||
}
|
||||
};
|
||||
var newOptions = {
|
||||
foo: false,
|
||||
bar: {
|
||||
b: [4,5,6]
|
||||
}
|
||||
};
|
||||
var result = videojs.mergeOptions(defaultOptions, newOptions);
|
||||
// result.foo = false;
|
||||
// result.bar.a = true;
|
||||
// result.bar.b = [4,5,6];
|
||||
```
|
||||
|
||||
## `bind()`
|
||||
|
||||
```js
|
||||
var someClass = function() {};
|
||||
var someObj = new someClass();
|
||||
videojs.bind(someObj, function() {
|
||||
// this will be the context of someObj here
|
||||
});
|
||||
```
|
||||
|
||||
## `plugin()`
|
||||
|
||||
**See the [plugin guide](plugins.md) in the docs for a more detailed example**
|
||||
|
||||
```js
|
||||
// Make a plugin that alerts when the player plays
|
||||
videojs.plugin('myPlugin', function(myPluginOptions) {
|
||||
myPluginOptions = myPluginOptions || {};
|
||||
|
||||
var player = this;
|
||||
var alertText = myPluginOptions.text || 'Player is playing!'
|
||||
|
||||
player.on('play', function() {
|
||||
alert(alertText);
|
||||
});
|
||||
});
|
||||
// USAGE EXAMPLES
|
||||
// EXAMPLE 1: New player with plugin options, call plugin immediately
|
||||
var player1 = videojs('idOne', {
|
||||
myPlugin: {
|
||||
text: 'Custom text!'
|
||||
}
|
||||
});
|
||||
// Click play
|
||||
// --> Should alert 'Custom text!'
|
||||
// EXAMPLE 3: New player, initialize plugin later
|
||||
var player3 = videojs('idThree');
|
||||
// Click play
|
||||
// --> NO ALERT
|
||||
// Click pause
|
||||
// Initialize plugin using the plugin function on the player instance
|
||||
player3.myPlugin({
|
||||
text: 'Plugin added later!'
|
||||
});
|
||||
// Click play
|
||||
// --> Should alert 'Plugin added later!'
|
||||
```
|
||||
|
||||
## `xhr()`
|
||||
|
||||
```js
|
||||
videojs.xhr({
|
||||
body: someJSONString,
|
||||
uri: "/foo",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}, function (err, resp, body) {
|
||||
// check resp.statusCode
|
||||
});
|
||||
```
|
||||
+62
-31
@@ -1,64 +1,95 @@
|
||||
# [Video.js][vjs] Documentation
|
||||
# [Video.js][vjs-website] Documentation
|
||||
|
||||
There are two categories of docs: [Guides][guides] and [API docs][api].
|
||||
There are two categories of docs: [Guides](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.
|
||||
|
||||
## Guides
|
||||
## Table of Contents
|
||||
|
||||
* [Resolving Issues](#resolving-issues)
|
||||
* [FAQ](#faq)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* [Guides](#guides)
|
||||
* [Getting Started](#getting-started)
|
||||
* [Setup](#setup)
|
||||
* [Player Workflows](#player-workflows)
|
||||
* [Options](#options)
|
||||
* [Tracks](#tracks)
|
||||
* [Customizing](#customizing)
|
||||
* [Skins](#skins)
|
||||
* [Plugins](#plugins)
|
||||
* [Components](#components)
|
||||
* [Tech](#tech)
|
||||
* [Languages](#languages)
|
||||
* [Hooks](#hooks)
|
||||
* [API Docs](#api-docs)
|
||||
|
||||
## Resolving Issues
|
||||
|
||||
### [FAQ](guides/faq.md)
|
||||
|
||||
The frequently asked questions for video.js.
|
||||
|
||||
### [Troubleshooting](guides/troubleshooting.md)
|
||||
|
||||
Troubleshooting help for video.js.
|
||||
|
||||
## [Guides](guides)
|
||||
|
||||
### Getting Started
|
||||
|
||||
#### [Setup][guides-setup]
|
||||
#### [Setup](guides/setup.md)
|
||||
|
||||
The setup guide covers all methods of setting up Video.js players. After mastering the basics, see the [player workflows guide][guides-workflow].
|
||||
The setup guide covers all methods of setting up Video.js players.
|
||||
|
||||
#### [Options][guides-options]
|
||||
#### [Player Workflows](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)
|
||||
|
||||
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]
|
||||
#### [Tracks](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]
|
||||
#### [Skins](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.
|
||||
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]
|
||||
#### [Plugins](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](https://github.com/videojs/video.js/wiki/Plugins).
|
||||
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]
|
||||
#### [Components](guides/components.md)
|
||||
|
||||
Video.js is built around a collection of components. These are the building blocks of the player UI.
|
||||
|
||||
#### [Tech][guides-tech]
|
||||
#### [Tech](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.
|
||||
|
||||
## API Docs
|
||||
#### [Languages](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)
|
||||
|
||||
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.
|
||||
|
||||
## [API Docs][api]
|
||||
|
||||
You can refer to the [full list of API docs][api], but the most relevant API doc is for the [Player][api-player].
|
||||
|
||||
[plugins-list]: http://videojs.com/plugins
|
||||
|
||||
[skins-list]: https://github.com/videojs/video.js/wiki/Skins
|
||||
|
||||
[api]: http://docs.videojs.com/docs/api/index.html
|
||||
|
||||
[api-player]: http://docs.videojs.com/docs/api/player.html
|
||||
[guides]: ./guides/
|
||||
[guides-api]: ./guides/api.md
|
||||
[guides-audio]: ./guides/audio.md
|
||||
[guides-components]: ./guides/components.md
|
||||
[guides-glossary]: ./guides/glossary.md
|
||||
[guides-languages]: ./guides/languages.md
|
||||
[guides-options]: ./guides/options.md
|
||||
[guides-plugins]: ./guides/plugins.md
|
||||
[guides-removing]: ./guides/removing.md
|
||||
[guides-setup]: ./guides/setup.md
|
||||
[guides-skins]: ./guides/skins.md
|
||||
[guides-tech]: ./guides/tech.md
|
||||
[guides-text-tracks]: ./guides/text-tracks.md
|
||||
[guides-tracks]: ./guides/tracks.md
|
||||
[guides-video-tracks]: ./guides/video-tracks.md
|
||||
[guides-workflow]: ./guides/player-workflows.md
|
||||
[vjs]: http://videojs.com
|
||||
|
||||
[vjs-website]: http://videojs.com
|
||||
|
||||
+1093
-47
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+38
-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,46 @@
|
||||
", opens captions settings dialog": ", öffnet Einstellungen für Untertitel",
|
||||
", opens subtitles settings dialog": ", öffnet Einstellungen für Untertitel",
|
||||
", selected": ", ausgewählt",
|
||||
"captions settings": "Untertiteleinstellungen",
|
||||
"subititles 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",
|
||||
"Defaults": "Standardeinstellungen",
|
||||
"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."
|
||||
}
|
||||
|
||||
+38
-1
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"Play": "Play",
|
||||
"Pause": "Pause",
|
||||
"Replay": "Replay",
|
||||
"Current Time": "Current Time",
|
||||
"Duration Time": "Duration Time",
|
||||
"Remaining Time": "Remaining Time",
|
||||
@@ -36,5 +37,41 @@
|
||||
", 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",
|
||||
"subititles 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",
|
||||
"Defaults": "Defaults",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"Play": "Lecture",
|
||||
"Pause": "Pause",
|
||||
"Replay": "Revoir",
|
||||
"Current Time": "Temps actuel",
|
||||
"Duration Time": "Durée",
|
||||
"Remaining Time": "Temps restant",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
+47
-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,62 @@
|
||||
"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ı",
|
||||
"subititles 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",
|
||||
"Defaults": "Varsayılan Ayarlar",
|
||||
"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
-24
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"version": "5.14.1",
|
||||
"version": "6.0.0-RC.1",
|
||||
"main": "./es5/video.js",
|
||||
"style": "./dist/video-js.css",
|
||||
"copyright": "Copyright Brightcove, Inc. <https://www.brightcove.com/>",
|
||||
@@ -22,10 +22,15 @@
|
||||
"clean": "grunt clean",
|
||||
"grunt": "grunt",
|
||||
"lint": "vjsstandard",
|
||||
"start": "grunt watchAll",
|
||||
"start": "grunt dev",
|
||||
"test": "grunt test",
|
||||
"toc": "doctoc",
|
||||
"docs": "jsdoc -r src/js -d docs/api -c .jsdoc.json"
|
||||
"docs": "npm run docs:lint && npm run docs:api",
|
||||
"jsdoc": "jsdoc",
|
||||
"docs:api": "jsdoc -c .jsdoc.json",
|
||||
"docs:lint": "remark -- './**/*.md'",
|
||||
"docs:fix": "remark --output -- './**/*.md'",
|
||||
"babel": "babel src/js -d es5",
|
||||
"prepublish": "not-in-install && npm run docs:api || in-install"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -38,54 +43,54 @@
|
||||
"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.0"
|
||||
"xhr": "2.3.3"
|
||||
},
|
||||
"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",
|
||||
"browserify-derequire": "^0.9.4",
|
||||
"browserify-istanbul": "^0.2.1",
|
||||
"browserify-istanbul": "^2.0.0",
|
||||
"browserify-versionify": "^1.0.4",
|
||||
"bundle-collapser": "^1.2.1",
|
||||
"chg": "^0.3.2",
|
||||
"conventional-changelog-cli": "^1.2.0",
|
||||
"conventional-changelog-videojs": "^3.0.0",
|
||||
"doctoc": "^1.2.0",
|
||||
"es5-shim": "^4.1.3",
|
||||
"es6-shim": "^0.35.1",
|
||||
"ghooks": "^1.3.2",
|
||||
"gkatsev-grunt-sass": "^1.1.1",
|
||||
"grunt": "^0.4.4",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-accessibility": "^5.0.0",
|
||||
"grunt-babel": "^6.0.0",
|
||||
"grunt-banner": "^0.4.0",
|
||||
"grunt-browserify": "3.5.1",
|
||||
"grunt-cli": "~0.1.13",
|
||||
"grunt-banner": "^0.6.0",
|
||||
"grunt-browserify": "5.0.0",
|
||||
"grunt-cli": "~1.2.0",
|
||||
"grunt-concurrent": "^2.3.1",
|
||||
"grunt-contrib-clean": "~0.4.0a",
|
||||
"grunt-contrib-concat": "^0.5.1",
|
||||
"grunt-contrib-connect": "~0.7.1",
|
||||
"grunt-contrib-copy": "^0.8.0",
|
||||
"grunt-contrib-clean": "^1.0.0",
|
||||
"grunt-contrib-concat": "^1.0.1",
|
||||
"grunt-contrib-connect": "~1.0.2",
|
||||
"grunt-contrib-copy": "^1.0.0",
|
||||
"grunt-contrib-cssmin": "~1.0.2",
|
||||
"grunt-contrib-uglify": "^2.0.0",
|
||||
"grunt-contrib-watch": "~0.1.4",
|
||||
"grunt-contrib-watch": "~1.0.0",
|
||||
"grunt-coveralls": "^1.0.0",
|
||||
"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": "~0.3.0",
|
||||
"grunt-version": "~1.1.1",
|
||||
"grunt-videojs-languages": "0.0.4",
|
||||
"grunt-zip": "0.10.2",
|
||||
"grunt-zip": "0.17.1",
|
||||
"in-publish": "^2.0.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"jsdoc": "^3.4.2",
|
||||
"karma": "^1.2.0",
|
||||
"karma-browserify": "^5.1.0",
|
||||
@@ -104,12 +109,18 @@
|
||||
"markdown-table": "^1.0.0",
|
||||
"npm-run": "^4.1.0",
|
||||
"proxyquireify": "^3.0.0",
|
||||
"qunitjs": "^1.23.1",
|
||||
"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",
|
||||
"shelljs": "^0.7.5",
|
||||
"sinon": "^1.16.1",
|
||||
"time-grunt": "^1.1.1",
|
||||
"tui-jsdoc-template": "^1.1.0",
|
||||
"uglify-js": "~2.7.3",
|
||||
"videojs-doc-generator": "0.0.1",
|
||||
"videojs-standard": "^5.2.0",
|
||||
"videojs-standard": "^6.0.1",
|
||||
"webpack": "^1.13.2"
|
||||
},
|
||||
"vjsstandard": {
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,3 +6,6 @@
|
||||
.video-js .vjs-play-control.vjs-playing {
|
||||
@extend .vjs-icon-pause;
|
||||
}
|
||||
.video-js .vjs-play-control.vjs-ended {
|
||||
@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,17 +1,6 @@
|
||||
// .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 {
|
||||
@include flex(auto);
|
||||
@include display-flex(center);
|
||||
@@ -22,35 +11,32 @@
|
||||
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
|
||||
// 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;
|
||||
@@ -64,86 +50,80 @@
|
||||
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. They are not components
|
||||
// themselves, but they are managed by the MouseTimeDisplay and PlayProgressBar
|
||||
// components individually.
|
||||
//
|
||||
// By default, they are hidden and only shown when hovering over the progress
|
||||
// control.
|
||||
.video-js .vjs-time-tooltip {
|
||||
@include background-color-with-alpha(#fff, 0.8);
|
||||
@include border-radius(0.3em);
|
||||
color: #000;
|
||||
display: inline-block;
|
||||
height: 2.4em;
|
||||
position: relative;
|
||||
|
||||
// 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-control:hover .vjs-time-tooltip {
|
||||
|
||||
// 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 +132,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;
|
||||
|
||||
@@ -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,127 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@extend .vjs-icon-volume-mid;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-control {
|
||||
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 +174,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 +187,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;
|
||||
|
||||
@@ -37,7 +37,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);
|
||||
}
|
||||
|
||||
|
||||
+6
-23
@@ -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,13 @@ class Button extends ClickableComponent {
|
||||
* @return {Element}
|
||||
* The element that gets created.
|
||||
*/
|
||||
createEl(tag = 'button', props = {}, attributes = {}) {
|
||||
createEl(tag, props = {}, attributes = {}) {
|
||||
tag = 'button';
|
||||
|
||||
props = assign({
|
||||
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({
|
||||
|
||||
@@ -92,9 +81,6 @@ class Button extends ClickableComponent {
|
||||
/**
|
||||
* Enable the `Button` element so that it can be activated or clicked. Use this with
|
||||
* {@link Button#disable}.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method is chainable.
|
||||
*/
|
||||
enable() {
|
||||
super.enable();
|
||||
@@ -104,9 +90,6 @@ class Button extends ClickableComponent {
|
||||
/**
|
||||
* Enable the `Button` element so that it cannot be activated or clicked. Use this with
|
||||
* {@link Button#enable}.
|
||||
*
|
||||
* @return {Component}
|
||||
* Returns itself; method is chainable.
|
||||
*/
|
||||
disable() {
|
||||
super.disable();
|
||||
|
||||
@@ -108,9 +108,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) {
|
||||
@@ -122,8 +121,6 @@ class ClickableComponent extends Component {
|
||||
this.controlText_ = text;
|
||||
this.controlTextEl_.innerHTML = localizedText;
|
||||
el.setAttribute('title', localizedText);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,9 +135,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 +146,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 +161,6 @@ class ClickableComponent extends Component {
|
||||
this.off('click', this.handleClick);
|
||||
this.off('focus', this.handleFocus);
|
||||
this.off('blur', this.handleBlur);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+152
-592
@@ -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';
|
||||
@@ -18,33 +20,6 @@ import mergeOptions from './utils/merge-options.js';
|
||||
* in the DOM. They can be children of other components, and can have
|
||||
* children themselves.
|
||||
*
|
||||
* Creating a button component.
|
||||
* ``` js
|
||||
* // adding a button to the player
|
||||
* var player = videojs('some-video-id');
|
||||
* var Component = videojs.getComponent('Component');
|
||||
* var button = new Component(player);
|
||||
*
|
||||
* console.log(button.el());
|
||||
* ```
|
||||
*
|
||||
* Above code will log this html.
|
||||
* ```html
|
||||
* <div class="video-js">
|
||||
* <div class="vjs-button">Button</div>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* Adding a button to the player
|
||||
* ``` js
|
||||
* // adding a button to the player
|
||||
* var player = videojs('some-video-id');
|
||||
* var button = player.addChild('button');
|
||||
*
|
||||
* console.log(button.el());
|
||||
* // will have the same html result as the previous example
|
||||
* ```
|
||||
*
|
||||
* Components can also use methods from {@link EventTarget}
|
||||
*/
|
||||
class Component {
|
||||
@@ -64,7 +39,12 @@ class Component {
|
||||
* The `Player` that this class should be attached to.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* The key/value store of player options.
|
||||
* The key/value store of player options.
|
||||
#
|
||||
* @param {Object[]} [options.children]
|
||||
* An array of children objects to intialize this component with. Children objects have
|
||||
* a name property that will be used if more than one component of the same type needs to be
|
||||
* added.
|
||||
*
|
||||
* @param {Component~ReadyCallback} [ready]
|
||||
* Function that gets called when the `Component` is ready.
|
||||
@@ -104,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_ = {};
|
||||
@@ -155,15 +139,12 @@ class Component {
|
||||
this.childIndex_ = null;
|
||||
this.childNameIndex_ = null;
|
||||
|
||||
// Remove all event listeners.
|
||||
this.off();
|
||||
|
||||
// Remove element from DOM
|
||||
if (this.el_.parentNode) {
|
||||
this.el_.parentNode.removeChild(this.el_);
|
||||
}
|
||||
|
||||
Dom.removeElData(this.el_);
|
||||
DomData.removeData(this.el_);
|
||||
this.el_ = null;
|
||||
}
|
||||
|
||||
@@ -182,41 +163,6 @@ class Component {
|
||||
* > Note: When both `obj` and `options` contain properties whose values are objects.
|
||||
* The two properties get merged using {@link module:mergeOptions}
|
||||
*
|
||||
* Example
|
||||
* ```js
|
||||
* var player = videojs('some-vid-id');
|
||||
* var Component = videojs.getComponent('Component');
|
||||
* var component = new Component(player, {
|
||||
* optionSet: {
|
||||
* childOne: {foo: 'bar', asdf: 'fdsa'},
|
||||
* childTwo: {},
|
||||
* childThree: {}
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* const newOptions = {
|
||||
* optionSet: {
|
||||
* childOne: {foo: 'baz', abc: '123'}
|
||||
* childTwo: null,
|
||||
* childFour: {}
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* console.log(component.options(newOptions));
|
||||
* ```
|
||||
*
|
||||
* Result
|
||||
* ```js
|
||||
* {
|
||||
* optionSet: {
|
||||
* childOne: {foo: 'baz', asdf: 'fdsa', abc: '123' },
|
||||
* childTwo: null,
|
||||
* childThree: {},
|
||||
* childFour: {}
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {Object} obj
|
||||
* The object that contains new options.
|
||||
*
|
||||
@@ -375,37 +321,6 @@ class Component {
|
||||
/**
|
||||
* Add a child `Component` inside the current `Component`.
|
||||
*
|
||||
* Example:
|
||||
* ```js
|
||||
* var player = videojs('some-vid-id');
|
||||
* var Component = videojs.getComponent('Component');
|
||||
* var myComponent = new Component(player);
|
||||
*
|
||||
* console.log(myComponent.el());
|
||||
* // -> <div class='my-component'></div>
|
||||
* console.log(myComponent.children());
|
||||
* // [empty array]
|
||||
*
|
||||
* var myButton = myComponent.addChild('MyButton');
|
||||
*
|
||||
* console.log(myComponent.el());
|
||||
* // -> <div class='my-component'><div class="my-button">myButton<div></div>
|
||||
* console.log(myComponent.children());
|
||||
* // -> myButton === myComponent.children()[0];
|
||||
* ```
|
||||
*
|
||||
* Pass in options for child constructors and options for children of the child.
|
||||
* ```js
|
||||
* var player = videojs('some-vid-id');
|
||||
* var Component = videojs.getComponent('Component');
|
||||
* var myComponent = new Component(player);
|
||||
* var myButton = myComponent.addChild('MyButton', {
|
||||
* text: 'Press Me',
|
||||
* buttonChildExample: {
|
||||
* buttonChildOption: true
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param {string|Component} child
|
||||
* The name or instance of a child to add.
|
||||
@@ -429,21 +344,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
|
||||
@@ -541,48 +441,6 @@ class Component {
|
||||
|
||||
/**
|
||||
* Add and initialize default child `Component`s based upon options.
|
||||
*
|
||||
* Example.
|
||||
* ```js
|
||||
* var MyComponent = videojs.extend(videojs.getComponent('Component'));
|
||||
* // when an instance of MyComponent is created, all children in options
|
||||
* // will be added to the instance by their name strings and options
|
||||
* MyComponent.prototype.options_ = {
|
||||
* children: [
|
||||
* 'myChildComponent'
|
||||
* ],
|
||||
* myChildComponent: {
|
||||
* myChildOption: true
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* // Or when creating the component
|
||||
* var player = videojs('some-player-id');
|
||||
* var myComp = new MyComponent(player, {
|
||||
* children: [
|
||||
* 'myChildComponent'
|
||||
* ],
|
||||
* myChildComponent: {
|
||||
* myChildOption: true
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* The children option can also be an array of child options objects
|
||||
* (that also include a 'name' key). This will get used if you have two child
|
||||
* components of the same type that need different options.
|
||||
* ```js
|
||||
* // MyComponent is from the above example
|
||||
* var myComp = new MyComponent(player, {
|
||||
* children: ['button', {
|
||||
* name: 'button',
|
||||
* someOtherOption: true
|
||||
* }, {
|
||||
* name: 'button',
|
||||
* someOtherOption: false
|
||||
* }]
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
initChildren() {
|
||||
const children = this.options_.children;
|
||||
@@ -694,311 +552,9 @@ class Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an `event listener` to this `Component`s element.
|
||||
*
|
||||
* ```js
|
||||
* var player = videojs('some-player-id');
|
||||
* var Component = videojs.getComponent('Component');
|
||||
* var myComponent = new Component(player);
|
||||
* var myFunc = function() {
|
||||
* var myComponent = this;
|
||||
* console.log('myFunc called');
|
||||
* };
|
||||
*
|
||||
* myComponent.on('eventType', myFunc);
|
||||
* myComponent.trigger('eventType');
|
||||
* // logs 'myFunc called'
|
||||
* ```
|
||||
*
|
||||
* The context of `myFunc` will be `myComponent` unless it is bound. You can add
|
||||
* a listener to another element or component.
|
||||
* ```js
|
||||
* var otherComponent = new Component(player);
|
||||
*
|
||||
* // myComponent/myFunc is from the above example
|
||||
* myComponent.on(otherComponent.el(), 'eventName', myFunc);
|
||||
* myComponent.on(otherComponent, 'eventName', myFunc);
|
||||
*
|
||||
* otherComponent.trigger('eventName');
|
||||
* // logs 'myFunc called' twice
|
||||
* ```
|
||||
*
|
||||
* The benefit of using this over the following:
|
||||
* - `VjsEvents.on(otherElement, 'eventName', myFunc)`
|
||||
* - `otherComponent.on('eventName', myFunc)`
|
||||
* Is that the listeners will get cleaned up when either component gets disposed.
|
||||
* 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.
|
||||
* ```js
|
||||
* var player = videojs('some-player-id');
|
||||
* var Component = videojs.getComponent('Component');
|
||||
* var myComponent = new Component(player);
|
||||
* var myFunc = function() {
|
||||
* var myComponent = this;
|
||||
* console.log('myFunc called');
|
||||
* };
|
||||
* myComponent.on('eventType', myFunc);
|
||||
* myComponent.trigger('eventType');
|
||||
* // logs 'myFunc called'
|
||||
*
|
||||
* myComponent.off('eventType', myFunc);
|
||||
* 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.
|
||||
* You can use `off` to remove listeners that get added to other elements or
|
||||
* components using:
|
||||
*
|
||||
* `myComponent.on(otherComponent...`
|
||||
*
|
||||
* In this case both the event type and listener function are **REQUIRED**.
|
||||
*
|
||||
* ```js
|
||||
* var otherComponent = new Component(player);
|
||||
*
|
||||
* // myComponent/myFunc is from the above example
|
||||
* myComponent.on(otherComponent.el(), 'eventName', myFunc);
|
||||
* myComponent.on(otherComponent, 'eventName', myFunc);
|
||||
*
|
||||
* otherComponent.trigger('eventName');
|
||||
* // logs 'myFunc called' twice
|
||||
* myComponent.off(ootherComponent.el(), 'eventName', myFunc);
|
||||
* myComponent.off(otherComponent, 'eventName', myFunc);
|
||||
* otherComponent.trigger('eventName');
|
||||
* // does nothing
|
||||
* ```
|
||||
*
|
||||
* @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.
|
||||
* ```js
|
||||
* var player = videojs('some-player-id');
|
||||
* var Component = videojs.getComponent('Component');
|
||||
* var myComponent = new Component(player);
|
||||
* var myFunc = function() {
|
||||
* var myComponent = this;
|
||||
* console.log('myFunc called');
|
||||
* };
|
||||
* myComponent.one('eventName', myFunc);
|
||||
* myComponent.trigger('eventName');
|
||||
* // logs 'myFunc called'
|
||||
*
|
||||
* myComponent.trigger('eventName');
|
||||
* // does nothing
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* You can also add a listener to another element or component that will get
|
||||
* triggered only once.
|
||||
* ```js
|
||||
* var otherComponent = new Component(player);
|
||||
*
|
||||
* // myComponent/myFunc is from the above example
|
||||
* myComponent.one(otherComponent.el(), 'eventName', myFunc);
|
||||
* myComponent.one(otherComponent, 'eventName', myFunc);
|
||||
*
|
||||
* otherComponent.trigger('eventName');
|
||||
* // logs 'myFunc called' twice
|
||||
*
|
||||
* otherComponent.trigger('eventName');
|
||||
* // does nothing
|
||||
* ```
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* ```js
|
||||
* var player = videojs('some-player-id');
|
||||
* var Component = videojs.getComponent('Component');
|
||||
* var myComponent = new Component(player);
|
||||
* var myFunc = function(data) {
|
||||
* var myComponent = this;
|
||||
* console.log('myFunc called');
|
||||
* console.log(data);
|
||||
* };
|
||||
* myComponent.one('eventName', myFunc);
|
||||
* myComponent.trigger('eventName');
|
||||
* // logs 'myFunc called' and 'undefined'
|
||||
*
|
||||
* myComponent.trigger({'type':'eventName'});
|
||||
* // logs 'myFunc called' and 'undefined'
|
||||
*
|
||||
* myComponent.trigger('eventName', {data: 'some data'});
|
||||
* // logs 'myFunc called' and "{data: 'some data'}"
|
||||
*
|
||||
* myComponent.trigger({'type':'eventName'}, {data: 'some data'});
|
||||
* // logs 'myFunc called' and "{data: 'some data'}"
|
||||
* ```
|
||||
*
|
||||
* @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.
|
||||
@@ -1017,7 +573,6 @@ class Component {
|
||||
this.readyQueue_.push(fn);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1107,7 +662,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1115,13 +670,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1129,13 +680,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1148,65 +695,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1237,14 +764,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1253,14 +776,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1273,10 +792,9 @@ class Component {
|
||||
* @param {boolean} [skipListeners]
|
||||
* Skip the resize 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);
|
||||
@@ -1292,10 +810,9 @@ class Component {
|
||||
* @param {boolean} [skipListeners]
|
||||
* Skip the resize 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);
|
||||
@@ -1309,13 +826,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);
|
||||
this.width(width, true);
|
||||
this.height(height);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1343,9 +858,8 @@ class Component {
|
||||
* @param {boolean} [skipListeners]
|
||||
* Skip resize 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) {
|
||||
@@ -1374,8 +888,7 @@ class Component {
|
||||
this.trigger('resize');
|
||||
}
|
||||
|
||||
// Return component
|
||||
return this;
|
||||
return;
|
||||
}
|
||||
|
||||
// Not setting a value, so getting it
|
||||
@@ -1775,6 +1288,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.
|
||||
*
|
||||
@@ -1788,15 +1376,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);
|
||||
@@ -1805,23 +1412,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1848,70 +1458,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,7 +22,7 @@ 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);
|
||||
|
||||
@@ -49,11 +49,7 @@ class AudioTrackButton extends TrackButton {
|
||||
* An array of menu items
|
||||
*/
|
||||
createItems(items = []) {
|
||||
const tracks = this.player_.audioTracks && this.player_.audioTracks();
|
||||
|
||||
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,9 +12,7 @@ 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';
|
||||
@@ -56,7 +54,7 @@ class ControlBar extends Component {
|
||||
ControlBar.prototype.options_ = {
|
||||
children: [
|
||||
'playToggle',
|
||||
'volumeMenuButton',
|
||||
'volumePanel',
|
||||
'currentTimeDisplay',
|
||||
'timeDivider',
|
||||
'durationDisplay',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,9 +88,9 @@ class MuteToggle extends Button {
|
||||
|
||||
// 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.addElClass(this.el_, `vjs-vol-${level}`);
|
||||
Dom.addClass(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,17 @@ class PlayToggle extends Button {
|
||||
this.controlText('Play');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the vjs-ended class to the element so it can change appearance
|
||||
*
|
||||
* @method handleEnded
|
||||
*/
|
||||
handleEnded(event) {
|
||||
this.removeClass('vjs-playing');
|
||||
this.addClass('vjs-ended');
|
||||
// change the button text to "Replay"
|
||||
this.controlText('Replay');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,14 +2,18 @@
|
||||
* @file mouse-time-display.js
|
||||
*/
|
||||
import Component from '../../component.js';
|
||||
import * as Dom from '../../utils/dom.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 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 +23,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 the DOM element for this class.
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created.
|
||||
@@ -60,94 +46,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,11 +2,13 @@
|
||||
* @file play-progress-bar.js
|
||||
*/
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.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
|
||||
*/
|
||||
@@ -16,31 +18,17 @@ class PlayProgressBar 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);
|
||||
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,20 +41,46 @@ 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());
|
||||
|
||||
this.getChild('timeTooltip').update(seekBarRect, seekBarPoint, content);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default options for {@link PlayProgressBar}.
|
||||
*
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
PlayProgressBar.prototype.options_ = {
|
||||
children: [
|
||||
'timeTooltip'
|
||||
]
|
||||
};
|
||||
|
||||
Component.registerComponent('PlayProgressBar', PlayProgressBar);
|
||||
export default PlayProgressBar;
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
* @file progress-control.js
|
||||
*/
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
|
||||
import './seek-bar.js';
|
||||
import './mouse-time-display.js';
|
||||
|
||||
/**
|
||||
* The Progress Control component contains the seek bar, load progress,
|
||||
@@ -14,6 +15,21 @@ 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 = Fn.throttle(Fn.bind(this, this.handleMouseMove), 25);
|
||||
this.on(this.el_, 'mousemove', this.handleMouseMove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the `Component`'s DOM element
|
||||
*
|
||||
@@ -25,6 +41,33 @@ 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 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;
|
||||
}
|
||||
|
||||
seekBar.getChild('mouseTimeDisplay').update(seekBarRect, seekBarPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,16 +3,20 @@
|
||||
*/
|
||||
import Slider from '../../slider/slider.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 './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 +33,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +52,7 @@ class SeekBar extends Slider {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +60,41 @@ 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', formatTime(time, duration));
|
||||
|
||||
// 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} Percentage played
|
||||
* @return {number}
|
||||
* 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,12 +108,12 @@ class SeekBar extends Slider {
|
||||
* @listens mousedown
|
||||
*/
|
||||
handleMouseDown(event) {
|
||||
super.handleMouseDown(event);
|
||||
|
||||
this.player_.scrubbing(true);
|
||||
|
||||
this.videoWasPlaying = !this.player_.paused();
|
||||
this.player_.pause();
|
||||
|
||||
super.handleMouseDown(event);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,18 +157,15 @@ 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
@@ -29,15 +29,12 @@ class DescriptionsButton extends TextTrackButton {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,10 +43,6 @@ class TextTrackButton extends TrackButton {
|
||||
|
||||
const tracks = this.player_.textTracks();
|
||||
|
||||
if (!tracks) {
|
||||
return items;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
const track = tracks[i];
|
||||
|
||||
|
||||
@@ -34,15 +34,12 @@ 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);
|
||||
});
|
||||
}
|
||||
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 +47,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() {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Check if volume control is supported and if it isn't hide the
|
||||
* `Component` that was passed using the `vjs-hidden` class.
|
||||
*
|
||||
* @param {Component} self
|
||||
* The component that should be hidden if volume is unsupported
|
||||
*
|
||||
* @param {Player} player
|
||||
* A reference to the player
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const checkVolumeSupport = function(self, player) {
|
||||
// hide volume controls when they're not supported by the current tech
|
||||
if (player.tech_ && !player.tech_.featuresVolumeControl) {
|
||||
self.addClass('vjs-hidden');
|
||||
}
|
||||
|
||||
self.on(player, 'loadstart', function() {
|
||||
if (!player.tech_.featuresVolumeControl) {
|
||||
self.addClass('vjs-hidden');
|
||||
} else {
|
||||
self.removeClass('vjs-hidden');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default checkVolumeSupport;
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
import Slider from '../../slider/slider.js';
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
|
||||
// Required children
|
||||
import './volume-level.js';
|
||||
@@ -26,8 +25,9 @@ class VolumeBar extends Slider {
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'volumechange', this.updateARIAAttributes);
|
||||
player.ready(Fn.bind(this, this.updateARIAAttributes));
|
||||
player.ready(() => this.updateARIAAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,7 +40,8 @@ class VolumeBar extends Slider {
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-volume-bar vjs-slider-bar'
|
||||
}, {
|
||||
'aria-label': 'volume level'
|
||||
'aria-label': 'volume level',
|
||||
'aria-live': 'polite'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* @file volume-control.js
|
||||
*/
|
||||
import Component from '../../component.js';
|
||||
import checkVolumeSupport from './check-volume-support';
|
||||
import {isPlain} from '../../utils/obj';
|
||||
|
||||
// Required children
|
||||
import './volume-bar.js';
|
||||
@@ -22,19 +24,33 @@ class VolumeControl extends Component {
|
||||
* @param {Object} [options={}]
|
||||
* The key/value store of player options.
|
||||
*/
|
||||
constructor(player, options) {
|
||||
constructor(player, options = {}) {
|
||||
options.vertical = options.vertical || false;
|
||||
|
||||
// Pass the vertical option down to the VolumeBar if
|
||||
// the VolumeBar is turned on.
|
||||
if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
|
||||
options.volumeBar = options.volumeBar || {};
|
||||
options.volumeBar.vertical = options.vertical;
|
||||
}
|
||||
|
||||
super(player, options);
|
||||
|
||||
// hide volume controls when they're not supported by the current tech
|
||||
if (player.tech_ && player.tech_.featuresVolumeControl === false) {
|
||||
this.addClass('vjs-hidden');
|
||||
}
|
||||
this.on(player, 'loadstart', function() {
|
||||
if (player.tech_.featuresVolumeControl === false) {
|
||||
this.addClass('vjs-hidden');
|
||||
} else {
|
||||
this.removeClass('vjs-hidden');
|
||||
}
|
||||
// hide this control if volume support is missing
|
||||
checkVolumeSupport(this, player);
|
||||
|
||||
// while the slider is active (the mouse has been pressed down and
|
||||
// is dragging) or in focus we do not want to hide the VolumeBar
|
||||
this.on(this.volumeBar, ['focus', 'slideractive'], () => {
|
||||
this.volumeBar.addClass('vjs-slider-active');
|
||||
this.addClass('vjs-slider-active');
|
||||
this.trigger('slideractive');
|
||||
});
|
||||
|
||||
this.on(this.volumeBar, ['blur', 'sliderinactive'], () => {
|
||||
this.volumeBar.removeClass('vjs-slider-active');
|
||||
this.removeClass('vjs-slider-active');
|
||||
this.trigger('sliderinactive');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,8 +61,14 @@ class VolumeControl extends Component {
|
||||
* The element that was created.
|
||||
*/
|
||||
createEl() {
|
||||
let orientationClass = 'vjs-volume-horizontal';
|
||||
|
||||
if (this.options_.vertical) {
|
||||
orientationClass = 'vjs-volume-vertical';
|
||||
}
|
||||
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-volume-control vjs-control'
|
||||
className: `vjs-volume-control vjs-control ${orientationClass}`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
/**
|
||||
* @file volume-menu-button.js
|
||||
*/
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import Component from '../component.js';
|
||||
import Popup from '../popup/popup.js';
|
||||
import PopupButton from '../popup/popup-button.js';
|
||||
import MuteToggle from './mute-toggle.js';
|
||||
import VolumeBar from './volume-control/volume-bar.js';
|
||||
|
||||
/**
|
||||
* Button for volume popup
|
||||
*
|
||||
* @extends PopupButton
|
||||
*/
|
||||
class VolumeMenuButton extends PopupButton {
|
||||
|
||||
/**
|
||||
* 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 = {}) {
|
||||
// Default to inline
|
||||
if (options.inline === undefined) {
|
||||
options.inline = true;
|
||||
}
|
||||
|
||||
// If the vertical option isn't passed at all, default to true.
|
||||
if (options.vertical === undefined) {
|
||||
// If an inline volumeMenuButton is used, we should default to using
|
||||
// a horizontal slider for obvious reasons.
|
||||
if (options.inline) {
|
||||
options.vertical = false;
|
||||
} else {
|
||||
options.vertical = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The vertical option needs to be set on the volumeBar as well,
|
||||
// since that will need to be passed along to the VolumeBar constructor
|
||||
options.volumeBar = options.volumeBar || {};
|
||||
options.volumeBar.vertical = !!options.vertical;
|
||||
|
||||
super(player, options);
|
||||
|
||||
// Same listeners as MuteToggle
|
||||
this.on(player, 'volumechange', this.volumeUpdate);
|
||||
this.on(player, 'loadstart', this.volumeUpdate);
|
||||
|
||||
// hide mute toggle if the current tech doesn't support volume control
|
||||
function updateVisibility() {
|
||||
if (player.tech_ && player.tech_.featuresVolumeControl === false) {
|
||||
this.addClass('vjs-hidden');
|
||||
} else {
|
||||
this.removeClass('vjs-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
updateVisibility.call(this);
|
||||
this.on(player, 'loadstart', updateVisibility);
|
||||
|
||||
this.on(this.volumeBar, ['slideractive', 'focus'], function() {
|
||||
this.addClass('vjs-slider-active');
|
||||
});
|
||||
|
||||
this.on(this.volumeBar, ['sliderinactive', 'blur'], function() {
|
||||
this.removeClass('vjs-slider-active');
|
||||
});
|
||||
|
||||
this.on(this.volumeBar, ['focus'], function() {
|
||||
this.addClass('vjs-lock-showing');
|
||||
});
|
||||
|
||||
this.on(this.volumeBar, ['blur'], function() {
|
||||
this.removeClass('vjs-lock-showing');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the default DOM `className`.
|
||||
*
|
||||
* @return {string}
|
||||
* The DOM `className` for this object.
|
||||
*/
|
||||
buildCSSClass() {
|
||||
let orientationClass = '';
|
||||
|
||||
if (this.options_.vertical) {
|
||||
orientationClass = 'vjs-volume-menu-button-vertical';
|
||||
} else {
|
||||
orientationClass = 'vjs-volume-menu-button-horizontal';
|
||||
}
|
||||
|
||||
return `vjs-volume-menu-button ${super.buildCSSClass()} ${orientationClass}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the VolumeMenuButton popup
|
||||
*/
|
||||
createPopup() {
|
||||
const popup = new Popup(this.player_, {
|
||||
contentElType: 'div'
|
||||
});
|
||||
|
||||
const vb = new VolumeBar(this.player_, this.options_.volumeBar);
|
||||
|
||||
popup.addChild(vb);
|
||||
|
||||
this.menuContent = popup;
|
||||
this.volumeBar = vb;
|
||||
|
||||
this.attachVolumeBarEvents();
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets called when an `VolumeMenuButton` is "clicked". See
|
||||
* {@link ClickableComponent} for more detailed information on what a click can be.
|
||||
*
|
||||
* @param {EventTarget~Event} [event]
|
||||
* The `keydown`, `tap`, or `click` event that caused this function to be
|
||||
* called.
|
||||
*
|
||||
* @listens tap
|
||||
* @listens click
|
||||
*/
|
||||
handleClick(event) {
|
||||
MuteToggle.prototype.handleClick.call(this);
|
||||
super.handleClick();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add events listeners to the created `VolumeBar`.
|
||||
*/
|
||||
attachVolumeBarEvents() {
|
||||
this.menuContent.on(['mousedown', 'touchdown'], Fn.bind(this, this.handleMouseDown));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the `mousedown` and `touchdown` events on the `VolumeBar`
|
||||
*
|
||||
* @param {EventTarget~Event} [event]
|
||||
* The `mousedown` or `touchdown` event that caused this to run.
|
||||
*
|
||||
* @listens mousedown
|
||||
* @listens touchdown
|
||||
*/
|
||||
handleMouseDown(event) {
|
||||
this.on(['mousemove', 'touchmove'], Fn.bind(this.volumeBar, this.volumeBar.handleMouseMove));
|
||||
this.on(this.el_.ownerDocument, ['mouseup', 'touchend'], this.handleMouseUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the `mouseup` and `touchend` events on the `VolumeBar`
|
||||
*
|
||||
* @param {EventTarget~Event} [event]
|
||||
* The `mouseup` or `touchend` event that caused this to run.
|
||||
*
|
||||
* @listens mouseup
|
||||
* @listens touchend
|
||||
*/
|
||||
handleMouseUp(event) {
|
||||
this.off(['mousemove', 'touchmove'], Fn.bind(this.volumeBar, this.volumeBar.handleMouseMove));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @borrows MuteToggle#update as VolumeMenuButton#volumeUpdate
|
||||
*/
|
||||
VolumeMenuButton.prototype.volumeUpdate = MuteToggle.prototype.update;
|
||||
|
||||
/**
|
||||
* The text that should display over the `VolumeMenuButton`s controls. Added for localization.
|
||||
*
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
VolumeMenuButton.prototype.controlText_ = 'Mute';
|
||||
|
||||
Component.registerComponent('VolumeMenuButton', VolumeMenuButton);
|
||||
export default VolumeMenuButton;
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* @file volume-control.js
|
||||
*/
|
||||
import Component from '../component.js';
|
||||
import checkVolumeSupport from './volume-control/check-volume-support';
|
||||
import {isPlain} from '../utils/obj';
|
||||
|
||||
// Required children
|
||||
import './volume-control/volume-control.js';
|
||||
import './mute-toggle.js';
|
||||
|
||||
/**
|
||||
* A Component to contain the MuteToggle and VolumeControl so that
|
||||
* they can work together.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class VolumePanel 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 = {}) {
|
||||
if (typeof options.inline !== 'undefined') {
|
||||
options.inline = options.inline;
|
||||
} else {
|
||||
options.inline = true;
|
||||
}
|
||||
|
||||
// pass the inline option down to the VolumeControl as vertical if
|
||||
// the VolumeControl is on.
|
||||
if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
|
||||
options.volumeControl = options.volumeControl || {};
|
||||
options.volumeControl.vertical = !options.inline;
|
||||
}
|
||||
|
||||
super(player, options);
|
||||
|
||||
// hide this control if volume support is missing
|
||||
checkVolumeSupport(this, player);
|
||||
|
||||
// while the slider is active (the mouse has been pressed down and
|
||||
// is dragging) or in focus we do not want to hide the VolumeBar
|
||||
this.on(this.volumeControl, ['slideractive'], this.sliderActive_);
|
||||
this.on(this.muteToggle, 'focus', this.sliderActive_);
|
||||
|
||||
this.on(this.volumeControl, ['sliderinactive'], this.sliderInactive_);
|
||||
this.on(this.muteToggle, 'blur', this.sliderInactive_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add vjs-slider-active class to the VolumePanel
|
||||
*
|
||||
* @listens VolumeControl#slideractive
|
||||
* @private
|
||||
*/
|
||||
sliderActive_() {
|
||||
this.addClass('vjs-slider-active');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes vjs-slider-active class to the VolumePanel
|
||||
*
|
||||
* @listens VolumeControl#sliderinactive
|
||||
* @private
|
||||
*/
|
||||
sliderInactive_() {
|
||||
this.removeClass('vjs-slider-active');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the `Component`'s DOM element
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created.
|
||||
*/
|
||||
createEl() {
|
||||
let orientationClass = 'vjs-volume-panel-horizontal';
|
||||
|
||||
if (!this.options_.inline) {
|
||||
orientationClass = 'vjs-volume-panel-vertical';
|
||||
}
|
||||
|
||||
return super.createEl('div', {
|
||||
className: `vjs-volume-panel vjs-control ${orientationClass}`
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default options for the `VolumeControl`
|
||||
*
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
VolumePanel.prototype.options_ = {
|
||||
children: [
|
||||
'muteToggle',
|
||||
'volumeControl'
|
||||
]
|
||||
};
|
||||
|
||||
Component.registerComponent('VolumePanel', VolumePanel);
|
||||
export default VolumePanel;
|
||||
@@ -48,20 +48,6 @@ EventTarget.prototype.allowedEvents_ = {};
|
||||
* Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
|
||||
* function that will get called when an event with a certain name gets triggered.
|
||||
*
|
||||
* ```js
|
||||
* var foo = new EventTarget();
|
||||
* var handleBar = function() {
|
||||
* console.log('bar was triggered');
|
||||
* };
|
||||
*
|
||||
* foo.on('bar', handleBar);
|
||||
*
|
||||
* // This causes any `event listeners` for the `bar` event to get called
|
||||
* // see {@link EventTarget#trigger} for more information
|
||||
* foo.trigger('bar');
|
||||
* // logs 'bar was triggered'
|
||||
* ```
|
||||
*
|
||||
* @param {string|string[]} type
|
||||
* An event name or an array of event names.
|
||||
*
|
||||
@@ -92,26 +78,6 @@ EventTarget.prototype.addEventListener = EventTarget.prototype.on;
|
||||
* This makes it so that the `event listener` will no longer get called when the
|
||||
* named event happens.
|
||||
*
|
||||
* ```js
|
||||
* var foo = new EventTarget();
|
||||
* var handleBar = function() {
|
||||
* console.log('bar was triggered');
|
||||
* };
|
||||
*
|
||||
* // adds an `event listener` for the `bar` event
|
||||
* // see {@link EventTarget#on} for more info
|
||||
* foo.on('bar', handleBar);
|
||||
*
|
||||
* // runs all `event listeners` for the `bar` event
|
||||
* // see {@link EventTarget#trigger} for more info
|
||||
* foo.trigger('bar');
|
||||
* // logs 'bar was triggered'
|
||||
*
|
||||
* foo.off('bar', handleBar);
|
||||
* foo.trigger('bar');
|
||||
* // does nothing
|
||||
* ```
|
||||
*
|
||||
* @param {string|string[]} type
|
||||
* An event name or an array of event names.
|
||||
*
|
||||
@@ -136,39 +102,6 @@ EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
|
||||
* first trigger it will get removed. This is like adding an `event listener`
|
||||
* with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
|
||||
*
|
||||
* Using {@link EventTarget#on} and {@link EventTarget#off} to mimic {@link EventTarget#one}
|
||||
* ```js
|
||||
* var foo = new EventTarget();
|
||||
* var handleBar = function() {
|
||||
* console.log('bar was triggered');
|
||||
* // after the first trigger remove this handler
|
||||
* foo.off('bar', handleBar);
|
||||
* };
|
||||
*
|
||||
* foo.on('bar', handleBar);
|
||||
* foo.trigger('bar');
|
||||
* // logs 'bar was triggered'
|
||||
*
|
||||
* foo.trigger('bar');
|
||||
* // does nothing
|
||||
* ```
|
||||
*
|
||||
* Using {@link EventTarget#one}
|
||||
* ```js
|
||||
* var foo = new EventTarget();
|
||||
* var handleBar = function() {
|
||||
* console.log('bar was triggered');
|
||||
* };
|
||||
*
|
||||
* // removed after the first trigger
|
||||
* foo.one('bar', handleBar);
|
||||
* foo.trigger('bar');
|
||||
* // logs 'bar was triggered'
|
||||
*
|
||||
* foo.trigger('bar');
|
||||
* // does nothing
|
||||
* ```
|
||||
*
|
||||
* @param {string|string[]} type
|
||||
* An event name or an array of event names.
|
||||
*
|
||||
@@ -197,23 +130,6 @@ EventTarget.prototype.one = function(type, fn) {
|
||||
* 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
|
||||
* `onClick` if it exists.
|
||||
*
|
||||
* ```js
|
||||
* var foo = new EventTarget();
|
||||
* var handleBar = function() {
|
||||
* console.log('bar was triggered');
|
||||
* };
|
||||
*
|
||||
* foo.on('bar', handleBar);
|
||||
* foo.trigger('bar');
|
||||
* // logs 'bar was triggered'
|
||||
*
|
||||
* foo.trigger('bar');
|
||||
* // logs 'bar was triggered'
|
||||
*
|
||||
* foo.trigger('foo');
|
||||
* // does nothing
|
||||
* ```
|
||||
*
|
||||
* @param {string|EventTarget~Event|Object} event
|
||||
* The name of the event, an `Event`, or an object with a key of type set to
|
||||
* an event name.
|
||||
|
||||
+23
-23
@@ -1,12 +1,20 @@
|
||||
import log from './utils/log';
|
||||
import {isObject} from './utils/obj';
|
||||
|
||||
/*
|
||||
* @file extend.js
|
||||
*
|
||||
* @module extend
|
||||
*/
|
||||
|
||||
/**
|
||||
* A combination of node inherits and babel's inherits (after transpile).
|
||||
* Both work the same but node adds `super_` to the subClass
|
||||
* and Bable adds the superClass as __proto__. Both seem useful.
|
||||
*
|
||||
* @param {Object} subClass
|
||||
* The class to inherit to
|
||||
*
|
||||
* @param {Object} superClass
|
||||
* The class to inherit from
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const _inherits = function(subClass, superClass) {
|
||||
if (typeof superClass !== 'function' && superClass !== null) {
|
||||
@@ -28,22 +36,18 @@ const _inherits = function(subClass, superClass) {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
/**
|
||||
* Function for subclassing using the same inheritance that
|
||||
* videojs uses internally
|
||||
* ```js
|
||||
* var Button = videojs.getComponent('Button');
|
||||
* ```
|
||||
* ```js
|
||||
* var MyButton = videojs.extend(Button, {
|
||||
* constructor: function(player, options) {
|
||||
* Button.call(this, player, options);
|
||||
* },
|
||||
* onClick: function() {
|
||||
* // doSomething
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param {Object} superClass
|
||||
* The class to inherit from
|
||||
*
|
||||
* @param {Object} [subClassMethods={}]
|
||||
* The class to inherit to
|
||||
*
|
||||
* @return {Object}
|
||||
* The new object with subClassMethods that inherited superClass.
|
||||
*/
|
||||
const extendFn = function(superClass, subClassMethods = {}) {
|
||||
let subClass = function() {
|
||||
@@ -52,11 +56,7 @@ const extendFn = function(superClass, subClassMethods = {}) {
|
||||
|
||||
let methods = {};
|
||||
|
||||
if (isObject(subClassMethods)) {
|
||||
if (typeof subClassMethods.init === 'function') {
|
||||
log.warn('Constructor logic via init() is deprecated; please use constructor() instead.');
|
||||
subClassMethods.constructor = subClassMethods.init;
|
||||
}
|
||||
if (typeof subClassMethods === 'object') {
|
||||
if (subClassMethods.constructor !== Object.prototype.constructor) {
|
||||
subClass = subClassMethods.constructor;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class MenuButton extends ClickableComponent {
|
||||
});
|
||||
|
||||
menu.children_.unshift(title);
|
||||
Dom.insertElFirst(title, menu.contentEl());
|
||||
Dom.prependTo(title, menu.contentEl());
|
||||
}
|
||||
|
||||
this.items = this.createItems();
|
||||
@@ -245,9 +245,6 @@ class MenuButton extends ClickableComponent {
|
||||
|
||||
/**
|
||||
* Disable the `MenuButton`. Don't allow it to be clicked.
|
||||
*
|
||||
* @return {MenuButton}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
disable() {
|
||||
// Unpress, but don't force focus on this button
|
||||
@@ -257,19 +254,15 @@ class MenuButton extends ClickableComponent {
|
||||
|
||||
this.enabled_ = false;
|
||||
|
||||
return super.disable();
|
||||
super.disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the `MenuButton`. Allow it to be clicked.
|
||||
*
|
||||
* @return {MenuButton}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
enable() {
|
||||
this.enabled_ = true;
|
||||
|
||||
return super.enable();
|
||||
super.enable();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
/**
|
||||
* @file mixins/evented.js
|
||||
* @module evented
|
||||
*/
|
||||
import * as Dom from '../utils/dom';
|
||||
import * as Events from '../utils/events';
|
||||
import * as Fn from '../utils/fn';
|
||||
import * as Obj from '../utils/obj';
|
||||
import EventTarget from '../event-target';
|
||||
|
||||
/**
|
||||
* Returns whether or not an object has had the evented mixin applied.
|
||||
*
|
||||
* @param {Object} object
|
||||
* An object to test.
|
||||
*
|
||||
* @return {boolean}
|
||||
* Whether or not the object appears to be evented.
|
||||
*/
|
||||
const isEvented = (object) =>
|
||||
object instanceof EventTarget ||
|
||||
!!object.eventBusEl_ &&
|
||||
['on', 'one', 'off', 'trigger'].every(k => typeof object[k] === 'function');
|
||||
|
||||
/**
|
||||
* Whether a value is a valid event type - non-empty string or array.
|
||||
*
|
||||
* @private
|
||||
* @param {string|Array} type
|
||||
* The type value to test.
|
||||
*
|
||||
* @return {boolean}
|
||||
* Whether or not the type is a valid event type.
|
||||
*/
|
||||
const isValidEventType = (type) =>
|
||||
// The regex here verifies that the `type` contains at least one non-
|
||||
// whitespace character.
|
||||
(typeof type === 'string' && (/\S/).test(type)) ||
|
||||
(Array.isArray(type) && !!type.length);
|
||||
|
||||
/**
|
||||
* Validates a value to determine if it is a valid event target. Throws if not.
|
||||
*
|
||||
* @private
|
||||
* @throws {Error}
|
||||
* If the target does not appear to be a valid event target.
|
||||
*
|
||||
* @param {Object} target
|
||||
* The object to test.
|
||||
*/
|
||||
const validateTarget = (target) => {
|
||||
if (!target.nodeName && !isEvented(target)) {
|
||||
throw new Error('Invalid target; must be a DOM node or evented object.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates a value to determine if it is a valid event target. Throws if not.
|
||||
*
|
||||
* @private
|
||||
* @throws {Error}
|
||||
* If the type does not appear to be a valid event type.
|
||||
*
|
||||
* @param {string|Array} type
|
||||
* The type to test.
|
||||
*/
|
||||
const validateEventType = (type) => {
|
||||
if (!isValidEventType(type)) {
|
||||
throw new Error('Invalid event type; must be a non-empty string or array.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates a value to determine if it is a valid listener. Throws if not.
|
||||
*
|
||||
* @private
|
||||
* @throws {Error}
|
||||
* If the listener is not a function.
|
||||
*
|
||||
* @param {Function} listener
|
||||
* The listener to test.
|
||||
*/
|
||||
const validateListener = (listener) => {
|
||||
if (typeof listener !== 'function') {
|
||||
throw new Error('Invalid listener; must be a function.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes an array of arguments given to `on()` or `one()`, validates them, and
|
||||
* normalizes them into an object.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} self
|
||||
* The evented object on which `on()` or `one()` was called. This
|
||||
* object will be bound as the `this` value for the listener.
|
||||
*
|
||||
* @param {Array} args
|
||||
* An array of arguments passed to `on()` or `one()`.
|
||||
*
|
||||
* @return {Object}
|
||||
* An object containing useful values for `on()` or `one()` calls.
|
||||
*/
|
||||
const normalizeListenArgs = (self, args) => {
|
||||
|
||||
// If the number of arguments is less than 3, the target is always the
|
||||
// evented object itself.
|
||||
const isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
|
||||
let target;
|
||||
let type;
|
||||
let listener;
|
||||
|
||||
if (isTargetingSelf) {
|
||||
target = self.eventBusEl_;
|
||||
|
||||
// Deal with cases where we got 3 arguments, but we are still listening to
|
||||
// the evented object itself.
|
||||
if (args.length >= 3) {
|
||||
args.shift();
|
||||
}
|
||||
|
||||
[type, listener] = args;
|
||||
} else {
|
||||
[target, type, listener] = args;
|
||||
}
|
||||
|
||||
validateTarget(target);
|
||||
validateEventType(type);
|
||||
validateListener(listener);
|
||||
|
||||
listener = Fn.bind(self, listener);
|
||||
|
||||
return {isTargetingSelf, target, type, listener};
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the listener to the event type(s) on the target, normalizing for
|
||||
* the type of target.
|
||||
*
|
||||
* @private
|
||||
* @param {Element|Object} target
|
||||
* A DOM node or evented object.
|
||||
*
|
||||
* @param {string} method
|
||||
* The event binding method to use ("on" or "one").
|
||||
*
|
||||
* @param {string|Array} type
|
||||
* One or more event type(s).
|
||||
*
|
||||
* @param {Function} listener
|
||||
* A listener function.
|
||||
*/
|
||||
const listen = (target, method, type, listener) => {
|
||||
validateTarget(target);
|
||||
|
||||
if (target.nodeName) {
|
||||
Events[method](target, type, listener);
|
||||
} else {
|
||||
target[method](type, listener);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains methods that provide event capabilites to an object which is passed
|
||||
* to {@link module:evented|evented}.
|
||||
*
|
||||
* @mixin EventedMixin
|
||||
*/
|
||||
const EventedMixin = {
|
||||
|
||||
/**
|
||||
* Add a listener to an event (or events) on this object or another evented
|
||||
* object.
|
||||
*
|
||||
* @param {string|Array|Element|Object} targetOrType
|
||||
* If this is a string or array, it represents the event type(s)
|
||||
* that will trigger the listener.
|
||||
*
|
||||
* Another evented object can be passed here instead, which will
|
||||
* cause the listener to listen for events on _that_ object.
|
||||
*
|
||||
* In either case, the listener's `this` value will be bound to
|
||||
* this object.
|
||||
*
|
||||
* @param {string|Array|Function} typeOrListener
|
||||
* If the first argument was a string or array, this should be the
|
||||
* listener function. Otherwise, this is a string or array of event
|
||||
* type(s).
|
||||
*
|
||||
* @param {Function} [listener]
|
||||
* If the first argument was another evented object, this will be
|
||||
* the listener function.
|
||||
*/
|
||||
on(...args) {
|
||||
const {isTargetingSelf, target, type, listener} = normalizeListenArgs(this, args);
|
||||
|
||||
listen(target, 'on', type, listener);
|
||||
|
||||
// If this object is listening to another evented object.
|
||||
if (!isTargetingSelf) {
|
||||
|
||||
// If this object is disposed, remove the listener.
|
||||
const removeListenerOnDispose = () => this.off(target, type, listener);
|
||||
|
||||
// Use the same function ID as the listener so we can remove it later it
|
||||
// using the ID of the original listener.
|
||||
removeListenerOnDispose.guid = listener.guid;
|
||||
|
||||
// Add a listener to the target's dispose event as well. This ensures
|
||||
// that if the target is disposed BEFORE this object, we remove the
|
||||
// removal listener that was just added. Otherwise, we create a memory leak.
|
||||
const removeRemoverOnTargetDispose = () => this.off('dispose', removeListenerOnDispose);
|
||||
|
||||
// Use the same function ID as the listener so we can remove it later
|
||||
// it using the ID of the original listener.
|
||||
removeRemoverOnTargetDispose.guid = listener.guid;
|
||||
|
||||
listen(this, 'on', 'dispose', removeListenerOnDispose);
|
||||
listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a listener to an event (or events) on this object or another evented
|
||||
* object. The listener will only be called once and then removed.
|
||||
*
|
||||
* @param {string|Array|Element|Object} targetOrType
|
||||
* If this is a string or array, it represents the event type(s)
|
||||
* that will trigger the listener.
|
||||
*
|
||||
* Another evented object can be passed here instead, which will
|
||||
* cause the listener to listen for events on _that_ object.
|
||||
*
|
||||
* In either case, the listener's `this` value will be bound to
|
||||
* this object.
|
||||
*
|
||||
* @param {string|Array|Function} typeOrListener
|
||||
* If the first argument was a string or array, this should be the
|
||||
* listener function. Otherwise, this is a string or array of event
|
||||
* type(s).
|
||||
*
|
||||
* @param {Function} [listener]
|
||||
* If the first argument was another evented object, this will be
|
||||
* the listener function.
|
||||
*/
|
||||
one(...args) {
|
||||
const {isTargetingSelf, target, type, listener} = normalizeListenArgs(this, args);
|
||||
|
||||
// Targeting this evented object.
|
||||
if (isTargetingSelf) {
|
||||
listen(target, 'one', type, listener);
|
||||
|
||||
// Targeting another evented object.
|
||||
} else {
|
||||
const wrapper = (...largs) => {
|
||||
this.off(target, type, wrapper);
|
||||
listener.apply(null, largs);
|
||||
};
|
||||
|
||||
// Use the same function ID as the listener so we can remove it later
|
||||
// it using the ID of the original listener.
|
||||
wrapper.guid = listener.guid;
|
||||
listen(target, 'one', type, wrapper);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes listener(s) from event(s) on an evented object.
|
||||
*
|
||||
* @param {string|Array|Element|Object} [targetOrType]
|
||||
* If this is a string or array, it represents the event type(s).
|
||||
*
|
||||
* Another evented object can be passed here instead, in which case
|
||||
* ALL 3 arguments are _required_.
|
||||
*
|
||||
* @param {string|Array|Function} [typeOrListener]
|
||||
* If the first argument was a string or array, this may be the
|
||||
* listener function. Otherwise, this is a string or array of event
|
||||
* type(s).
|
||||
*
|
||||
* @param {Function} [listener]
|
||||
* If the first argument was another evented object, this will be
|
||||
* the listener function; otherwise, _all_ listeners bound to the
|
||||
* event type(s) will be removed.
|
||||
*/
|
||||
off(targetOrType, typeOrListener, listener) {
|
||||
|
||||
// Targeting this evented object.
|
||||
if (!targetOrType || isValidEventType(targetOrType)) {
|
||||
Events.off(this.eventBusEl_, targetOrType, typeOrListener);
|
||||
|
||||
// Targeting another evented object.
|
||||
} else {
|
||||
const target = targetOrType;
|
||||
const type = typeOrListener;
|
||||
|
||||
// Fail fast and in a meaningful way!
|
||||
validateTarget(target);
|
||||
validateEventType(type);
|
||||
validateListener(listener);
|
||||
|
||||
// Ensure there's at least a guid, even if the function hasn't been used
|
||||
listener = Fn.bind(this, listener);
|
||||
|
||||
// Remove the dispose listener on this evented object, which was given
|
||||
// the same guid as the event listener in on().
|
||||
this.off('dispose', listener);
|
||||
|
||||
if (target.nodeName) {
|
||||
Events.off(target, type, listener);
|
||||
Events.off(target, 'dispose', listener);
|
||||
} else if (isEvented(target)) {
|
||||
target.off(type, listener);
|
||||
target.off('dispose', listener);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fire an event on this evented object, causing its listeners to be called.
|
||||
*
|
||||
* @param {string|Object} event
|
||||
* An event type or an object with a type property.
|
||||
*
|
||||
* @param {Object} [hash]
|
||||
* An additional object to pass along to listeners.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* Whether or not the default behavior was prevented.
|
||||
*/
|
||||
trigger(event, hash) {
|
||||
return Events.trigger(this.eventBusEl_, event, hash);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
|
||||
*
|
||||
* @param {Object} target
|
||||
* The object to which to add event methods.
|
||||
*
|
||||
* @param {Object} [options={}]
|
||||
* Options for customizing the mixin behavior.
|
||||
*
|
||||
* @param {String} [options.eventBusKey]
|
||||
* By default, adds a `eventBusEl_` DOM element to the target object,
|
||||
* which is used as an event bus. If the target object already has a
|
||||
* DOM element that should be used, pass its key here.
|
||||
*
|
||||
* @return {Object}
|
||||
* The target object.
|
||||
*/
|
||||
function evented(target, options = {}) {
|
||||
const {eventBusKey} = options;
|
||||
|
||||
// Set or create the eventBusEl_.
|
||||
if (eventBusKey) {
|
||||
if (!target[eventBusKey].nodeName) {
|
||||
throw new Error(`The eventBusKey "${eventBusKey}" does not refer to an element.`);
|
||||
}
|
||||
target.eventBusEl_ = target[eventBusKey];
|
||||
} else {
|
||||
target.eventBusEl_ = Dom.createEl('span', {className: 'vjs-event-bus'});
|
||||
}
|
||||
|
||||
Obj.assign(target, EventedMixin);
|
||||
|
||||
// When any evented object is disposed, it removes all its listeners.
|
||||
target.on('dispose', () => target.off());
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
export default evented;
|
||||
export {isEvented};
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* @file mixins/stateful.js
|
||||
* @module stateful
|
||||
*/
|
||||
import {isEvented} from './evented';
|
||||
import * as Obj from '../utils/obj';
|
||||
|
||||
/**
|
||||
* Contains methods that provide statefulness to an object which is passed
|
||||
* to {@link module:stateful}.
|
||||
*
|
||||
* @mixin StatefulMixin
|
||||
*/
|
||||
const StatefulMixin = {
|
||||
|
||||
/**
|
||||
* A hash containing arbitrary keys and values representing the state of
|
||||
* the object.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
state: {},
|
||||
|
||||
/**
|
||||
* Set the state of an object by mutating its
|
||||
* {@link module:stateful~StatefulMixin.state|state} object in place.
|
||||
*
|
||||
* @fires module:stateful~StatefulMixin#statechanged
|
||||
* @param {Object|Function} stateUpdates
|
||||
* A new set of properties to shallow-merge into the plugin state.
|
||||
* Can be a plain object or a function returning a plain object.
|
||||
*
|
||||
* @returns {Object|undefined}
|
||||
* An object containing changes that occurred. If no changes
|
||||
* occurred, returns `undefined`.
|
||||
*/
|
||||
setState(stateUpdates) {
|
||||
|
||||
// Support providing the `stateUpdates` state as a function.
|
||||
if (typeof stateUpdates === 'function') {
|
||||
stateUpdates = stateUpdates();
|
||||
}
|
||||
|
||||
let changes;
|
||||
|
||||
Obj.each(stateUpdates, (value, key) => {
|
||||
|
||||
// Record the change if the value is different from what's in the
|
||||
// current state.
|
||||
if (this.state[key] !== value) {
|
||||
changes = changes || {};
|
||||
changes[key] = {
|
||||
from: this.state[key],
|
||||
to: value
|
||||
};
|
||||
}
|
||||
|
||||
this.state[key] = value;
|
||||
});
|
||||
|
||||
// Only trigger "statechange" if there were changes AND we have a trigger
|
||||
// function. This allows us to not require that the target object be an
|
||||
// evented object.
|
||||
if (changes && isEvented(this)) {
|
||||
|
||||
/**
|
||||
* An event triggered on an object that is both
|
||||
* {@link module:stateful|stateful} and {@link module:evented|evented}
|
||||
* indicating that its state has changed.
|
||||
*
|
||||
* @event module:stateful~StatefulMixin#statechanged
|
||||
* @type {Object}
|
||||
* @property {Object} changes
|
||||
* A hash containing the properties that were changed and
|
||||
* the values they were changed `from` and `to`.
|
||||
*/
|
||||
this.trigger({
|
||||
changes,
|
||||
type: 'statechanged'
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
|
||||
* object.
|
||||
*
|
||||
* If the target object is {@link module:evented|evented} and has a
|
||||
* `handleStateChanged` method, that method will be automatically bound to the
|
||||
* `statechanged` event on itself.
|
||||
*
|
||||
* @param {Object} target
|
||||
* The object to be made stateful.
|
||||
*
|
||||
* @param {Object} [defaultState]
|
||||
* A default set of properties to populate the newly-stateful object's
|
||||
* `state` property.
|
||||
*
|
||||
* @returns {Object}
|
||||
* Returns the `target`.
|
||||
*/
|
||||
function stateful(target, defaultState) {
|
||||
Obj.assign(target, StatefulMixin);
|
||||
|
||||
// This happens after the mixing-in because we need to replace the `state`
|
||||
// added in that step.
|
||||
target.state = Obj.assign({}, target.state, defaultState);
|
||||
|
||||
// Auto-bind the `handleStateChanged` method of the target object if it exists.
|
||||
if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
|
||||
target.on('statechanged', target.handleStateChanged);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
export default stateful;
|
||||
+49
-68
@@ -153,20 +153,17 @@ class ModalDialog extends Component {
|
||||
*
|
||||
* @fires ModalDialog#beforemodalopen
|
||||
* @fires ModalDialog#modalopen
|
||||
*
|
||||
* @return {ModalDialog}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
open() {
|
||||
if (!this.opened_) {
|
||||
const player = this.player();
|
||||
|
||||
/**
|
||||
* Fired just before a `ModalDialog` is opened.
|
||||
*
|
||||
* @event ModalDialog#beforemodalopen
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
* Fired just before a `ModalDialog` is opened.
|
||||
*
|
||||
* @event ModalDialog#beforemodalopen
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('beforemodalopen');
|
||||
this.opened_ = true;
|
||||
|
||||
@@ -193,15 +190,14 @@ class ModalDialog extends Component {
|
||||
this.el().setAttribute('aria-hidden', 'false');
|
||||
|
||||
/**
|
||||
* Fired just after a `ModalDialog` is opened.
|
||||
*
|
||||
* @event ModalDialog#modalopen
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
* Fired just after a `ModalDialog` is opened.
|
||||
*
|
||||
* @event ModalDialog#modalopen
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('modalopen');
|
||||
this.hasBeenOpened_ = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,48 +222,45 @@ class ModalDialog extends Component {
|
||||
*
|
||||
* @fires ModalDialog#beforemodalclose
|
||||
* @fires ModalDialog#modalclose
|
||||
*
|
||||
* @return {ModalDialog}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
close() {
|
||||
if (this.opened_) {
|
||||
const player = this.player();
|
||||
|
||||
/**
|
||||
* Fired just before a `ModalDialog` is closed.
|
||||
*
|
||||
* @event ModalDialog#beforemodalclose
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('beforemodalclose');
|
||||
this.opened_ = false;
|
||||
|
||||
if (this.wasPlaying_) {
|
||||
player.play();
|
||||
}
|
||||
|
||||
if (this.closeable()) {
|
||||
this.off(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||
}
|
||||
|
||||
player.controls(true);
|
||||
this.hide();
|
||||
this.el().setAttribute('aria-hidden', 'true');
|
||||
|
||||
/**
|
||||
* Fired just after a `ModalDialog` is closed.
|
||||
*
|
||||
* @event ModalDialog#modalclose
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('modalclose');
|
||||
|
||||
if (this.options_.temporary) {
|
||||
this.dispose();
|
||||
}
|
||||
if (!this.opened_) {
|
||||
return;
|
||||
}
|
||||
const player = this.player();
|
||||
|
||||
/**
|
||||
* Fired just before a `ModalDialog` is closed.
|
||||
*
|
||||
* @event ModalDialog#beforemodalclose
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('beforemodalclose');
|
||||
this.opened_ = false;
|
||||
|
||||
if (this.wasPlaying_) {
|
||||
player.play();
|
||||
}
|
||||
|
||||
if (this.closeable()) {
|
||||
this.off(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress));
|
||||
}
|
||||
|
||||
player.controls(true);
|
||||
this.hide();
|
||||
this.el().setAttribute('aria-hidden', 'true');
|
||||
|
||||
/**
|
||||
* Fired just after a `ModalDialog` is closed.
|
||||
*
|
||||
* @event ModalDialog#modalclose
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('modalclose');
|
||||
|
||||
if (this.options_.temporary) {
|
||||
this.dispose();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,12 +303,9 @@ class ModalDialog extends Component {
|
||||
/**
|
||||
* Fill the modal's content element with the modal's "content" option.
|
||||
* The content element will be emptied before this change takes place.
|
||||
*
|
||||
* @return {ModalDialog}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
fill() {
|
||||
return this.fillWith(this.content());
|
||||
this.fillWith(this.content());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -325,11 +315,8 @@ class ModalDialog extends Component {
|
||||
* @fires ModalDialog#beforemodalfill
|
||||
* @fires ModalDialog#modalfill
|
||||
*
|
||||
* @param {Mixed} [content]
|
||||
* The same rules apply to this as apply to the `content` option.
|
||||
*
|
||||
* @return {ModalDialog}
|
||||
* Returns itself; method can be chained.
|
||||
* @param {Mixed} [content]
|
||||
* The same rules apply to this as apply to the `content` option.
|
||||
*/
|
||||
fillWith(content) {
|
||||
const contentEl = this.contentEl();
|
||||
@@ -364,8 +351,6 @@ class ModalDialog extends Component {
|
||||
} else {
|
||||
parentEl.appendChild(contentEl);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,9 +358,6 @@ class ModalDialog extends Component {
|
||||
*
|
||||
* @fires ModalDialog#beforemodalempty
|
||||
* @fires ModalDialog#modalempty
|
||||
*
|
||||
* @return {ModalDialog}
|
||||
* Returns itself; method can be chained.
|
||||
*/
|
||||
empty() {
|
||||
/**
|
||||
@@ -394,7 +376,6 @@ class ModalDialog extends Component {
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('modalempty');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+423
-602
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -0,0 +1,444 @@
|
||||
/**
|
||||
* @file plugin.js
|
||||
*/
|
||||
import evented from './mixins/evented';
|
||||
import stateful from './mixins/stateful';
|
||||
import * as Events from './utils/events';
|
||||
import * as Fn from './utils/fn';
|
||||
import Player from './player';
|
||||
|
||||
/**
|
||||
* The base plugin name.
|
||||
*
|
||||
* @private
|
||||
* @constant
|
||||
* @type {string}
|
||||
*/
|
||||
const BASE_PLUGIN_NAME = 'plugin';
|
||||
|
||||
/**
|
||||
* The key on which a player's active plugins cache is stored.
|
||||
*
|
||||
* @private
|
||||
* @constant
|
||||
* @type {string}
|
||||
*/
|
||||
const PLUGIN_CACHE_KEY = 'activePlugins_';
|
||||
|
||||
/**
|
||||
* Stores registered plugins in a private space.
|
||||
*
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
const pluginStorage = {};
|
||||
|
||||
/**
|
||||
* Reports whether or not a plugin has been registered.
|
||||
*
|
||||
* @private
|
||||
* @param {string} name
|
||||
* The name of a plugin.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* Whether or not the plugin has been registered.
|
||||
*/
|
||||
const pluginExists = (name) => pluginStorage.hasOwnProperty(name);
|
||||
|
||||
/**
|
||||
* Get a single registered plugin by name.
|
||||
*
|
||||
* @private
|
||||
* @param {string} name
|
||||
* The name of a plugin.
|
||||
*
|
||||
* @returns {Function|undefined}
|
||||
* The plugin (or undefined).
|
||||
*/
|
||||
const getPlugin = (name) => pluginExists(name) ? pluginStorage[name] : undefined;
|
||||
|
||||
/**
|
||||
* Marks a plugin as "active" on a player.
|
||||
*
|
||||
* Also, ensures that the player has an object for tracking active plugins.
|
||||
*
|
||||
* @private
|
||||
* @param {Player} player
|
||||
* A Video.js player instance.
|
||||
*
|
||||
* @param {string} name
|
||||
* The name of a plugin.
|
||||
*/
|
||||
const markPluginAsActive = (player, name) => {
|
||||
player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
|
||||
player[PLUGIN_CACHE_KEY][name] = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a basic plugin function and returns a wrapper function which marks
|
||||
* on the player that the plugin has been activated.
|
||||
*
|
||||
* @private
|
||||
* @param {string} name
|
||||
* The name of the plugin.
|
||||
*
|
||||
* @param {Function} plugin
|
||||
* The basic plugin.
|
||||
*
|
||||
* @returns {Function}
|
||||
* A wrapper function for the given plugin.
|
||||
*/
|
||||
const createBasicPlugin = (name, plugin) => function() {
|
||||
const instance = plugin.apply(this, arguments);
|
||||
|
||||
markPluginAsActive(this, name);
|
||||
|
||||
// We trigger the "pluginsetup" event on the player regardless, but we want
|
||||
// the hash to be consistent with the hash provided for advanced plugins.
|
||||
// The only potentially counter-intuitive thing here is the `instance` is the
|
||||
// value returned by the `plugin` function.
|
||||
this.trigger('pluginsetup', {name, plugin, instance});
|
||||
return instance;
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a plugin sub-class and returns a factory function for generating
|
||||
* instances of it.
|
||||
*
|
||||
* This factory function will replace itself with an instance of the requested
|
||||
* sub-class of Plugin.
|
||||
*
|
||||
* @private
|
||||
* @param {string} name
|
||||
* The name of the plugin.
|
||||
*
|
||||
* @param {Plugin} PluginSubClass
|
||||
* The advanced plugin.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
const createPluginFactory = (name, PluginSubClass) => {
|
||||
|
||||
// Add a `name` property to the plugin prototype so that each plugin can
|
||||
// refer to itself by name.
|
||||
PluginSubClass.prototype.name = name;
|
||||
|
||||
return function(...args) {
|
||||
const instance = new PluginSubClass(...[this, ...args]);
|
||||
|
||||
// The plugin is replaced by a function that returns the current instance.
|
||||
this[name] = () => instance;
|
||||
|
||||
return instance;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parent class for all advanced plugins.
|
||||
*
|
||||
* @mixes module:evented~EventedMixin
|
||||
* @mixes module:stateful~StatefulMixin
|
||||
* @fires Player#pluginsetup
|
||||
* @listens Player#dispose
|
||||
* @throws {Error}
|
||||
* If attempting to instantiate the base {@link Plugin} class
|
||||
* directly instead of via a sub-class.
|
||||
*/
|
||||
class Plugin {
|
||||
|
||||
/**
|
||||
* Creates an instance of this class.
|
||||
*
|
||||
* Sub-classes should call `super` to ensure plugins are properly initialized.
|
||||
*
|
||||
* @param {Player} player
|
||||
* A Video.js player instance.
|
||||
*/
|
||||
constructor(player) {
|
||||
this.player = player;
|
||||
|
||||
if (this.constructor === Plugin) {
|
||||
throw new Error('Plugin must be sub-classed; not directly instantiated.');
|
||||
}
|
||||
|
||||
// Make this object evented, but remove the added `trigger` method so we
|
||||
// use the prototype version instead.
|
||||
evented(this);
|
||||
delete this.trigger;
|
||||
|
||||
stateful(this, this.constructor.defaultState);
|
||||
markPluginAsActive(player, this.name);
|
||||
|
||||
// Auto-bind the dispose method so we can use it as a listener and unbind
|
||||
// it later easily.
|
||||
this.dispose = Fn.bind(this, this.dispose);
|
||||
|
||||
// If the player is disposed, dispose the plugin.
|
||||
player.on('dispose', this.dispose);
|
||||
player.trigger('pluginsetup', this.getEventHash());
|
||||
}
|
||||
|
||||
/**
|
||||
* Each event triggered by plugins includes a hash of additional data with
|
||||
* conventional properties.
|
||||
*
|
||||
* This returns that object or mutates an existing hash.
|
||||
*
|
||||
* @param {Object} [hash={}]
|
||||
* An object to be used as event an event hash.
|
||||
*
|
||||
* @returns {Plugin~PluginEventHash}
|
||||
* An event hash object with provided properties mixed-in.
|
||||
*/
|
||||
getEventHash(hash = {}) {
|
||||
hash.name = this.name;
|
||||
hash.plugin = this.constructor;
|
||||
hash.instance = this;
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an event on the plugin object and overrides
|
||||
* {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
|
||||
*
|
||||
* @param {string|Object} event
|
||||
* An event type or an object with a type property.
|
||||
*
|
||||
* @param {Object} [hash={}]
|
||||
* Additional data hash to merge with a
|
||||
* {@link Plugin~PluginEventHash|PluginEventHash}.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* Whether or not default was prevented.
|
||||
*/
|
||||
trigger(event, hash = {}) {
|
||||
return Events.trigger(this.eventBusEl_, event, this.getEventHash(hash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "statechanged" events on the plugin. No-op by default, override by
|
||||
* subclassing.
|
||||
*
|
||||
* @abstract
|
||||
* @param {Event} e
|
||||
* An event object provided by a "statechanged" event.
|
||||
*
|
||||
* @param {Object} e.changes
|
||||
* An object describing changes that occurred with the "statechanged"
|
||||
* event.
|
||||
*/
|
||||
handleStateChanged(e) {}
|
||||
|
||||
/**
|
||||
* Disposes a plugin.
|
||||
*
|
||||
* Subclasses can override this if they want, but for the sake of safety,
|
||||
* it's probably best to subscribe the "dispose" event.
|
||||
*
|
||||
* @fires Plugin#dispose
|
||||
*/
|
||||
dispose() {
|
||||
const {name, player} = this;
|
||||
|
||||
/**
|
||||
* Signals that a advanced plugin is about to be disposed.
|
||||
*
|
||||
* @event Plugin#dispose
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('dispose');
|
||||
this.off();
|
||||
player.off('dispose', this.dispose);
|
||||
|
||||
// Eliminate any possible sources of leaking memory by clearing up
|
||||
// references between the player and the plugin instance and nulling out
|
||||
// the plugin's state and replacing methods with a function that throws.
|
||||
player[PLUGIN_CACHE_KEY][name] = false;
|
||||
this.player = this.state = null;
|
||||
|
||||
// Finally, replace the plugin name on the player with a new factory
|
||||
// function, so that the plugin is ready to be set up again.
|
||||
player[name] = createPluginFactory(name, pluginStorage[name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
|
||||
*
|
||||
* @param {string|Function} plugin
|
||||
* If a string, matches the name of a plugin. If a function, will be
|
||||
* tested directly.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* Whether or not a plugin is a basic plugin.
|
||||
*/
|
||||
static isBasic(plugin) {
|
||||
const p = (typeof plugin === 'string') ? getPlugin(plugin) : plugin;
|
||||
|
||||
return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a Video.js plugin.
|
||||
*
|
||||
* @param {string} name
|
||||
* The name of the plugin to be registered. Must be a string and
|
||||
* must not match an existing plugin or a method on the `Player`
|
||||
* prototype.
|
||||
*
|
||||
* @param {Function} plugin
|
||||
* A sub-class of `Plugin` or a function for basic plugins.
|
||||
*
|
||||
* @returns {Function}
|
||||
* For advanced plugins, a factory function for that plugin. For
|
||||
* basic plugins, a wrapper function that initializes the plugin.
|
||||
*/
|
||||
static registerPlugin(name, plugin) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new Error(`Illegal plugin name, "${name}", must be a string, was ${typeof name}.`);
|
||||
}
|
||||
|
||||
if (pluginExists(name) || Player.prototype.hasOwnProperty(name)) {
|
||||
throw new Error(`Illegal plugin name, "${name}", already exists.`);
|
||||
}
|
||||
|
||||
if (typeof plugin !== 'function') {
|
||||
throw new Error(`Illegal plugin for "${name}", must be a function, was ${typeof plugin}.`);
|
||||
}
|
||||
|
||||
pluginStorage[name] = plugin;
|
||||
|
||||
// Add a player prototype method for all sub-classed plugins (but not for
|
||||
// the base Plugin class).
|
||||
if (name !== BASE_PLUGIN_NAME) {
|
||||
if (Plugin.isBasic(plugin)) {
|
||||
Player.prototype[name] = createBasicPlugin(name, plugin);
|
||||
} else {
|
||||
Player.prototype[name] = createPluginFactory(name, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* De-register a Video.js plugin.
|
||||
*
|
||||
* @param {string} name
|
||||
* The name of the plugin to be deregistered.
|
||||
*/
|
||||
static deregisterPlugin(name) {
|
||||
if (name === BASE_PLUGIN_NAME) {
|
||||
throw new Error('Cannot de-register base plugin.');
|
||||
}
|
||||
if (pluginExists(name)) {
|
||||
delete pluginStorage[name];
|
||||
delete Player.prototype[name];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object containing multiple Video.js plugins.
|
||||
*
|
||||
* @param {Array} [names]
|
||||
* If provided, should be an array of plugin names. Defaults to _all_
|
||||
* plugin names.
|
||||
*
|
||||
* @returns {Object|undefined}
|
||||
* An object containing plugin(s) associated with their name(s) or
|
||||
* `undefined` if no matching plugins exist).
|
||||
*/
|
||||
static getPlugins(names = Object.keys(pluginStorage)) {
|
||||
let result;
|
||||
|
||||
names.forEach(name => {
|
||||
const plugin = getPlugin(name);
|
||||
|
||||
if (plugin) {
|
||||
result = result || {};
|
||||
result[name] = plugin;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a plugin's version, if available
|
||||
*
|
||||
* @param {string} name
|
||||
* The name of a plugin.
|
||||
*
|
||||
* @returns {string}
|
||||
* The plugin's version or an empty string.
|
||||
*/
|
||||
static getPluginVersion(name) {
|
||||
const plugin = getPlugin(name);
|
||||
|
||||
return plugin && plugin.VERSION || '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a plugin by name if it exists.
|
||||
*
|
||||
* @static
|
||||
* @method getPlugin
|
||||
* @memberOf Plugin
|
||||
* @param {string} name
|
||||
* The name of a plugin.
|
||||
*
|
||||
* @returns {Function|undefined}
|
||||
* The plugin (or `undefined`).
|
||||
*/
|
||||
Plugin.getPlugin = getPlugin;
|
||||
|
||||
/**
|
||||
* The name of the base plugin class as it is registered.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
|
||||
|
||||
Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
|
||||
|
||||
/**
|
||||
* Documented in player.js
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
Player.prototype.usingPlugin = function(name) {
|
||||
return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Documented in player.js
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
Player.prototype.hasPlugin = function(name) {
|
||||
return !!pluginExists(name);
|
||||
};
|
||||
|
||||
export default Plugin;
|
||||
|
||||
/**
|
||||
* Signals that a plugin has just been set up on a player.
|
||||
*
|
||||
* @event Player#pluginsetup
|
||||
* @type {Plugin~PluginEventHash}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Plugin~PluginEventHash
|
||||
*
|
||||
* @property {string} instance
|
||||
* For basic plugins, the return value of the plugin function. For
|
||||
* advanced plugins, the plugin instance on which the event is fired.
|
||||
*
|
||||
* @property {string} name
|
||||
* The name of the plugin.
|
||||
*
|
||||
* @property {string} plugin
|
||||
* For basic plugins, the plugin function. For advanced plugins, the
|
||||
* plugin class/constructor.
|
||||
*/
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* @file plugins.js
|
||||
* @module plugins
|
||||
*/
|
||||
import Player from './player.js';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* The method for registering a video.js plugin. {@link videojs:videojs.registerPlugin].
|
||||
*
|
||||
* @param {string} name
|
||||
* The name of the plugin that is being registered
|
||||
*
|
||||
* @param {plugins:PluginFn} init
|
||||
* The function that gets run when a `Player` initializes.
|
||||
*/
|
||||
const plugin = function(name, init) {
|
||||
Player.prototype[name] = init;
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
@@ -89,7 +89,7 @@ class PosterImage extends ClickableComponent {
|
||||
/**
|
||||
* Set the source of the `PosterImage` depending on the display method.
|
||||
*
|
||||
* @param {String} url
|
||||
* @param {string} url
|
||||
* The URL to the source for the `PosterImage`.
|
||||
*/
|
||||
setSrc(url) {
|
||||
@@ -121,7 +121,10 @@ class PosterImage extends ClickableComponent {
|
||||
*/
|
||||
handleClick(event) {
|
||||
// We don't want a click to trigger playback when controls are disabled
|
||||
// but CSS should be hiding the poster to prevent that from happening
|
||||
if (!this.player_.controls()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.player_.paused()) {
|
||||
this.player_.play();
|
||||
} else {
|
||||
|
||||
+17
-5
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Functions for setting up a player without user insteraction based on the data-setup
|
||||
* `attribute` of the video tag.
|
||||
* @file setup.js - Functions for setting up a player without
|
||||
* user interaction based on the data-setup `attribute` of the video tag.
|
||||
*
|
||||
* @file setup.js
|
||||
* @module setup
|
||||
*/
|
||||
import * as Dom from './utils/dom';
|
||||
import * as Events from './utils/events.js';
|
||||
import document from 'global/document';
|
||||
import window from 'global/window';
|
||||
@@ -16,6 +16,12 @@ let videojs;
|
||||
* Set up any tags that have a data-setup `attribute` when the player is started.
|
||||
*/
|
||||
const autoSetup = function() {
|
||||
|
||||
// Protect against breakage in non-browser environments.
|
||||
if (!Dom.isReal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// One day, when we stop supporting IE8, go back to this, but in the meantime...*hack hack hack*
|
||||
// var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
|
||||
// var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
|
||||
@@ -78,16 +84,22 @@ const autoSetup = function() {
|
||||
/**
|
||||
* Wait until the page is loaded before running autoSetup. This will be called in
|
||||
* autoSetup if `hasLoaded` returns false.
|
||||
*
|
||||
* @param {number} wait
|
||||
* How long to wait in ms
|
||||
*
|
||||
* @param {videojs} [vjs]
|
||||
* The videojs library function
|
||||
*/
|
||||
function autoSetupTimeout(wait, vjs) {
|
||||
if (vjs) {
|
||||
videojs = vjs;
|
||||
}
|
||||
|
||||
setTimeout(autoSetup, wait);
|
||||
window.setTimeout(autoSetup, wait);
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete') {
|
||||
if (Dom.isReal() && document.readyState === 'complete') {
|
||||
_windowLoaded = true;
|
||||
} else {
|
||||
/**
|
||||
|
||||
+22
-12
@@ -38,7 +38,10 @@ class Slider extends Component {
|
||||
this.on('click', this.handleClick);
|
||||
|
||||
this.on(player, 'controlsvisible', this.update);
|
||||
this.on(player, this.playerEvent, this.update);
|
||||
|
||||
if (this.playerEvent) {
|
||||
this.on(player, this.playerEvent, this.update);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,16 +159,23 @@ class Slider extends Component {
|
||||
|
||||
/**
|
||||
* Update the progress bar of the `Slider`.
|
||||
*
|
||||
* @returns {number}
|
||||
* The percentage of progress the progress bar represents as a
|
||||
* number from 0 to 1.
|
||||
*/
|
||||
update() {
|
||||
// In VolumeBar init we have a setTimeout for update that pops and update to the end of the
|
||||
// execution stack. The player is destroyed before then update will cause an error
|
||||
|
||||
// In VolumeBar init we have a setTimeout for update that pops and update
|
||||
// to the end of the execution stack. The player is destroyed before then
|
||||
// update will cause an error
|
||||
if (!this.el_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
|
||||
// On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
|
||||
// If scrubbing, we could use a cached value to make the handle keep up
|
||||
// with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
|
||||
// some flash players are slow, so we might want to utilize this later.
|
||||
// var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
|
||||
let progress = this.getPercent();
|
||||
const bar = this.bar;
|
||||
@@ -185,13 +195,16 @@ class Slider extends Component {
|
||||
|
||||
// Convert to a percentage for setting
|
||||
const percentage = (progress * 100).toFixed(2) + '%';
|
||||
const style = bar.el().style;
|
||||
|
||||
// Set the new bar width or height
|
||||
if (this.vertical()) {
|
||||
bar.el().style.height = percentage;
|
||||
style.height = percentage;
|
||||
} else {
|
||||
bar.el().style.width = percentage;
|
||||
style.width = percentage;
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,10 +294,9 @@ class Slider extends Component {
|
||||
* - true if slider is vertical,
|
||||
* - false is horizontal
|
||||
*
|
||||
* @return {boolean|Slider}
|
||||
* @return {boolean}
|
||||
* - true if slider is vertical, and getting
|
||||
* - false is horizontal, and getting
|
||||
* - a reference to this object when setting
|
||||
* - false if the slider is horizontal, and getting
|
||||
*/
|
||||
vertical(bool) {
|
||||
if (bool === undefined) {
|
||||
@@ -298,8 +310,6 @@ class Slider extends Component {
|
||||
} else {
|
||||
this.addClass('vjs-slider-horizontal');
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
/**
|
||||
* @file flash-rtmp.js
|
||||
* @module flash-rtmp
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add RTMP properties to the {@link Flash} Tech.
|
||||
*
|
||||
* @param {Flash} Flash
|
||||
* The flash tech class.
|
||||
*
|
||||
* @mixin FlashRtmpDecorator
|
||||
*/
|
||||
function FlashRtmpDecorator(Flash) {
|
||||
Flash.streamingFormats = {
|
||||
'rtmp/mp4': 'MP4',
|
||||
'rtmp/flv': 'FLV'
|
||||
};
|
||||
|
||||
/**
|
||||
* Join connection and stream with an ampersand.
|
||||
*
|
||||
* @param {string} connection
|
||||
* The connection string.
|
||||
*
|
||||
* @param {string} stream
|
||||
* The stream string.
|
||||
*/
|
||||
Flash.streamFromParts = function(connection, stream) {
|
||||
return connection + '&' + stream;
|
||||
};
|
||||
|
||||
/**
|
||||
* The flash parts object that contains connection and stream info.
|
||||
*
|
||||
* @typedef {Object} Flash~PartsObject
|
||||
*
|
||||
* @property {string} connection
|
||||
* The connection string of a source, defaults to an empty string.
|
||||
*
|
||||
* @property {string} stream
|
||||
* The stream string of the source, defaults to an empty string.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a source url into a stream and connection parts.
|
||||
*
|
||||
* @param {string} src
|
||||
* the source url
|
||||
*
|
||||
* @return {Flash~PartsObject}
|
||||
* The parts object that contains a connection and a stream
|
||||
*/
|
||||
Flash.streamToParts = function(src) {
|
||||
const parts = {
|
||||
connection: '',
|
||||
stream: ''
|
||||
};
|
||||
|
||||
if (!src) {
|
||||
return parts;
|
||||
}
|
||||
|
||||
// Look for the normal URL separator we expect, '&'.
|
||||
// If found, we split the URL into two pieces around the
|
||||
// first '&'.
|
||||
let connEnd = src.search(/&(?!\w+=)/);
|
||||
let streamBegin;
|
||||
|
||||
if (connEnd !== -1) {
|
||||
streamBegin = connEnd + 1;
|
||||
} else {
|
||||
// If there's not a '&', we use the last '/' as the delimiter.
|
||||
connEnd = streamBegin = src.lastIndexOf('/') + 1;
|
||||
if (connEnd === 0) {
|
||||
// really, there's not a '/'?
|
||||
connEnd = streamBegin = src.length;
|
||||
}
|
||||
}
|
||||
|
||||
parts.connection = src.substring(0, connEnd);
|
||||
parts.stream = src.substring(streamBegin, src.length);
|
||||
|
||||
return parts;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the source type is a streaming type.
|
||||
*
|
||||
* @param {string} srcType
|
||||
* The mime type to check.
|
||||
*
|
||||
* @return {boolean}
|
||||
* - True if the source type is a streaming type.
|
||||
* - False if the source type is not a streaming type.
|
||||
*/
|
||||
Flash.isStreamingType = function(srcType) {
|
||||
return srcType in Flash.streamingFormats;
|
||||
};
|
||||
|
||||
// RTMP has four variations, any string starting
|
||||
// with one of these protocols should be valid
|
||||
|
||||
/**
|
||||
* Regular expression used to check if the source is an rtmp source.
|
||||
*
|
||||
* @property
|
||||
* @type {RegExp}
|
||||
*/
|
||||
Flash.RTMP_RE = /^rtmp[set]?:\/\//i;
|
||||
|
||||
/**
|
||||
* Check if the source itself is a streaming type.
|
||||
*
|
||||
* @param {string} src
|
||||
* The url to the source.
|
||||
*
|
||||
* @return {boolean}
|
||||
* - True if the source url indicates that the source is streaming.
|
||||
* - False if the shource url indicates that the source url is not streaming.
|
||||
*/
|
||||
Flash.isStreamingSrc = function(src) {
|
||||
return Flash.RTMP_RE.test(src);
|
||||
};
|
||||
|
||||
/**
|
||||
* A source handler for RTMP urls
|
||||
* @type {Object}
|
||||
*/
|
||||
Flash.rtmpSourceHandler = {};
|
||||
|
||||
/**
|
||||
* Check if Flash can play the given mime type.
|
||||
*
|
||||
* @param {string} type
|
||||
* The mime type to check
|
||||
*
|
||||
* @return {string}
|
||||
* 'maybe', or '' (empty string)
|
||||
*/
|
||||
Flash.rtmpSourceHandler.canPlayType = function(type) {
|
||||
if (Flash.isStreamingType(type)) {
|
||||
return 'maybe';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if Flash can handle the source natively
|
||||
*
|
||||
* @param {Object} source
|
||||
* The source object
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* The options passed to the tech
|
||||
*
|
||||
* @return {string}
|
||||
* 'maybe', or '' (empty string)
|
||||
*/
|
||||
Flash.rtmpSourceHandler.canHandleSource = function(source, options) {
|
||||
const can = Flash.rtmpSourceHandler.canPlayType(source.type);
|
||||
|
||||
if (can) {
|
||||
return can;
|
||||
}
|
||||
|
||||
if (Flash.isStreamingSrc(source.src)) {
|
||||
return 'maybe';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Pass the source to the flash object.
|
||||
*
|
||||
* @param {Object} source
|
||||
* The source object
|
||||
*
|
||||
* @param {Flash} tech
|
||||
* The instance of the Flash tech
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* The options to pass to the source
|
||||
*/
|
||||
Flash.rtmpSourceHandler.handleSource = function(source, tech, options) {
|
||||
const srcParts = Flash.streamToParts(source.src);
|
||||
|
||||
tech.setRtmpConnection(srcParts.connection);
|
||||
tech.setRtmpStream(srcParts.stream);
|
||||
};
|
||||
|
||||
// Register the native source handler
|
||||
Flash.registerSourceHandler(Flash.rtmpSourceHandler);
|
||||
|
||||
return Flash;
|
||||
}
|
||||
|
||||
export default FlashRtmpDecorator;
|
||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+240
-263
@@ -2,10 +2,8 @@
|
||||
* @file html5.js
|
||||
*/
|
||||
import Tech from './tech.js';
|
||||
import Component from '../component';
|
||||
import * as Dom from '../utils/dom.js';
|
||||
import * as Url from '../utils/url.js';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import log from '../utils/log.js';
|
||||
import tsml from 'tsml';
|
||||
import * as browser from '../utils/browser.js';
|
||||
@@ -14,6 +12,7 @@ import window from 'global/window';
|
||||
import {assign} from '../utils/obj';
|
||||
import mergeOptions from '../utils/merge-options.js';
|
||||
import toTitleCase from '../utils/to-title-case.js';
|
||||
import {NORMAL as TRACK_TYPES} from '../tracks/track-types';
|
||||
|
||||
/**
|
||||
* HTML5 Media Controller - Wrapper for HTML5 Media API
|
||||
@@ -68,7 +67,7 @@ class Html5 extends Tech {
|
||||
} else {
|
||||
// store HTMLTrackElement and TextTrack to remote list
|
||||
this.remoteTextTrackEls().addTrackElement_(node);
|
||||
this.remoteTextTracks().addTrack_(node.track);
|
||||
this.remoteTextTracks().addTrack(node.track);
|
||||
if (!crossoriginTracks &&
|
||||
!this.el_.hasAttribute('crossorigin') &&
|
||||
Url.isCrossOrigin(node.src)) {
|
||||
@@ -83,52 +82,10 @@ class Html5 extends Tech {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add text tracks into this list
|
||||
const trackTypes = ['audio', 'video'];
|
||||
|
||||
// ProxyNative Video/Audio Track
|
||||
trackTypes.forEach((type) => {
|
||||
const elTracks = this.el()[`${type}Tracks`];
|
||||
const techTracks = this[`${type}Tracks`]();
|
||||
const capitalType = toTitleCase(type);
|
||||
|
||||
if (!this[`featuresNative${capitalType}Tracks`] ||
|
||||
!elTracks ||
|
||||
!elTracks.addEventListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
this[`handle${capitalType}TrackChange_`] = (e) => {
|
||||
techTracks.trigger({
|
||||
type: 'change',
|
||||
target: techTracks,
|
||||
currentTarget: techTracks,
|
||||
srcElement: techTracks
|
||||
});
|
||||
};
|
||||
|
||||
this[`handle${capitalType}TrackAdd_`] = (e) => techTracks.addTrack(e.track);
|
||||
this[`handle${capitalType}TrackRemove_`] = (e) => techTracks.removeTrack(e.track);
|
||||
|
||||
elTracks.addEventListener('change', this[`handle${capitalType}TrackChange_`]);
|
||||
elTracks.addEventListener('addtrack', this[`handle${capitalType}TrackAdd_`]);
|
||||
elTracks.addEventListener('removetrack', this[`handle${capitalType}TrackRemove_`]);
|
||||
this[`removeOld${capitalType}Tracks_`] = (e) => this.removeOldTracks_(techTracks, elTracks);
|
||||
|
||||
// Remove (native) tracks that are not used anymore
|
||||
this.on('loadstart', this[`removeOld${capitalType}Tracks_`]);
|
||||
});
|
||||
|
||||
if (this.featuresNativeTextTracks) {
|
||||
if (crossoriginTracks) {
|
||||
log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.
|
||||
this.proxyNativeTracks_();
|
||||
if (this.featuresNativeTextTracks && crossoriginTracks) {
|
||||
log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.
|
||||
This may prevent text tracks from loading.`);
|
||||
}
|
||||
|
||||
this.handleTextTrackChange_ = Fn.bind(this, this.handleTextTrackChange);
|
||||
this.handleTextTrackAdd_ = Fn.bind(this, this.handleTextTrackAdd);
|
||||
this.handleTextTrackRemove_ = Fn.bind(this, this.handleTextTrackRemove);
|
||||
this.proxyNativeTextTracks_();
|
||||
}
|
||||
|
||||
// Determine if native controls should be used
|
||||
@@ -151,28 +108,81 @@ class Html5 extends Tech {
|
||||
* Dispose of `HTML5` media element and remove all tracks.
|
||||
*/
|
||||
dispose() {
|
||||
// Un-ProxyNativeTracks
|
||||
['audio', 'video', 'text'].forEach((type) => {
|
||||
const capitalType = toTitleCase(type);
|
||||
const tl = this.el_[`${type}Tracks`];
|
||||
|
||||
if (tl && tl.removeEventListener) {
|
||||
tl.removeEventListener('change', this[`handle${capitalType}TrackChange_`]);
|
||||
tl.removeEventListener('addtrack', this[`handle${capitalType}TrackAdd_`]);
|
||||
tl.removeEventListener('removetrack', this[`handle${capitalType}TrackRemove_`]);
|
||||
}
|
||||
|
||||
// Stop removing old text tracks
|
||||
if (tl) {
|
||||
this.off('loadstart', this[`removeOld${capitalType}Tracks_`]);
|
||||
}
|
||||
});
|
||||
|
||||
Html5.disposeMediaElement(this.el_);
|
||||
// tech will handle clearing of the emulated track list
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy all native track list events to our track lists if the browser we are playing
|
||||
* in supports that type of track list.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
proxyNativeTracks_() {
|
||||
TRACK_TYPES.names.forEach((name) => {
|
||||
const props = TRACK_TYPES[name];
|
||||
const elTracks = this.el()[props.getterName];
|
||||
const techTracks = this[props.getterName]();
|
||||
|
||||
if (!this[`featuresNative${props.capitalName}Tracks`] ||
|
||||
!elTracks ||
|
||||
!elTracks.addEventListener) {
|
||||
return;
|
||||
}
|
||||
const listeners = {
|
||||
change(e) {
|
||||
techTracks.trigger({
|
||||
type: 'change',
|
||||
target: techTracks,
|
||||
currentTarget: techTracks,
|
||||
srcElement: techTracks
|
||||
});
|
||||
},
|
||||
addtrack(e) {
|
||||
techTracks.addTrack(e.track);
|
||||
},
|
||||
removetrack(e) {
|
||||
techTracks.removeTrack(e.track);
|
||||
}
|
||||
};
|
||||
const removeOldTracks = function() {
|
||||
const removeTracks = [];
|
||||
|
||||
for (let i = 0; i < techTracks.length; i++) {
|
||||
let found = false;
|
||||
|
||||
for (let j = 0; j < elTracks.length; j++) {
|
||||
if (elTracks[j] === techTracks[i]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
removeTracks.push(techTracks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
while (removeTracks.length) {
|
||||
techTracks.removeTrack(removeTracks.shift());
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(listeners).forEach((eventName) => {
|
||||
const listener = listeners[eventName];
|
||||
|
||||
elTracks.addEventListener(eventName, listener);
|
||||
this.on('dispose', (e) => elTracks.removeEventListener(eventName, listener));
|
||||
});
|
||||
|
||||
// Remove (native) tracks that are not used anymore
|
||||
this.on('loadstart', removeOldTracks);
|
||||
this.on('dispose', (e) => this.off('loadstart', removeOldTracks));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the `Html5` Tech's DOM element.
|
||||
*
|
||||
@@ -185,27 +195,33 @@ class Html5 extends Tech {
|
||||
// Check if this browser supports moving the element into the box.
|
||||
// On the iPhone video will break if you move the element,
|
||||
// So we have to create a brand new element.
|
||||
if (!el || this.movingMediaElementInDOM === false) {
|
||||
// If we ingested the player div, we do not need to move the media element.
|
||||
if (!el ||
|
||||
!(this.options_.playerElIngest ||
|
||||
this.movingMediaElementInDOM)) {
|
||||
|
||||
// If the original tag is still there, clone and remove it.
|
||||
if (el) {
|
||||
const clone = el.cloneNode(true);
|
||||
|
||||
el.parentNode.insertBefore(clone, el);
|
||||
if (el.parentNode) {
|
||||
el.parentNode.insertBefore(clone, el);
|
||||
}
|
||||
Html5.disposeMediaElement(el);
|
||||
el = clone;
|
||||
|
||||
} else {
|
||||
el = document.createElement('video');
|
||||
|
||||
// determine if native controls should be used
|
||||
const tagAttributes = this.options_.tag && Dom.getElAttributes(this.options_.tag);
|
||||
const tagAttributes = this.options_.tag && Dom.getAttributes(this.options_.tag);
|
||||
const attributes = mergeOptions({}, tagAttributes);
|
||||
|
||||
if (!browser.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
|
||||
delete attributes.controls;
|
||||
}
|
||||
|
||||
Dom.setElAttributes(el,
|
||||
Dom.setAttributes(el,
|
||||
assign(attributes, {
|
||||
id: this.options_.techId,
|
||||
class: 'vjs-tech'
|
||||
@@ -226,7 +242,7 @@ class Html5 extends Tech {
|
||||
if (typeof this.options_[attr] !== 'undefined') {
|
||||
overwriteAttrs[attr] = this.options_[attr];
|
||||
}
|
||||
Dom.setElAttributes(el, overwriteAttrs);
|
||||
Dom.setAttributes(el, overwriteAttrs);
|
||||
}
|
||||
|
||||
return el;
|
||||
@@ -325,131 +341,6 @@ class Html5 extends Tech {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listeners to native text track events. This adds the native text tracks
|
||||
* to our emulated {@link TextTrackList}.
|
||||
*/
|
||||
proxyNativeTextTracks_() {
|
||||
const tt = this.el().textTracks;
|
||||
|
||||
if (tt) {
|
||||
// Add tracks - if player is initialised after DOM loaded, textTracks
|
||||
// will not trigger addtrack
|
||||
for (let i = 0; i < tt.length; i++) {
|
||||
this.textTracks().addTrack_(tt[i]);
|
||||
}
|
||||
|
||||
if (tt.addEventListener) {
|
||||
tt.addEventListener('change', this.handleTextTrackChange_);
|
||||
tt.addEventListener('addtrack', this.handleTextTrackAdd_);
|
||||
tt.addEventListener('removetrack', this.handleTextTrackRemove_);
|
||||
}
|
||||
|
||||
// Remove (native) texttracks that are not used anymore
|
||||
this.on('loadstart', this.removeOldTextTracks_);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle any {@link TextTrackList} `change` event.
|
||||
*
|
||||
* @param {EventTarget~Event} e
|
||||
* The `change` event that caused this to run.
|
||||
*
|
||||
* @listens TextTrackList#change
|
||||
*/
|
||||
handleTextTrackChange(e) {
|
||||
const tt = this.textTracks();
|
||||
|
||||
this.textTracks().trigger({
|
||||
type: 'change',
|
||||
target: tt,
|
||||
currentTarget: tt,
|
||||
srcElement: tt
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle any {@link TextTrackList} `addtrack` event.
|
||||
*
|
||||
* @param {EventTarget~Event} e
|
||||
* The `addtrack` event that caused this to run.
|
||||
*
|
||||
* @listens TextTrackList#addtrack
|
||||
*/
|
||||
handleTextTrackAdd(e) {
|
||||
this.textTracks().addTrack_(e.track);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle any {@link TextTrackList} `removetrack` event.
|
||||
*
|
||||
* @param {EventTarget~Event} e
|
||||
* The `removetrack` event that caused this to run.
|
||||
*
|
||||
* @listens TextTrackList#removetrack
|
||||
*/
|
||||
handleTextTrackRemove(e) {
|
||||
this.textTracks().removeTrack_(e.track);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function removes any {@link AudioTrack}s, {@link VideoTrack}s, or
|
||||
* {@link TextTrack}s that are not in the media elements TrackList.
|
||||
*
|
||||
* @param {TrackList} techTracks
|
||||
* HTML5 Tech's TrackList to search through
|
||||
*
|
||||
* @param {TrackList} elTracks
|
||||
* HTML5 media elements TrackList to search trough.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
removeOldTracks_(techTracks, elTracks) {
|
||||
// This will loop over the techTracks and check if they are still used by the HTML5 media element
|
||||
// If not, they will be removed from the emulated list
|
||||
const removeTracks = [];
|
||||
|
||||
if (!elTracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < techTracks.length; i++) {
|
||||
const techTrack = techTracks[i];
|
||||
let found = false;
|
||||
|
||||
for (let j = 0; j < elTracks.length; j++) {
|
||||
if (elTracks[j] === techTrack) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
removeTracks.push(techTrack);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < removeTracks.length; i++) {
|
||||
const track = removeTracks[i];
|
||||
|
||||
techTracks.removeTrack_(track);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove {@link TextTrack}s that dont exist in the native track list from our
|
||||
* emulated {@link TextTrackList}.
|
||||
*
|
||||
* @listens Tech#loadstart
|
||||
*/
|
||||
removeOldTextTracks_(e) {
|
||||
const techTracks = this.textTracks();
|
||||
const elTracks = this.el().textTracks;
|
||||
|
||||
this.removeOldTracks_(techTracks, elTracks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link Player#play} to play using the `Html5` `Tech`.
|
||||
*/
|
||||
@@ -792,20 +683,23 @@ class Html5 extends Tech {
|
||||
|
||||
/* HTML5 Support Testing ---------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Element for testing browser HTML5 media capabilities
|
||||
*
|
||||
* @type {Element}
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
Html5.TEST_VID = document.createElement('video');
|
||||
const track = document.createElement('track');
|
||||
if (Dom.isReal()) {
|
||||
|
||||
track.kind = 'captions';
|
||||
track.srclang = 'en';
|
||||
track.label = 'English';
|
||||
Html5.TEST_VID.appendChild(track);
|
||||
/**
|
||||
* Element for testing browser HTML5 media capabilities
|
||||
*
|
||||
* @type {Element}
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
Html5.TEST_VID = document.createElement('video');
|
||||
const track = document.createElement('track');
|
||||
|
||||
track.kind = 'captions';
|
||||
track.srclang = 'en';
|
||||
track.label = 'English';
|
||||
Html5.TEST_VID.appendChild(track);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if HTML5 media is supported by this browser/device.
|
||||
@@ -822,7 +716,30 @@ Html5.isSupported = function() {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!Html5.TEST_VID.canPlayType;
|
||||
return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the tech can support the given type
|
||||
*
|
||||
* @param {string} type
|
||||
* The mimetype to check
|
||||
* @return {string} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Html5.canPlayType = function(type) {
|
||||
return Html5.TEST_VID.canPlayType(type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the tech can support the given source
|
||||
* @param {Object} srcObj
|
||||
* The source object
|
||||
* @param {Object} options
|
||||
* The options passed to the tech
|
||||
* @return {string} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Html5.canPlaySource = function(srcObj, options) {
|
||||
return Html5.canPlayType(srcObj.type);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -889,9 +806,7 @@ Html5.supportsNativeTextTracks = function() {
|
||||
* - False otherwise
|
||||
*/
|
||||
Html5.supportsNativeVideoTracks = function() {
|
||||
const supportsVideoTracks = !!Html5.TEST_VID.videoTracks;
|
||||
|
||||
return supportsVideoTracks;
|
||||
return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -902,9 +817,7 @@ Html5.supportsNativeVideoTracks = function() {
|
||||
* - False otherwise
|
||||
*/
|
||||
Html5.supportsNativeAudioTracks = function() {
|
||||
const supportsAudioTracks = !!Html5.TEST_VID.audioTracks;
|
||||
|
||||
return supportsAudioTracks;
|
||||
return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1020,31 +933,23 @@ Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
|
||||
Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks();
|
||||
|
||||
// HTML5 Feature detection and Device Fixes --------------------------------- //
|
||||
let canPlayType;
|
||||
const canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
|
||||
const mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
|
||||
const mp4RE = /^video\/mp4/i;
|
||||
|
||||
Html5.patchCanPlayType = function() {
|
||||
|
||||
// Android 4.0 and above can play HLS to some extent but it reports being unable to do so
|
||||
if (browser.ANDROID_VERSION >= 4.0 && !browser.IS_FIREFOX) {
|
||||
if (!canPlayType) {
|
||||
canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType;
|
||||
}
|
||||
|
||||
Html5.TEST_VID.constructor.prototype.canPlayType = function(type) {
|
||||
if (type && mpegurlRE.test(type)) {
|
||||
return 'maybe';
|
||||
}
|
||||
return canPlayType.call(this, type);
|
||||
};
|
||||
}
|
||||
|
||||
// Override Android 2.2 and less canPlayType method which is broken
|
||||
if (browser.IS_OLD_ANDROID) {
|
||||
if (!canPlayType) {
|
||||
canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType;
|
||||
}
|
||||
|
||||
} else if (browser.IS_OLD_ANDROID) {
|
||||
Html5.TEST_VID.constructor.prototype.canPlayType = function(type) {
|
||||
if (type && mp4RE.test(type)) {
|
||||
return 'maybe';
|
||||
@@ -1058,7 +963,6 @@ Html5.unpatchCanPlayType = function() {
|
||||
const r = Html5.TEST_VID.constructor.prototype.canPlayType;
|
||||
|
||||
Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
|
||||
canPlayType = null;
|
||||
return r;
|
||||
};
|
||||
|
||||
@@ -1132,7 +1036,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* Get the value of `paused` from the media element. `paused` indicates whether the media element
|
||||
* is currently paused or not.
|
||||
*
|
||||
* @method Html5.prototype.paused
|
||||
* @method Html5#paused
|
||||
* @return {boolean}
|
||||
* The value of `paused` from the media element.
|
||||
*
|
||||
@@ -1144,7 +1048,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* Get the value of `currentTime` from the media element. `currentTime` indicates
|
||||
* the current second that the media is at in playback.
|
||||
*
|
||||
* @method Html5.prototype.currentTime
|
||||
* @method Html5#currentTime
|
||||
* @return {number}
|
||||
* The value of `currentTime` from the media element.
|
||||
*
|
||||
@@ -1157,7 +1061,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* object that represents the parts of the media that are already downloaded and
|
||||
* available for playback.
|
||||
*
|
||||
* @method Html5.prototype.buffered
|
||||
* @method Html5#buffered
|
||||
* @return {TimeRange}
|
||||
* The value of `buffered` from the media element.
|
||||
*
|
||||
@@ -1170,7 +1074,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* the current playback volume of audio for a media. `volume` will be a value from 0
|
||||
* (silent) to 1 (loudest and default).
|
||||
*
|
||||
* @method Html5.prototype.volume
|
||||
* @method Html5#volume
|
||||
* @return {number}
|
||||
* The value of `volume` from the media element. Value will be between 0-1.
|
||||
*
|
||||
@@ -1183,7 +1087,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* that the volume for the media should be set to silent. This does not actually change
|
||||
* the `volume` attribute.
|
||||
*
|
||||
* @method Html5.prototype.muted
|
||||
* @method Html5#muted
|
||||
* @return {boolean}
|
||||
* - True if the value of `volume` should be ignored and the audio set to silent.
|
||||
* - False if the value of `volume` should be used.
|
||||
@@ -1192,11 +1096,26 @@ Html5.resetMediaElement = function(el) {
|
||||
*/
|
||||
'muted',
|
||||
|
||||
/**
|
||||
* Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
|
||||
* that the volume for the media should be set to silent when the video first starts.
|
||||
* This does not actually change the `volume` attribute. After playback has started `muted`
|
||||
* will indicate the current status of the volume and `defaultMuted` will not.
|
||||
*
|
||||
* @method Html5.prototype.defaultMuted
|
||||
* @return {boolean}
|
||||
* - True if the value of `volume` should be ignored and the audio set to silent.
|
||||
* - False if the value of `volume` should be used.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
|
||||
*/
|
||||
'defaultMuted',
|
||||
|
||||
/**
|
||||
* Get the value of `poster` from the media element. `poster` indicates
|
||||
* that the url of an image file that can/will be shown when no media data is available.
|
||||
*
|
||||
* @method Html5.prototype.poster
|
||||
* @method Html5#poster
|
||||
* @return {string}
|
||||
* The value of `poster` from the media element. Value will be a url to an
|
||||
* image.
|
||||
@@ -1215,7 +1134,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* - auto: allow the media and metadata for the media to be downloaded before
|
||||
* interaction
|
||||
*
|
||||
* @method Html5.prototype.preload
|
||||
* @method Html5#preload
|
||||
* @return {string}
|
||||
* The value of `preload` from the media element. Will be 'none', 'metadata',
|
||||
* or 'auto'.
|
||||
@@ -1228,7 +1147,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* Get the value of `autoplay` from the media element. `autoplay` indicates
|
||||
* that the media should start to play as soon as the page is ready.
|
||||
*
|
||||
* @method Html5.prototype.autoplay
|
||||
* @method Html5#autoplay
|
||||
* @return {boolean}
|
||||
* - The value of `autoplay` from the media element.
|
||||
* - True indicates that the media should start as soon as the page loads.
|
||||
@@ -1242,7 +1161,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* Get the value of `controls` from the media element. `controls` indicates
|
||||
* whether the native media controls should be shown or hidden.
|
||||
*
|
||||
* @method Html5.prototype.controls
|
||||
* @method Html5#controls
|
||||
* @return {boolean}
|
||||
* - The value of `controls` from the media element.
|
||||
* - True indicates that native controls should be showing.
|
||||
@@ -1257,7 +1176,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* that the media should return to the start of the media and continue playing once
|
||||
* it reaches the end.
|
||||
*
|
||||
* @method Html5.prototype.loop
|
||||
* @method Html5#loop
|
||||
* @return {boolean}
|
||||
* - The value of `loop` from the media element.
|
||||
* - True indicates that playback should seek back to start once
|
||||
@@ -1274,7 +1193,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* MediaError that may have occured during playback. If error returns null there is no
|
||||
* current error.
|
||||
*
|
||||
* @method Html5.prototype.error
|
||||
* @method Html5#error
|
||||
* @return {MediaError|null}
|
||||
* The value of `error` from the media element. Will be `MediaError` if there
|
||||
* is a current error and null otherwise.
|
||||
@@ -1287,7 +1206,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* Get the value of `seeking` from the media element. `seeking` indicates whether the
|
||||
* media is currently seeking to a new position or not.
|
||||
*
|
||||
* @method Html5.prototype.seeking
|
||||
* @method Html5#seeking
|
||||
* @return {boolean}
|
||||
* - The value of `seeking` from the media element.
|
||||
* - True indicates that the media is currently seeking to a new position.
|
||||
@@ -1301,7 +1220,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* Get the value of `seekable` from the media element. `seekable` returns a
|
||||
* `TimeRange` object indicating ranges of time that can currently be `seeked` to.
|
||||
*
|
||||
* @method Html5.prototype.seekable
|
||||
* @method Html5#seekable
|
||||
* @return {TimeRange}
|
||||
* The value of `seekable` from the media element. A `TimeRange` object
|
||||
* indicating the current ranges of time that can be seeked to.
|
||||
@@ -1314,7 +1233,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* Get the value of `ended` from the media element. `ended` indicates whether
|
||||
* the media has reached the end or not.
|
||||
*
|
||||
* @method Html5.prototype.ended
|
||||
* @method Html5#ended
|
||||
* @return {boolean}
|
||||
* - The value of `ended` from the media element.
|
||||
* - True indicates that the media has ended.
|
||||
@@ -1327,10 +1246,10 @@ Html5.resetMediaElement = function(el) {
|
||||
/**
|
||||
* Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
|
||||
* whether the media should start muted or not. Only changes the default state of the
|
||||
* media. `muted` and `defaultMuted` can have different values. `muted` indicates the
|
||||
* media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
|
||||
* current state.
|
||||
*
|
||||
* @method Html5.prototype.defaultMuted
|
||||
* @method Html5#defaultMuted
|
||||
* @return {boolean}
|
||||
* - The value of `defaultMuted` from the media element.
|
||||
* - True indicates that the media should start muted.
|
||||
@@ -1346,7 +1265,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* - if playbackRate is set to 2, media will play twice as fast.
|
||||
* - if playbackRate is set to 0.5, media will play half as fast.
|
||||
*
|
||||
* @method Html5.prototype.playbackRate
|
||||
* @method Html5#playbackRate
|
||||
* @return {number}
|
||||
* The value of `playbackRate` from the media element. A number indicating
|
||||
* the current playback speed of the media, where 1 is normal speed.
|
||||
@@ -1355,11 +1274,29 @@ Html5.resetMediaElement = function(el) {
|
||||
*/
|
||||
'playbackRate',
|
||||
|
||||
/**
|
||||
* Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
|
||||
* the rate at which the media is currently playing back. This value will not indicate the current
|
||||
* `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
|
||||
*
|
||||
* Examples:
|
||||
* - if defaultPlaybackRate is set to 2, media will play twice as fast.
|
||||
* - if defaultPlaybackRate is set to 0.5, media will play half as fast.
|
||||
*
|
||||
* @method Html5.prototype.defaultPlaybackRate
|
||||
* @return {number}
|
||||
* The value of `defaultPlaybackRate` from the media element. A number indicating
|
||||
* the current playback speed of the media, where 1 is normal speed.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
|
||||
*/
|
||||
'defaultPlaybackRate',
|
||||
|
||||
/**
|
||||
* Get the value of `played` from the media element. `played` returns a `TimeRange`
|
||||
* object representing points in the media timeline that have been played.
|
||||
*
|
||||
* @method Html5.prototype.played
|
||||
* @method Html5#played
|
||||
* @return {TimeRange}
|
||||
* The value of `played` from the media element. A `TimeRange` object indicating
|
||||
* the ranges of time that have been played.
|
||||
@@ -1376,7 +1313,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* - 2: NETWORK_LOADING
|
||||
* - 3: NETWORK_NO_SOURCE
|
||||
*
|
||||
* @method Html5.prototype.networkState
|
||||
* @method Html5#networkState
|
||||
* @return {number}
|
||||
* The value of `networkState` from the media element. This will be a number
|
||||
* from the list in the description.
|
||||
@@ -1395,7 +1332,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* - 3: HAVE_FUTURE_DATA
|
||||
* - 4: HAVE_ENOUGH_DATA
|
||||
*
|
||||
* @method Html5.prototype.readyState
|
||||
* @method Html5#readyState
|
||||
* @return {number}
|
||||
* The value of `readyState` from the media element. This will be a number
|
||||
* from the list in the description.
|
||||
@@ -1408,7 +1345,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* Get the value of `videoWidth` from the video element. `videoWidth` indicates
|
||||
* the current width of the video in css pixels.
|
||||
*
|
||||
* @method Html5.prototype.videoWidth
|
||||
* @method Html5#videoWidth
|
||||
* @return {number}
|
||||
* The value of `videoWidth` from the video element. This will be a number
|
||||
* in css pixels.
|
||||
@@ -1421,7 +1358,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* Get the value of `videoHeight` from the video element. `videoHeigth` indicates
|
||||
* the current height of the video in css pixels.
|
||||
*
|
||||
* @method Html5.prototype.videoHeight
|
||||
* @method Html5#videoHeight
|
||||
* @return {number}
|
||||
* The value of `videoHeight` from the video element. This will be a number
|
||||
* in css pixels.
|
||||
@@ -1443,7 +1380,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
|
||||
* so on.
|
||||
*
|
||||
* @method Html5.prototype.setVolume
|
||||
* @method Html5#setVolume
|
||||
* @param {number} percentAsDecimal
|
||||
* The volume percent as a decimal. Valid range is from 0-1.
|
||||
*
|
||||
@@ -1452,10 +1389,10 @@ Html5.resetMediaElement = function(el) {
|
||||
'volume',
|
||||
|
||||
/**
|
||||
* Set the value of `muted` on the media element. `muted` indicates the current
|
||||
* Set the value of `muted` on the media element. `muted` indicates that the current
|
||||
* audio level should be silent.
|
||||
*
|
||||
* @method Html5.prototype.setMuted
|
||||
* @method Html5#setMuted
|
||||
* @param {boolean} muted
|
||||
* - True if the audio should be set to silent
|
||||
* - False otherwise
|
||||
@@ -1464,11 +1401,24 @@ Html5.resetMediaElement = function(el) {
|
||||
*/
|
||||
'muted',
|
||||
|
||||
/**
|
||||
* Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
|
||||
* audio level should be silent, but will only effect the muted level on intial playback..
|
||||
*
|
||||
* @method Html5.prototype.setDefaultMuted
|
||||
* @param {boolean} defaultMuted
|
||||
* - True if the audio should be set to silent
|
||||
* - False otherwise
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
|
||||
*/
|
||||
'defaultMuted',
|
||||
|
||||
/**
|
||||
* Set the value of `src` on the media element. `src` indicates the current
|
||||
* {@link Tech~SourceObject} for the media.
|
||||
*
|
||||
* @method Html5.prototype.setSrc
|
||||
* @method Html5#setSrc
|
||||
* @param {Tech~SourceObject} src
|
||||
* The source object to set as the current source.
|
||||
*
|
||||
@@ -1480,7 +1430,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* Set the value of `poster` on the media element. `poster` is the url to
|
||||
* an image file that can/will be shown when no media data is available.
|
||||
*
|
||||
* @method Html5.prototype.setPoster
|
||||
* @method Html5#setPoster
|
||||
* @param {string} poster
|
||||
* The url to an image that should be used as the `poster` for the media
|
||||
* element.
|
||||
@@ -1499,7 +1449,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* - auto: allow the media and metadata for the media to be downloaded before
|
||||
* interaction
|
||||
*
|
||||
* @method Html5.prototype.setPreload
|
||||
* @method Html5#setPreload
|
||||
* @param {string} preload
|
||||
* The value of `preload` to set on the media element. Must be 'none', 'metadata',
|
||||
* or 'auto'.
|
||||
@@ -1512,7 +1462,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* Set the value of `autoplay` on the media element. `autoplay` indicates
|
||||
* that the media should start to play as soon as the page is ready.
|
||||
*
|
||||
* @method Html5.prototype.setAutoplay
|
||||
* @method Html5#setAutoplay
|
||||
* @param {boolean} autoplay
|
||||
* - True indicates that the media should start as soon as the page loads.
|
||||
* - False indicates that the media should not start as soon as the page loads.
|
||||
@@ -1526,7 +1476,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* that the media should return to the start of the media and continue playing once
|
||||
* it reaches the end.
|
||||
*
|
||||
* @method Html5.prototype.setLoop
|
||||
* @method Html5#setLoop
|
||||
* @param {boolean} loop
|
||||
* - True indicates that playback should seek back to start once
|
||||
* the end of a media is reached.
|
||||
@@ -1543,14 +1493,33 @@ Html5.resetMediaElement = function(el) {
|
||||
* - if playbackRate is set to 2, media will play twice as fast.
|
||||
* - if playbackRate is set to 0.5, media will play half as fast.
|
||||
*
|
||||
* @method Html5.prototype.setPlaybackRate
|
||||
* @method Html5#setPlaybackRate
|
||||
* @return {number}
|
||||
* The value of `playbackRate` from the media element. A number indicating
|
||||
* the current playback speed of the media, where 1 is normal speed.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
|
||||
*/
|
||||
'playbackRate'
|
||||
'playbackRate',
|
||||
|
||||
/**
|
||||
* Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
|
||||
* the rate at which the media should play back upon initial startup. Changing this value
|
||||
* after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
|
||||
*
|
||||
* Example Values:
|
||||
* - if playbackRate is set to 2, media will play twice as fast.
|
||||
* - if playbackRate is set to 0.5, media will play half as fast.
|
||||
*
|
||||
* @method Html5.prototype.setDefaultPlaybackRate
|
||||
* @return {number}
|
||||
* The value of `defaultPlaybackRate` from the media element. A number indicating
|
||||
* the current playback speed of the media, where 1 is normal speed.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
|
||||
*/
|
||||
'defaultPlaybackRate'
|
||||
|
||||
].forEach(function(prop) {
|
||||
Html5.prototype['set' + toTitleCase(prop)] = function(v) {
|
||||
this.el_[prop] = v;
|
||||
@@ -1563,7 +1532,7 @@ Html5.resetMediaElement = function(el) {
|
||||
* A wrapper around the media elements `pause` function. This will call the `HTML5`
|
||||
* media elements `pause` function.
|
||||
*
|
||||
* @method Html5.prototype.pause
|
||||
* @method Html5#pause
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
|
||||
*/
|
||||
'pause',
|
||||
@@ -1572,10 +1541,19 @@ Html5.resetMediaElement = function(el) {
|
||||
* A wrapper around the media elements `load` function. This will call the `HTML5`s
|
||||
* media element `load` function.
|
||||
*
|
||||
* @method Html5.prototype.load
|
||||
* @method Html5#load
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
|
||||
*/
|
||||
'load'
|
||||
'load',
|
||||
|
||||
/**
|
||||
* A wrapper around the media elements `play` function. This will call the `HTML5`s
|
||||
* media element `play` function.
|
||||
*
|
||||
* @method Html5#play
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
|
||||
*/
|
||||
'play'
|
||||
].forEach(function(prop) {
|
||||
Html5.prototype[prop] = function() {
|
||||
return this.el_[prop]();
|
||||
@@ -1666,6 +1644,5 @@ Html5.nativeSourceHandler.dispose = function() {};
|
||||
// Register the native source handler
|
||||
Html5.registerSourceHandler(Html5.nativeSourceHandler);
|
||||
|
||||
Component.registerComponent('Html5', Html5);
|
||||
Tech.registerTech('Html5', Html5);
|
||||
export default Html5;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { assign } from '../utils/obj.js';
|
||||
|
||||
const middlewares = {};
|
||||
|
||||
export function use(type, middleware) {
|
||||
middlewares[type] = middlewares[type] || [];
|
||||
middlewares[type].push(middleware);
|
||||
}
|
||||
|
||||
export function getMiddleware(type) {
|
||||
if (type) {
|
||||
return middlewares[type];
|
||||
}
|
||||
|
||||
return middlewares;
|
||||
}
|
||||
|
||||
export function setSource(player, src, next) {
|
||||
player.setTimeout(() => setSourceHelper(src, middlewares[src.type], next, player), 1);
|
||||
}
|
||||
|
||||
export function setTech(middleware, tech) {
|
||||
middleware.forEach((mw) => mw.setTech && mw.setTech(tech));
|
||||
}
|
||||
|
||||
export function get(middleware, tech, method) {
|
||||
return middleware.reduceRight(middlewareIterator(method), tech[method]());
|
||||
}
|
||||
|
||||
export function set(middleware, tech, method, arg) {
|
||||
return tech[method](middleware.reduce(middlewareIterator(method), arg));
|
||||
}
|
||||
|
||||
export const allowedGetters = {
|
||||
buffered: 1,
|
||||
currentTime: 1,
|
||||
duration: 1,
|
||||
seekable: 1,
|
||||
played: 1
|
||||
};
|
||||
|
||||
export const allowedSetters = {
|
||||
setCurrentTime: 1
|
||||
};
|
||||
|
||||
function middlewareIterator(method) {
|
||||
return (value, mw) => {
|
||||
if (mw[method]) {
|
||||
return mw[method](value);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
function setSourceHelper(src = {}, middleware = [], next, player, acc = [], lastRun = false) {
|
||||
const [mwFactory, ...mwrest] = middleware;
|
||||
|
||||
// if mwFactory is a string, then we're at a fork in the road
|
||||
if (typeof mwFactory === 'string') {
|
||||
setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
|
||||
|
||||
// if we have an mwFactory, call it with the player to get the mw,
|
||||
// then call the mw's setSource method
|
||||
} else if (mwFactory) {
|
||||
const mw = mwFactory(player);
|
||||
|
||||
mw.setSource(assign({}, src), function(err, _src) {
|
||||
|
||||
// something happened, try the next middleware on the current level
|
||||
// make sure to use the old src
|
||||
if (err) {
|
||||
return setSourceHelper(src, mwrest, next, player, acc, lastRun);
|
||||
}
|
||||
|
||||
// we've succeeded, now we need to go deeper
|
||||
acc.push(mw);
|
||||
|
||||
// if it's the same time, continue does the current chain
|
||||
// otherwise, we want to go down the new chain
|
||||
setSourceHelper(_src,
|
||||
src.type === _src.type ? mwrest : middlewares[_src.type],
|
||||
next,
|
||||
player,
|
||||
acc,
|
||||
lastRun);
|
||||
});
|
||||
} else if (mwrest.length) {
|
||||
setSourceHelper(src, mwrest, next, player, acc, lastRun);
|
||||
} else if (lastRun) {
|
||||
next(src, acc);
|
||||
} else {
|
||||
setSourceHelper(src, middlewares['*'], next, player, acc, true);
|
||||
}
|
||||
}
|
||||
+169
-182
@@ -3,13 +3,7 @@
|
||||
*/
|
||||
|
||||
import Component from '../component';
|
||||
import HTMLTrackElement from '../tracks/html-track-element';
|
||||
import HTMLTrackElementList from '../tracks/html-track-element-list';
|
||||
import mergeOptions from '../utils/merge-options.js';
|
||||
import TextTrack from '../tracks/text-track';
|
||||
import TextTrackList from '../tracks/text-track-list';
|
||||
import VideoTrackList from '../tracks/video-track-list';
|
||||
import AudioTrackList from '../tracks/audio-track-list';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import log from '../utils/log.js';
|
||||
import { createTimeRange } from '../utils/time-ranges.js';
|
||||
@@ -17,18 +11,14 @@ import { bufferedPercent } from '../utils/buffer.js';
|
||||
import MediaError from '../media-error.js';
|
||||
import window from 'global/window';
|
||||
import document from 'global/document';
|
||||
import {isPlain} from '../utils/obj';
|
||||
import * as TRACK_TYPES from '../tracks/track-types';
|
||||
|
||||
/**
|
||||
* An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
|
||||
* that just contains the src url alone.
|
||||
*
|
||||
* ``` js
|
||||
* var SourceObject = {
|
||||
* src: 'http://example.com/some-video.mp4',
|
||||
* type: 'video/mp4'
|
||||
* };
|
||||
* var SourceString = 'http://example.com/some-video.mp4';
|
||||
* ```
|
||||
* * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
|
||||
* `var SourceString = 'http://example.com/some-video.mp4';`
|
||||
*
|
||||
* @typedef {Object|string} Tech~SourceObject
|
||||
*
|
||||
@@ -73,9 +63,9 @@ function createTrackHelper(self, kind, label, language, options = {}) {
|
||||
}
|
||||
options.tech = self;
|
||||
|
||||
const track = new TextTrack(options);
|
||||
const track = new TRACK_TYPES.ALL.text.TrackClass(options);
|
||||
|
||||
tracks.addTrack_(track);
|
||||
tracks.addTrack(track);
|
||||
|
||||
return track;
|
||||
}
|
||||
@@ -113,9 +103,13 @@ class Tech extends Component {
|
||||
this.hasStarted_ = false;
|
||||
});
|
||||
|
||||
this.textTracks_ = options.textTracks;
|
||||
this.videoTracks_ = options.videoTracks;
|
||||
this.audioTracks_ = options.audioTracks;
|
||||
TRACK_TYPES.ALL.names.forEach((name) => {
|
||||
const props = TRACK_TYPES.ALL[name];
|
||||
|
||||
if (options && options[props.getterName]) {
|
||||
this[props.privateName] = options[props.getterName];
|
||||
}
|
||||
});
|
||||
|
||||
// Manually track progress in cases where the browser/flash player doesn't report it.
|
||||
if (!this.featuresProgressEvents) {
|
||||
@@ -141,13 +135,18 @@ class Tech extends Component {
|
||||
this.emulateTextTracks();
|
||||
}
|
||||
|
||||
this.autoRemoteTextTracks_ = new TextTrackList();
|
||||
this.autoRemoteTextTracks_ = new TRACK_TYPES.ALL.text.ListClass();
|
||||
|
||||
this.initTextTrackListeners();
|
||||
this.initTrackListeners();
|
||||
|
||||
// Turn on component tap events
|
||||
this.emitTapEvents();
|
||||
// Turn on component tap events only if not using native controls
|
||||
if (!options.nativeControlsForTouch) {
|
||||
this.emitTapEvents();
|
||||
}
|
||||
|
||||
if (this.constructor) {
|
||||
this.name_ = this.constructor.name || 'Unknown Tech';
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallbacks for unsupported event types
|
||||
@@ -331,7 +330,7 @@ class Tech extends Component {
|
||||
dispose() {
|
||||
|
||||
// clear out all tracks because we can't reuse them between techs
|
||||
this.clearTracks(['audio', 'video', 'text']);
|
||||
this.clearTracks(TRACK_TYPES.NORMAL.names);
|
||||
|
||||
// Turn off any manual progress or timeupdate tracking
|
||||
if (this.manualProgress) {
|
||||
@@ -368,7 +367,7 @@ class Tech extends Component {
|
||||
if (type === 'text') {
|
||||
this.removeRemoteTextTrack(track);
|
||||
}
|
||||
list.removeTrack_(track);
|
||||
list.removeTrack(track);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -449,49 +448,16 @@ class Tech extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on listeners for {@link TextTrackList} events. This adds
|
||||
* {@link EventTarget~EventListeners} for `texttrackchange`, `addtrack` and
|
||||
* `removetrack`.
|
||||
* Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
|
||||
* {@link TextTrackList} events.
|
||||
*
|
||||
* @fires Tech#texttrackchange
|
||||
*/
|
||||
initTextTrackListeners() {
|
||||
const textTrackListChanges = Fn.bind(this, function() {
|
||||
/**
|
||||
* Triggered when tracks are added or removed on the Tech {@link TextTrackList}
|
||||
*
|
||||
* @event Tech#texttrackchange
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('texttrackchange');
|
||||
});
|
||||
|
||||
const tracks = this.textTracks();
|
||||
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
tracks.addEventListener('removetrack', textTrackListChanges);
|
||||
tracks.addEventListener('addtrack', textTrackListChanges);
|
||||
|
||||
this.on('dispose', Fn.bind(this, function() {
|
||||
tracks.removeEventListener('removetrack', textTrackListChanges);
|
||||
tracks.removeEventListener('addtrack', textTrackListChanges);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on listeners for {@link VideoTrackList} and {@link {AudioTrackList} events.
|
||||
* This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`.
|
||||
*
|
||||
* @fires Tech#audiotrackchange
|
||||
* @fires Tech#videotrackchange
|
||||
* @fires Tech#texttrackchange
|
||||
*/
|
||||
initTrackListeners() {
|
||||
const trackTypes = ['video', 'audio'];
|
||||
|
||||
trackTypes.forEach((type) => {
|
||||
/**
|
||||
* Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
|
||||
*
|
||||
@@ -505,11 +471,20 @@ class Tech extends Component {
|
||||
* @event Tech#videotrackchange
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Triggered when tracks are added or removed on the Tech {@link TextTrackList}
|
||||
*
|
||||
* @event Tech#texttrackchange
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
TRACK_TYPES.NORMAL.names.forEach((name) => {
|
||||
const props = TRACK_TYPES.NORMAL[name];
|
||||
const trackListChanges = () => {
|
||||
this.trigger(`${type}trackchange`);
|
||||
this.trigger(`${name}trackchange`);
|
||||
};
|
||||
|
||||
const tracks = this[`${type}Tracks`]();
|
||||
const tracks = this[props.getterName]();
|
||||
|
||||
tracks.addEventListener('removetrack', trackListChanges);
|
||||
tracks.addEventListener('addtrack', trackListChanges);
|
||||
@@ -526,13 +501,27 @@ class Tech extends Component {
|
||||
*
|
||||
* @fires Tech#vttjsloaded
|
||||
* @fires Tech#vttjserror
|
||||
* @fires Tech#texttrackchange
|
||||
*/
|
||||
addWebVttScript_() {
|
||||
if (!window.WebVTT && this.el().parentNode !== null && this.el().parentNode !== undefined) {
|
||||
const vtt = require('videojs-vtt.js');
|
||||
|
||||
// load via require if available and vtt.js script location was not passed in
|
||||
// as an option. novtt builds will turn the above require call into an empty object
|
||||
// which will cause this if check to always fail.
|
||||
if (!this.options_['vtt.js'] && isPlain(vtt) && Object.keys(vtt).length > 0) {
|
||||
Object.keys(vtt).forEach(function(k) {
|
||||
window[k] = vtt[k];
|
||||
});
|
||||
this.trigger('vttjsloaded');
|
||||
return;
|
||||
}
|
||||
|
||||
// load vtt.js via the script location option or the cdn of no location was
|
||||
// passed in
|
||||
const script = document.createElement('script');
|
||||
|
||||
script.src = this.options_['vtt.js'] || '../node_modules/videojs-vtt.js/dist/vtt.js';
|
||||
script.src = this.options_['vtt.js'] || 'https://cdn.rawgit.com/gkatsev/vtt.js/vjs-v0.12.1/dist/vtt.min.js';
|
||||
script.onload = () => {
|
||||
/**
|
||||
* Fired when vtt.js is loaded.
|
||||
@@ -569,18 +558,12 @@ class Tech extends Component {
|
||||
*/
|
||||
emulateTextTracks() {
|
||||
const tracks = this.textTracks();
|
||||
const remoteTracks = this.remoteTextTracks();
|
||||
const handleAddTrack = (e) => tracks.addTrack(e.track);
|
||||
const handleRemoveTrack = (e) => tracks.removeTrack(e.track);
|
||||
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.remoteTextTracks().on('addtrack', (e) => {
|
||||
this.textTracks().addTrack_(e.track);
|
||||
});
|
||||
|
||||
this.remoteTextTracks().on('removetrack', (e) => {
|
||||
this.textTracks().removeTrack_(e.track);
|
||||
});
|
||||
remoteTracks.on('addtrack', handleAddTrack);
|
||||
remoteTracks.on('removetrack', handleRemoveTrack);
|
||||
|
||||
// Initially, Tech.el_ is a child of a dummy-div wait until the Component system
|
||||
// signals that the Tech is ready at which point Tech.el_ is part of the DOM
|
||||
@@ -588,6 +571,7 @@ class Tech extends Component {
|
||||
this.on('ready', this.addWebVttScript_);
|
||||
|
||||
const updateDisplay = () => this.trigger('texttrackchange');
|
||||
|
||||
const textTracksChanges = () => {
|
||||
updateDisplay();
|
||||
|
||||
@@ -605,67 +589,18 @@ class Tech extends Component {
|
||||
tracks.addEventListener('change', textTracksChanges);
|
||||
|
||||
this.on('dispose', function() {
|
||||
remoteTracks.off('addtrack', handleAddTrack);
|
||||
remoteTracks.off('removetrack', handleRemoveTrack);
|
||||
tracks.removeEventListener('change', textTracksChanges);
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
const track = tracks[i];
|
||||
|
||||
track.removeEventListener('cuechange', updateDisplay);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `Tech`s {@link VideoTrackList}.
|
||||
*
|
||||
* @return {VideoTrackList}
|
||||
* The video track list that the Tech is currently using.
|
||||
*/
|
||||
videoTracks() {
|
||||
this.videoTracks_ = this.videoTracks_ || new VideoTrackList();
|
||||
return this.videoTracks_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `Tech`s {@link AudioTrackList}.
|
||||
*
|
||||
* @return {AudioTrackList}
|
||||
* The audio track list that the Tech is currently using.
|
||||
*/
|
||||
audioTracks() {
|
||||
this.audioTracks_ = this.audioTracks_ || new AudioTrackList();
|
||||
return this.audioTracks_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `Tech`s {@link TextTrackList}.
|
||||
*
|
||||
* @return {TextTrackList}
|
||||
* The text track list that the Tech is currently using.
|
||||
*/
|
||||
textTracks() {
|
||||
this.textTracks_ = this.textTracks_ || new TextTrackList();
|
||||
return this.textTracks_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `Tech`s remote {@link TextTrackList}, which is created from elements
|
||||
* that were added to the DOM.
|
||||
*
|
||||
* @return {TextTrackList}
|
||||
* The remote text track list that the Tech is currently using.
|
||||
*/
|
||||
remoteTextTracks() {
|
||||
this.remoteTextTracks_ = this.remoteTextTracks_ || new TextTrackList();
|
||||
return this.remoteTextTracks_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get The `Tech`s {HTMLTrackElementList}, which are the elements in the DOM that are
|
||||
* being used as TextTracks.
|
||||
*
|
||||
* @return {HTMLTrackElementList}
|
||||
* The current HTML track elements that exist for the tech.
|
||||
*/
|
||||
remoteTextTrackEls() {
|
||||
this.remoteTextTrackEls_ = this.remoteTextTrackEls_ || new HTMLTrackElementList();
|
||||
return this.remoteTextTrackEls_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and returns a remote {@link TextTrack} object.
|
||||
*
|
||||
@@ -715,7 +650,7 @@ class Tech extends Component {
|
||||
tech: this
|
||||
});
|
||||
|
||||
return new HTMLTrackElement(track);
|
||||
return new TRACK_TYPES.REMOTE.remoteTextEl.TrackClass(track);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -749,11 +684,11 @@ class Tech extends Component {
|
||||
|
||||
// store HTMLTrackElement and TextTrack to remote list
|
||||
this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
|
||||
this.remoteTextTracks().addTrack_(htmlTrackElement.track);
|
||||
this.remoteTextTracks().addTrack(htmlTrackElement.track);
|
||||
|
||||
if (manualCleanup !== true) {
|
||||
// create the TextTrackList if it doesn't exist
|
||||
this.autoRemoteTextTracks_.addTrack_(htmlTrackElement.track);
|
||||
this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
|
||||
}
|
||||
|
||||
return htmlTrackElement;
|
||||
@@ -770,8 +705,8 @@ class Tech extends Component {
|
||||
|
||||
// remove HTMLTrackElement and TextTrack from remote list
|
||||
this.remoteTextTrackEls().removeTrackElement_(trackElement);
|
||||
this.remoteTextTracks().removeTrack_(track);
|
||||
this.autoRemoteTextTracks_.removeTrack_(track);
|
||||
this.remoteTextTracks().removeTrack(track);
|
||||
this.autoRemoteTextTracks_.removeTrack(track);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -801,6 +736,32 @@ class Tech extends Component {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the type is supported by this tech.
|
||||
*
|
||||
* The base tech does not support any type, but source handlers might
|
||||
* overwrite this.
|
||||
*
|
||||
* @param {string} type
|
||||
* The media type to check
|
||||
* @return {string} Returns the native video element's response
|
||||
*/
|
||||
static canPlayType() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the tech can support the given source
|
||||
* @param {Object} srcObj
|
||||
* The source object
|
||||
* @param {Object} options
|
||||
* The options passed to the tech
|
||||
* @return {string} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
static canPlaySource(srcObj, options) {
|
||||
return Tech.canPlayType(srcObj.type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return whether the argument is a Tech or not.
|
||||
* Can be passed either a Class like `Html5` or a instance like `player.tech_`
|
||||
@@ -837,7 +798,18 @@ class Tech extends Component {
|
||||
throw new Error(`Tech ${name} must be a Tech`);
|
||||
}
|
||||
|
||||
if (!Tech.canPlayType) {
|
||||
throw new Error('Techs must have a static canPlayType method on them');
|
||||
}
|
||||
if (!Tech.canPlaySource) {
|
||||
throw new Error('Techs must have a static canPlaySource method on them');
|
||||
}
|
||||
|
||||
Tech.techs_[name] = tech;
|
||||
if (name !== 'Tech') {
|
||||
// camel case the techName for use in techOrder
|
||||
Tech.defaultTechs_.push(name.charAt(0).toLowerCase() + name.slice(1));
|
||||
}
|
||||
return tech;
|
||||
}
|
||||
|
||||
@@ -863,28 +835,72 @@ class Tech extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* List of associated text tracks.
|
||||
* Get the {@link VideoTrackList}
|
||||
*
|
||||
* @returns {VideoTrackList}
|
||||
* @method Tech.prototype.videoTracks
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the {@link AudioTrackList}
|
||||
*
|
||||
* @returns {AudioTrackList}
|
||||
* @method Tech.prototype.audioTracks
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the {@link TextTrackList}
|
||||
*
|
||||
* @returns {TextTrackList}
|
||||
* @method Tech.prototype.textTracks
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the remote element {@link TextTrackList}
|
||||
*
|
||||
* @returns {TextTrackList}
|
||||
* @method Tech.prototype.remoteTextTracks
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the remote element {@link HTMLTrackElementList}
|
||||
*
|
||||
* @returns {HTMLTrackElementList}
|
||||
* @method Tech.prototype.remoteTextTrackEls
|
||||
*/
|
||||
|
||||
TRACK_TYPES.ALL.names.forEach(function(name) {
|
||||
const props = TRACK_TYPES.ALL[name];
|
||||
|
||||
Tech.prototype[props.getterName] = function() {
|
||||
this[props.privateName] = this[props.privateName] || new props.ListClass();
|
||||
return this[props.privateName];
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* List of associated text tracks
|
||||
*
|
||||
* @type {TextTrackList}
|
||||
* @private
|
||||
* @property Tech#textTracks_
|
||||
*/
|
||||
Tech.prototype.textTracks_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* List of associated audio tracks.
|
||||
*
|
||||
* @type {AudioTrackList}
|
||||
* @private
|
||||
* @property Tech#audioTracks_
|
||||
*/
|
||||
Tech.prototype.audioTracks_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* List of associated video tracks.
|
||||
*
|
||||
* @type {VideoTrackList}
|
||||
* @private
|
||||
* @property Tech#videoTracks_
|
||||
*/
|
||||
Tech.prototype.videoTracks_; // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Boolean indicating wether the `Tech` supports volume control.
|
||||
@@ -948,10 +964,7 @@ Tech.prototype.featuresNativeTextTracks = false;
|
||||
* Source handlers are scripts for handling specific formats.
|
||||
* The source handler pattern is used for adaptive formats (HLS, DASH) that
|
||||
* manually load video data and feed it into a Source Buffer (Media Source Extensions)
|
||||
*
|
||||
* ```js
|
||||
* Tech.withSourceHandlers.call(MyTech);
|
||||
* ```
|
||||
* Example: `Tech.withSourceHandlers.call(MyTech);`
|
||||
*
|
||||
* @param {Tech} _Tech
|
||||
* The tech to add source handler functions to.
|
||||
@@ -1106,9 +1119,6 @@ Tech.withSourceHandlers = function(_Tech) {
|
||||
*
|
||||
* @param {Tech~SourceObject} source
|
||||
* A source object with src and type keys
|
||||
*
|
||||
* @return {Tech}
|
||||
* Returns itself; this method is chainable
|
||||
*/
|
||||
_Tech.prototype.setSource = function(source) {
|
||||
let sh = _Tech.selectSourceHandler(source, this.options_);
|
||||
@@ -1129,38 +1139,10 @@ Tech.withSourceHandlers = function(_Tech) {
|
||||
|
||||
if (sh !== _Tech.nativeSourceHandler) {
|
||||
this.currentSource_ = source;
|
||||
|
||||
// Catch if someone replaced the src without calling setSource.
|
||||
// If they do, set currentSource_ to null and dispose our source handler.
|
||||
this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
|
||||
this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
|
||||
this.one(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
|
||||
}
|
||||
|
||||
this.sourceHandler_ = sh.handleSource(source, this, this.options_);
|
||||
this.on('dispose', this.disposeSourceHandler);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called once for the first loadstart of a video.
|
||||
*
|
||||
* @listens Tech#loadstart
|
||||
*/
|
||||
_Tech.prototype.firstLoadStartListener_ = function() {
|
||||
this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
|
||||
};
|
||||
|
||||
// On successive loadstarts when setSource has not been called again
|
||||
/**
|
||||
* Called after the first loadstart for a video occurs.
|
||||
*
|
||||
* @listens Tech#loadstart
|
||||
*/
|
||||
_Tech.prototype.successiveLoadStartListener_ = function() {
|
||||
this.disposeSourceHandler();
|
||||
this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1181,8 +1163,6 @@ Tech.withSourceHandlers = function(_Tech) {
|
||||
this.cleanupAutoTextTracks();
|
||||
|
||||
if (this.sourceHandler_) {
|
||||
this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
|
||||
this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
|
||||
|
||||
if (this.sourceHandler_.dispose) {
|
||||
this.sourceHandler_.dispose();
|
||||
@@ -1194,9 +1174,16 @@ Tech.withSourceHandlers = function(_Tech) {
|
||||
|
||||
};
|
||||
|
||||
// The base Tech class needs to be registered as a Component. It is the only
|
||||
// Tech that can be registered as a Component.
|
||||
Component.registerComponent('Tech', Tech);
|
||||
// Old name for Tech
|
||||
// @deprecated
|
||||
Component.registerComponent('MediaTechController', Tech);
|
||||
Tech.registerTech('Tech', Tech);
|
||||
|
||||
/**
|
||||
* A list of techs that should be added to techOrder on Players
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Tech.defaultTechs_ = [];
|
||||
|
||||
export default Tech;
|
||||
|
||||
@@ -81,15 +81,14 @@ class AudioTrackList extends TrackList {
|
||||
* @param {AudioTrack} track
|
||||
* The AudioTrack to add to the list
|
||||
*
|
||||
* @fires Track#addtrack
|
||||
* @private
|
||||
* @fires TrackList#addtrack
|
||||
*/
|
||||
addTrack_(track) {
|
||||
addTrack(track) {
|
||||
if (track.enabled) {
|
||||
disableOthers(this, track);
|
||||
}
|
||||
|
||||
super.addTrack_(track);
|
||||
super.addTrack(track);
|
||||
// native tracks don't have this
|
||||
if (!track.addEventListener) {
|
||||
return;
|
||||
@@ -112,31 +111,6 @@ class AudioTrackList extends TrackList {
|
||||
this.trigger('change');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an {@link AudioTrack} to the `AudioTrackList`.
|
||||
*
|
||||
* @param {AudioTrack} track
|
||||
* The AudioTrack to add to the list
|
||||
*
|
||||
* @fires Track#addtrack
|
||||
*/
|
||||
addTrack(track) {
|
||||
this.addTrack_(track);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an {@link AudioTrack} from the `AudioTrackList`.
|
||||
*
|
||||
* @param {AudioTrack} track
|
||||
* The AudioTrack to remove from the list
|
||||
*
|
||||
* @fires Track#removetrack
|
||||
*/
|
||||
removeTrack(track) {
|
||||
super.removeTrack_(track);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AudioTrackList;
|
||||
|
||||
@@ -106,7 +106,7 @@ class TextTrackDisplay extends Component {
|
||||
const tracks = this.options_.playerOptions.tracks || [];
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
this.player_.addRemoteTextTrack(tracks[i]);
|
||||
this.player_.addRemoteTextTrack(tracks[i], true);
|
||||
}
|
||||
|
||||
const modes = {captions: 1, subtitles: 1};
|
||||
@@ -114,28 +114,26 @@ class TextTrackDisplay extends Component {
|
||||
let firstDesc;
|
||||
let firstCaptions;
|
||||
|
||||
if (trackList) {
|
||||
for (let i = 0; i < trackList.length; i++) {
|
||||
const track = trackList[i];
|
||||
for (let i = 0; i < trackList.length; i++) {
|
||||
const track = trackList[i];
|
||||
|
||||
if (track.default) {
|
||||
if (track.kind === 'descriptions' && !firstDesc) {
|
||||
firstDesc = track;
|
||||
} else if (track.kind in modes && !firstCaptions) {
|
||||
firstCaptions = track;
|
||||
}
|
||||
if (track.default) {
|
||||
if (track.kind === 'descriptions' && !firstDesc) {
|
||||
firstDesc = track;
|
||||
} else if (track.kind in modes && !firstCaptions) {
|
||||
firstCaptions = track;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We want to show the first default track but captions and subtitles
|
||||
// take precedence over descriptions.
|
||||
// So, display the first default captions or subtitles track
|
||||
// and otherwise the first default descriptions track.
|
||||
if (firstCaptions) {
|
||||
firstCaptions.mode = 'showing';
|
||||
} else if (firstDesc) {
|
||||
firstDesc.mode = 'showing';
|
||||
}
|
||||
// We want to show the first default track but captions and subtitles
|
||||
// take precedence over descriptions.
|
||||
// So, display the first default captions or subtitles track
|
||||
// and otherwise the first default descriptions track.
|
||||
if (firstCaptions) {
|
||||
firstCaptions.mode = 'showing';
|
||||
} else if (firstDesc) {
|
||||
firstDesc.mode = 'showing';
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -192,17 +190,12 @@ class TextTrackDisplay extends Component {
|
||||
|
||||
this.clearDisplay();
|
||||
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track display prioritization model: if multiple tracks are 'showing',
|
||||
// display the first 'subtitles' or 'captions' track which is 'showing',
|
||||
// otherwise display the first 'descriptions' track which is 'showing'
|
||||
|
||||
let descriptionsTrack = null;
|
||||
let captionsSubtitlesTrack = null;
|
||||
|
||||
let i = tracks.length;
|
||||
|
||||
while (i--) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* Utilities for capturing text track state and re-creating tracks
|
||||
* based on a capture.
|
||||
* @file text-track-list-converter.js Utilities for capturing text track state and
|
||||
* re-creating tracks based on a capture.
|
||||
*
|
||||
* @file text-track-list-converter.js
|
||||
* @module text-track-list-converter
|
||||
*/
|
||||
|
||||
|
||||
@@ -50,10 +50,9 @@ class TextTrackList extends TrackList {
|
||||
* The text track to add to the list.
|
||||
*
|
||||
* @fires TrackList#addtrack
|
||||
* @private
|
||||
*/
|
||||
addTrack_(track) {
|
||||
super.addTrack_(track);
|
||||
addTrack(track) {
|
||||
super.addTrack(track);
|
||||
|
||||
/**
|
||||
* @listens TextTrack#modechange
|
||||
|
||||
@@ -297,7 +297,7 @@ class TextTrackSettings extends Component {
|
||||
return [
|
||||
createEl('label', {
|
||||
className: 'vjs-label',
|
||||
textContent: config.label
|
||||
textContent: this.localize(config.label)
|
||||
}, {
|
||||
for: id
|
||||
}),
|
||||
@@ -442,7 +442,7 @@ class TextTrackSettings extends Component {
|
||||
|
||||
const doneButton = createEl('button', {
|
||||
className: 'vjs-done-button',
|
||||
textContent: 'Done'
|
||||
textContent: this.localize('Done')
|
||||
});
|
||||
|
||||
return createEl('div', {
|
||||
@@ -468,7 +468,7 @@ class TextTrackSettings extends Component {
|
||||
const heading = createEl('div', {
|
||||
className: 'vjs-control-text',
|
||||
id: `TTsettingsDialogLabel-${this.id_}`,
|
||||
textContent: 'Caption Settings Dialog'
|
||||
textContent: this.localize('Caption Settings Dialog')
|
||||
}, {
|
||||
'aria-level': '1',
|
||||
'role': 'heading'
|
||||
@@ -477,7 +477,7 @@ class TextTrackSettings extends Component {
|
||||
const description = createEl('div', {
|
||||
className: 'vjs-control-text',
|
||||
id: `TTsettingsDialogDescription-${this.id_}`,
|
||||
textContent: 'Beginning of dialog window. Escape will cancel and close the window.'
|
||||
textContent: this.localize('Beginning of dialog window. Escape will cancel and close the window.')
|
||||
});
|
||||
|
||||
const doc = createEl('div', undefined, {
|
||||
|
||||
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