Comparar commits
50 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 9289d47860 | |||
| 406c203f17 | |||
| eb5e78bb88 | |||
| 430be94e7f | |||
| 72fcb6c659 | |||
| 252bcee959 | |||
| 6b477bb737 | |||
| 23f0fa05cb | |||
| 2a05633159 | |||
| 3fc9b83958 | |||
| 50f8ad214c | |||
| f05a9271b8 | |||
| 996507744f | |||
| b6d521f472 | |||
| 4859bb9e3d | |||
| 45ffa810fb | |||
| e9e5b5f782 | |||
| 8d51235b30 | |||
| 77357b1cd2 | |||
| 11a096d60f | |||
| 5f42130b82 | |||
| 6889e925b4 | |||
| b2c5b2a412 | |||
| fb74c71ba6 | |||
| 74cddcad73 | |||
| 2e720afb65 | |||
| 8f7eb121bb | |||
| d11fd50a63 | |||
| 726367abc5 | |||
| 9702618c02 | |||
| 685404d018 | |||
| 202da2d468 | |||
| 028559ccb0 | |||
| de1b363470 | |||
| 9d77268f76 | |||
| ed59531f78 | |||
| 2988f6ae53 | |||
| f599ef4c51 | |||
| 254683b5cd | |||
| 9e20386a94 | |||
| fe760a44d7 | |||
| 49e29bac85 | |||
| ac0329f875 | |||
| 7b9574bb19 | |||
| c51f22680f | |||
| 66922a818e | |||
| 16c855931b | |||
| 8845bd36c1 | |||
| e932061024 | |||
| 44ec0e4e75 |
@@ -10,6 +10,7 @@ Please list the specific changes involved in this pull request.
|
||||
## Requirements Checklist
|
||||
- [ ] Feature implemented / Bug fixed
|
||||
- [ ] If necessary, more likely in a feature request than a bug fix
|
||||
- [ ] Change has been verified in an actual browser (Chome, Firefox, IE)
|
||||
- [ ] Unit Tests updated or fixed
|
||||
- [ ] Docs/guides updated
|
||||
- [ ] Example created ([starter template on JSBin](http://jsbin.com/axedog/edit?html,output))
|
||||
|
||||
+107
-1
@@ -1,5 +1,103 @@
|
||||
<a name="5.13.2"></a>
|
||||
## [5.13.2](https://github.com/videojs/video.js/compare/v5.13.1...v5.13.2) (2016-11-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **html5:** exit early on emulated tracks in html5 ([#3772](https://github.com/videojs/video.js/issues/3772)) ([252bcee](https://github.com/videojs/video.js/commit/252bcee))
|
||||
* **HtmlTrackElementList:** allow to reference by index via bracket notation ([#3776](https://github.com/videojs/video.js/issues/3776)) ([430be94](https://github.com/videojs/video.js/commit/430be94))
|
||||
|
||||
### Chores
|
||||
|
||||
* fix CHANGELOG 5.13.1 header ([23f0fa0](https://github.com/videojs/video.js/commit/23f0fa0))
|
||||
* fixup CHANGELOG for 5.13.1 release ([2a05633](https://github.com/videojs/video.js/commit/2a05633))
|
||||
* **package:** update karma-detect-browsers to version 2.2.3 ([#3770](https://github.com/videojs/video.js/issues/3770)) ([6b477bb](https://github.com/videojs/video.js/commit/6b477bb))
|
||||
* **pr_template:** add checkbox to verify changes in a browser ([#3775](https://github.com/videojs/video.js/issues/3775)) ([72fcb6c](https://github.com/videojs/video.js/commit/72fcb6c))
|
||||
|
||||
<a name="5.13.1"></a>
|
||||
# [5.13.1](https://github.com/videojs/video.js/compare/v5.12.6...v5.13.1) (2016-11-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **clickable-component:** Disable interaction with disabled clickable components ([#3525](https://github.com/videojs/video.js/issues/3525)) ([de1b363](https://github.com/videojs/video.js/commit/de1b363))
|
||||
* **component:** attribute get/set/remove methods ([202da2d](https://github.com/videojs/video.js/commit/202da2d))
|
||||
* **fluid:** use default aspect ratio for fluid players if width unknown ([#3614](https://github.com/videojs/video.js/issues/3614)) ([2988f6a](https://github.com/videojs/video.js/commit/2988f6a))
|
||||
* add a safe computedStyle to videojs. ([#3664](https://github.com/videojs/video.js/issues/3664)) ([9702618](https://github.com/videojs/video.js/commit/9702618))
|
||||
* add ability to get current source object and all source objects ([#2678](https://github.com/videojs/video.js/issues/2678)) ([028559c](https://github.com/videojs/video.js/commit/028559c)), closes [#2443](https://github.com/videojs/video.js/issues/2443)
|
||||
* Components are now accessible via `camelCase` and `UpperCamelCase` ([#3439](https://github.com/videojs/video.js/issues/3439)) ([9d77268](https://github.com/videojs/video.js/commit/9d77268)), closes [#3436](https://github.com/videojs/video.js/issues/3436)
|
||||
* **lang:** update ru.json ([#3654](https://github.com/videojs/video.js/issues/3654)) ([d11fd50](https://github.com/videojs/video.js/commit/d11fd50))
|
||||
* **lang:** update uk.json ([#3675](https://github.com/videojs/video.js/issues/3675)) ([8f7eb12](https://github.com/videojs/video.js/commit/8f7eb12))
|
||||
* implement player lifecycle hooks and trigger beforesetup/setup hooks ([#3639](https://github.com/videojs/video.js/issues/3639)) ([77357b1](https://github.com/videojs/video.js/commit/77357b1))
|
||||
* option to have remoteTextTracks automatically 'garbage-collected' when sources change ([#3736](https://github.com/videojs/video.js/issues/3736)) ([f05a927](https://github.com/videojs/video.js/commit/f05a927))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow rounded value for fluid player ratio test ([#3739](https://github.com/videojs/video.js/issues/3739)) ([2e720af](https://github.com/videojs/video.js/commit/2e720af))
|
||||
* aria-live="assertive" only for descriptions ([685404d](https://github.com/videojs/video.js/commit/685404d)), closes [#3554](https://github.com/videojs/video.js/issues/3554)
|
||||
* currentDimension can return 0 for fluid player on IE ([#3738](https://github.com/videojs/video.js/issues/3738)) ([74cddca](https://github.com/videojs/video.js/commit/74cddca))
|
||||
* Suppress Infinity duration on Android Chrome before playback ([#3476](https://github.com/videojs/video.js/issues/3476)) ([ed59531](https://github.com/videojs/video.js/commit/ed59531)), closes [#3079](https://github.com/videojs/video.js/issues/3079)
|
||||
|
||||
### Chores
|
||||
|
||||
* **changelog.md:** update 5.12.6 and 5.12.3 ([#3715](https://github.com/videojs/video.js/issues/3715)) ([254683b](https://github.com/videojs/video.js/commit/254683b))
|
||||
* pin karma-detect-browsers to 2.1.0 ([#3764](https://github.com/videojs/video.js/issues/3764)) ([4859bb9](https://github.com/videojs/video.js/commit/4859bb9))
|
||||
* **package:** update grunt-accessibility to version 5.0.0 ([#3747](https://github.com/videojs/video.js/issues/3747)) ([b6d521f](https://github.com/videojs/video.js/commit/b6d521f))
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **texttracksettings:** DRYer code and remove massive HTML blob ([#3679](https://github.com/videojs/video.js/issues/3679)) ([fb74c71](https://github.com/videojs/video.js/commit/fb74c71))
|
||||
* remove un-needed contructor and function overrides ([#3721](https://github.com/videojs/video.js/issues/3721)) ([6889e92](https://github.com/videojs/video.js/commit/6889e92))
|
||||
|
||||
### Documentation
|
||||
|
||||
* Change registerSourceHandler param doc from first to index ([#3737](https://github.com/videojs/video.js/issues/3737)) ([b2c5b2a](https://github.com/videojs/video.js/commit/b2c5b2a))
|
||||
* **collaborator_guide:** add collaborator guide ([#3724](https://github.com/videojs/video.js/issues/3724)) ([8d51235](https://github.com/videojs/video.js/commit/8d51235))
|
||||
* **contributing.md:** update CONTRIBUTING.md with latest info ([#3722](https://github.com/videojs/video.js/issues/3722)) ([11a096d](https://github.com/videojs/video.js/commit/11a096d))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* Dispatch Flash events asynchronously ([#3700](https://github.com/videojs/video.js/pull/3700))
|
||||
* Cache currentTime and buffered from Flash ([#3705](https://github.com/videojs/video.js/issues/3705)) ([45ffa81](https://github.com/videojs/video.js/commit/45ffa81))
|
||||
* Use ES6 rest operator and allow V8 to optimize mergeOptions ([#3743](https://github.com/videojs/video.js/issues/3743)) ([5f42130](https://github.com/videojs/video.js/commit/5f42130))
|
||||
|
||||
### Tests
|
||||
|
||||
* **dom:** fix removeElClass test in Safari 10. ([#3768](https://github.com/videojs/video.js/issues/3768)) ([9965077](https://github.com/videojs/video.js/commit/9965077))
|
||||
* **hooks:** fix hooks unit test in ie8 ([#3745](https://github.com/videojs/video.js/issues/3745)) ([e9e5b5f](https://github.com/videojs/video.js/commit/e9e5b5f))
|
||||
|
||||
<a name="5.12.6"></a>
|
||||
## [5.12.6](https://github.com/videojs/video.js/compare/v5.12.5...v5.12.6) (2016-10-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make sure that document.createElement exists before using ([#3706](https://github.com/videojs/video.js/issues/3706)) ([49e29ba](https://github.com/videojs/video.js/commit/49e29ba)), closes [#3665](https://github.com/videojs/video.js/issues/3665)
|
||||
* remove unnecessary comments from video.min.js ([#3709](https://github.com/videojs/video.js/issues/3709)) ([fe760a4](https://github.com/videojs/video.js/commit/fe760a4)), closes [#3707](https://github.com/videojs/video.js/issues/3707)
|
||||
|
||||
<a name="5.12.5"></a>
|
||||
## [5.12.5](https://github.com/videojs/video.js/compare/v5.12.4...v5.12.5) (2016-10-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* move html5 source handler incantation to bottom ([#3695](https://github.com/videojs/video.js/issues/3695)) ([7b9574b](https://github.com/videojs/video.js/commit/7b9574b))
|
||||
|
||||
<a name="5.12.4"></a>
|
||||
## [5.12.4](https://github.com/videojs/video.js/compare/v5.12.3...v5.12.4) (2016-10-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* logging failing on browsers that don't always have console ([#3686](https://github.com/videojs/video.js/issues/3686)) ([e932061](https://github.com/videojs/video.js/commit/e932061))
|
||||
* Restore timeupdate/loadedmetadata listeners for duration display ([#3682](https://github.com/videojs/video.js/issues/3682)) ([44ec0e4](https://github.com/videojs/video.js/commit/44ec0e4))
|
||||
|
||||
### Chores
|
||||
|
||||
* **grunt:** fix getting changelog by switching to npm-run ([#3687](https://github.com/videojs/video.js/issues/3687)) ([8845bd3](https://github.com/videojs/video.js/commit/8845bd3)), closes [#3683](https://github.com/videojs/video.js/issues/3683)
|
||||
|
||||
### Documentation
|
||||
|
||||
* **options.md:** Remove Bad Apostrophe ([#3677](https://github.com/videojs/video.js/issues/3677)) ([16c8559](https://github.com/videojs/video.js/commit/16c8559))
|
||||
* **tech.md:** Add a note on Flash permissions in sandboxed environments ([#3684](https://github.com/videojs/video.js/issues/3684)) ([66922a8](https://github.com/videojs/video.js/commit/66922a8))
|
||||
|
||||
<a name="5.12.3"></a>
|
||||
## [5.12.3](https://github.com/videojs/video.js/compare/v5.10.7...v5.12.3) (2016-10-06)
|
||||
## [5.12.3](https://github.com/videojs/video.js/compare/v5.12.2...v5.12.3) (2016-10-06)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -63,6 +161,14 @@
|
||||
* @gkatsev added tests for webpack and browserify bundling and node.js requiring ([view](https://github.com/videojs/video.js/pull/3558))
|
||||
* @rlchung fixed tests that weren't disposing players when they finished ([view](https://github.com/videojs/video.js/pull/3524))
|
||||
|
||||
## 5.11.9 (2016-10-25)
|
||||
* greenkeeper updated karma dependencies ([view](https://github.com/videojs/video.js/pull/3523))
|
||||
* update to latest uglify to fix preserve comments issue. Disable screw ie8 option. ([view](https://github.com/videojs/video.js/pull/3709))
|
||||
* remove sourcemap generation ([view](https://github.com/videojs/video.js/pull/3710))
|
||||
|
||||
## 5.11.8 (2016-10-17)
|
||||
* @misteroneill restore timeupdate/loadedmetadata listeners for duration display ([view](https://github.com/videojs/video.js/pull/3682))
|
||||
|
||||
## 5.11.7 (2016-09-28)
|
||||
* @gkatsev checked throwIfWhitespace first in hasElClass ([view](https://github.com/videojs/video.js/pull/3640))
|
||||
* @misteroneill pinned grunt-contrib-uglify to ~0.11 to pin uglify to ~2.6 ([view](https://github.com/videojs/video.js/pull/3634))
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
# 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**
|
||||
|
||||
- [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
|
||||
|
||||
Full courtesey should always be shown in video.js projects.
|
||||
|
||||
Collaborators may manage issues they feel qualified to handle, being mindful of our guidelines.
|
||||
|
||||
Any issue and PR can be closed if they are not relevant, when in doubt leave it open for more discussion. Issues can always be re-opened if new information is made available.
|
||||
|
||||
If issues or PRs are very short and don't contain much information, ask for more by linking to the [issue][issue template] or [PR][pr template] template. There is also a [response guide](https://github.com/videojs/video.js/wiki/New-Issue-Response-Guide) if you're unsure.
|
||||
|
||||
## Accepting changes
|
||||
|
||||
Any code change in video.js should be happening through Pull Requests on GitHub. This includes core committers.
|
||||
|
||||
Before a PR is merged, it must be reviewed by at least two core committers, at least one if it comes from a core committer.
|
||||
|
||||
Feel free to @-mention a particular core committer if you know they are experts in the area that is being changed.
|
||||
|
||||
If you are unsure about the modification and cannot take responsibility for it, defer to another core committer.
|
||||
|
||||
Before merging the change, it should be left open for other core committers to comment on. At least 24 hours during a weekday, and the 48 hours on a weekend. Trivial changes or bug fixes that have been reviewed by multiple committers may be merged without delay.
|
||||
|
||||
For non-breaking changes, if there is no disagreeming between the collaborators, the PR may be landed assuming it was reviewed. If there is still disagreement, it may need to be [escalated to the TSC](#involving-the-tsc).
|
||||
|
||||
Bug fixes require a test case that fails beforehand and succeeds after. All code changes should contain tests and pass on the CI.
|
||||
|
||||
### Involving the TSC
|
||||
|
||||
A change or issue can be elevated to the TSC by assing the `tsc-agent` label. This should be done in the following scenarios:
|
||||
|
||||
* There will be a major impact on the codebase or project
|
||||
* The change is inherently controversial
|
||||
* No agreement was reached between collaborators participating in the discussion
|
||||
|
||||
The TSC will be the final arbiter when required.
|
||||
|
||||
## Landing a PR
|
||||
|
||||
Landing a PR is fairly easy given that we can use the GitHub UI for it.
|
||||
|
||||
When using the big green button on GitHub, make sure the "squash and merge" is selected -- it should be the only allowed option. If a PR has two features in it and should be merged as two separate commits, either ask the contributor to break it up into two, or follow the [manual steps](#landing-a-pr-manually).
|
||||
|
||||
The commit message should follow our [conventional changelog conventions][conventions]. They are based on the angularjs changelog conventions. The changelog is then generated from these commit messages on release.
|
||||
|
||||
The first line of the commit message -- the header and first text box on GitHub -- should be prefixed with a type and optional scope followed by a short description of the commit.
|
||||
The type is required. Two common ones are `fix` and `feat` for bug fixes and new features. Scope is optional and can be anything.
|
||||
|
||||
The body should contain extra information, potentially copied from the original comment of the PR.
|
||||
|
||||
The footer should contain things like whether this is a breaking change or what issues were fixed by this PR.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```
|
||||
fix(html5): a regression with html5 tech
|
||||
|
||||
This is where you'd explain what the regression is.
|
||||
|
||||
Fixes #123
|
||||
```
|
||||
|
||||
### Landing a PR manually
|
||||
|
||||
_Optional:_ ensure you're not in a weird rebase or merge state:
|
||||
|
||||
```sh
|
||||
$ git am --abort
|
||||
$ git rebase --abort
|
||||
```
|
||||
|
||||
Checkout and update the master branch:
|
||||
|
||||
```sh
|
||||
$ 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}}
|
||||
```
|
||||
|
||||
> For example:
|
||||
> ```sh
|
||||
> $ 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
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Now get the author from the original commit:
|
||||
|
||||
```sh
|
||||
$ git log -n 1 --pretty=short gkatsev-html5-fix
|
||||
```
|
||||
Which shows:
|
||||
```
|
||||
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>"
|
||||
```
|
||||
|
||||
Now that it's committed, push to master
|
||||
|
||||
```sh
|
||||
$ 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
|
||||
```
|
||||
|
||||
This will give you an output like the following:
|
||||
|
||||
```
|
||||
pick b4dc15d Update CONTRIBUTING.md with latest info
|
||||
pick 8592149 Add Dev certificate of origin
|
||||
pick 259dee6 Add grunt and doctoc npm scripts
|
||||
pick f12af12 Add conventional-changelog-videojs link
|
||||
pick ae4613a Update node's CONTRIBUTING.md url
|
||||
pick 433c582 Update TOC
|
||||
|
||||
# Rebase f599ef4..433c582 onto f599ef4 (6 command(s))
|
||||
#
|
||||
# Commands:
|
||||
# p, pick = use commit
|
||||
# r, reword = use commit, but edit the commit message
|
||||
# e, edit = use commit, but stop for amending
|
||||
# s, squash = use commit, but meld into previous commit
|
||||
# f, fixup = like "squash", but discard this commit's log message
|
||||
# x, exec = run command (the rest of the line) using shell
|
||||
# d, drop = remove commit
|
||||
#
|
||||
# These lines can be re-ordered; they are executed from top to bottom.
|
||||
#
|
||||
# If you remove a line here THAT COMMIT WILL BE LOST.
|
||||
#
|
||||
# However, if you remove everything, the rebase will be aborted.
|
||||
#
|
||||
# Note that empty commits are commented out
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
edit b4dc15d Update CONTRIBUTING.md with latest info
|
||||
fixup 8592149 Add Dev certificate of origin
|
||||
fixup f12af12 Add conventional-changelog-videojs link
|
||||
fixup ae4613a Update node's CONTRIBUTING.md url
|
||||
fixup 433c582 Update TOC
|
||||
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
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
This will put our two commits into master:
|
||||
```
|
||||
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>
|
||||
```
|
||||
|
||||
Now you're ready to push to master as in the normal instructions.
|
||||
|
||||
#### I just made a mistake
|
||||
|
||||
While `git` allows you to update the remote branch with a force push (`git push -f`). This is generally frowned upon since you're rewriting public history. However, if you just pushed the change and it's been less than 10 minutes since you've done with, you may force push to update the commit, assuming no one else has already pushed after you.
|
||||
|
||||
##### I accidentally pushed a broken commit or incorrect commit to master
|
||||
|
||||
Assuming no more than 10 minutes have passed, you may force-push to update or remove the commit. If someone else has already pushed to master or 10 minutes have passed, you should instead use the revert command (`git revert`) to revert the commit and then commit the proper change, or just fix it forward with a followup commit that fixes things.
|
||||
|
||||
##### I lost changes
|
||||
|
||||
Assuming that the changes were committed, even if you lost the commit in your current history does not mean that it is lost. In a lot of cases you can still recover it from the PR branch or if all else fails look at [git's reflog](https://git-scm.com/docs/git-reflog).
|
||||
|
||||
##### I accidentally committed a broken change to master
|
||||
|
||||
This is a great time to discover that something is broken. Because it hasn't been pushed to GitHub yet, it's very easy to reset the change as if nothing has happened and try again.
|
||||
|
||||
To do so, just reset the branch against master.
|
||||
|
||||
```sh
|
||||
$ git reset --hard upstream/master
|
||||
```
|
||||
|
||||
## video.js releases
|
||||
|
||||
Releasing video.js is partially automated through [`conrib.json`](/contrib.json) scripts. To do a release, you need a couple of things: npm access, GitHub personal access token.
|
||||
|
||||
Releases in video.js are done on npm and bower and GitHub and eventually posted on the CDN. This is the instruction for the npm/bower/GitHub releases.
|
||||
|
||||
When we do a release, we release it as a `next` tag on npm first and then at least a week later, we promote this release to `latest` on npm.
|
||||
|
||||
### Getting dependencies
|
||||
|
||||
#### Install contrib
|
||||
|
||||
You can install it globally
|
||||
|
||||
```sh
|
||||
npm i -g contrib/contrib
|
||||
```
|
||||
|
||||
#### npm access
|
||||
|
||||
To see who currently has access run this:
|
||||
|
||||
```sh
|
||||
npm owner ls video.js
|
||||
```
|
||||
|
||||
If you are a core committer, you can request access to npm from one of the current owners.
|
||||
|
||||
#### GitHub personal access token
|
||||
|
||||
This is used to make a GitHub release on videojs. You can get a token from the [personal access tokens](https://github.com/settings/tokens) page.
|
||||
|
||||
After generating one, make sure to keep it safe because GitHub will not show the token for you again. A good place to save it is Lastpass Secure Notes.
|
||||
|
||||
### Doing a release
|
||||
|
||||
To do a release, check out the master branch
|
||||
|
||||
```sh
|
||||
$ 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.
|
||||
|
||||
```sh
|
||||
VJS_GITHUB_USER=gkatsev VJS_GITHUB_TOKEN=my-personal-access-token contrib release next patch
|
||||
```
|
||||
|
||||
This makes a patch release, you can also do a `minor` and a `major` release.
|
||||
|
||||
After it's done, verify that the GitHub release has the correct changelog output.
|
||||
|
||||
## Doc credit
|
||||
|
||||
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
|
||||
+211
-159
@@ -1,121 +1,125 @@
|
||||
CONTRIBUTING
|
||||
============
|
||||
# CONTRIBUTING
|
||||
|
||||
So you want to help out? Great! There's a number of ways you can get involved.
|
||||
|
||||
* [File and discuss issues](#filing-issues)
|
||||
* [Contribute code](#contributing-code)
|
||||
* [Build and share plugins](docs/guides/plugins.md)
|
||||
* [Answer questions on Stack Overflow](http://stackoverflow.com/questions/tagged/video.js)
|
||||
<!-- 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**
|
||||
|
||||
- [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
|
||||
|
||||
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/video-js-swf)
|
||||
* [Player skin designer](https://github.com/videojs/designer)
|
||||
* [Contribflow](https://github.com/zencoder/contribflow)
|
||||
* [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
|
||||
|
||||
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
|
||||
### 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!
|
||||
|
||||
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.
|
||||
|
||||
2. Check if the issue has already been fixed — try to reproduce it using the latest `master` branch in the repository.
|
||||
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.
|
||||
|
||||
3. 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/7/edit) as a starting point.
|
||||
|
||||
A good bug report should be as detailed as possible, so that others won't have to follow up for the essential details.
|
||||
|
||||
Here's an example:
|
||||
|
||||
> Short yet concise Bug Summary
|
||||
>
|
||||
> Description:
|
||||
> Happens on Windows 7 and OSX. Seen with IE9, Firefox 19 OSX, Chrome 21, Flash 11.6 and 11.2
|
||||
>
|
||||
> 1. This is the first step
|
||||
> 2. This is the second step
|
||||
> 3. Further steps, etc.
|
||||
>
|
||||
> Expected:
|
||||
> (describe the expected outcome of the steps above)
|
||||
>
|
||||
> Actual:
|
||||
> (describe what actually happens)
|
||||
>
|
||||
> `<url>` (a link to the reduced test case, if it exists)
|
||||
>
|
||||
> Any other information you want to share that is relevant to the issue being
|
||||
> reported. This might include the lines of code that you have identified as
|
||||
> causing the bug, and potential solutions (and your opinions on their
|
||||
> merits).
|
||||
|
||||
**[File a bug report](https://github.com/videojs/video.js/issues/new)**
|
||||
|
||||
### Requesting a Feature
|
||||
|
||||
1. [Check the plugin list](https://github.com/videojs/video.js/wiki/Plugins) for any plugins that may already support the 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][]
|
||||
|
||||
**[Request a feature](https://github.com/videojs/video.js/issues/new)**
|
||||
|
||||
Contributing code
|
||||
-----------------
|
||||
## Contributing code
|
||||
|
||||
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 -- Install grunt-cli globally so that you will have the correct version of grunt available for any project that needs it.
|
||||
|
||||
On Unix-based systems, you'll have to do this as a superuser:
|
||||
|
||||
```bash
|
||||
sudo npm install -g grunt-cli
|
||||
```
|
||||
On Windows, you can just run:
|
||||
|
||||
```bash
|
||||
npm install -g grunt-cli
|
||||
- `grunt-cli`
|
||||
Optionally, install `grunt-cli` globally to use grunt directly. It can always be run via an npm script:
|
||||
```sh
|
||||
$ npm run grunt
|
||||
```
|
||||
|
||||
- Contribflow -- A homegrown git workflow tool for managing feature/hotfix branches and submitting pull requests. If you have your own preferred git workflow, contribflow isn't required, but the following instructions will assume you're using it.
|
||||
|
||||
On Unix-based systems, you'll have to do this as a superuser:
|
||||
|
||||
```bash
|
||||
sudo npm install -g contribflow
|
||||
```sh
|
||||
$ npm install -g grunt-cli
|
||||
```
|
||||
|
||||
On Windows, you can just run:
|
||||
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`.
|
||||
|
||||
```bash
|
||||
npm install -g contribflow
|
||||
```
|
||||
### Building video.js locally
|
||||
|
||||
### Building your own copy of Video.js
|
||||
#### Forking and cloning the repository
|
||||
|
||||
First, [fork](http://help.github.com/fork-a-repo/) the video.js git repository. At the top of every github page, there is a Fork button. Click it, and the forking process will copy Video.js into your own GitHub account.
|
||||
First, [fork](http://help.github.com/fork-a-repo/) the video.js git repository. At the top of every GitHub page, there is a Fork button. Click it, and the forking process will copy Video.js into your own GitHub account.
|
||||
|
||||
Clone your fork of the repo into your code directory
|
||||
|
||||
```bash
|
||||
git clone https://github.com/<your-username>/video.js.git
|
||||
```sh
|
||||
$ git clone https://github.com/<your-username>/video.js.git
|
||||
```
|
||||
|
||||
Navigate to the newly cloned directory
|
||||
|
||||
```bash
|
||||
cd video.js
|
||||
```sh
|
||||
$ cd video.js
|
||||
```
|
||||
|
||||
Assign the original repo to a remote called "upstream"
|
||||
@@ -124,152 +128,200 @@ Assign the original repo to a remote called "upstream"
|
||||
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:
|
||||
> In the future, if you want to pull in updates to video.js that happened after you cloned the main repo, you can run:
|
||||
>
|
||||
> ```bash
|
||||
> git checkout master
|
||||
> git pull upstream master
|
||||
> ```sh
|
||||
> $ git remote update
|
||||
> $ git checkout master
|
||||
> $ git pull upstream master
|
||||
> ```
|
||||
|
||||
#### Installing local dependencies
|
||||
|
||||
Install the required node.js modules using node package manager
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```sh
|
||||
$ 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.
|
||||
> `npm config set color false`
|
||||
> Note that this change takes effect when a new command prompt window is opened; the current window will not be affected.
|
||||
|
||||
Build a local copy of video.js and run tests
|
||||
#### Running tests
|
||||
|
||||
```bash
|
||||
grunt dist
|
||||
grunt test
|
||||
To run the tests all you need to do is run
|
||||
|
||||
```sh
|
||||
$ npm test
|
||||
```
|
||||
|
||||
Video.js is also configured to run tests with Karma. Karma is installed as a grunt plugin to run QUnit tests in real browsers, as opposed to simply running the tests in phantomjs, a headless browser. To run the tests with Karma:
|
||||
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.
|
||||
|
||||
```bash
|
||||
grunt karma:dev
|
||||
#### Building videojs
|
||||
|
||||
To build video.js, simply run
|
||||
|
||||
```sh
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
At this point you should have a built copy of video.js in a directory named `dist`, and all tests should be passing.
|
||||
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.
|
||||
|
||||
#### Sandbox test directory
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
See [the following section](#running-a-local-web-server) for how to open the page in a browser.
|
||||
|
||||
#### Running a local web server
|
||||
|
||||
This ties in nicely with the sandbox directory. You can always open the `sandbox/index.html` file directly but in some cases it may not work properly.
|
||||
|
||||
> Flash files (`.swf`) that are local and loaded into a locally accessed page (file:///) will NOT run.
|
||||
> To get around this you must use a local web server.
|
||||
|
||||
To run the local webserver, you can run it in a couple of ways.
|
||||
|
||||
```sh
|
||||
$ grunt connect
|
||||
$ open http://localhost:9999/sandbox/index.html
|
||||
```
|
||||
or
|
||||
```sh
|
||||
$ npm start
|
||||
```
|
||||
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
|
||||
```
|
||||
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
|
||||
|
||||
#### Step 1: Verify
|
||||
|
||||
Whether you're adding something new, making something better, or fixing a bug, you'll first want to search the [GitHub issues](https://github.com/videojs/video.js/issues) and [plugins list](https://github.com/videojs/video.js/wiki/Plugins) to make sure you're aware of any previous discussion or work. If an unclaimed issue exists, claim it via a comment. If no issue exists for your change, submit one, follwing the [issue filing guidelines](#filing-issues).
|
||||
|
||||
There are two categories of changes in video.js land, features and hotfixes (Video.js follows a branching model similar to [gitflow](http://nvie.com/posts/a-successful-git-branching-model/)). Hotfixes are for urgent fixes that need to be released immediately as a patch. Features are for everything else (including non-urgent fixes). If you think you have a hotfix scenario, verify that (via comment) before starting the work. We'll focus on features here, but you can swap `hotfix` for `feature` in any command.
|
||||
#### Step 2: Update remote
|
||||
|
||||
Start a new development branch
|
||||
Before starting work, you want to update your local repository to have all the latest changes.
|
||||
|
||||
```bash
|
||||
contrib feature start
|
||||
```sh
|
||||
$ git remote update
|
||||
$ git checkout master
|
||||
$ git rebase upstream/master
|
||||
```
|
||||
|
||||
You'll be prompted to name the branch. After that, contrib will create the branch locally, and use git to push it up to your origin, and track it. You're now ready to start building your feature or fixing that bug! Be sure to read the [Code Style Guide](#code-style-guide).
|
||||
#### Step 3: Branch
|
||||
|
||||
While you're developing, you can ensure your changes are working by writing tests (in the `test` directory) and running `grunt test`.
|
||||
You want to do your work in a separate branch.
|
||||
|
||||
There's also a sandbox directory where you can add any file and it won't get tracked as a change. To start you can copy the example index file and see a working version of a player (using the local source code) by loading it in a browser.
|
||||
|
||||
```bash
|
||||
cp sandbox/index.html.example sandbox/index.html
|
||||
open sandbox/index.html
|
||||
```sh
|
||||
$ 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:
|
||||
|
||||
### Testing Locally
|
||||
A simple Connect server is available via the Grunt plugin. The commands below will allow you to setup a test sandbox and begin development.
|
||||
1. The first line should be less than 50 characters and contain a short description of the commit.
|
||||
1. The body should contain a more detailed description. It can contain things like reasoning for the change and specifics of what changed.
|
||||
1. A footer can be added if this fixes a particular issue on GitHub.
|
||||
|
||||
```bash
|
||||
cp sandbox/index.html.example sandbox/index.html
|
||||
grunt connect
|
||||
open http://localhost:9999/sandbox/index.html
|
||||
```sh
|
||||
$ git add src/js/player.js
|
||||
$ git commit
|
||||
```
|
||||
|
||||
> NOTES regarding local testing in Chrome 21+ (as of 2013/01/01)
|
||||
> Flash files that are local and loaded into a locally accessed page (file:///) will NOT run.
|
||||
> To get around this you can do either of the following:
|
||||
An example commit message:
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
Any testing considerations or things to think about when looking at the commit.
|
||||
|
||||
Fixes #123. The footer can contain Fixes messages.
|
||||
```
|
||||
|
||||
> Make sure that git knows your name and email:
|
||||
>
|
||||
> 1. Do your development and testing using a local HTTP server. See Grunt commands above.
|
||||
>
|
||||
> 2. [Disable the version of Flash included with Chrome](http://helpx.adobe.com/flash-player/kb/flash-player-google-chrome.html#How_can_I_run_debugger_or_alternate_versions_of_Flash_Player_in_Google_Chrome) and enable a system-wide version of Flash instead.
|
||||
|
||||
Commit and push changes as you go (using git directly). Write thorough descriptions of your changes in your commit messages.
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -av
|
||||
git push
|
||||
```
|
||||
|
||||
> GitHub allows you to close an issue through your commit message using the [fixes](https://github.com/blog/831-issues-2-0-the-next-generation) keyword.
|
||||
>
|
||||
> ```bash
|
||||
> My commit message. fixes #123
|
||||
> Testing: (briefly describe any testing here, for example, 'unit tests and cross-browser manual tests around playback and network interruption')
|
||||
> ```sh
|
||||
> $ git config --global user.name "Random User"
|
||||
> $ git config --global user.email "random.user@example.com"
|
||||
> ```
|
||||
|
||||
### Submitting your changes
|
||||
#### Step 5: Test
|
||||
|
||||
First, thoroughly test your feature or fix, including writing tests to make sure your change doesn't get regressed in a future update. If you're fixing a bug, we recommend in addition to testing the fix itself, to do some testing around the areas that your fix has touched. For example, a brief smoketest of the player never hurts.
|
||||
Any code change should come with corresponding test changes. Especially bug fixes.
|
||||
Tests attached to bug fixes should fail before the change and succeed with it.
|
||||
|
||||
Make sure your changes are pushed to origin
|
||||
|
||||
```bash
|
||||
git push
|
||||
```sh
|
||||
$ npm test
|
||||
```
|
||||
|
||||
Use contrib to submit a pull request (make sure you're in your feature branch)
|
||||
#### Step 6: Push
|
||||
|
||||
```bash
|
||||
contrib feature submit
|
||||
```sh
|
||||
$ git push origin my-branch
|
||||
```
|
||||
|
||||
You'll be prompted for title and description for the Pull Request. After that, contrib will use Git to submit your pull request to video.js.
|
||||
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)
|
||||
|
||||
You're Done! (except for cleanup.) To clean up your feature or hotfix branch:
|
||||
### Code Style Guide
|
||||
|
||||
First, checkout your feature or issue branch:
|
||||
Our javascript is linted using [videojs-standard][linter].
|
||||
|
||||
```bash
|
||||
git checkout (branchname)
|
||||
```
|
||||
## [Developer's Certificate of Origin 1.1](https://github.com/nodejs/node/blob/master/CONTRIBUTING.md#developers-certificate-of-origin-11)
|
||||
|
||||
Run this command to clean up your feature:
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
```bash
|
||||
contrib feature delete
|
||||
```
|
||||
* (a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
Run this command to clean up your bug fix:
|
||||
* (b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
```bash
|
||||
contrib hotfix delete
|
||||
```
|
||||
> PLEASE NOTE: THIS WILL DELETE YOUR LOCAL AND REMOTE COPIES OF THE FEATURE.
|
||||
> This is meant to clean up your local and remote branches, so make sure any changes you don't want to lose have been pulled into the parent project or another branch first.
|
||||
* (c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
Code Style Guide
|
||||
----------------
|
||||
Please follow [Google's JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml) to the letter. If your editor supports [.editorconfig](http://editorconfig.org/#download) it will make it easier to manage differences from your own coding style.
|
||||
* (d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
|
||||
### Style examples include:
|
||||
* Two space indents.
|
||||
* Delimit strings with single-quotes `'`, not double-quotes `"`.
|
||||
* No trailing whitespace, except in markdown files where a linebreak must be forced.
|
||||
* No more than [one assignment](http://benalman.com/news/2012/05/multiple-var-statements-javascript/) per `var` statement.
|
||||
* Prefer `if` and `else` to ["clever"](http://programmers.stackexchange.com/a/25281) uses of `? :` conditional or `||`, `&&` logical operators.
|
||||
* **When in doubt, follow the conventions you see used in the source already.**
|
||||
|
||||
If you happen to find something in the codebase that does not follow the style guide, that's a good opportunity to make your first contribution!
|
||||
|
||||
---
|
||||
### Doc Credit
|
||||
## 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),
|
||||
[jquery](https://github.com/jquery/jquery/blob/master/CONTRIBUTING.md),
|
||||
and [node.js](https://github.com/joyent/node/wiki/Contributing).
|
||||
and [node.js](https://github.com/nodejs/node/blob/master/CONTRIBUTING.md).
|
||||
|
||||
[issue template]: /.github/ISSUE_TEMPLATE.md
|
||||
[linter]: https://github.com/videojs/standard
|
||||
|
||||
+6
-2
@@ -1,6 +1,6 @@
|
||||
import {gruntCustomizer, gruntOptionsMaker} from './options-customizer.js';
|
||||
import chg from 'chg';
|
||||
import nodeCli from 'shelljs-nodecli';
|
||||
import npmRun from 'npm-run';
|
||||
|
||||
module.exports = function(grunt) {
|
||||
require('time-grunt')(grunt);
|
||||
@@ -32,7 +32,10 @@ module.exports = function(grunt) {
|
||||
release: {
|
||||
tag_name: 'v'+ version.full,
|
||||
name: version.full,
|
||||
body: nodeCli.exec('conventional-changelog', '-p videojs', {silent: true}).output
|
||||
body: npmRun.execSync('conventional-changelog -p videojs', {
|
||||
silent: true,
|
||||
encoding: 'utf8'
|
||||
})
|
||||
},
|
||||
},
|
||||
files: {
|
||||
@@ -102,6 +105,7 @@ module.exports = function(grunt) {
|
||||
uglify: {
|
||||
options: {
|
||||
preserveComments: 'some',
|
||||
screwIE8: false,
|
||||
mangle: true,
|
||||
compress: {
|
||||
sequences: true,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"version": "5.12.3",
|
||||
"version": "5.13.2",
|
||||
"keywords": [
|
||||
"videojs",
|
||||
"html5",
|
||||
|
||||
externo
+1550
-779
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+9
-6743
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+15
-1
@@ -18,9 +18,23 @@ videojs.addLanguage("ru",{
|
||||
"Captions": "Подписи",
|
||||
"captions off": "Подписи выкл.",
|
||||
"Chapters": "Главы",
|
||||
"Close Modal Dialog": "Закрыть модальное окно",
|
||||
"Descriptions": "Описания",
|
||||
"descriptions off": "описания выкл.",
|
||||
"Audio Track": "Звуковая дорожка",
|
||||
"You aborted the media playback": "Вы прервали воспроизведение видео",
|
||||
"A network error caused the media download to fail part-way.": "Ошибка сети вызвала сбой во время загрузки видео.",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Невозможно загрузить видео из-за сетевого или серверного сбоя либо формат не поддерживается.",
|
||||
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Воспроизведение видео было приостановлено из-за повреждения либо в связи с тем, что видео использует функции, неподдерживаемые вашим браузером.",
|
||||
"No compatible source was found for this media.": "Совместимые источники для этого видео отсутствуют."
|
||||
"No compatible source was found for this media.": "Совместимые источники для этого видео отсутствуют.",
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "Видео в зашифрованном виде, и у нас нет ключей для расшифровки.",
|
||||
"Play Video": "Воспроизвести видео",
|
||||
"Close": "Закрыть",
|
||||
"Modal Window": "Модальное окно",
|
||||
"This is a modal window": "Это модальное окно.",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "Модальное окно можно закрыть нажав Esc или кнопку закрытия окна.",
|
||||
", opens captions settings dialog": ", откроется диалог настройки подписей",
|
||||
", opens subtitles settings dialog": ", откроется диалог настройки субтитров",
|
||||
", opens descriptions settings dialog": ", откроется диалог настройки описаний",
|
||||
", selected": ", выбрано"
|
||||
});
|
||||
externo
+15
-1
@@ -18,9 +18,23 @@ videojs.addLanguage("uk",{
|
||||
"Captions": "Підписи",
|
||||
"captions off": "Без підписів",
|
||||
"Chapters": "Розділи",
|
||||
"Close Modal Dialog": "Закрити модальний діалог",
|
||||
"Descriptions": "Описи",
|
||||
"descriptions off": "Без описів",
|
||||
"Audio Track": "Аудіодоріжка",
|
||||
"You aborted the media playback": "Ви припинили відтворення відео",
|
||||
"A network error caused the media download to fail part-way.": "Помилка мережі викликала збій під час завантаження відео.",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Неможливо завантажити відео через мережевий чи серверний збій або формат не підтримується.",
|
||||
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Відтворення відео було припинено через пошкодження або у зв'язку з тим, що відео використовує функції, які не підтримуються вашим браузером.",
|
||||
"No compatible source was found for this media.": "Сумісні джерела для цього відео відсутні."
|
||||
"No compatible source was found for this media.": "Сумісні джерела для цього відео відсутні.",
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "Відео в зашифрованому вигляді, і ми не маємо ключі для розшифровки.",
|
||||
"Play Video": "Відтворити відео",
|
||||
"Close": "Закрити",
|
||||
"Modal Window": "Модальне вікно",
|
||||
"This is a modal window": "Це модальне вікно.",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "Модальне вікно можна закрити, натиснувши клавішу Esc або кнопку закриття вікна.",
|
||||
", opens captions settings dialog": ", відкриється діалогове вікно налаштування підписів",
|
||||
", opens subtitles settings dialog": ", відкриється діалогове вікно налаштування субтитрів",
|
||||
", opens descriptions settings dialog": ", відкриється діалогове вікно налаштування описів",
|
||||
", selected": ", обраний"
|
||||
});
|
||||
externo
+14
-1
@@ -18,10 +18,23 @@ videojs.addLanguage("zh-TW",{
|
||||
"Captions": "內嵌字幕",
|
||||
"captions off": "關閉內嵌字幕",
|
||||
"Chapters": "章節",
|
||||
"Close Modal Dialog": "關閉對話框",
|
||||
"Descriptions": "描述",
|
||||
"descriptions off": "關閉描述",
|
||||
"Audio Track": "音軌",
|
||||
"You aborted the media playback": "影片播放已終止",
|
||||
"A network error caused the media download to fail part-way.": "網路錯誤導致影片下載失敗。",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "影片因格式不支援或者伺服器或網路的問題無法載入。",
|
||||
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "由於影片檔案損毀或是該影片使用了您的瀏覽器不支援的功能,播放終止。",
|
||||
"No compatible source was found for this media.": "無法找到相容此影片的來源。",
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "影片已加密,無法解密。"
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "影片已加密,無法解密。",
|
||||
"Play Video": "播放影片",
|
||||
"Close": "關閉",
|
||||
"Modal Window": "對話框",
|
||||
"This is a modal window": "這是一個對話框",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "可以按ESC按鍵或啟用關閉按鈕來關閉此對話框。",
|
||||
", opens captions settings dialog": ", 開啟標題設定對話框",
|
||||
", opens subtitles settings dialog": ", 開啟字幕設定對話框",
|
||||
", opens descriptions settings dialog": ", 開啟描述設定對話框",
|
||||
", selected": ", 選擇"
|
||||
});
|
||||
Arquivo binário não exibido.
externo
+1550
-779
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+10
-7095
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,120 @@
|
||||
# Hooks
|
||||
Hooks exist so that users can "hook" on to certain video.js player lifecycle
|
||||
|
||||
|
||||
## 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
|
||||
* 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
|
||||
|
||||
videoEl.className += ' some-super-class';
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
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
|
||||
* Don't have to return anything
|
||||
|
||||
Example: adding setup hook
|
||||
```js
|
||||
var setup = function(player) {
|
||||
// initialize the foo plugin
|
||||
player.foo();
|
||||
};
|
||||
var foo = function() {};
|
||||
|
||||
videojs.plugin('foo', foo);
|
||||
videojs.hook('setup', setup);
|
||||
var player = videojs('some-id', {autoplay: true, controls: true});
|
||||
```
|
||||
|
||||
## 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});
|
||||
```
|
||||
|
||||
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');
|
||||
```
|
||||
|
||||
### 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) {};
|
||||
|
||||
// add the hook
|
||||
videojs.hook('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) {});
|
||||
|
||||
var setupHooks = videojs.hooks('setup');
|
||||
|
||||
// remove the hook you just added
|
||||
videojs.removeHook('setup', setupHooks[setupHooks.length - 1]);
|
||||
```
|
||||
|
||||
@@ -54,7 +54,7 @@ or
|
||||
|
||||
### autoplay ###
|
||||
If autoplay is true, the video will start playing as soon as page is loaded (without any interaction from the user).
|
||||
NOT SUPPORTED BY APPLE iOS DEVICES. Apple blocks the autoplay functionality in an effort to protect it's customers from unwillingly using a lot of their (often expensive) monthly data plans. A user touch/click is required to start the video in this case.
|
||||
NOT SUPPORTED BY APPLE iOS DEVICES. Apple blocks the autoplay functionality in an effort to protect its customers from unwillingly using a lot of their (often expensive) monthly data plans. A user touch/click is required to start the video in this case.
|
||||
```html
|
||||
<video autoplay ...>
|
||||
or
|
||||
|
||||
@@ -100,3 +100,6 @@ You may optionally use the last `/` as the separator between connection and stre
|
||||
<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
|
||||
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.
|
||||
|
||||
@@ -252,20 +252,7 @@ If you add or update a translation run `grunt check-translations` to update the
|
||||
| | , opens subtitles settings dialog |
|
||||
| | , opens descriptions settings dialog |
|
||||
| | , selected |
|
||||
| ru.json (missing 14) | Close Modal Dialog |
|
||||
| | Descriptions |
|
||||
| | descriptions off |
|
||||
| | Audio Track |
|
||||
| | The media is encrypted and we do not have the keys to decrypt it. |
|
||||
| | Play Video |
|
||||
| | Close |
|
||||
| | Modal Window |
|
||||
| | This is a modal window |
|
||||
| | This modal can be closed by pressing the Escape key or activating the close button. |
|
||||
| | , opens captions settings dialog |
|
||||
| | , opens subtitles settings dialog |
|
||||
| | , opens descriptions settings dialog |
|
||||
| | , selected |
|
||||
| ru.json (Complete) | |
|
||||
| sr.json (missing 14) | Close Modal Dialog |
|
||||
| | Descriptions |
|
||||
| | descriptions off |
|
||||
@@ -300,20 +287,7 @@ If you add or update a translation run `grunt check-translations` to update the
|
||||
| | Audio Track |
|
||||
| | The media is encrypted and we do not have the keys to decrypt it. |
|
||||
| | , opens descriptions settings dialog |
|
||||
| uk.json (missing 14) | Close Modal Dialog |
|
||||
| | Descriptions |
|
||||
| | descriptions off |
|
||||
| | Audio Track |
|
||||
| | The media is encrypted and we do not have the keys to decrypt it. |
|
||||
| | Play Video |
|
||||
| | Close |
|
||||
| | Modal Window |
|
||||
| | This is a modal window |
|
||||
| | This modal can be closed by pressing the Escape key or activating the close button. |
|
||||
| | , opens captions settings dialog |
|
||||
| | , opens subtitles settings dialog |
|
||||
| | , opens descriptions settings dialog |
|
||||
| | , selected |
|
||||
| uk.json (Complete) | |
|
||||
| vi.json (missing 14) | Close Modal Dialog |
|
||||
| | Descriptions |
|
||||
| | descriptions off |
|
||||
@@ -341,17 +315,5 @@ If you add or update a translation run `grunt check-translations` to update the
|
||||
| | , opens subtitles settings dialog |
|
||||
| | , opens descriptions settings dialog |
|
||||
| | , selected |
|
||||
| zh-TW.json (missing 13) | Close Modal Dialog |
|
||||
| | Descriptions |
|
||||
| | descriptions off |
|
||||
| | Audio Track |
|
||||
| | Play Video |
|
||||
| | Close |
|
||||
| | Modal Window |
|
||||
| | This is a modal window |
|
||||
| | This modal can be closed by pressing the Escape key or activating the close button. |
|
||||
| | , opens captions settings dialog |
|
||||
| | , opens subtitles settings dialog |
|
||||
| | , opens descriptions settings dialog |
|
||||
| | , selected |
|
||||
<!-- END langtable -->
|
||||
| zh-TW.json (Complete) | |
|
||||
<!-- END langtable -->
|
||||
|
||||
+15
-1
@@ -18,9 +18,23 @@
|
||||
"Captions": "Подписи",
|
||||
"captions off": "Подписи выкл.",
|
||||
"Chapters": "Главы",
|
||||
"Close Modal Dialog": "Закрыть модальное окно",
|
||||
"Descriptions": "Описания",
|
||||
"descriptions off": "описания выкл.",
|
||||
"Audio Track": "Звуковая дорожка",
|
||||
"You aborted the media playback": "Вы прервали воспроизведение видео",
|
||||
"A network error caused the media download to fail part-way.": "Ошибка сети вызвала сбой во время загрузки видео.",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Невозможно загрузить видео из-за сетевого или серверного сбоя либо формат не поддерживается.",
|
||||
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Воспроизведение видео было приостановлено из-за повреждения либо в связи с тем, что видео использует функции, неподдерживаемые вашим браузером.",
|
||||
"No compatible source was found for this media.": "Совместимые источники для этого видео отсутствуют."
|
||||
"No compatible source was found for this media.": "Совместимые источники для этого видео отсутствуют.",
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "Видео в зашифрованном виде, и у нас нет ключей для расшифровки.",
|
||||
"Play Video": "Воспроизвести видео",
|
||||
"Close": "Закрыть",
|
||||
"Modal Window": "Модальное окно",
|
||||
"This is a modal window": "Это модальное окно.",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "Модальное окно можно закрыть нажав Esc или кнопку закрытия окна.",
|
||||
", opens captions settings dialog": ", откроется диалог настройки подписей",
|
||||
", opens subtitles settings dialog": ", откроется диалог настройки субтитров",
|
||||
", opens descriptions settings dialog": ", откроется диалог настройки описаний",
|
||||
", selected": ", выбрано"
|
||||
}
|
||||
|
||||
+15
-1
@@ -18,9 +18,23 @@
|
||||
"Captions": "Підписи",
|
||||
"captions off": "Без підписів",
|
||||
"Chapters": "Розділи",
|
||||
"Close Modal Dialog": "Закрити модальний діалог",
|
||||
"Descriptions": "Описи",
|
||||
"descriptions off": "Без описів",
|
||||
"Audio Track": "Аудіодоріжка",
|
||||
"You aborted the media playback": "Ви припинили відтворення відео",
|
||||
"A network error caused the media download to fail part-way.": "Помилка мережі викликала збій під час завантаження відео.",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Неможливо завантажити відео через мережевий чи серверний збій або формат не підтримується.",
|
||||
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Відтворення відео було припинено через пошкодження або у зв'язку з тим, що відео використовує функції, які не підтримуються вашим браузером.",
|
||||
"No compatible source was found for this media.": "Сумісні джерела для цього відео відсутні."
|
||||
"No compatible source was found for this media.": "Сумісні джерела для цього відео відсутні.",
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "Відео в зашифрованому вигляді, і ми не маємо ключі для розшифровки.",
|
||||
"Play Video": "Відтворити відео",
|
||||
"Close": "Закрити",
|
||||
"Modal Window": "Модальне вікно",
|
||||
"This is a modal window": "Це модальне вікно.",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "Модальне вікно можна закрити, натиснувши клавішу Esc або кнопку закриття вікна.",
|
||||
", opens captions settings dialog": ", відкриється діалогове вікно налаштування підписів",
|
||||
", opens subtitles settings dialog": ", відкриється діалогове вікно налаштування субтитрів",
|
||||
", opens descriptions settings dialog": ", відкриється діалогове вікно налаштування описів",
|
||||
", selected": ", обраний"
|
||||
}
|
||||
|
||||
+14
-2
@@ -18,11 +18,23 @@
|
||||
"Captions": "內嵌字幕",
|
||||
"captions off": "關閉內嵌字幕",
|
||||
"Chapters": "章節",
|
||||
"Close Modal Dialog": "關閉對話框",
|
||||
"Descriptions": "描述",
|
||||
"descriptions off": "關閉描述",
|
||||
"Audio Track": "音軌",
|
||||
"You aborted the media playback": "影片播放已終止",
|
||||
"A network error caused the media download to fail part-way.": "網路錯誤導致影片下載失敗。",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "影片因格式不支援或者伺服器或網路的問題無法載入。",
|
||||
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "由於影片檔案損毀或是該影片使用了您的瀏覽器不支援的功能,播放終止。",
|
||||
"No compatible source was found for this media.": "無法找到相容此影片的來源。",
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "影片已加密,無法解密。"
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "影片已加密,無法解密。",
|
||||
"Play Video": "播放影片",
|
||||
"Close": "關閉",
|
||||
"Modal Window": "對話框",
|
||||
"This is a modal window": "這是一個對話框",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "可以按ESC按鍵或啟用關閉按鈕來關閉此對話框。",
|
||||
", opens captions settings dialog": ", 開啟標題設定對話框",
|
||||
", opens subtitles settings dialog": ", 開啟字幕設定對話框",
|
||||
", opens descriptions settings dialog": ", 開啟描述設定對話框",
|
||||
", selected": ", 選擇"
|
||||
}
|
||||
|
||||
|
||||
+9
-6
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"version": "5.12.3",
|
||||
"version": "5.13.2",
|
||||
"main": "./es5/video.js",
|
||||
"style": "./dist/video-js.css",
|
||||
"copyright": "Copyright Brightcove, Inc. <https://www.brightcove.com/>",
|
||||
@@ -20,9 +20,11 @@
|
||||
"build": "grunt dist",
|
||||
"change": "grunt chg-add",
|
||||
"clean": "grunt clean",
|
||||
"grunt": "grunt",
|
||||
"lint": "vjsstandard",
|
||||
"start": "grunt watchAll",
|
||||
"test": "grunt test"
|
||||
"test": "grunt test",
|
||||
"toc": "doctoc"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -58,12 +60,13 @@
|
||||
"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-accessibility": "^4.1.0",
|
||||
"grunt-accessibility": "^5.0.0",
|
||||
"grunt-babel": "^6.0.0",
|
||||
"grunt-banner": "^0.4.0",
|
||||
"grunt-browserify": "3.5.1",
|
||||
@@ -74,7 +77,7 @@
|
||||
"grunt-contrib-connect": "~0.7.1",
|
||||
"grunt-contrib-copy": "^0.8.0",
|
||||
"grunt-contrib-cssmin": "~1.0.2",
|
||||
"grunt-contrib-uglify": "~0.11.0",
|
||||
"grunt-contrib-uglify": "^2.0.0",
|
||||
"grunt-contrib-watch": "~0.1.4",
|
||||
"grunt-coveralls": "^1.0.0",
|
||||
"grunt-fastly": "^0.1.3",
|
||||
@@ -89,7 +92,7 @@
|
||||
"karma-browserstack-launcher": "^1.0.1",
|
||||
"karma-chrome-launcher": "^2.0.0",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-detect-browsers": "^2.1.0",
|
||||
"karma-detect-browsers": "~2.2.3",
|
||||
"karma-firefox-launcher": "^1.0.0",
|
||||
"karma-ie-launcher": "^1.0.0",
|
||||
"karma-opera-launcher": "^1.0.0",
|
||||
@@ -98,9 +101,9 @@
|
||||
"karma-sinon": "^1.0.5",
|
||||
"load-grunt-tasks": "^3.1.0",
|
||||
"markdown-table": "^1.0.0",
|
||||
"npm-run": "^4.1.0",
|
||||
"proxyquireify": "^3.0.0",
|
||||
"qunitjs": "^1.23.1",
|
||||
"shelljs-nodecli": "^0.1.1",
|
||||
"sinon": "^1.16.1",
|
||||
"time-grunt": "^1.1.1",
|
||||
"uglify-js": "~2.7.3",
|
||||
|
||||
@@ -15,10 +15,6 @@ import Component from './component.js';
|
||||
*/
|
||||
class BigPlayButton extends Button {
|
||||
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow sub components to stack CSS class names
|
||||
*
|
||||
|
||||
+22
-4
@@ -16,10 +16,6 @@ import assign from 'object.assign';
|
||||
*/
|
||||
class Button extends ClickableComponent {
|
||||
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the component's DOM element
|
||||
*
|
||||
@@ -83,6 +79,28 @@ class Button extends ClickableComponent {
|
||||
return Component.prototype.addChild.call(this, child, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the button element
|
||||
*
|
||||
* @return {Component}
|
||||
* @method enable
|
||||
*/
|
||||
enable() {
|
||||
super.enable();
|
||||
this.el_.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the button element
|
||||
*
|
||||
* @return {Component}
|
||||
* @method disable
|
||||
*/
|
||||
disable() {
|
||||
super.disable();
|
||||
this.el_.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle KeyPress (document level) - Extend with specific functionality for button
|
||||
*
|
||||
|
||||
@@ -24,10 +24,7 @@ class ClickableComponent extends Component {
|
||||
|
||||
this.emitTapEvents();
|
||||
|
||||
this.on('tap', this.handleClick);
|
||||
this.on('click', this.handleClick);
|
||||
this.on('focus', this.handleFocus);
|
||||
this.on('blur', this.handleBlur);
|
||||
this.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +54,8 @@ class ClickableComponent extends Component {
|
||||
'aria-live': 'polite'
|
||||
}, attributes);
|
||||
|
||||
this.tabIndex_ = props.tabIndex;
|
||||
|
||||
const el = super.createEl(tag, props, attributes);
|
||||
|
||||
this.createControlTextEl(el);
|
||||
@@ -117,26 +116,6 @@ class ClickableComponent extends Component {
|
||||
return `vjs-control vjs-button ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a child component inside this clickable-component
|
||||
*
|
||||
* @param {String|Component} child The class name or instance of a child to add
|
||||
* @param {Object=} options Options, including options to be passed to children of the child.
|
||||
* @return {Component} The child component (created by this process if a string was used)
|
||||
* @method addChild
|
||||
*/
|
||||
addChild(child, options = {}) {
|
||||
// TODO: Fix adding an actionable child to a ClickableComponent; currently
|
||||
// it will cause issues with assistive technology (e.g. screen readers)
|
||||
// which support ARIA, since an element with role="button" cannot have
|
||||
// actionable child elements.
|
||||
|
||||
// let className = this.constructor.name;
|
||||
// log.warn(`Adding a child to a ClickableComponent (${className}) can cause issues with assistive technology which supports ARIA, since an element with role="button" cannot have actionable child elements.`);
|
||||
|
||||
return super.addChild(child, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the component element
|
||||
*
|
||||
@@ -146,6 +125,13 @@ class ClickableComponent extends Component {
|
||||
enable() {
|
||||
this.removeClass('vjs-disabled');
|
||||
this.el_.setAttribute('aria-disabled', 'false');
|
||||
if (typeof this.tabIndex_ !== 'undefined') {
|
||||
this.el_.setAttribute('tabIndex', this.tabIndex_);
|
||||
}
|
||||
this.on('tap', this.handleClick);
|
||||
this.on('click', this.handleClick);
|
||||
this.on('focus', this.handleFocus);
|
||||
this.on('blur', this.handleBlur);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -158,6 +144,13 @@ class ClickableComponent extends Component {
|
||||
disable() {
|
||||
this.addClass('vjs-disabled');
|
||||
this.el_.setAttribute('aria-disabled', 'true');
|
||||
if (typeof this.tabIndex_ !== 'undefined') {
|
||||
this.el_.removeAttribute('tabIndex');
|
||||
}
|
||||
this.off('tap', this.handleClick);
|
||||
this.off('click', this.handleClick);
|
||||
this.off('focus', this.handleFocus);
|
||||
this.off('blur', this.handleBlur);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
+68
-10
@@ -306,6 +306,12 @@ class Component {
|
||||
* @method getChild
|
||||
*/
|
||||
getChild(name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
name = toTitleCase(name);
|
||||
|
||||
return this.childNameIndex_[name];
|
||||
}
|
||||
|
||||
@@ -341,9 +347,9 @@ class Component {
|
||||
let component;
|
||||
let componentName;
|
||||
|
||||
// If child is a string, create nt with options
|
||||
// If child is a string, create component with options
|
||||
if (typeof child === 'string') {
|
||||
componentName = child;
|
||||
componentName = toTitleCase(child);
|
||||
|
||||
// Options can also be specified as a boolean, so convert to an empty object if false.
|
||||
if (!options) {
|
||||
@@ -356,9 +362,7 @@ class Component {
|
||||
options = {};
|
||||
}
|
||||
|
||||
// If no componentClass in options, assume componentClass is the name lowercased
|
||||
// (e.g. playButton)
|
||||
const componentClassName = options.componentClass || toTitleCase(componentName);
|
||||
const componentClassName = options.componentClass || componentName;
|
||||
|
||||
// Set name through options
|
||||
options.name = componentName;
|
||||
@@ -981,6 +985,42 @@ class Component {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of an attribute on the component's element
|
||||
*
|
||||
* @param {String} attribute Attribute to get
|
||||
* @return {String}
|
||||
* @method getAttribute
|
||||
*/
|
||||
getAttribute(attribute) {
|
||||
return Dom.getAttribute(this.el_, attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of an attribute on the component's element
|
||||
*
|
||||
* @param {String} attribute Attribute to set
|
||||
* @param {String} value Value to set the attribute to
|
||||
* @return {Component}
|
||||
* @method setAttribute
|
||||
*/
|
||||
setAttribute(attribute, value) {
|
||||
Dom.setAttribute(this.el_, attribute, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an attribute from the component's element
|
||||
*
|
||||
* @param {String} attribute Attribute to remove
|
||||
* @return {Component}
|
||||
* @method removeAttribute
|
||||
*/
|
||||
removeAttribute(attribute) {
|
||||
Dom.removeAttribute(this.el_, attribute);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or get the width of the component (CSS values)
|
||||
* Setting the video tag dimension values only works with values in pixels.
|
||||
@@ -1108,16 +1148,20 @@ class Component {
|
||||
const computedStyle = window.getComputedStyle(this.el_);
|
||||
|
||||
computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
|
||||
} else if (this.el_.currentStyle) {
|
||||
// ie 8 doesn't support computed style, shim it
|
||||
// return clientWidth or clientHeight instead for better accuracy
|
||||
}
|
||||
|
||||
// remove 'px' from variable and parse as integer
|
||||
computedWidthOrHeight = parseFloat(computedWidthOrHeight);
|
||||
|
||||
// if the computed value is still 0, it's possible that the browser is lying
|
||||
// and we want to check the offset values.
|
||||
// This code also runs on IE8 and wherever getComputedStyle doesn't exist.
|
||||
if (computedWidthOrHeight === 0) {
|
||||
const rule = `offset${toTitleCase(widthOrHeight)}`;
|
||||
|
||||
computedWidthOrHeight = this.el_[rule];
|
||||
}
|
||||
|
||||
// remove 'px' from variable and parse as integer
|
||||
computedWidthOrHeight = parseFloat(computedWidthOrHeight);
|
||||
return computedWidthOrHeight;
|
||||
}
|
||||
|
||||
@@ -1386,11 +1430,18 @@ class Component {
|
||||
* @method registerComponent
|
||||
*/
|
||||
static registerComponent(name, comp) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
name = toTitleCase(name);
|
||||
|
||||
if (!Component.components_) {
|
||||
Component.components_ = {};
|
||||
}
|
||||
|
||||
Component.components_[name] = comp;
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
@@ -1403,12 +1454,19 @@ class Component {
|
||||
* @method getComponent
|
||||
*/
|
||||
static getComponent(name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
name = toTitleCase(name);
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* @file mouse-time-display.js
|
||||
*/
|
||||
import window from 'global/window';
|
||||
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 throttle from 'lodash-compat/function/throttle';
|
||||
import computedStyle from '../../utils/computed-style.js';
|
||||
|
||||
/**
|
||||
* The Mouse Time Display component shows the time you will seek to
|
||||
@@ -71,7 +71,7 @@ class MouseTimeDisplay extends Component {
|
||||
if (this.keepTooltipsInside) {
|
||||
const clampedPosition = this.clampPosition_(position);
|
||||
const difference = position - clampedPosition + 1;
|
||||
const tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width);
|
||||
const tooltipWidth = parseFloat(computedStyle(this.tooltip, 'width'));
|
||||
const tooltipWidthHalf = tooltipWidth / 2;
|
||||
|
||||
this.tooltip.innerHTML = time;
|
||||
@@ -98,8 +98,8 @@ class MouseTimeDisplay extends Component {
|
||||
return position;
|
||||
}
|
||||
|
||||
const playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width);
|
||||
const tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width);
|
||||
const playerWidth = parseFloat(computedStyle(this.player().el(), 'width'));
|
||||
const tooltipWidth = parseFloat(computedStyle(this.tooltip, 'width'));
|
||||
const tooltipWidthHalf = tooltipWidth / 2;
|
||||
let actualPosition = position;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* @file seek-bar.js
|
||||
*/
|
||||
import window from 'global/window';
|
||||
import Slider from '../../slider/slider.js';
|
||||
import Component from '../../component.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';
|
||||
@@ -65,8 +65,8 @@ class SeekBar extends Slider {
|
||||
this.updateAriaAttributes(this.tooltipProgressBar.el_);
|
||||
this.tooltipProgressBar.el_.style.width = this.bar.el_.style.width;
|
||||
|
||||
const playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width);
|
||||
const tooltipWidth = parseFloat(window.getComputedStyle(this.tooltipProgressBar.tooltip).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';
|
||||
|
||||
@@ -19,6 +19,12 @@ class DurationDisplay extends Component {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'durationchange', this.updateContent);
|
||||
|
||||
// Also listen for timeupdate and loadedmetadata because removing those
|
||||
// listeners could have broken dependent applications/libraries. These
|
||||
// can likely be removed for 6.0.
|
||||
this.on(player, 'timeupdate', this.updateContent);
|
||||
this.on(player, 'loadedmetadata', this.updateContent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+62
-6
@@ -496,7 +496,7 @@ class Player extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/remove the vjs-fluid class
|
||||
* Get/set fluid mode
|
||||
*
|
||||
* @param {Boolean} bool Value of true adds the class, value of false removes the class
|
||||
*/
|
||||
@@ -512,6 +512,8 @@ class Player extends Component {
|
||||
} else {
|
||||
this.removeClass('vjs-fluid');
|
||||
}
|
||||
|
||||
this.updateStyleEl_();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -568,7 +570,7 @@ class Player extends Component {
|
||||
if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
|
||||
// Use any aspectRatio that's been specifically set
|
||||
aspectRatio = this.aspectRatio_;
|
||||
} else if (this.videoWidth()) {
|
||||
} else if (this.videoWidth() > 0) {
|
||||
// Otherwise try to get the aspect ratio from the video metadata
|
||||
aspectRatio = this.videoWidth() + ':' + this.videoHeight();
|
||||
} else {
|
||||
@@ -673,10 +675,13 @@ class Player extends Component {
|
||||
|
||||
if (source) {
|
||||
this.currentType_ = source.type;
|
||||
|
||||
if (source.src === this.cache_.src && this.cache_.currentTime > 0) {
|
||||
techOptions.startTime = this.cache_.currentTime;
|
||||
}
|
||||
|
||||
this.cache_.sources = null;
|
||||
this.cache_.source = source;
|
||||
this.cache_.src = source.src;
|
||||
}
|
||||
|
||||
@@ -1864,7 +1869,10 @@ class Player extends Component {
|
||||
// the tech loop to check for a compatible technology
|
||||
this.sourceList_([source]);
|
||||
} else {
|
||||
this.cache_.sources = null;
|
||||
this.cache_.source = source;
|
||||
this.cache_.src = source.src;
|
||||
|
||||
this.currentType_ = source.type || '';
|
||||
|
||||
// wait until the tech is ready to set the source
|
||||
@@ -1913,6 +1921,8 @@ class Player extends Component {
|
||||
// load this technology with the chosen source
|
||||
this.loadTech_(sourceTech.tech, sourceTech.source);
|
||||
}
|
||||
|
||||
this.cache_.sources = sources;
|
||||
} else {
|
||||
// We need to wrap this in a timeout to give folks a chance to add error event handlers
|
||||
this.setTimeout(function() {
|
||||
@@ -1947,6 +1957,41 @@ class Player extends Component {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current source objects.
|
||||
*
|
||||
* @return {Object[]} The current source objects
|
||||
* @method currentSources
|
||||
*/
|
||||
currentSources() {
|
||||
const source = this.currentSource();
|
||||
const sources = [];
|
||||
|
||||
// assume `{}` or `{ src }`
|
||||
if (Object.keys(source).length !== 0) {
|
||||
sources.push(source);
|
||||
}
|
||||
|
||||
return this.cache_.sources || sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current source object.
|
||||
*
|
||||
* @return {Object} The current source object
|
||||
* @method currentSource
|
||||
*/
|
||||
currentSource() {
|
||||
const source = {};
|
||||
const src = this.currentSrc();
|
||||
|
||||
if (src) {
|
||||
source.src = src;
|
||||
}
|
||||
|
||||
return this.cache_.source || source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
|
||||
* Can be used in conjuction with `currentType` to assist in rebuilding the current source object.
|
||||
@@ -2476,13 +2521,20 @@ class Player extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a remote text track
|
||||
* Creates a remote text track object and returns an html track element.
|
||||
*
|
||||
* @param {Object} options Options for remote text track
|
||||
* @param {Object} options The object should contain values for
|
||||
* kind, language, label, and src (location of the WebVTT file)
|
||||
* @param {Boolean} [manualCleanup=true] if set to false, the TextTrack will be
|
||||
* automatically removed from the video element whenever the source changes
|
||||
* @return {HTMLTrackElement} An Html Track Element.
|
||||
* This can be an emulated {@link HTMLTrackElement} or a native one.
|
||||
* @deprecated The default value of the "manualCleanup" parameter will default
|
||||
* to "false" in upcoming versions of Video.js
|
||||
*/
|
||||
addRemoteTextTrack(options) {
|
||||
addRemoteTextTrack(options, manualCleanup) {
|
||||
if (this.tech_) {
|
||||
return this.tech_.addRemoteTextTrack(options);
|
||||
return this.tech_.addRemoteTextTrack(options, manualCleanup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2623,6 +2675,10 @@ class Player extends Component {
|
||||
const tagOptions = Dom.getElAttributes(tag);
|
||||
const dataSetup = tagOptions['data-setup'];
|
||||
|
||||
if (Dom.hasElClass(tag, 'vjs-fluid')) {
|
||||
tagOptions.fluid = true;
|
||||
}
|
||||
|
||||
// Check if data-setup attr exists.
|
||||
if (dataSetup !== null) {
|
||||
// Parse options JSON
|
||||
|
||||
@@ -485,8 +485,16 @@ Flash.checkReady = function(tech) {
|
||||
// Trigger events from the swf on the player
|
||||
Flash.onEvent = function(swfID, eventName) {
|
||||
const tech = Dom.getEl(swfID).tech;
|
||||
const args = Array.prototype.slice.call(arguments, 2);
|
||||
|
||||
tech.trigger(eventName, Array.prototype.slice.call(arguments, 2));
|
||||
// dispatch Flash events asynchronously for two reasons:
|
||||
// - Flash swallows any exceptions generated by javascript it
|
||||
// invokes
|
||||
// - Flash is suspended until the javascript returns which may cause
|
||||
// playback performance issues
|
||||
tech.setTimeout(function() {
|
||||
tech.trigger(eventName, args);
|
||||
}, 1);
|
||||
};
|
||||
|
||||
// Log errors from the swf
|
||||
|
||||
+129
-97
@@ -428,7 +428,30 @@ class Html5 extends Tech {
|
||||
* @return {Number}
|
||||
*/
|
||||
duration() {
|
||||
return this.el_.duration || 0;
|
||||
// Android Chrome will report duration as Infinity for VOD HLS until after
|
||||
// playback has started, which triggers the live display erroneously.
|
||||
// Return NaN if playback has not started and trigger a durationupdate once
|
||||
// the duration can be reliably known.
|
||||
if (this.el_.duration === Infinity &&
|
||||
browser.IS_ANDROID && browser.IS_CHROME) {
|
||||
if (this.el_.currentTime === 0) {
|
||||
// Wait for the first `timeupdate` with currentTime > 0 - there may be
|
||||
// several with 0
|
||||
const checkProgress = () => {
|
||||
if (this.el_.currentTime > 0) {
|
||||
// Trigger durationchange for genuinely live video
|
||||
if (this.el_.duration === Infinity) {
|
||||
this.trigger('durationchange');
|
||||
}
|
||||
this.off(this.player_, 'timeupdate', checkProgress);
|
||||
}
|
||||
};
|
||||
|
||||
this.on(this.player_, 'timeupdate', checkProgress);
|
||||
return NaN;
|
||||
}
|
||||
}
|
||||
return this.el_.duration || NaN;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -585,17 +608,16 @@ class Html5 extends Tech {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a remote text track object and returns a html track element
|
||||
* Creates either native TextTrack or an emulated TextTrack depending
|
||||
* on the value of `featuresNativeTextTracks`
|
||||
*
|
||||
* @param {Object} options The object should contain values for
|
||||
* kind, language, label and src (location of the WebVTT file)
|
||||
* @return {HTMLTrackElement}
|
||||
*/
|
||||
addRemoteTextTrack(options = {}) {
|
||||
createRemoteTextTrack(options) {
|
||||
if (!this.featuresNativeTextTracks) {
|
||||
return super.addRemoteTextTrack(options);
|
||||
return super.createRemoteTextTrack(options);
|
||||
}
|
||||
|
||||
const htmlTrackElement = document.createElement('track');
|
||||
|
||||
if (options.kind) {
|
||||
@@ -617,11 +639,27 @@ class Html5 extends Tech {
|
||||
htmlTrackElement.src = options.src;
|
||||
}
|
||||
|
||||
this.el().appendChild(htmlTrackElement);
|
||||
return htmlTrackElement;
|
||||
}
|
||||
|
||||
// store HTMLTrackElement and TextTrack to remote list
|
||||
this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
|
||||
this.remoteTextTracks().addTrack_(htmlTrackElement.track);
|
||||
/**
|
||||
* Creates a remote text track object and returns an html track element.
|
||||
*
|
||||
* @param {Object} options The object should contain values for
|
||||
* kind, language, label, and src (location of the WebVTT file)
|
||||
* @param {Boolean} [manualCleanup=true] if set to false, the TextTrack will be
|
||||
* automatically removed from the video element whenever the source changes
|
||||
* @return {HTMLTrackElement} An Html Track Element.
|
||||
* This can be an emulated {@link HTMLTrackElement} or a native one.
|
||||
* @deprecated The default value of the "manualCleanup" parameter will default
|
||||
* to "false" in upcoming versions of Video.js
|
||||
*/
|
||||
addRemoteTextTrack(options, manualCleanup) {
|
||||
const htmlTrackElement = super.addRemoteTextTrack(options, manualCleanup);
|
||||
|
||||
if (this.featuresNativeTextTracks) {
|
||||
this.el().appendChild(htmlTrackElement);
|
||||
}
|
||||
|
||||
return htmlTrackElement;
|
||||
}
|
||||
@@ -632,23 +670,17 @@ class Html5 extends Tech {
|
||||
* @param {TextTrackObject} track Texttrack object to remove
|
||||
*/
|
||||
removeRemoteTextTrack(track) {
|
||||
if (!this.featuresNativeTextTracks) {
|
||||
return super.removeRemoteTextTrack(track);
|
||||
}
|
||||
super.removeRemoteTextTrack(track);
|
||||
|
||||
const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
|
||||
if (this.featuresNativeTextTracks) {
|
||||
const tracks = this.$$('track');
|
||||
|
||||
// remove HTMLTrackElement and TextTrack from remote list
|
||||
this.remoteTextTrackEls().removeTrackElement_(trackElement);
|
||||
this.remoteTextTracks().removeTrack_(track);
|
||||
let i = tracks.length;
|
||||
|
||||
const tracks = this.$$('track');
|
||||
|
||||
let i = tracks.length;
|
||||
|
||||
while (i--) {
|
||||
if (track === tracks[i] || track === tracks[i].track) {
|
||||
this.el().removeChild(tracks[i]);
|
||||
while (i--) {
|
||||
if (track === tracks[i] || track === tracks[i].track) {
|
||||
this.el().removeChild(tracks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -688,79 +720,6 @@ Html5.isSupported = function() {
|
||||
return !!Html5.TEST_VID.canPlayType;
|
||||
};
|
||||
|
||||
// Add Source Handler pattern functions to this tech
|
||||
Tech.withSourceHandlers(Html5);
|
||||
|
||||
/**
|
||||
* The default native source handler.
|
||||
* This simply passes the source to the video element. Nothing fancy.
|
||||
*
|
||||
* @param {Object} source The source object
|
||||
* @param {Html5} tech The instance of the HTML5 tech
|
||||
*/
|
||||
Html5.nativeSourceHandler = {};
|
||||
|
||||
/**
|
||||
* Check if the video element can play the given videotype
|
||||
*
|
||||
* @param {String} type The mimetype to check
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Html5.nativeSourceHandler.canPlayType = function(type) {
|
||||
// IE9 on Windows 7 without MediaPlayer throws an error here
|
||||
// https://github.com/videojs/video.js/issues/519
|
||||
try {
|
||||
return Html5.TEST_VID.canPlayType(type);
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the video element can handle the source natively
|
||||
*
|
||||
* @param {Object} source The source object
|
||||
* @param {Object} options The options passed to the tech
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Html5.nativeSourceHandler.canHandleSource = function(source, options) {
|
||||
|
||||
// If a type was provided we should rely on that
|
||||
if (source.type) {
|
||||
return Html5.nativeSourceHandler.canPlayType(source.type);
|
||||
|
||||
// If no type, fall back to checking 'video/[EXTENSION]'
|
||||
} else if (source.src) {
|
||||
const ext = Url.getFileExtension(source.src);
|
||||
|
||||
return Html5.nativeSourceHandler.canPlayType(`video/${ext}`);
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Pass the source to the video element
|
||||
* Adaptive source handlers will have more complicated workflows before passing
|
||||
* video data to the video element
|
||||
*
|
||||
* @param {Object} source The source object
|
||||
* @param {Html5} tech The instance of the Html5 tech
|
||||
* @param {Object} options The options to pass to the source
|
||||
*/
|
||||
Html5.nativeSourceHandler.handleSource = function(source, tech, options) {
|
||||
tech.setSrc(source.src);
|
||||
};
|
||||
|
||||
/*
|
||||
* Clean up the source handler when disposing the player or switching sources..
|
||||
* (no cleanup is needed when supporting the format natively)
|
||||
*/
|
||||
Html5.nativeSourceHandler.dispose = function() {};
|
||||
|
||||
// Register the native source handler
|
||||
Html5.registerSourceHandler(Html5.nativeSourceHandler);
|
||||
|
||||
/**
|
||||
* Check if the volume can be changed in this browser/device.
|
||||
* Volume cannot be changed in a lot of mobile devices.
|
||||
@@ -1314,6 +1273,79 @@ Html5.resetMediaElement = function(el) {
|
||||
};
|
||||
});
|
||||
|
||||
// Add Source Handler pattern functions to this tech
|
||||
Tech.withSourceHandlers(Html5);
|
||||
|
||||
/**
|
||||
* The default native source handler.
|
||||
* This simply passes the source to the video element. Nothing fancy.
|
||||
*
|
||||
* @param {Object} source The source object
|
||||
* @param {Html5} tech The instance of the HTML5 tech
|
||||
*/
|
||||
Html5.nativeSourceHandler = {};
|
||||
|
||||
/**
|
||||
* Check if the video element can play the given videotype
|
||||
*
|
||||
* @param {String} type The mimetype to check
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Html5.nativeSourceHandler.canPlayType = function(type) {
|
||||
// IE9 on Windows 7 without MediaPlayer throws an error here
|
||||
// https://github.com/videojs/video.js/issues/519
|
||||
try {
|
||||
return Html5.TEST_VID.canPlayType(type);
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the video element can handle the source natively
|
||||
*
|
||||
* @param {Object} source The source object
|
||||
* @param {Object} options The options passed to the tech
|
||||
* @return {String} 'probably', 'maybe', or '' (empty string)
|
||||
*/
|
||||
Html5.nativeSourceHandler.canHandleSource = function(source, options) {
|
||||
|
||||
// If a type was provided we should rely on that
|
||||
if (source.type) {
|
||||
return Html5.nativeSourceHandler.canPlayType(source.type);
|
||||
|
||||
// If no type, fall back to checking 'video/[EXTENSION]'
|
||||
} else if (source.src) {
|
||||
const ext = Url.getFileExtension(source.src);
|
||||
|
||||
return Html5.nativeSourceHandler.canPlayType(`video/${ext}`);
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Pass the source to the video element
|
||||
* Adaptive source handlers will have more complicated workflows before passing
|
||||
* video data to the video element
|
||||
*
|
||||
* @param {Object} source The source object
|
||||
* @param {Html5} tech The instance of the Html5 tech
|
||||
* @param {Object} options The options to pass to the source
|
||||
*/
|
||||
Html5.nativeSourceHandler.handleSource = function(source, tech, options) {
|
||||
tech.setSrc(source.src);
|
||||
};
|
||||
|
||||
/*
|
||||
* Clean up the source handler when disposing the player or switching sources..
|
||||
* (no cleanup is needed when supporting the format natively)
|
||||
*/
|
||||
Html5.nativeSourceHandler.dispose = function() {};
|
||||
|
||||
// Register the native source handler
|
||||
Html5.registerSourceHandler(Html5.nativeSourceHandler);
|
||||
|
||||
Component.registerComponent('Html5', Html5);
|
||||
Tech.registerTech('Html5', Html5);
|
||||
export default Html5;
|
||||
|
||||
+101
-35
@@ -85,9 +85,11 @@ class Tech extends Component {
|
||||
}
|
||||
|
||||
if (!this.featuresNativeTextTracks) {
|
||||
this.on('ready', this.emulateTextTracks);
|
||||
this.emulateTextTracks();
|
||||
}
|
||||
|
||||
this.autoRemoteTextTracks_ = new TextTrackList();
|
||||
|
||||
this.initTextTrackListeners();
|
||||
this.initTrackListeners();
|
||||
|
||||
@@ -291,6 +293,23 @@ class Tech extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any TextTracks added via addRemoteTextTrack that are
|
||||
* flagged for automatic garbage collection
|
||||
*
|
||||
* @method cleanupAutoTextTracks
|
||||
*/
|
||||
cleanupAutoTextTracks() {
|
||||
const list = this.autoRemoteTextTracks_ || [];
|
||||
let i = list.length;
|
||||
|
||||
while (i--) {
|
||||
const track = list[i];
|
||||
|
||||
this.removeRemoteTextTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the tech. Removes all sources and resets readyState.
|
||||
*
|
||||
@@ -394,17 +413,11 @@ class Tech extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulate texttracks
|
||||
* Add vtt.js if necessary
|
||||
*
|
||||
* @method emulateTextTracks
|
||||
* @private
|
||||
*/
|
||||
emulateTextTracks() {
|
||||
const tracks = this.textTracks();
|
||||
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
addWebVttScript_() {
|
||||
if (!window.WebVTT && this.el().parentNode !== null && this.el().parentNode !== undefined) {
|
||||
const script = document.createElement('script');
|
||||
|
||||
@@ -424,6 +437,32 @@ class Tech extends Component {
|
||||
window.WebVTT = true;
|
||||
this.el().parentNode.appendChild(script);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulate texttracks
|
||||
*
|
||||
* @method emulateTextTracks
|
||||
*/
|
||||
emulateTextTracks() {
|
||||
const tracks = this.textTracks();
|
||||
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.remoteTextTracks().on('addtrack', (e) => {
|
||||
this.textTracks().addTrack_(e.track);
|
||||
});
|
||||
|
||||
this.remoteTextTracks().on('removetrack', (e) => {
|
||||
this.textTracks().removeTrack_(e.track);
|
||||
});
|
||||
|
||||
// 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
|
||||
// before inserting the WebVTT script
|
||||
this.on('ready', this.addWebVttScript_);
|
||||
|
||||
const updateDisplay = () => this.trigger('texttrackchange');
|
||||
const textTracksChanges = () => {
|
||||
@@ -527,26 +566,51 @@ class Tech extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a remote text track object and returns a emulated html track element
|
||||
* Create an emulated TextTrack for use by addRemoteTextTrack
|
||||
*
|
||||
* This is intended to be overridden by classes that inherit from
|
||||
* Tech in order to create native or custom TextTracks.
|
||||
*
|
||||
* @param {Object} options The object should contain values for
|
||||
* kind, language, label and src (location of the WebVTT file)
|
||||
* @return {HTMLTrackElement}
|
||||
* @method addRemoteTextTrack
|
||||
*/
|
||||
addRemoteTextTrack(options) {
|
||||
createRemoteTextTrack(options) {
|
||||
const track = mergeOptions(options, {
|
||||
tech: this
|
||||
});
|
||||
|
||||
const htmlTrackElement = new HTMLTrackElement(track);
|
||||
return new HTMLTrackElement(track);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a remote text track object and returns an html track element.
|
||||
*
|
||||
* @param {Object} options The object should contain values for
|
||||
* kind, language, label, and src (location of the WebVTT file)
|
||||
* @param {Boolean} [manualCleanup=true] if set to false, the TextTrack will be
|
||||
* automatically removed from the video element whenever the source changes
|
||||
* @return {HTMLTrackElement} An Html Track Element.
|
||||
* This can be an emulated {@link HTMLTrackElement} or a native one.
|
||||
* @deprecated The default value of the "manualCleanup" parameter will default
|
||||
* to "false" in upcoming versions of Video.js
|
||||
*/
|
||||
addRemoteTextTrack(options = {}, manualCleanup) {
|
||||
const htmlTrackElement = this.createRemoteTextTrack(options);
|
||||
|
||||
if (manualCleanup !== true && manualCleanup !== false) {
|
||||
// deprecation warning
|
||||
log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
|
||||
manualCleanup = true;
|
||||
}
|
||||
|
||||
// store HTMLTrackElement and TextTrack to remote list
|
||||
this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
|
||||
this.remoteTextTracks().addTrack_(htmlTrackElement.track);
|
||||
|
||||
// must come after remoteTextTracks()
|
||||
this.textTracks().addTrack_(htmlTrackElement.track);
|
||||
if (manualCleanup !== true) {
|
||||
// create the TextTrackList if it doesn't exist
|
||||
this.autoRemoteTextTracks_.addTrack_(htmlTrackElement.track);
|
||||
}
|
||||
|
||||
return htmlTrackElement;
|
||||
}
|
||||
@@ -558,13 +622,12 @@ class Tech extends Component {
|
||||
* @method removeRemoteTextTrack
|
||||
*/
|
||||
removeRemoteTextTrack(track) {
|
||||
this.textTracks().removeTrack_(track);
|
||||
|
||||
const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
|
||||
|
||||
// remove HTMLTrackElement and TextTrack from remote list
|
||||
this.remoteTextTrackEls().removeTrackElement_(trackElement);
|
||||
this.remoteTextTracks().removeTrack_(track);
|
||||
this.autoRemoteTextTracks_.removeTrack_(track);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -685,7 +748,7 @@ Tech.prototype.featuresNativeTextTracks = false;
|
||||
*
|
||||
* ##### EXAMPLE:
|
||||
*
|
||||
* Tech.withSourceHandlers.call(MyTech);
|
||||
* Tech.withSourceHandlers(MyTech);
|
||||
*
|
||||
*/
|
||||
Tech.withSourceHandlers = function(_Tech) {
|
||||
@@ -696,7 +759,7 @@ Tech.withSourceHandlers = function(_Tech) {
|
||||
* 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)
|
||||
* @param {Function} handler The source handler
|
||||
* @param {Boolean} first Register it before any existing handlers
|
||||
* @param {Number} index The index to register the handler among existing handlers
|
||||
*/
|
||||
_Tech.registerSourceHandler = function(handler, index) {
|
||||
let handlers = _Tech.sourceHandlers;
|
||||
@@ -820,17 +883,7 @@ Tech.withSourceHandlers = function(_Tech) {
|
||||
this.disposeSourceHandler();
|
||||
this.off('dispose', this.disposeSourceHandler);
|
||||
|
||||
// if we have a source and get another one
|
||||
// then we are loading something new
|
||||
// than clear all of our current tracks
|
||||
if (this.currentSource_) {
|
||||
this.clearTracks(['audio', 'video']);
|
||||
|
||||
this.currentSource_ = null;
|
||||
}
|
||||
|
||||
if (sh !== _Tech.nativeSourceHandler) {
|
||||
|
||||
this.currentSource_ = source;
|
||||
|
||||
// Catch if someone replaced the src without calling setSource.
|
||||
@@ -838,7 +891,6 @@ Tech.withSourceHandlers = function(_Tech) {
|
||||
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_);
|
||||
@@ -854,7 +906,6 @@ Tech.withSourceHandlers = function(_Tech) {
|
||||
|
||||
// On successive loadstarts when setSource has not been called again
|
||||
_Tech.prototype.successiveLoadStartListener_ = function() {
|
||||
this.currentSource_ = null;
|
||||
this.disposeSourceHandler();
|
||||
this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
|
||||
};
|
||||
@@ -863,10 +914,25 @@ Tech.withSourceHandlers = function(_Tech) {
|
||||
* Clean up any existing source handler
|
||||
*/
|
||||
_Tech.prototype.disposeSourceHandler = function() {
|
||||
if (this.sourceHandler_ && this.sourceHandler_.dispose) {
|
||||
// if we have a source and get another one
|
||||
// then we are loading something new
|
||||
// than clear all of our current tracks
|
||||
if (this.currentSource_) {
|
||||
this.clearTracks(['audio', 'video']);
|
||||
this.currentSource_ = null;
|
||||
}
|
||||
|
||||
// always clean up auto-text tracks
|
||||
this.cleanupAutoTextTracks();
|
||||
|
||||
if (this.sourceHandler_) {
|
||||
this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
|
||||
this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
|
||||
this.sourceHandler_.dispose();
|
||||
|
||||
if (this.sourceHandler_.dispose) {
|
||||
this.sourceHandler_.dispose();
|
||||
}
|
||||
|
||||
this.sourceHandler_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -37,7 +37,20 @@ class HtmlTrackElementList {
|
||||
}
|
||||
|
||||
addTrackElement_(trackElement) {
|
||||
this.trackElements_.push(trackElement);
|
||||
const index = this.trackElements_.length;
|
||||
|
||||
if (!('' + index in this)) {
|
||||
Object.defineProperty(this, index, {
|
||||
get() {
|
||||
return this.trackElements_[index];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Do not add duplicate elements
|
||||
if (this.trackElements_.indexOf(trackElement) === -1) {
|
||||
this.trackElements_.push(trackElement);
|
||||
}
|
||||
}
|
||||
|
||||
getTrackElementByTrack_(track) {
|
||||
|
||||
@@ -145,7 +145,7 @@ class TextTrackDisplay extends Component {
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-text-track-display'
|
||||
}, {
|
||||
'aria-live': 'assertive',
|
||||
'aria-live': 'off',
|
||||
'aria-atomic': 'true'
|
||||
});
|
||||
}
|
||||
@@ -197,8 +197,14 @@ class TextTrackDisplay extends Component {
|
||||
}
|
||||
|
||||
if (captionsSubtitlesTrack) {
|
||||
if (this.getAttribute('aria-live') !== 'off') {
|
||||
this.setAttribute('aria-live', 'off');
|
||||
}
|
||||
this.updateForTrack(captionsSubtitlesTrack);
|
||||
} else if (descriptionsTrack) {
|
||||
if (this.getAttribute('aria-live') !== 'assertive') {
|
||||
this.setAttribute('aria-live', 'assertive');
|
||||
}
|
||||
this.updateForTrack(descriptionsTrack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,168 +1,220 @@
|
||||
/**
|
||||
* @file text-track-settings.js
|
||||
*/
|
||||
import Component from '../component';
|
||||
import * as Events from '../utils/events.js';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import log from '../utils/log.js';
|
||||
import safeParseTuple from 'safe-json-parse/tuple';
|
||||
import window from 'global/window';
|
||||
import Component from '../component';
|
||||
import {createEl} from '../utils/dom';
|
||||
import * as Fn from '../utils/fn';
|
||||
import * as Obj from '../utils/obj';
|
||||
import log from '../utils/log';
|
||||
|
||||
function captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId) {
|
||||
const template = `
|
||||
<div role="document">
|
||||
<div role="heading" aria-level="1" id="${dialogLabelId}" class="vjs-control-text">Captions Settings Dialog</div>
|
||||
<div id="${dialogDescriptionId}" class="vjs-control-text">Beginning of dialog window. Escape will cancel and close the window.</div>
|
||||
<div class="vjs-tracksettings">
|
||||
<div class="vjs-tracksettings-colors">
|
||||
<fieldset class="vjs-fg-color vjs-tracksetting">
|
||||
<legend>Text</legend>
|
||||
<label class="vjs-label" for="captions-foreground-color-${uniqueId}">Color</label>
|
||||
<select id="captions-foreground-color-${uniqueId}">
|
||||
<option value="#FFF" selected>White</option>
|
||||
<option value="#000">Black</option>
|
||||
<option value="#F00">Red</option>
|
||||
<option value="#0F0">Green</option>
|
||||
<option value="#00F">Blue</option>
|
||||
<option value="#FF0">Yellow</option>
|
||||
<option value="#F0F">Magenta</option>
|
||||
<option value="#0FF">Cyan</option>
|
||||
</select>
|
||||
<span class="vjs-text-opacity vjs-opacity">
|
||||
<label class="vjs-label" for="captions-foreground-opacity-${uniqueId}">Transparency</label>
|
||||
<select id="captions-foreground-opacity-${uniqueId}">
|
||||
<option value="1" selected>Opaque</option>
|
||||
<option value="0.5">Semi-Opaque</option>
|
||||
</select>
|
||||
</span>
|
||||
</fieldset>
|
||||
<fieldset class="vjs-bg-color vjs-tracksetting">
|
||||
<legend>Background</legend>
|
||||
<label class="vjs-label" for="captions-background-color-${uniqueId}">Color</label>
|
||||
<select id="captions-background-color-${uniqueId}">
|
||||
<option value="#000" selected>Black</option>
|
||||
<option value="#FFF">White</option>
|
||||
<option value="#F00">Red</option>
|
||||
<option value="#0F0">Green</option>
|
||||
<option value="#00F">Blue</option>
|
||||
<option value="#FF0">Yellow</option>
|
||||
<option value="#F0F">Magenta</option>
|
||||
<option value="#0FF">Cyan</option>
|
||||
</select>
|
||||
<span class="vjs-bg-opacity vjs-opacity">
|
||||
<label class="vjs-label" for="captions-background-opacity-${uniqueId}">Transparency</label>
|
||||
<select id="captions-background-opacity-${uniqueId}">
|
||||
<option value="1" selected>Opaque</option>
|
||||
<option value="0.5">Semi-Transparent</option>
|
||||
<option value="0">Transparent</option>
|
||||
</select>
|
||||
</span>
|
||||
</fieldset>
|
||||
<fieldset class="window-color vjs-tracksetting">
|
||||
<legend>Window</legend>
|
||||
<label class="vjs-label" for="captions-window-color-${uniqueId}">Color</label>
|
||||
<select id="captions-window-color-${uniqueId}">
|
||||
<option value="#000" selected>Black</option>
|
||||
<option value="#FFF">White</option>
|
||||
<option value="#F00">Red</option>
|
||||
<option value="#0F0">Green</option>
|
||||
<option value="#00F">Blue</option>
|
||||
<option value="#FF0">Yellow</option>
|
||||
<option value="#F0F">Magenta</option>
|
||||
<option value="#0FF">Cyan</option>
|
||||
</select>
|
||||
<span class="vjs-window-opacity vjs-opacity">
|
||||
<label class="vjs-label" for="captions-window-opacity-${uniqueId}">Transparency</label>
|
||||
<select id="captions-window-opacity-${uniqueId}">
|
||||
<option value="0" selected>Transparent</option>
|
||||
<option value="0.5">Semi-Transparent</option>
|
||||
<option value="1">Opaque</option>
|
||||
</select>
|
||||
</span>
|
||||
</fieldset>
|
||||
</div> <!-- vjs-tracksettings-colors -->
|
||||
<div class="vjs-tracksettings-font">
|
||||
<div class="vjs-font-percent vjs-tracksetting">
|
||||
<label class="vjs-label" for="captions-font-size-${uniqueId}">Font Size</label>
|
||||
<select id="captions-font-size-${uniqueId}">
|
||||
<option value="0.50">50%</option>
|
||||
<option value="0.75">75%</option>
|
||||
<option value="1.00" selected>100%</option>
|
||||
<option value="1.25">125%</option>
|
||||
<option value="1.50">150%</option>
|
||||
<option value="1.75">175%</option>
|
||||
<option value="2.00">200%</option>
|
||||
<option value="3.00">300%</option>
|
||||
<option value="4.00">400%</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="vjs-edge-style vjs-tracksetting">
|
||||
<label class="vjs-label" for="captions-edge-style-${uniqueId}">Text Edge Style</label>
|
||||
<select id="captions-edge-style-${uniqueId}">
|
||||
<option value="none" selected>None</option>
|
||||
<option value="raised">Raised</option>
|
||||
<option value="depressed">Depressed</option>
|
||||
<option value="uniform">Uniform</option>
|
||||
<option value="dropshadow">Dropshadow</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="vjs-font-family vjs-tracksetting">
|
||||
<label class="vjs-label" for="captions-font-family-${uniqueId}">Font Family</label>
|
||||
<select id="captions-font-family-${uniqueId}">
|
||||
<option value="proportionalSansSerif" selected>Proportional Sans-Serif</option>
|
||||
<option value="monospaceSansSerif">Monospace Sans-Serif</option>
|
||||
<option value="proportionalSerif">Proportional Serif</option>
|
||||
<option value="monospaceSerif">Monospace Serif</option>
|
||||
<option value="casual">Casual</option>
|
||||
<option value="script">Script</option>
|
||||
<option value="small-caps">Small Caps</option>
|
||||
</select>
|
||||
</div>
|
||||
</div> <!-- vjs-tracksettings-font -->
|
||||
<div class="vjs-tracksettings-controls">
|
||||
<button class="vjs-default-button">Defaults</button>
|
||||
<button class="vjs-done-button">Done</button>
|
||||
</div>
|
||||
</div> <!-- vjs-tracksettings -->
|
||||
</div> <!-- role="document" -->
|
||||
`;
|
||||
const LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
|
||||
|
||||
return template;
|
||||
}
|
||||
const COLOR_BLACK = ['#000', 'Black'];
|
||||
const COLOR_BLUE = ['#00F', 'Blue'];
|
||||
const COLOR_CYAN = ['#0FF', 'Cyan'];
|
||||
const COLOR_GREEN = ['#0F0', 'Green'];
|
||||
const COLOR_MAGENTA = ['#F0F', 'Magenta'];
|
||||
const COLOR_RED = ['#F00', 'Red'];
|
||||
const COLOR_WHITE = ['#FFF', 'White'];
|
||||
const COLOR_YELLOW = ['#FF0', 'Yellow'];
|
||||
|
||||
function getSelectedOptionValue(target) {
|
||||
let selectedOption;
|
||||
const OPACITY_OPAQUE = ['1', 'Opaque'];
|
||||
const OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
|
||||
const OPACITY_TRANS = ['0', 'Transparent'];
|
||||
|
||||
// not all browsers support selectedOptions, so, fallback to options
|
||||
if (target.selectedOptions) {
|
||||
selectedOption = target.selectedOptions[0];
|
||||
} else if (target.options) {
|
||||
selectedOption = target.options[target.options.selectedIndex];
|
||||
// Configuration for the various <select> elements in the DOM of this component.
|
||||
//
|
||||
// Possible keys include:
|
||||
//
|
||||
// `default`:
|
||||
// The default option index. Only needs to be provided if not zero.
|
||||
// `parser`:
|
||||
// A function which is used to parse the value from the selected option in
|
||||
// a customized way.
|
||||
// `selector`:
|
||||
// The selector used to find the associated <select> element.
|
||||
const selectConfigs = {
|
||||
backgroundColor: {
|
||||
selector: '.vjs-bg-color > select',
|
||||
id: 'captions-background-color-%s',
|
||||
label: 'Color',
|
||||
options: [
|
||||
COLOR_BLACK,
|
||||
COLOR_WHITE,
|
||||
COLOR_RED,
|
||||
COLOR_GREEN,
|
||||
COLOR_BLUE,
|
||||
COLOR_YELLOW,
|
||||
COLOR_MAGENTA,
|
||||
COLOR_CYAN
|
||||
]
|
||||
},
|
||||
|
||||
backgroundOpacity: {
|
||||
selector: '.vjs-bg-opacity > select',
|
||||
id: 'captions-background-opacity-%s',
|
||||
label: 'Transparency',
|
||||
options: [
|
||||
OPACITY_OPAQUE,
|
||||
OPACITY_SEMI,
|
||||
OPACITY_TRANS
|
||||
]
|
||||
},
|
||||
|
||||
color: {
|
||||
selector: '.vjs-fg-color > select',
|
||||
id: 'captions-foreground-color-%s',
|
||||
label: 'Color',
|
||||
options: [
|
||||
COLOR_WHITE,
|
||||
COLOR_BLACK,
|
||||
COLOR_RED,
|
||||
COLOR_GREEN,
|
||||
COLOR_BLUE,
|
||||
COLOR_YELLOW,
|
||||
COLOR_MAGENTA,
|
||||
COLOR_CYAN
|
||||
]
|
||||
},
|
||||
|
||||
edgeStyle: {
|
||||
selector: '.vjs-edge-style > select',
|
||||
id: '%s',
|
||||
label: 'Text Edge Style',
|
||||
options: [
|
||||
['none', 'None'],
|
||||
['raised', 'Raised'],
|
||||
['depressed', 'Depressed'],
|
||||
['uniform', 'Uniform'],
|
||||
['dropshadow', 'Dropshadow']
|
||||
]
|
||||
},
|
||||
|
||||
fontFamily: {
|
||||
selector: '.vjs-font-family > select',
|
||||
id: 'captions-font-family-%s',
|
||||
label: 'Font Family',
|
||||
options: [
|
||||
['proportionalSansSerif', 'Proportional Sans-Serif'],
|
||||
['monospaceSansSerif', 'Monospace Sans-Serif'],
|
||||
['proportionalSerif', 'Proportional Serif'],
|
||||
['monospaceSerif', 'Monospace Serif'],
|
||||
['casual', 'Casual'],
|
||||
['script', 'Script'],
|
||||
['small-caps', 'Small Caps']
|
||||
]
|
||||
},
|
||||
|
||||
fontPercent: {
|
||||
selector: '.vjs-font-percent > select',
|
||||
id: 'captions-font-size-%s',
|
||||
label: 'Font Size',
|
||||
options: [
|
||||
['0.50', '50%'],
|
||||
['0.75', '75%'],
|
||||
['1.00', '100%'],
|
||||
['1.25', '125%'],
|
||||
['1.50', '150%'],
|
||||
['1.75', '175%'],
|
||||
['2.00', '200%'],
|
||||
['3.00', '300%'],
|
||||
['4.00', '400%']
|
||||
],
|
||||
default: 2,
|
||||
parser: (v) => v === '1.00' ? null : Number(v)
|
||||
},
|
||||
|
||||
textOpacity: {
|
||||
selector: '.vjs-text-opacity > select',
|
||||
id: 'captions-foreground-opacity-%s',
|
||||
label: 'Transparency',
|
||||
options: [
|
||||
OPACITY_OPAQUE,
|
||||
OPACITY_SEMI
|
||||
]
|
||||
},
|
||||
|
||||
// Options for this object are defined below.
|
||||
windowColor: {
|
||||
selector: '.vjs-window-color > select',
|
||||
id: 'captions-window-color-%s',
|
||||
label: 'Color'
|
||||
},
|
||||
|
||||
// Options for this object are defined below.
|
||||
windowOpacity: {
|
||||
selector: '.vjs-window-opacity > select',
|
||||
id: 'captions-window-opacity-%s',
|
||||
label: 'Transparency',
|
||||
options: [
|
||||
OPACITY_TRANS,
|
||||
OPACITY_SEMI,
|
||||
OPACITY_OPAQUE
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
|
||||
|
||||
/**
|
||||
* Parses out option values.
|
||||
*
|
||||
* @private
|
||||
* @param {String} value
|
||||
* @param {Function} [parser]
|
||||
* Optional function to adjust the value.
|
||||
* @return {Mixed}
|
||||
* Will be `undefined` if no value exists (or if given value is "none").
|
||||
*/
|
||||
function parseOptionValue(value, parser) {
|
||||
if (parser) {
|
||||
value = parser(value);
|
||||
}
|
||||
|
||||
return selectedOption.value;
|
||||
if (value && value !== 'none') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectedOption(target, value) {
|
||||
/**
|
||||
* Gets the value of the selected <option> element within a <select> element.
|
||||
*
|
||||
* @param {Object} config
|
||||
* @param {Function} [parser]
|
||||
* Optional function to adjust the value.
|
||||
* @return {Mixed}
|
||||
*/
|
||||
function getSelectedOptionValue(el, parser) {
|
||||
const value = el.options[el.options.selectedIndex].value;
|
||||
|
||||
return parseOptionValue(value, parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected <option> element within a <select> element based on a
|
||||
* given value.
|
||||
*
|
||||
* @param {Object} el
|
||||
* @param {String} value
|
||||
* @param {Function} [parser]
|
||||
* Optional function to adjust the value before comparing.
|
||||
*/
|
||||
function setSelectedOption(el, value, parser) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let i;
|
||||
|
||||
for (i = 0; i < target.options.length; i++) {
|
||||
const option = target.options[i];
|
||||
|
||||
if (option.value === value) {
|
||||
for (let i = 0; i < el.options.length; i++) {
|
||||
if (parseOptionValue(el.options[i].value, parser) === value) {
|
||||
el.selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
target.selectedIndex = i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulate settings of texttracks
|
||||
* Manipulate settings of text tracks
|
||||
*
|
||||
* @param {Object} player Main Player
|
||||
* @param {Object=} options Object of option names and values
|
||||
@@ -173,46 +225,196 @@ class TextTrackSettings extends Component {
|
||||
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.setDefaults();
|
||||
this.hide();
|
||||
|
||||
this.updateDisplay = Fn.bind(this, this.updateDisplay);
|
||||
|
||||
// Grab `persistTextTrackSettings` from the player options if not passed in child options
|
||||
if (options.persistTextTrackSettings === undefined) {
|
||||
this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings;
|
||||
}
|
||||
|
||||
Events.on(this.$('.vjs-done-button'), 'click', Fn.bind(this, function() {
|
||||
this.on(this.$('.vjs-done-button'), 'click', () => {
|
||||
this.saveSettings();
|
||||
this.hide();
|
||||
}));
|
||||
});
|
||||
|
||||
Events.on(this.$('.vjs-default-button'), 'click', Fn.bind(this, function() {
|
||||
this.$('.vjs-fg-color > select').selectedIndex = 0;
|
||||
this.$('.vjs-bg-color > select').selectedIndex = 0;
|
||||
this.$('.window-color > select').selectedIndex = 0;
|
||||
this.$('.vjs-text-opacity > select').selectedIndex = 0;
|
||||
this.$('.vjs-bg-opacity > select').selectedIndex = 0;
|
||||
this.$('.vjs-window-opacity > select').selectedIndex = 0;
|
||||
this.$('.vjs-edge-style select').selectedIndex = 0;
|
||||
this.$('.vjs-font-family select').selectedIndex = 0;
|
||||
this.$('.vjs-font-percent select').selectedIndex = 2;
|
||||
this.on(this.$('.vjs-default-button'), 'click', () => {
|
||||
this.setDefaults();
|
||||
this.updateDisplay();
|
||||
}));
|
||||
});
|
||||
|
||||
Events.on(this.$('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.window-color > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-text-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-bg-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-window-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-font-percent select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-edge-style select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Events.on(this.$('.vjs-font-family select'), 'change', Fn.bind(this, this.updateDisplay));
|
||||
Obj.each(selectConfigs, config => {
|
||||
this.on(this.$(config.selector), 'change', this.updateDisplay);
|
||||
});
|
||||
|
||||
if (this.options_.persistTextTrackSettings) {
|
||||
this.restoreSettings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a <select> element with configured options.
|
||||
*
|
||||
* @private
|
||||
* @return {Element}
|
||||
* @method createElSelect_
|
||||
*/
|
||||
createElSelect_(key) {
|
||||
const config = selectConfigs[key];
|
||||
const id = config.id.replace('%s', this.id_);
|
||||
|
||||
return [
|
||||
createEl('label', {
|
||||
className: 'vjs-label',
|
||||
textContent: config.label
|
||||
}, {
|
||||
for: id
|
||||
}),
|
||||
createEl('select', {id}, undefined, config.options.map(o => {
|
||||
return createEl('option', {
|
||||
textContent: this.localize(o[1]),
|
||||
value: o[0]
|
||||
});
|
||||
}))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create foreground color element for the component
|
||||
*
|
||||
* @private
|
||||
* @return {Element}
|
||||
* @method createElFgColor_
|
||||
*/
|
||||
createElFgColor_() {
|
||||
const legend = createEl('legend', {
|
||||
textContent: this.localize('Text')
|
||||
});
|
||||
|
||||
const select = this.createElSelect_('color');
|
||||
|
||||
const opacity = createEl('span', {
|
||||
className: 'vjs-text-opacity vjs-opacity'
|
||||
}, undefined, this.createElSelect_('textOpacity'));
|
||||
|
||||
return createEl('fieldset', {
|
||||
className: 'vjs-fg-color vjs-tracksetting'
|
||||
}, undefined, [legend].concat(select, opacity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create background color element for the component
|
||||
*
|
||||
* @private
|
||||
* @return {Element}
|
||||
* @method createElBgColor_
|
||||
*/
|
||||
createElBgColor_() {
|
||||
const legend = createEl('legend', {
|
||||
textContent: this.localize('Background')
|
||||
});
|
||||
|
||||
const select = this.createElSelect_('backgroundColor');
|
||||
|
||||
const opacity = createEl('span', {
|
||||
className: 'vjs-bg-opacity vjs-opacity'
|
||||
}, undefined, this.createElSelect_('backgroundOpacity'));
|
||||
|
||||
return createEl('fieldset', {
|
||||
className: 'vjs-bg-color vjs-tracksetting'
|
||||
}, undefined, [legend].concat(select, opacity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create window color element for the component
|
||||
*
|
||||
* @private
|
||||
* @return {Element}
|
||||
* @method createElWinColor_
|
||||
*/
|
||||
createElWinColor_() {
|
||||
const legend = createEl('legend', {
|
||||
textContent: this.localize('Window')
|
||||
});
|
||||
|
||||
const select = this.createElSelect_('windowColor');
|
||||
|
||||
const opacity = createEl('span', {
|
||||
className: 'vjs-window-opacity vjs-opacity'
|
||||
}, undefined, this.createElSelect_('windowOpacity'));
|
||||
|
||||
return createEl('fieldset', {
|
||||
className: 'vjs-window-color vjs-tracksetting'
|
||||
}, undefined, [legend].concat(select, opacity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create color elements for the component
|
||||
*
|
||||
* @private
|
||||
* @return {Element}
|
||||
* @method createElColors_
|
||||
*/
|
||||
createElColors_() {
|
||||
return createEl('div', {
|
||||
className: 'vjs-tracksettings-colors'
|
||||
}, undefined, [
|
||||
this.createElFgColor_(),
|
||||
this.createElBgColor_(),
|
||||
this.createElWinColor_()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create font elements for the component
|
||||
*
|
||||
* @private
|
||||
* @return {Element}
|
||||
* @method createElFont_
|
||||
*/
|
||||
createElFont_() {
|
||||
const fontPercent = createEl('div', {
|
||||
className: 'vjs-font-percent vjs-tracksetting'
|
||||
}, undefined, this.createElSelect_('fontPercent'));
|
||||
|
||||
const edgeStyle = createEl('div', {
|
||||
className: 'vjs-edge-style vjs-tracksetting'
|
||||
}, undefined, this.createElSelect_('edgeStyle'));
|
||||
|
||||
const fontFamily = createEl('div', {
|
||||
className: 'vjs-font-family vjs-tracksetting'
|
||||
}, undefined, this.createElSelect_('fontFamily'));
|
||||
|
||||
return createEl('div', {
|
||||
className: 'vjs-tracksettings-font'
|
||||
}, undefined, [fontPercent, edgeStyle, fontFamily]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create controls for the component
|
||||
*
|
||||
* @private
|
||||
* @return {Element}
|
||||
* @method createElControls_
|
||||
*/
|
||||
createElControls_() {
|
||||
const defaultsButton = createEl('button', {
|
||||
className: 'vjs-default-button',
|
||||
textContent: this.localize('Defaults')
|
||||
});
|
||||
|
||||
const doneButton = createEl('button', {
|
||||
className: 'vjs-done-button',
|
||||
textContent: 'Done'
|
||||
});
|
||||
|
||||
return createEl('div', {
|
||||
className: 'vjs-tracksettings-controls'
|
||||
}, undefined, [defaultsButton, doneButton]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the component's DOM element
|
||||
*
|
||||
@@ -220,99 +422,86 @@ class TextTrackSettings extends Component {
|
||||
* @method createEl
|
||||
*/
|
||||
createEl() {
|
||||
const uniqueId = this.id_;
|
||||
const dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId;
|
||||
const dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId;
|
||||
const settings = createEl('div', {
|
||||
className: 'vjs-tracksettings'
|
||||
}, undefined, [
|
||||
this.createElColors_(),
|
||||
this.createElFont_(),
|
||||
this.createElControls_()
|
||||
]);
|
||||
|
||||
return super.createEl('div', {
|
||||
const heading = createEl('div', {
|
||||
className: 'vjs-control-text',
|
||||
id: `TTsettingsDialogLabel-${this.id_}`,
|
||||
textContent: 'Caption Settings Dialog'
|
||||
}, {
|
||||
'aria-level': '1',
|
||||
'role': 'heading'
|
||||
});
|
||||
|
||||
const description = createEl('div', {
|
||||
className: 'vjs-control-text',
|
||||
id: `TTsettingsDialogDescription-${this.id_}`,
|
||||
textContent: 'Beginning of dialog window. Escape will cancel and close the window.'
|
||||
});
|
||||
|
||||
const doc = createEl('div', undefined, {
|
||||
role: 'document'
|
||||
}, [heading, description, settings]);
|
||||
|
||||
return createEl('div', {
|
||||
className: 'vjs-caption-settings vjs-modal-overlay',
|
||||
innerHTML: captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId),
|
||||
tabIndex: -1
|
||||
}, {
|
||||
'role': 'dialog',
|
||||
'aria-labelledby': dialogLabelId,
|
||||
'aria-describedby': dialogDescriptionId
|
||||
'aria-labelledby': heading.id,
|
||||
'aria-describedby': description.id
|
||||
}, doc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object of text track settings (or null).
|
||||
*
|
||||
* @return {Object}
|
||||
* An object with config values parsed from the DOM or localStorage.
|
||||
* @method getValues
|
||||
*/
|
||||
getValues() {
|
||||
return Obj.reduce(selectConfigs, (accum, config, key) => {
|
||||
const value = getSelectedOptionValue(this.$(config.selector), config.parser);
|
||||
|
||||
if (value !== undefined) {
|
||||
accum[key] = value;
|
||||
}
|
||||
|
||||
return accum;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets text track settings from an object of values.
|
||||
*
|
||||
* @param {Object} values
|
||||
* An object with config values parsed from the DOM or localStorage.
|
||||
* @method setValues
|
||||
*/
|
||||
setValues(values) {
|
||||
Obj.each(selectConfigs, (config, key) => {
|
||||
setSelectedOption(this.$(config.selector), values[key], config.parser);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get texttrack settings
|
||||
* Settings are
|
||||
* .vjs-edge-style
|
||||
* .vjs-font-family
|
||||
* .vjs-fg-color
|
||||
* .vjs-text-opacity
|
||||
* .vjs-bg-color
|
||||
* .vjs-bg-opacity
|
||||
* .window-color
|
||||
* .vjs-window-opacity
|
||||
* Sets all <select> elements to their default values.
|
||||
*
|
||||
* @return {Object}
|
||||
* @method getValues
|
||||
* @method setDefaults
|
||||
*/
|
||||
getValues() {
|
||||
const textEdge = getSelectedOptionValue(this.$('.vjs-edge-style select'));
|
||||
const fontFamily = getSelectedOptionValue(this.$('.vjs-font-family select'));
|
||||
const fgColor = getSelectedOptionValue(this.$('.vjs-fg-color > select'));
|
||||
const textOpacity = getSelectedOptionValue(this.$('.vjs-text-opacity > select'));
|
||||
const bgColor = getSelectedOptionValue(this.$('.vjs-bg-color > select'));
|
||||
const bgOpacity = getSelectedOptionValue(this.$('.vjs-bg-opacity > select'));
|
||||
const windowColor = getSelectedOptionValue(this.$('.window-color > select'));
|
||||
const windowOpacity = getSelectedOptionValue(this.$('.vjs-window-opacity > select'));
|
||||
const fontPercent = window.parseFloat(getSelectedOptionValue(this.$('.vjs-font-percent > select')));
|
||||
setDefaults() {
|
||||
Obj.each(selectConfigs, (config) => {
|
||||
const index = config.hasOwnProperty('default') ? config.default : 0;
|
||||
|
||||
const result = {
|
||||
fontPercent,
|
||||
fontFamily,
|
||||
textOpacity,
|
||||
windowColor,
|
||||
windowOpacity,
|
||||
backgroundOpacity: bgOpacity,
|
||||
edgeStyle: textEdge,
|
||||
color: fgColor,
|
||||
backgroundColor: bgColor
|
||||
};
|
||||
|
||||
for (const name in result) {
|
||||
if (result[name] === '' || result[name] === 'none' || (name === 'fontPercent' && result[name] === 1.00)) {
|
||||
delete result[name];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set texttrack settings
|
||||
* Settings are
|
||||
* .vjs-edge-style
|
||||
* .vjs-font-family
|
||||
* .vjs-fg-color
|
||||
* .vjs-text-opacity
|
||||
* .vjs-bg-color
|
||||
* .vjs-bg-opacity
|
||||
* .window-color
|
||||
* .vjs-window-opacity
|
||||
*
|
||||
* @param {Object} values Object with texttrack setting values
|
||||
* @method setValues
|
||||
*/
|
||||
setValues(values) {
|
||||
setSelectedOption(this.$('.vjs-edge-style select'), values.edgeStyle);
|
||||
setSelectedOption(this.$('.vjs-font-family select'), values.fontFamily);
|
||||
setSelectedOption(this.$('.vjs-fg-color > select'), values.color);
|
||||
setSelectedOption(this.$('.vjs-text-opacity > select'), values.textOpacity);
|
||||
setSelectedOption(this.$('.vjs-bg-color > select'), values.backgroundColor);
|
||||
setSelectedOption(this.$('.vjs-bg-opacity > select'), values.backgroundOpacity);
|
||||
setSelectedOption(this.$('.window-color > select'), values.windowColor);
|
||||
setSelectedOption(this.$('.vjs-window-opacity > select'), values.windowOpacity);
|
||||
|
||||
let fontPercent = values.fontPercent;
|
||||
|
||||
if (fontPercent) {
|
||||
fontPercent = fontPercent.toFixed(2);
|
||||
}
|
||||
|
||||
setSelectedOption(this.$('.vjs-font-percent > select'), fontPercent);
|
||||
this.$(config.selector).selectedIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,17 +510,12 @@ class TextTrackSettings extends Component {
|
||||
* @method restoreSettings
|
||||
*/
|
||||
restoreSettings() {
|
||||
let err;
|
||||
let values;
|
||||
|
||||
try {
|
||||
[err, values] = safeParseTuple(window.localStorage.getItem('vjs-text-track-settings'));
|
||||
|
||||
if (err) {
|
||||
log.error(err);
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn(e);
|
||||
values = JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_KEY));
|
||||
} catch (err) {
|
||||
log.warn(err);
|
||||
}
|
||||
|
||||
if (values) {
|
||||
@@ -340,7 +524,7 @@ class TextTrackSettings extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save texttrack settings to local storage
|
||||
* Save text track settings to local storage
|
||||
*
|
||||
* @method saveSettings
|
||||
*/
|
||||
@@ -352,18 +536,18 @@ class TextTrackSettings extends Component {
|
||||
const values = this.getValues();
|
||||
|
||||
try {
|
||||
if (Object.getOwnPropertyNames(values).length > 0) {
|
||||
window.localStorage.setItem('vjs-text-track-settings', JSON.stringify(values));
|
||||
if (Object.keys(values).length) {
|
||||
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
|
||||
} else {
|
||||
window.localStorage.removeItem('vjs-text-track-settings');
|
||||
window.localStorage.removeItem(LOCAL_STORAGE_KEY);
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn(e);
|
||||
} catch (err) {
|
||||
log.warn(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update display of texttrack settings
|
||||
* Update display of text track settings
|
||||
*
|
||||
* @method updateDisplay
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import window from 'global/window';
|
||||
|
||||
/**
|
||||
* A safe getComputedStyle with an IE8 fallback.
|
||||
*
|
||||
* This is because in Firefox, if the player is loaded in an iframe with `display:none`,
|
||||
* then `getComputedStyle` returns `null`, so, we do a null-check to make sure
|
||||
* that the player doesn't break in these cases.
|
||||
* See https://bugzilla.mozilla.org/show_bug.cgi?id=548397 for more details.
|
||||
*
|
||||
* @function computedStyle
|
||||
* @param el the element you want the computed style of
|
||||
* @param prop the property name you want
|
||||
*/
|
||||
export default function computedStyle(el, prop) {
|
||||
if (!el || !prop) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (typeof window.getComputedStyle === 'function') {
|
||||
const cs = window.getComputedStyle(el);
|
||||
|
||||
return cs ? cs[prop] : '';
|
||||
}
|
||||
|
||||
return el.currentStyle[prop] || '';
|
||||
}
|
||||
+47
-1
@@ -96,10 +96,11 @@ export function getEl(id) {
|
||||
* @param {String} [tagName='div'] Name of tag to be created.
|
||||
* @param {Object} [properties={}] Element properties to be applied.
|
||||
* @param {Object} [attributes={}] Element attributes to be applied.
|
||||
* @param {String|Element|TextNode|Array|Function} [content] Contents for the element (see: `normalizeContent`)
|
||||
* @return {Element}
|
||||
* @function createEl
|
||||
*/
|
||||
export function createEl(tagName = 'div', properties = {}, attributes = {}) {
|
||||
export function createEl(tagName = 'div', properties = {}, attributes = {}, content) {
|
||||
const el = document.createElement(tagName);
|
||||
|
||||
Object.getOwnPropertyNames(properties).forEach(function(propName) {
|
||||
@@ -113,6 +114,11 @@ export function createEl(tagName = 'div', properties = {}, attributes = {}) {
|
||||
has been deprecated. Use the third argument instead.
|
||||
createEl(type, properties, attributes). Attempting to set ${propName} to ${val}.`);
|
||||
el.setAttribute(propName, val);
|
||||
|
||||
// Handle textContent since it's not supported everywhere and we have a
|
||||
// method for it.
|
||||
} else if (propName === 'textContent') {
|
||||
textContent(el, val);
|
||||
} else {
|
||||
el[propName] = val;
|
||||
}
|
||||
@@ -122,6 +128,10 @@ export function createEl(tagName = 'div', properties = {}, attributes = {}) {
|
||||
el.setAttribute(attrName, attributes[attrName]);
|
||||
});
|
||||
|
||||
if (content) {
|
||||
appendContent(el, content);
|
||||
}
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
@@ -139,6 +149,7 @@ export function textContent(el, text) {
|
||||
} else {
|
||||
el.textContent = text;
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,6 +415,41 @@ export function getElAttributes(tag) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of an element's attribute
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {String} attribute Attribute to get
|
||||
* @return {String} value of the attribute
|
||||
* @method getAttribute
|
||||
*/
|
||||
export function getAttribute(el, attribute) {
|
||||
return el.getAttribute(attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of an element's attribute
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {String} attribute Attribute to set
|
||||
* @param {String} value Value to set the attribute to
|
||||
* @method setAttribute
|
||||
*/
|
||||
export function setAttribute(el, attribute, value) {
|
||||
el.setAttribute(attribute, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element's attribute
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {String} attribute Attribute to remove
|
||||
* @method removeAttribute
|
||||
*/
|
||||
export function removeAttribute(el, attribute) {
|
||||
el.removeAttribute(attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to block the ability to select text while dragging controls
|
||||
*
|
||||
|
||||
+14
-9
@@ -19,14 +19,6 @@ let log;
|
||||
*/
|
||||
export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 11) => {
|
||||
|
||||
// If there's no console then don't try to output messages, but they will
|
||||
// still be stored in `log.history`.
|
||||
//
|
||||
// Was setting these once outside of this function, but containing them
|
||||
// in the function makes it easier to test cases where console doesn't exist
|
||||
// when the module is executed.
|
||||
const fn = window.console && window.console[type] || function() {};
|
||||
|
||||
if (type !== 'log') {
|
||||
|
||||
// add the type to the front of the message when it's not "log"
|
||||
@@ -39,6 +31,19 @@ export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 1
|
||||
// add console prefix after adding to history
|
||||
args.unshift('VIDEOJS:');
|
||||
|
||||
// If there's no console then don't try to output messages, but they will
|
||||
// still be stored in `log.history`.
|
||||
//
|
||||
// Was setting these once outside of this function, but containing them
|
||||
// in the function makes it easier to test cases where console doesn't exist
|
||||
// when the module is executed.
|
||||
const fn = window.console && window.console[type];
|
||||
|
||||
// Bail out if there's no console.
|
||||
if (!fn) {
|
||||
return;
|
||||
}
|
||||
|
||||
// IEs previous to 11 log objects uselessly as "[object Object]"; so, JSONify
|
||||
// objects and arrays for those less-capable browsers.
|
||||
if (stringify) {
|
||||
@@ -62,7 +67,7 @@ export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 1
|
||||
if (!fn.apply) {
|
||||
fn(args);
|
||||
} else {
|
||||
fn[Array.isArray(args) ? 'apply' : 'call'](console, args);
|
||||
fn[Array.isArray(args) ? 'apply' : 'call'](window.console, args);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -41,20 +41,17 @@ function customizer(destination, source) {
|
||||
* provided objects
|
||||
* @function mergeOptions
|
||||
*/
|
||||
export default function mergeOptions() {
|
||||
// contruct the call dynamically to handle the variable number of
|
||||
// objects to merge
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
export default function mergeOptions(...objects) {
|
||||
|
||||
// unshift an empty object into the front of the call as the target
|
||||
// of the merge
|
||||
args.unshift({});
|
||||
objects.unshift({});
|
||||
|
||||
// customize conflict resolution to match our historical merge behavior
|
||||
args.push(customizer);
|
||||
objects.push(customizer);
|
||||
|
||||
merge.apply(null, args);
|
||||
merge.apply(null, objects);
|
||||
|
||||
// return the mutated result object
|
||||
return args[0];
|
||||
return objects[0];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @file obj.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* Array-like iteration for objects.
|
||||
*
|
||||
* @param {Object} object
|
||||
* @param {Function} fn
|
||||
* A callback function which is called for each key in the object. It
|
||||
* receives the value and key as arguments.
|
||||
*/
|
||||
export function each(object, fn) {
|
||||
Object.keys(object).forEach(key => fn(object[key], key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Array-like reduce for objects.
|
||||
*
|
||||
* @param {Object} object
|
||||
* @param {Function} fn
|
||||
* A callback function which is called for each key in the object. It
|
||||
* receives the accumulated value and the per-iteration value and key
|
||||
* as arguments.
|
||||
* @param {Mixed} [initial = 0]
|
||||
* @return {Mixed}
|
||||
*/
|
||||
export function reduce(object, fn, initial = 0) {
|
||||
return Object.keys(object).reduce(
|
||||
(accum, key) => fn(accum, object[key], key),
|
||||
initial);
|
||||
}
|
||||
@@ -9,6 +9,10 @@
|
||||
* @method toTitleCase
|
||||
*/
|
||||
function toTitleCase(string) {
|
||||
if (typeof string !== 'string') {
|
||||
return string;
|
||||
}
|
||||
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
|
||||
+93
-3
@@ -25,6 +25,7 @@ import log from './utils/log.js';
|
||||
import * as Dom from './utils/dom.js';
|
||||
import * as browser from './utils/browser.js';
|
||||
import * as Url from './utils/url.js';
|
||||
import computedStyle from './utils/computed-style.js';
|
||||
import extendFn from './extend.js';
|
||||
import merge from 'lodash-compat/object/merge';
|
||||
import xhr from 'xhr';
|
||||
@@ -33,7 +34,9 @@ import xhr from 'xhr';
|
||||
import Tech from './tech/tech.js';
|
||||
|
||||
// HTML5 Element Shim for IE8
|
||||
if (typeof HTMLVideoElement === 'undefined') {
|
||||
if (typeof HTMLVideoElement === 'undefined' &&
|
||||
window.document &&
|
||||
window.document.createElement) {
|
||||
document.createElement('video');
|
||||
document.createElement('audio');
|
||||
document.createElement('track');
|
||||
@@ -57,6 +60,8 @@ if (typeof HTMLVideoElement === 'undefined') {
|
||||
function videojs(id, options, ready) {
|
||||
let tag;
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Allow for element or ID to be passed in
|
||||
// String ID
|
||||
if (typeof id === 'string') {
|
||||
@@ -96,10 +101,81 @@ function videojs(id, options, ready) {
|
||||
}
|
||||
|
||||
// Element may have a player attr referring to an already created player instance.
|
||||
// If not, set up a new player and return the instance.
|
||||
return tag.player || Player.players[tag.playerId] || new Player(tag, options, ready);
|
||||
// If so return that otherwise set up a new player below
|
||||
if (tag.player || Player.players[tag.playerId]) {
|
||||
return tag.player || Player.players[tag.playerId];
|
||||
}
|
||||
|
||||
videojs.hooks('beforesetup').forEach(function(hookFunction) {
|
||||
const opts = hookFunction(tag, mergeOptions(options));
|
||||
|
||||
if (!opts || typeof opts !== 'object' || Array.isArray(opts)) {
|
||||
videojs.log.error('please return an object in beforesetup hooks');
|
||||
return;
|
||||
}
|
||||
|
||||
options = mergeOptions(options, opts);
|
||||
});
|
||||
|
||||
// If not, set up a new player
|
||||
const player = new Player(tag, options, ready);
|
||||
|
||||
videojs.hooks('setup').forEach((hookFunction) => hookFunction(player));
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* An Object that contains lifecycle hooks as keys which point to an array
|
||||
* of functions that are run when a lifecycle is triggered
|
||||
*/
|
||||
videojs.hooks_ = {};
|
||||
|
||||
/**
|
||||
* Get a list of hooks for a specific lifecycle
|
||||
*
|
||||
* @param {String} type the lifecyle to get hooks from
|
||||
* @param {Function=} optionally add a hook to the lifecycle that your are getting
|
||||
* @return {Array} an array of hooks, or an empty array if there are none
|
||||
*/
|
||||
videojs.hooks = function(type, fn) {
|
||||
videojs.hooks_[type] = videojs.hooks_[type] || [];
|
||||
if (fn) {
|
||||
videojs.hooks_[type] = videojs.hooks_[type].concat(fn);
|
||||
}
|
||||
return videojs.hooks_[type];
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a function hook to a specific videojs lifecycle
|
||||
*
|
||||
* @param {String} type the lifecycle to hook the function to
|
||||
* @param {Function|Array} fn the function to attach
|
||||
*/
|
||||
videojs.hook = function(type, fn) {
|
||||
videojs.hooks(type, fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a hook from a specific videojs lifecycle
|
||||
*
|
||||
* @param {String} type the lifecycle that the function hooked to
|
||||
* @param {Function} fn the hooked function to remove
|
||||
* @return {Boolean} the function that was removed or undef
|
||||
*/
|
||||
videojs.removeHook = function(type, fn) {
|
||||
const index = videojs.hooks(type).indexOf(fn);
|
||||
|
||||
if (index <= -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
videojs.hooks_[type] = videojs.hooks_[type].slice();
|
||||
videojs.hooks_[type].splice(index, 1);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Add default styles
|
||||
if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
|
||||
let style = Dom.$('.vjs-styles-defaults');
|
||||
@@ -719,6 +795,20 @@ videojs.appendContent = Dom.appendContent;
|
||||
*/
|
||||
videojs.insertContent = Dom.insertContent;
|
||||
|
||||
/**
|
||||
* A safe getComputedStyle with an IE8 fallback.
|
||||
*
|
||||
* This is because in Firefox, if the player is loaded in an iframe with `display:none`,
|
||||
* then `getComputedStyle` returns `null`, so, we do a null-check to make sure
|
||||
* that the player doesn't break in these cases.
|
||||
* See https://bugzilla.mozilla.org/show_bug.cgi?id=548397 for more details.
|
||||
*
|
||||
* @function computedStyle
|
||||
* @param el the element you want the computed style of
|
||||
* @param prop the property name you want
|
||||
*/
|
||||
videojs.computedStyle = computedStyle;
|
||||
|
||||
/*
|
||||
* Custom Universal Module Definition (UMD)
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-env qunit */
|
||||
import ClickableComponent from '../../src/js/clickable-component.js';
|
||||
import TestHelpers from './test-helpers.js';
|
||||
import * as Events from '../../src/js/utils/events.js';
|
||||
|
||||
QUnit.module('ClickableComponent');
|
||||
|
||||
@@ -39,3 +40,34 @@ QUnit.test('should be enabled/disabled', function(assert) {
|
||||
testClickableComponent.dispose();
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('handleClick should not be triggered when disabled', function() {
|
||||
let clicks = 0;
|
||||
|
||||
class TestClickableComponent extends ClickableComponent {
|
||||
handleClick() {
|
||||
clicks++;
|
||||
}
|
||||
}
|
||||
|
||||
const player = TestHelpers.makePlayer({});
|
||||
const testClickableComponent = new TestClickableComponent(player);
|
||||
const el = testClickableComponent.el();
|
||||
|
||||
// 1st click
|
||||
Events.trigger(el, 'click');
|
||||
QUnit.equal(clicks, 1, 'click on enabled ClickableComponent is handled');
|
||||
|
||||
testClickableComponent.disable();
|
||||
// No click should happen.
|
||||
Events.trigger(el, 'click');
|
||||
QUnit.equal(clicks, 1, 'click on disabled ClickableComponent is not handled');
|
||||
|
||||
testClickableComponent.enable();
|
||||
// 2nd Click
|
||||
Events.trigger(el, 'click');
|
||||
QUnit.equal(clicks, 2, 'click on re-enabled ClickableComponent is handled');
|
||||
|
||||
testClickableComponent.dispose();
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
@@ -99,6 +99,18 @@ QUnit.test('addChild should throw if the child does not exist', function(assert)
|
||||
|
||||
});
|
||||
|
||||
QUnit.test('should add a child component with title case name', function(assert) {
|
||||
const comp = new Component(getFakePlayer());
|
||||
|
||||
const child = comp.addChild('Component');
|
||||
|
||||
assert.ok(comp.children().length === 1);
|
||||
assert.ok(comp.children()[0] === child);
|
||||
assert.ok(comp.el().childNodes[0] === child.el());
|
||||
assert.ok(comp.getChild('Component') === child);
|
||||
assert.ok(comp.getChildById(child.id()) === child);
|
||||
});
|
||||
|
||||
QUnit.test('should init child components from options', function(assert) {
|
||||
const comp = new Component(getFakePlayer(), {
|
||||
children: {
|
||||
@@ -178,8 +190,8 @@ QUnit.test('should init child components from component options', function(asser
|
||||
testComponent4: {}
|
||||
});
|
||||
|
||||
assert.ok(!testComp.childNameIndex_.testComponent2, 'we do not have testComponent2');
|
||||
assert.ok(testComp.childNameIndex_.testComponent4, 'we have a testComponent4');
|
||||
assert.ok(!testComp.childNameIndex_.TestComponent2, 'we do not have testComponent2');
|
||||
assert.ok(testComp.childNameIndex_.TestComponent4, 'we have a testComponent4');
|
||||
});
|
||||
|
||||
QUnit.test('should allows setting child options at the parent options level', function(assert) {
|
||||
|
||||
@@ -120,6 +120,136 @@ QUnit.test('should get tag, source, and track settings', function(assert) {
|
||||
assert.equal(player.el(), null, 'player el killed');
|
||||
});
|
||||
|
||||
QUnit.test('should get current source from source tag', function(assert) {
|
||||
const fixture = document.getElementById('qunit-fixture');
|
||||
|
||||
const html = [
|
||||
'<video id="example_1" class="video-js" preload="none">',
|
||||
'<source src="http://google.com" type="video/mp4">',
|
||||
'<source src="http://hugo.com" type="video/webm">',
|
||||
'</video>'
|
||||
].join('');
|
||||
|
||||
fixture.innerHTML += html;
|
||||
|
||||
const tag = document.getElementById('example_1');
|
||||
const player = TestHelpers.makePlayer({}, tag);
|
||||
|
||||
assert.ok(player.currentSource().src === 'http://google.com');
|
||||
assert.ok(player.currentSource().type === 'video/mp4');
|
||||
});
|
||||
|
||||
QUnit.test('should get current sources from source tag', function(assert) {
|
||||
const fixture = document.getElementById('qunit-fixture');
|
||||
|
||||
const html = [
|
||||
'<video id="example_1" class="video-js" preload="none">',
|
||||
'<source src="http://google.com" type="video/mp4">',
|
||||
'<source src="http://hugo.com" type="video/webm">',
|
||||
'</video>'
|
||||
].join('');
|
||||
|
||||
fixture.innerHTML += html;
|
||||
|
||||
const tag = document.getElementById('example_1');
|
||||
const player = TestHelpers.makePlayer({}, tag);
|
||||
|
||||
assert.ok(player.currentSources()[0].src === 'http://google.com');
|
||||
assert.ok(player.currentSources()[0].type === 'video/mp4');
|
||||
assert.ok(player.currentSources()[1].src === 'http://hugo.com');
|
||||
assert.ok(player.currentSources()[1].type === 'video/webm');
|
||||
|
||||
// when redefining src expect sources to update accordingly
|
||||
player.src('http://google.com');
|
||||
|
||||
assert.ok(player.currentSources()[0].src === 'http://google.com');
|
||||
assert.ok(player.currentSources()[0].type === undefined);
|
||||
assert.ok(player.currentSources()[1] === undefined);
|
||||
});
|
||||
|
||||
QUnit.test('should get current source from src set', function(assert) {
|
||||
const fixture = document.getElementById('qunit-fixture');
|
||||
|
||||
const html = '<video id="example_1" class="video-js" preload="none"></video>';
|
||||
|
||||
fixture.innerHTML += html;
|
||||
|
||||
const tag = document.getElementById('example_1');
|
||||
const player = TestHelpers.makePlayer({}, tag);
|
||||
|
||||
player.loadTech_('Html5');
|
||||
|
||||
// check for matching undefined src
|
||||
assert.deepEqual(player.currentSource(), {});
|
||||
|
||||
player.src('http://google.com');
|
||||
|
||||
assert.ok(player.currentSource().src === 'http://google.com');
|
||||
assert.ok(player.currentSource().type === undefined);
|
||||
|
||||
player.src({
|
||||
src: 'http://google.com'
|
||||
});
|
||||
|
||||
assert.ok(player.currentSource().src === 'http://google.com');
|
||||
assert.ok(player.currentSource().type === undefined);
|
||||
|
||||
player.src({
|
||||
src: 'http://google.com',
|
||||
type: 'video/mp4'
|
||||
});
|
||||
|
||||
assert.ok(player.currentSource().src === 'http://google.com');
|
||||
assert.ok(player.currentSource().type === 'video/mp4');
|
||||
});
|
||||
|
||||
QUnit.test('should get current sources from src set', function(assert) {
|
||||
const fixture = document.getElementById('qunit-fixture');
|
||||
|
||||
const html = '<video id="example_1" class="video-js" preload="none"></video>';
|
||||
|
||||
fixture.innerHTML += html;
|
||||
|
||||
const tag = document.getElementById('example_1');
|
||||
const player = TestHelpers.makePlayer({}, tag);
|
||||
|
||||
player.loadTech_('Html5');
|
||||
|
||||
// check for matching undefined src
|
||||
assert.ok(player.currentSources(), []);
|
||||
|
||||
player.src([{
|
||||
src: 'http://google.com'
|
||||
}, {
|
||||
src: 'http://hugo.com'
|
||||
}]);
|
||||
|
||||
assert.ok(player.currentSources()[0].src === 'http://google.com');
|
||||
assert.ok(player.currentSources()[0].type === undefined);
|
||||
assert.ok(player.currentSources()[1].src === 'http://hugo.com');
|
||||
assert.ok(player.currentSources()[1].type === undefined);
|
||||
|
||||
player.src([{
|
||||
src: 'http://google.com',
|
||||
type: 'video/mp4'
|
||||
}, {
|
||||
src: 'http://hugo.com',
|
||||
type: 'video/webm'
|
||||
}]);
|
||||
|
||||
assert.ok(player.currentSources()[0].src === 'http://google.com');
|
||||
assert.ok(player.currentSources()[0].type === 'video/mp4');
|
||||
assert.ok(player.currentSources()[1].src === 'http://hugo.com');
|
||||
assert.ok(player.currentSources()[1].type === 'video/webm');
|
||||
|
||||
// when redefining src expect sources to update accordingly
|
||||
player.src('http://hugo.com');
|
||||
|
||||
assert.ok(player.currentSources()[0].src === 'http://hugo.com');
|
||||
assert.ok(player.currentSources()[0].type === undefined);
|
||||
assert.ok(player.currentSources()[1] === undefined);
|
||||
});
|
||||
|
||||
QUnit.test('should asynchronously fire error events during source selection', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
@@ -191,6 +321,24 @@ QUnit.test('should set the width, height, and aspect ratio via a css class', fun
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('should default to 16:9 when fluid', function(assert) {
|
||||
const player = TestHelpers.makePlayer({fluid: true});
|
||||
const ratio = player.currentHeight() / player.currentWidth();
|
||||
|
||||
// IE8 rounds 0.5625 up to 0.563
|
||||
assert.ok(((ratio >= 0.562) && (ratio <= 0.563)), 'fluid player without dimensions defaults to 16:9');
|
||||
});
|
||||
|
||||
QUnit.test('should set fluid to true if element has vjs-fluid class', function(assert) {
|
||||
const tag = TestHelpers.makeTag();
|
||||
|
||||
tag.className += ' vjs-fluid';
|
||||
|
||||
const player = TestHelpers.makePlayer({}, tag);
|
||||
|
||||
assert.ok(player.fluid(), 'fluid is true with vjs-fluid class');
|
||||
});
|
||||
|
||||
QUnit.test('should use an class name that begins with an alpha character', function(assert) {
|
||||
const alphaPlayer = TestHelpers.makePlayer({ id: 'alpha1' });
|
||||
const numericPlayer = TestHelpers.makePlayer({ id: '1numeric' });
|
||||
|
||||
@@ -562,3 +562,22 @@ QUnit.test('Exception in play promise should be caught', function() {
|
||||
|
||||
tech.el_ = oldEl;
|
||||
});
|
||||
|
||||
test('When Android Chrome reports Infinity duration with currentTime 0, return NaN', function() {
|
||||
const oldIsAndroid = browser.IS_ANDROID;
|
||||
const oldIsChrome = browser.IS_CHROME;
|
||||
const oldEl = tech.el_;
|
||||
|
||||
browser.IS_ANDROID = true;
|
||||
browser.IS_CHROME = true;
|
||||
|
||||
tech.el_ = {
|
||||
duration: Infinity,
|
||||
currentTime: 0
|
||||
};
|
||||
ok(Number.isNaN(tech.duration()), 'returned NaN with currentTime 0');
|
||||
|
||||
browser.IS_ANDROID = oldIsAndroid;
|
||||
browser.IS_CHROME = oldIsChrome;
|
||||
tech.el_ = oldEl;
|
||||
});
|
||||
|
||||
@@ -39,6 +39,9 @@ class TechFaker extends Tech {
|
||||
src() {
|
||||
return 'movie.mp4';
|
||||
}
|
||||
currentSrc() {
|
||||
return 'movie.mp4';
|
||||
}
|
||||
volume() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ QUnit.test('dispose() should clear all tracks that are added after creation', fu
|
||||
|
||||
assert.equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
|
||||
assert.equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
|
||||
assert.equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
|
||||
assert.equal(tech.textTracks().length, 2, 'should have two text tracks at the start');
|
||||
assert.equal(tech.remoteTextTrackEls().length,
|
||||
2,
|
||||
'should have two remote text tracks els');
|
||||
@@ -167,6 +167,62 @@ QUnit.test('dispose() should clear all tracks that are added after creation', fu
|
||||
assert.equal(tech.textTracks().length, 0, 'should have zero video tracks after dispose');
|
||||
});
|
||||
|
||||
QUnit.test('switching sources should clear all remote tracks that are added with manualCleanup = false', function(assert) {
|
||||
// Define a new tech class
|
||||
const MyTech = extendFn(Tech);
|
||||
|
||||
// Create source handler
|
||||
const handler = {
|
||||
canPlayType: () => 'probably',
|
||||
canHandleSource: () => 'probably',
|
||||
handleSource: () => {
|
||||
return {
|
||||
dispose: () => {}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Extend Tech with source handlers
|
||||
Tech.withSourceHandlers(MyTech);
|
||||
|
||||
MyTech.registerSourceHandler(handler);
|
||||
|
||||
const tech = new MyTech();
|
||||
|
||||
// set the initial source
|
||||
tech.setSource({src: 'foo.mp4', type: 'mp4'});
|
||||
|
||||
// default value for manualCleanup is true
|
||||
tech.addRemoteTextTrack({});
|
||||
// should be automatically cleaned up when source changes
|
||||
tech.addRemoteTextTrack({}, false);
|
||||
|
||||
assert.equal(tech.textTracks().length, 2, 'should have two text tracks at the start');
|
||||
assert.equal(tech.remoteTextTrackEls().length,
|
||||
2,
|
||||
'should have two remote text tracks els');
|
||||
assert.equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
||||
assert.equal(tech.autoRemoteTextTracks_.length,
|
||||
1,
|
||||
'should have one auto-cleanup remote text track');
|
||||
|
||||
// change source to force cleanup of auto remote text tracks
|
||||
tech.setSource({src: 'bar.mp4', type: 'mp4'});
|
||||
|
||||
assert.equal(tech.textTracks().length,
|
||||
1,
|
||||
'should have one text track after source change');
|
||||
assert.equal(tech.remoteTextTrackEls().length,
|
||||
1,
|
||||
'should have one remote remote text track els after source change');
|
||||
assert.equal(tech.remoteTextTracks().length,
|
||||
1,
|
||||
'should have one remote text track after source change');
|
||||
assert.equal(tech.autoRemoteTextTracks_.length,
|
||||
0,
|
||||
'should have zero auto-cleanup remote text tracks');
|
||||
});
|
||||
|
||||
QUnit.test('should add the source handler interface to a tech', function(assert) {
|
||||
const sourceA = { src: 'foo.mp4', type: 'video/mp4' };
|
||||
const sourceB = { src: 'no-support', type: 'no-support' };
|
||||
|
||||
@@ -31,6 +31,7 @@ QUnit.test('should update settings', function(assert) {
|
||||
tracks,
|
||||
persistTextTrackSettings: true
|
||||
});
|
||||
|
||||
const newSettings = {
|
||||
backgroundOpacity: '0.5',
|
||||
textOpacity: '0.5',
|
||||
@@ -44,43 +45,52 @@ QUnit.test('should update settings', function(assert) {
|
||||
};
|
||||
|
||||
player.textTrackSettings.setValues(newSettings);
|
||||
|
||||
assert.deepEqual(player.textTrackSettings.getValues(),
|
||||
newSettings,
|
||||
'values are updated');
|
||||
newSettings,
|
||||
'values are updated');
|
||||
|
||||
assert.equal(player.$('.vjs-fg-color > select').selectedIndex,
|
||||
2,
|
||||
'fg-color is set to new value');
|
||||
2,
|
||||
'fg-color is set to new value');
|
||||
|
||||
assert.equal(player.$('.vjs-bg-color > select').selectedIndex,
|
||||
1,
|
||||
'bg-color is set to new value');
|
||||
assert.equal(player.$('.window-color > select').selectedIndex,
|
||||
1,
|
||||
'window-color is set to new value');
|
||||
1,
|
||||
'bg-color is set to new value');
|
||||
|
||||
assert.equal(player.$('.vjs-window-color > select').selectedIndex,
|
||||
1,
|
||||
'window-color is set to new value');
|
||||
|
||||
assert.equal(player.$('.vjs-text-opacity > select').selectedIndex,
|
||||
1,
|
||||
'text-opacity is set to new value');
|
||||
1,
|
||||
'text-opacity is set to new value');
|
||||
|
||||
assert.equal(player.$('.vjs-bg-opacity > select').selectedIndex,
|
||||
1,
|
||||
'bg-opacity is set to new value');
|
||||
1,
|
||||
'bg-opacity is set to new value');
|
||||
|
||||
assert.equal(player.$('.vjs-window-opacity > select').selectedIndex,
|
||||
1,
|
||||
'window-opacity is set to new value');
|
||||
1,
|
||||
'window-opacity is set to new value');
|
||||
|
||||
assert.equal(player.$('.vjs-edge-style select').selectedIndex,
|
||||
1,
|
||||
'edge-style is set to new value');
|
||||
1,
|
||||
'edge-style is set to new value');
|
||||
|
||||
assert.equal(player.$('.vjs-font-family select').selectedIndex,
|
||||
3,
|
||||
'font-family is set to new value');
|
||||
3,
|
||||
'font-family is set to new value');
|
||||
|
||||
assert.equal(player.$('.vjs-font-percent select').selectedIndex,
|
||||
3,
|
||||
'font-percent is set to new value');
|
||||
3,
|
||||
'font-percent is set to new value');
|
||||
|
||||
Events.trigger(player.$('.vjs-done-button'), 'click');
|
||||
assert.deepEqual(safeParseTuple(
|
||||
window.localStorage.getItem('vjs-text-track-settings'))[1],
|
||||
newSettings,
|
||||
'values are saved');
|
||||
|
||||
assert.deepEqual(safeParseTuple(window.localStorage.getItem('vjs-text-track-settings'))[1],
|
||||
newSettings,
|
||||
'values are saved');
|
||||
|
||||
player.dispose();
|
||||
});
|
||||
@@ -93,7 +103,7 @@ QUnit.test('should restore default settings', function(assert) {
|
||||
|
||||
player.$('.vjs-fg-color > select').selectedIndex = 1;
|
||||
player.$('.vjs-bg-color > select').selectedIndex = 1;
|
||||
player.$('.window-color > select').selectedIndex = 1;
|
||||
player.$('.vjs-window-color > select').selectedIndex = 1;
|
||||
player.$('.vjs-text-opacity > select').selectedIndex = 1;
|
||||
player.$('.vjs-bg-opacity > select').selectedIndex = 1;
|
||||
player.$('.vjs-window-opacity > select').selectedIndex = 1;
|
||||
@@ -106,8 +116,8 @@ QUnit.test('should restore default settings', function(assert) {
|
||||
Events.trigger(player.$('.vjs-done-button'), 'click');
|
||||
|
||||
assert.deepEqual(player.textTrackSettings.getValues(),
|
||||
defaultSettings,
|
||||
'values are defaulted');
|
||||
defaultSettings,
|
||||
'values are defaulted');
|
||||
// TODO:
|
||||
// MikeA: need to figure out how to modify saveSettings
|
||||
// to factor in defaults are no longer null
|
||||
@@ -116,32 +126,40 @@ QUnit.test('should restore default settings', function(assert) {
|
||||
// 'values are saved');
|
||||
|
||||
assert.equal(player.$('.vjs-fg-color > select').selectedIndex,
|
||||
0,
|
||||
'fg-color is set to default value');
|
||||
0,
|
||||
'fg-color is set to default value');
|
||||
|
||||
assert.equal(player.$('.vjs-bg-color > select').selectedIndex,
|
||||
0,
|
||||
'bg-color is set to default value');
|
||||
assert.equal(player.$('.window-color > select').selectedIndex,
|
||||
0,
|
||||
'window-color is set to default value');
|
||||
0,
|
||||
'bg-color is set to default value');
|
||||
|
||||
assert.equal(player.$('.vjs-window-color > select').selectedIndex,
|
||||
0,
|
||||
'window-color is set to default value');
|
||||
|
||||
assert.equal(player.$('.vjs-text-opacity > select').selectedIndex,
|
||||
0,
|
||||
'text-opacity is set to default value');
|
||||
0,
|
||||
'text-opacity is set to default value');
|
||||
|
||||
assert.equal(player.$('.vjs-bg-opacity > select').selectedIndex,
|
||||
0,
|
||||
'bg-opacity is set to default value');
|
||||
0,
|
||||
'bg-opacity is set to default value');
|
||||
|
||||
assert.equal(player.$('.vjs-window-opacity > select').selectedIndex,
|
||||
0,
|
||||
'window-opacity is set to default value');
|
||||
0,
|
||||
'window-opacity is set to default value');
|
||||
|
||||
assert.equal(player.$('.vjs-edge-style select').selectedIndex,
|
||||
0,
|
||||
'edge-style is set to default value');
|
||||
0,
|
||||
'edge-style is set to default value');
|
||||
|
||||
assert.equal(player.$('.vjs-font-family select').selectedIndex,
|
||||
0,
|
||||
'font-family is set to default value');
|
||||
0,
|
||||
'font-family is set to default value');
|
||||
|
||||
assert.equal(player.$('.vjs-font-percent select').selectedIndex,
|
||||
2,
|
||||
'font-percent is set to default value');
|
||||
2,
|
||||
'font-percent is set to default value');
|
||||
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
@@ -377,7 +377,6 @@ QUnit.test('removes cuechange event when text track is hidden for emulated track
|
||||
endTime: 5
|
||||
});
|
||||
player.tech_.textTracks().addTrack_(tt);
|
||||
player.tech_.emulateTextTracks();
|
||||
|
||||
let numTextTrackChanges = 0;
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ QUnit.test('should return the element with the ID', function(assert) {
|
||||
el1.id = 'test_id1';
|
||||
el2.id = 'test_id2';
|
||||
|
||||
assert.ok(Dom.getEl('test_id1') === el1, 'found element for ID');
|
||||
assert.ok(Dom.getEl('#test_id2') === el2, 'found element for CSS ID');
|
||||
assert.strictEqual(Dom.getEl('test_id1'), el1, 'found element for ID');
|
||||
assert.strictEqual(Dom.getEl('#test_id2'), el2, 'found element for CSS ID');
|
||||
});
|
||||
|
||||
QUnit.test('should create an element', function(assert) {
|
||||
@@ -27,10 +27,27 @@ QUnit.test('should create an element', function(assert) {
|
||||
'data-test': 'asdf'
|
||||
});
|
||||
|
||||
assert.ok(div.nodeName === 'DIV');
|
||||
assert.ok(span.nodeName === 'SPAN');
|
||||
assert.ok(span.getAttribute('data-test') === 'asdf');
|
||||
assert.ok(span.innerHTML === 'fdsa');
|
||||
assert.strictEqual(div.nodeName, 'DIV');
|
||||
assert.strictEqual(span.nodeName, 'SPAN');
|
||||
assert.strictEqual(span.getAttribute('data-test'), 'asdf');
|
||||
assert.strictEqual(span.innerHTML, 'fdsa');
|
||||
});
|
||||
|
||||
QUnit.test('should create an element, supporting textContent', function(assert) {
|
||||
const span = Dom.createEl('span', {textContent: 'howdy'});
|
||||
|
||||
if (span.textContent) {
|
||||
assert.strictEqual(span.textContent, 'howdy', 'works in browsers that support textContent');
|
||||
} else {
|
||||
assert.strictEqual(span.innerText, 'howdy', 'works in browsers that DO NOT support textContent');
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('should create an element with content', function(assert) {
|
||||
const span = Dom.createEl('span');
|
||||
const div = Dom.createEl('div', undefined, undefined, span);
|
||||
|
||||
assert.strictEqual(div.firstChild, span);
|
||||
});
|
||||
|
||||
QUnit.test('should insert an element first in another', function(assert) {
|
||||
@@ -39,28 +56,28 @@ QUnit.test('should insert an element first in another', function(assert) {
|
||||
const parent = document.createElement('div');
|
||||
|
||||
Dom.insertElFirst(el1, parent);
|
||||
assert.ok(parent.firstChild === el1, 'inserts first into empty parent');
|
||||
assert.strictEqual(parent.firstChild, el1, 'inserts first into empty parent');
|
||||
|
||||
Dom.insertElFirst(el2, parent);
|
||||
assert.ok(parent.firstChild === el2, 'inserts first into parent with child');
|
||||
assert.strictEqual(parent.firstChild, el2, 'inserts first into parent with child');
|
||||
});
|
||||
|
||||
QUnit.test('should get and remove data from an element', function(assert) {
|
||||
const el = document.createElement('div');
|
||||
const data = Dom.getElData(el);
|
||||
|
||||
assert.ok(typeof data === 'object', 'data object created');
|
||||
assert.strictEqual(typeof data, 'object', 'data object created');
|
||||
|
||||
// Add data
|
||||
const testData = { asdf: 'fdsa' };
|
||||
const testData = {asdf: 'fdsa'};
|
||||
|
||||
data.test = testData;
|
||||
assert.ok(Dom.getElData(el).test === testData, 'data added');
|
||||
assert.strictEqual(Dom.getElData(el).test, testData, 'data added');
|
||||
|
||||
// Remove all data
|
||||
Dom.removeElData(el);
|
||||
|
||||
assert.ok(!Dom.hasElData(el), 'cached item emptied');
|
||||
assert.notOk(Dom.hasElData(el), 'cached item emptied');
|
||||
});
|
||||
|
||||
QUnit.test('addElClass()', function(assert) {
|
||||
@@ -88,17 +105,12 @@ QUnit.test('addElClass()', function(assert) {
|
||||
QUnit.test('removeElClass()', function(assert) {
|
||||
const el = document.createElement('div');
|
||||
|
||||
el.className = 'test-class foo foo test2_className FOO bar';
|
||||
el.className = 'test-class test2_className FOO bar';
|
||||
|
||||
assert.expect(5);
|
||||
assert.expect(4);
|
||||
|
||||
Dom.removeElClass(el, 'test-class');
|
||||
assert.strictEqual(el.className, 'foo foo test2_className FOO bar', 'removes one class');
|
||||
|
||||
Dom.removeElClass(el, 'foo');
|
||||
assert.strictEqual(el.className,
|
||||
'test2_className FOO bar',
|
||||
'removes all instances of a class');
|
||||
assert.strictEqual(el.className, 'test2_className FOO bar', 'removes one class');
|
||||
|
||||
assert.throws(function() {
|
||||
Dom.removeElClass(el, 'test2_className bar');
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/* eslint-env qunit */
|
||||
import sinon from 'sinon';
|
||||
import * as Obj from '../../../src/js/utils/obj';
|
||||
|
||||
QUnit.module('utils/obj');
|
||||
|
||||
QUnit.test('each', function(assert) {
|
||||
const spy = sinon.spy();
|
||||
|
||||
Obj.each({
|
||||
a: 1,
|
||||
b: 'foo',
|
||||
c: null
|
||||
}, spy);
|
||||
|
||||
assert.strictEqual(spy.callCount, 3);
|
||||
assert.ok(spy.calledWith(1, 'a'));
|
||||
assert.ok(spy.calledWith('foo', 'b'));
|
||||
assert.ok(spy.calledWith(null, 'c'));
|
||||
|
||||
Obj.each({}, spy);
|
||||
assert.strictEqual(spy.callCount, 3, 'an empty object was not iterated over');
|
||||
});
|
||||
|
||||
QUnit.test('reduce', function(assert) {
|
||||
const first = Obj.reduce({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
d: 4
|
||||
}, (accum, value) => accum + value);
|
||||
|
||||
assert.strictEqual(first, 10);
|
||||
|
||||
const second = Obj.reduce({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
d: 4
|
||||
}, (accum, value) => accum + value, 10);
|
||||
|
||||
assert.strictEqual(second, 20);
|
||||
|
||||
const third = Obj.reduce({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
d: 4
|
||||
}, (accum, value, key) => {
|
||||
accum[key] = 0 - value;
|
||||
return accum;
|
||||
}, {});
|
||||
|
||||
assert.strictEqual(third.a, -1);
|
||||
assert.strictEqual(third.b, -2);
|
||||
assert.strictEqual(third.c, -3);
|
||||
assert.strictEqual(third.d, -4);
|
||||
});
|
||||
@@ -166,3 +166,184 @@ QUnit.test('should expose DOM functions', function(assert) {
|
||||
`videojs.${vjsName} is a reference to Dom.${domName}`);
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.module('video.js:hooks ', {
|
||||
beforeEach() {
|
||||
videojs.hooks_ = {};
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('should be able to add a hook', function(assert) {
|
||||
videojs.hook('foo', function() {});
|
||||
assert.equal(Object.keys(videojs.hooks_).length, 1, 'should have 1 hook type');
|
||||
assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
|
||||
|
||||
videojs.hook('bar', function() {});
|
||||
assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hook types');
|
||||
assert.equal(videojs.hooks_.bar.length, 1, 'should have 1 bar hook');
|
||||
assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
|
||||
|
||||
videojs.hook('bar', function() {});
|
||||
assert.equal(videojs.hooks_.bar.length, 2, 'should have 2 bar hooks');
|
||||
assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
|
||||
|
||||
videojs.hook('foo', function() {});
|
||||
videojs.hook('foo', function() {});
|
||||
videojs.hook('foo', function() {});
|
||||
assert.equal(videojs.hooks_.foo.length, 4, 'should have 4 foo hooks');
|
||||
assert.equal(videojs.hooks_.bar.length, 2, 'should have 2 bar hooks');
|
||||
});
|
||||
|
||||
QUnit.test('should be able to remove a hook', function(assert) {
|
||||
const noop = function() {};
|
||||
|
||||
videojs.hook('foo', noop);
|
||||
assert.equal(Object.keys(videojs.hooks_).length, 1, 'should have 1 hook types');
|
||||
assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
|
||||
|
||||
videojs.hook('bar', noop);
|
||||
assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
|
||||
assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
|
||||
assert.equal(videojs.hooks_.bar.length, 1, 'should have 1 bar hook');
|
||||
|
||||
const fooRetval = videojs.removeHook('foo', noop);
|
||||
|
||||
assert.equal(fooRetval, true, 'should return true');
|
||||
assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
|
||||
assert.equal(videojs.hooks_.foo.length, 0, 'should have 0 foo hook');
|
||||
assert.equal(videojs.hooks_.bar.length, 1, 'should have 0 bar hook');
|
||||
|
||||
const barRetval = videojs.removeHook('bar', noop);
|
||||
|
||||
assert.equal(barRetval, true, 'should return true');
|
||||
assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
|
||||
assert.equal(videojs.hooks_.foo.length, 0, 'should have 0 foo hook');
|
||||
assert.equal(videojs.hooks_.bar.length, 0, 'should have 0 bar hook');
|
||||
|
||||
const errRetval = videojs.removeHook('bar', noop);
|
||||
|
||||
assert.equal(errRetval, false, 'should return false');
|
||||
assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
|
||||
assert.equal(videojs.hooks_.foo.length, 0, 'should have 0 foo hook');
|
||||
assert.equal(videojs.hooks_.bar.length, 0, 'should have 0 bar hook');
|
||||
});
|
||||
|
||||
QUnit.test('should be able get all hooks for a type', function(assert) {
|
||||
const noop = function() {};
|
||||
|
||||
videojs.hook('foo', noop);
|
||||
assert.equal(Object.keys(videojs.hooks_).length, 1, 'should have 1 hook types');
|
||||
assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
|
||||
|
||||
videojs.hook('bar', noop);
|
||||
assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
|
||||
assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
|
||||
assert.equal(videojs.hooks_.bar.length, 1, 'should have 1 bar hook');
|
||||
|
||||
const fooHooks = videojs.hooks('foo');
|
||||
const barHooks = videojs.hooks('bar');
|
||||
|
||||
assert.deepEqual(videojs.hooks_.foo, fooHooks, 'should return the exact foo list from videojs.hooks_');
|
||||
assert.deepEqual(videojs.hooks_.bar, barHooks, 'should return the exact bar list from videojs.hooks_');
|
||||
});
|
||||
|
||||
QUnit.test('should be get all hooks for a type and add at the same time', function(assert) {
|
||||
const noop = function() {};
|
||||
|
||||
videojs.hook('foo', noop);
|
||||
assert.equal(Object.keys(videojs.hooks_).length, 1, 'should have 1 hook types');
|
||||
assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
|
||||
|
||||
videojs.hook('bar', noop);
|
||||
assert.equal(Object.keys(videojs.hooks_).length, 2, 'should have 2 hooks types');
|
||||
assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
|
||||
assert.equal(videojs.hooks_.bar.length, 1, 'should have 1 bar hook');
|
||||
|
||||
const fooHooks = videojs.hooks('foo', noop);
|
||||
const barHooks = videojs.hooks('bar', noop);
|
||||
|
||||
assert.deepEqual(videojs.hooks_.foo.length, 2, 'foo should have two noop hooks');
|
||||
assert.deepEqual(videojs.hooks_.bar.length, 2, 'bar should have two noop hooks');
|
||||
assert.deepEqual(videojs.hooks_.foo, fooHooks, 'should return the exact foo list from videojs.hooks_');
|
||||
assert.deepEqual(videojs.hooks_.bar, barHooks, 'should return the exact bar list from videojs.hooks_');
|
||||
});
|
||||
|
||||
QUnit.test('should trigger beforesetup and setup during videojs setup', function(assert) {
|
||||
const vjsOptions = {techOrder: ['techFaker']};
|
||||
let setupCalled = false;
|
||||
let beforeSetupCalled = false;
|
||||
const beforeSetup = function(video, options) {
|
||||
beforeSetupCalled = true;
|
||||
assert.equal(setupCalled, false, 'setup should be called after beforesetup');
|
||||
assert.deepEqual(options, vjsOptions, 'options should be the same');
|
||||
assert.equal(video.id, 'test_vid_id', 'video id should be correct');
|
||||
};
|
||||
const setup = function(player) {
|
||||
setupCalled = true;
|
||||
|
||||
assert.equal(beforeSetupCalled, true, 'beforesetup should have been called already');
|
||||
assert.ok(player, 'created player from tag');
|
||||
assert.ok(player.id() === 'test_vid_id');
|
||||
assert.ok(videojs.getPlayers().test_vid_id === player,
|
||||
'added player to global reference');
|
||||
};
|
||||
|
||||
const fixture = document.getElementById('qunit-fixture');
|
||||
|
||||
fixture.innerHTML += '<video id="test_vid_id"><source type="video/mp4"></video>';
|
||||
|
||||
const vid = document.getElementById('test_vid_id');
|
||||
|
||||
videojs.hook('beforesetup', beforeSetup);
|
||||
videojs.hook('setup', setup);
|
||||
|
||||
const player = videojs(vid, vjsOptions);
|
||||
|
||||
assert.ok(player.options_, 'returning null in beforesetup does not lose options');
|
||||
assert.equal(beforeSetupCalled, true, 'beforeSetup was called');
|
||||
assert.equal(setupCalled, true, 'setup was called');
|
||||
});
|
||||
|
||||
QUnit.test('beforesetup returns dont break videojs options', function(assert) {
|
||||
const vjsOptions = {techOrder: ['techFaker']};
|
||||
const fixture = document.getElementById('qunit-fixture');
|
||||
|
||||
fixture.innerHTML += '<video id="test_vid_id"><source type="video/mp4"></video>';
|
||||
|
||||
const vid = document.getElementById('test_vid_id');
|
||||
|
||||
videojs.hook('beforesetup', function() {
|
||||
return null;
|
||||
});
|
||||
videojs.hook('beforesetup', function() {
|
||||
return '';
|
||||
});
|
||||
videojs.hook('beforesetup', function() {
|
||||
return [];
|
||||
});
|
||||
|
||||
const player = videojs(vid, vjsOptions);
|
||||
|
||||
assert.ok(player.options_, 'beforesetup should not destory options');
|
||||
assert.equal(player.options_.techOrder, vjsOptions.techOrder, 'options set by user should exist');
|
||||
});
|
||||
|
||||
QUnit.test('beforesetup options override videojs options', function(assert) {
|
||||
const vjsOptions = {techOrder: ['techFaker'], autoplay: false};
|
||||
const fixture = document.getElementById('qunit-fixture');
|
||||
|
||||
fixture.innerHTML += '<video id="test_vid_id"><source type="video/mp4"></video>';
|
||||
|
||||
const vid = document.getElementById('test_vid_id');
|
||||
|
||||
videojs.hook('beforesetup', function(tag, options) {
|
||||
assert.equal(options.autoplay, false, 'false was passed to us');
|
||||
return {autoplay: true};
|
||||
});
|
||||
|
||||
const player = videojs(vid, vjsOptions);
|
||||
|
||||
assert.ok(player.options_, 'beforesetup should not destory options');
|
||||
assert.equal(player.options_.techOrder, vjsOptions.techOrder, 'options set by user should exist');
|
||||
assert.equal(player.options_.autoplay, true, 'autoplay should be set to true now');
|
||||
});
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário