Comparar commits
61 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 14ae1e9658 | |||
| b1ac2e0249 | |||
| 269e504d26 | |||
| 971f633c73 | |||
| 03fd402ea9 | |||
| 3a600d0eea | |||
| 1e80e59614 | |||
| 7579fc1800 | |||
| 4f43616160 | |||
| 687aae50ff | |||
| 6986dbb851 | |||
| 39ff0f4fb6 | |||
| 82c8b80548 | |||
| f5cc165cad | |||
| b4dc4f85b0 | |||
| b63666379d | |||
| 77ba3d13d9 | |||
| 7f7ea70cb7 | |||
| e0824c8294 | |||
| cc6e82442f | |||
| 97021554cd | |||
| c4bbe5d120 | |||
| 66a0d23e05 | |||
| 4bce4a2954 | |||
| f87b12c3af | |||
| 9329e3ef8e | |||
| 546bef5321 | |||
| c31836c30c | |||
| a5a68e819a | |||
| 98160700e5 | |||
| e5e1c7f269 | |||
| 0c16c5f3cf | |||
| d57f09f7cf | |||
| 2a26c7f87b | |||
| eade52e174 | |||
| 188ead1c81 | |||
| 8f67b4fc54 | |||
| cb6005eca4 | |||
| 8d80a5846e | |||
| 35df35143f | |||
| 3087830ed7 | |||
| 483e5a2ca5 | |||
| 3dcfa9568a | |||
| b5c60f339b | |||
| 03bab83dde | |||
| f773c473f9 | |||
| ae423df4f5 | |||
| 0a19cf0d6a | |||
| da1d8613d7 | |||
| da0f1ee681 | |||
| cff2e503ef | |||
| 561d5ddf76 | |||
| d6e56e9222 | |||
| 7490a499ce | |||
| 1ea00419c9 | |||
| 230743ecb1 | |||
| 39fd73f781 | |||
| 92e5d9fb5a | |||
| 083f643e63 | |||
| 02721c7c03 | |||
| c20ca5cc97 |
+6
-2
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"presets": [ "es3", ["es2015", {"loose": true}] ],
|
||||
"plugins": ["inline-json"]
|
||||
"presets": [
|
||||
"es3",
|
||||
["es2015", {
|
||||
"loose": true
|
||||
}]
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ test/*.map
|
||||
.s3config.json
|
||||
|
||||
node_modules
|
||||
npm-debug.log
|
||||
npm-debug.log*
|
||||
|
||||
sandbox/*
|
||||
!sandbox/*.example
|
||||
|
||||
+2
-1
@@ -35,6 +35,7 @@
|
||||
},
|
||||
"plugins": ["plugins/markdown"],
|
||||
"markdown": {
|
||||
"tags": ["example"]
|
||||
"tags": ["example"],
|
||||
"idInHeadings": true
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -2,9 +2,13 @@ language: node_js
|
||||
node_js:
|
||||
- 4.4
|
||||
before_install:
|
||||
- export CHROME_BIN=chromium-browser
|
||||
- export CHROME_BIN=/usr/bin/google-chrome
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
after_failure:
|
||||
- npm ls --depth=1
|
||||
after_success:
|
||||
- npm run assets
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
@@ -13,6 +17,8 @@ notifications:
|
||||
on_success: never
|
||||
webhooks:
|
||||
- http://pam.videojs.com/savage/travis
|
||||
slack:
|
||||
secure: LrF8K6mCYWlUt6SvdbGHazyQZSk/opKoiB/wgoGYaGc9+3wYXkVexY0WkO1m6wBKhUqXRAMVMFszr1wqKgdcxtItmFMMj8HqTLI1MVqgKqYX4Ux3CnEHJQiwxIk0aVL7lHLsZTXV/2Y0QIOYmAnCrgy46klETrk0ZuXf5okpu2Q=
|
||||
env:
|
||||
global:
|
||||
- secure: K6JpKwMkfNaJix3Bb0tLjVMzHMJgtBXdd/dvfw1BMb9DCBpd81PqXbDs7yXCddUxnUPTBPxZCrQgWsw71Wn+qEoIG5MU3uOT5A2rBbx/yZonVAGv5ed/9w0xk0OzO383CmPMFqwqtp9YmdmqGjQBkYXVXJjTvNTOAExFSdhO+3U=
|
||||
@@ -20,8 +26,12 @@ env:
|
||||
- secure: gglh7xDnURKfXp9T543DD7NG1pQ8HeWh1XtRspBAwr0H7RqJBVDqqODSYSPRFhfld7M6sYmvQIXgil7XlyefnKNTXqCarvaoTg3lbip8kSltXMiNw2V6AVpsQGuja7+XbaM0do70ETTKjW4Kw6wnxEHb78BvGN/hXIeqizUAjanlDAjd7fouaxpTBIbMESe2rI+WRHPis1cmnv8v70Mrh/8Un/NO4gkebGyvA47LTDNIaVqIVjonsndr8WjMv1/PNxQ8LyCO6D64MufrobS7Sec+VuN30apwEsBw8v82MK/MZ3qXu0lUp4+ERTbuc/rymh2wDFTQeG20Kf/NTauSaH6f414KNzIRFj0/xyLAzVZKIscXM2DKXMuskkZuvHLZvaspnZWcPYTjPZl0P88N0RBqnoLdR80dR5bDljNwU2QnSBeol/q1wXNEr6I1VTRFOB+qsHrD1blVMB1I5W3I0ti1aQ7XtgMOGi1kcPb4oFcJdl+3dLFDnyRyaNfdMOnOZYBBHdQCo19Mj/L+nqPGWeeYiEAM6JsuhNjHn5Za5nGf1ztXTimVPOQjyATin0x9kST3soLWSVmdW2dBHUGDVSMhvoLLR+nKSdNQ0KfpqtgrzeLxoVnRYHVBlih41tapM9IG/6BMYnDMaRcc0i54YeUP4oxlxGSyASIenkAgC6w=
|
||||
- secure: WtIEOSnqDkCZuTlBsxwlVwaRpVTbz7ol8+XSJIZb0aFo1lLisF9cz6s9WrAfX36MaxIcDN9LFZkpXzMvNrNkZWQa1kacGWH1rbx0SiiQ8LMweAcKdnZ5uXlSplBxbJ8bZfXKB1sIHsOsYw/vWhHKkcsDUkAEzQrIiMOhuoUV3s0uKM0knKXIAfNIF0EbDzLIojm+nm+F0n5vM60LRdKesaSt/o2p2LKxdZVoFGrg48D7bdA9VEfMWWRL/evDxJmnX4p+AjBc7mklqZ5F2pYsY6XXQuuS+2Sy+lnxz01kLg+RC4Cpv5dyYfK3h0j8KeyK8IuixycVONWVe9rANq8UaIsMrRN+6uDSC8zXiH4P+h6UDMm3jetc2ZyAfhBA8OyIs5QEShae2Rd7Y3WFJxBp6UVgyj6SkXGxrEdb1ZJgTTl4dyqiP0bYrLePNP2qSJ6OTfNdG791HF077uzXI96ABdMG54Wv9N9T/hmxKwV2Lajx/GZJMmHuwT9tkHKhkcxWea1HYam9QYSFUyJ5THfNk2A9u/r8DkL62MZ85zIQBisrlFjbPAGRejq6qyirBJPAy+FCjhM+oO/i2f2bGkkAfHGT0Og1BcrWVXs54yWdO7UZgie2F+Rmdwinb/GxebZJ+21ZQ4OkVr2t1Skr/PRni9+U7q/6xCLwUJgx45XJ0FE=
|
||||
sudo: false
|
||||
dist: trusty
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
addons:
|
||||
firefox: latest
|
||||
apt:
|
||||
packages:
|
||||
- google-chrome-stable
|
||||
|
||||
+211
@@ -1,3 +1,132 @@
|
||||
<a name="6.2.3"></a>
|
||||
## [6.2.3](https://github.com/videojs/video.js/compare/v6.2.2...v6.2.3) (2017-07-14)
|
||||
|
||||
### Chores
|
||||
|
||||
* **gh-release:** add prerelease flag and find right zip ([#4488](https://github.com/videojs/video.js/issues/4488)) ([b1ac2e0](https://github.com/videojs/video.js/commit/b1ac2e0))
|
||||
|
||||
<a name="6.2.2"></a>
|
||||
## [6.2.2](https://github.com/videojs/video.js/compare/v6.2.1...v6.2.2) (2017-07-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **playback rate menu:** cycling rates via click ([#4486](https://github.com/videojs/video.js/issues/4486)) ([4f43616](https://github.com/videojs/video.js/commit/4f43616))
|
||||
|
||||
### Chores
|
||||
|
||||
* **build:** remove unused var in build/version.js ([#4458](https://github.com/videojs/video.js/issues/4458)) ([6986dbb](https://github.com/videojs/video.js/commit/6986dbb))
|
||||
* add automatic github release ([#4466](https://github.com/videojs/video.js/issues/4466)) ([3a600d0](https://github.com/videojs/video.js/commit/3a600d0))
|
||||
* switch to using chrome for testing PRs on travis ([#4462](https://github.com/videojs/video.js/issues/4462)) ([687aae5](https://github.com/videojs/video.js/commit/687aae5))
|
||||
* **package:** update rollup to version 0.45.2 ([#4487](https://github.com/videojs/video.js/issues/4487)) ([971f633](https://github.com/videojs/video.js/commit/971f633)), closes [#4475](https://github.com/videojs/video.js/issues/4475)
|
||||
|
||||
### Documentation
|
||||
|
||||
* Fix Player#src API documentation. ([#4454](https://github.com/videojs/video.js/issues/4454)) ([7579fc1](https://github.com/videojs/video.js/commit/7579fc1))
|
||||
* make jsdoc generate anchor names so ToC links work ([#4471](https://github.com/videojs/video.js/issues/4471)) ([03fd402](https://github.com/videojs/video.js/commit/03fd402))
|
||||
|
||||
### Tests
|
||||
|
||||
* add unit tests for player.duration() ([#4459](https://github.com/videojs/video.js/issues/4459)) ([1e80e59](https://github.com/videojs/video.js/commit/1e80e59))
|
||||
|
||||
<a name="6.2.1"></a>
|
||||
## [6.2.1](https://github.com/videojs/video.js/compare/v6.2.0...v6.2.1) (2017-06-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* auto-removal remote text tracks being removed when not supposed to ([#4450](https://github.com/videojs/video.js/issues/4450)) ([82c8b80](https://github.com/videojs/video.js/commit/82c8b80)), closes [#4403](https://github.com/videojs/video.js/issues/4403) [#4315](https://github.com/videojs/video.js/issues/4315)
|
||||
* IE10 issue for disableOthers when property access results in "permission denied" ([#4395](https://github.com/videojs/video.js/issues/4395)) ([7f7ea70](https://github.com/videojs/video.js/commit/7f7ea70)), closes [#4378](https://github.com/videojs/video.js/issues/4378)
|
||||
* player.duration() should return NaN if duration is not known ([#4443](https://github.com/videojs/video.js/issues/4443)) ([f5cc165](https://github.com/videojs/video.js/commit/f5cc165))
|
||||
* Safari picture-in-picture triggers fullscreenchange ([#4437](https://github.com/videojs/video.js/issues/4437)) ([b636663](https://github.com/videojs/video.js/commit/b636663))
|
||||
* Update translations to match correct string ([#4383](https://github.com/videojs/video.js/issues/4383)) ([e0824c8](https://github.com/videojs/video.js/commit/e0824c8))
|
||||
* Use passive event listeners for touchstart/touchmove ([#4440](https://github.com/videojs/video.js/issues/4440)) ([b4dc4f8](https://github.com/videojs/video.js/commit/b4dc4f8)), closes [#4432](https://github.com/videojs/video.js/issues/4432)
|
||||
|
||||
### Chores
|
||||
|
||||
* **package:** update husky to version 0.14.1 ([#4444](https://github.com/videojs/video.js/issues/4444)) ([66a0d23](https://github.com/videojs/video.js/commit/66a0d23)), closes [#4436](https://github.com/videojs/video.js/issues/4436)
|
||||
* **package:** update rollup to version 0.42.0 ([#4392](https://github.com/videojs/video.js/issues/4392)) ([f87b12c](https://github.com/videojs/video.js/commit/f87b12c))
|
||||
* **package:** update rollup-watch to version 4.0.0 ([#4396](https://github.com/videojs/video.js/issues/4396)) ([4bce4a2](https://github.com/videojs/video.js/commit/4bce4a2))
|
||||
* **sandbox:** Fix paths in sandbox files. ([#4416](https://github.com/videojs/video.js/issues/4416)) ([c4bbe5d](https://github.com/videojs/video.js/commit/c4bbe5d))
|
||||
|
||||
### Documentation
|
||||
|
||||
* Fix links in API docs for several Player events. ([#4427](https://github.com/videojs/video.js/issues/4427)) ([cc6e824](https://github.com/videojs/video.js/commit/cc6e824))
|
||||
* Fixing player.remoteTextTracks jsdoc ([#4417](https://github.com/videojs/video.js/issues/4417)) ([9329e3e](https://github.com/videojs/video.js/commit/9329e3e))
|
||||
* Update name of FullscreenToggle in documentation ([#4410](https://github.com/videojs/video.js/issues/4410)) ([9702155](https://github.com/videojs/video.js/commit/9702155))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* Various small performance improvements. ([#4426](https://github.com/videojs/video.js/issues/4426)) ([77ba3d1](https://github.com/videojs/video.js/commit/77ba3d1))
|
||||
|
||||
<a name="6.2.0"></a>
|
||||
# [6.2.0](https://github.com/videojs/video.js/compare/v6.1.0...v6.2.0) (2017-05-30)
|
||||
|
||||
### Features
|
||||
|
||||
* Persist caption/description choice over source changes in emulated tracks ([#4295](https://github.com/videojs/video.js/issues/4295)) ([188ead1](https://github.com/videojs/video.js/commit/188ead1))
|
||||
* **lang:** Adding galician ([#4334](https://github.com/videojs/video.js/issues/4334)) ([2a26c7f](https://github.com/videojs/video.js/commit/2a26c7f))
|
||||
* **lang:** Create sk.json ([#4374](https://github.com/videojs/video.js/issues/4374)) ([e5e1c7f](https://github.com/videojs/video.js/commit/e5e1c7f))
|
||||
* **lang:** Update zh-CN.json ([#4370](https://github.com/videojs/video.js/issues/4370)) ([0c16c5f](https://github.com/videojs/video.js/commit/0c16c5f))
|
||||
* Use Rollup to generate dist files ([#4301](https://github.com/videojs/video.js/issues/4301)) ([c31836c](https://github.com/videojs/video.js/commit/c31836c))
|
||||
|
||||
### Chores
|
||||
|
||||
* **package:** update grunt-contrib-cssmin to version 2.2.0 ([#4345](https://github.com/videojs/video.js/issues/4345)) ([d57f09f](https://github.com/videojs/video.js/commit/d57f09f))
|
||||
* **package:** update videojs-flash to version 2.0.0 ([#4375](https://github.com/videojs/video.js/issues/4375)) ([9816070](https://github.com/videojs/video.js/commit/9816070))
|
||||
* update translations needed ([#4380](https://github.com/videojs/video.js/issues/4380)) ([a5a68e8](https://github.com/videojs/video.js/commit/a5a68e8))
|
||||
|
||||
### Tests
|
||||
|
||||
* **TextTrackDisplay:** Removing incorrect test techOrder ([#4379](https://github.com/videojs/video.js/issues/4379)) ([eade52e](https://github.com/videojs/video.js/commit/eade52e))
|
||||
|
||||
<a name="6.1.0"></a>
|
||||
# [6.1.0](https://github.com/videojs/video.js/compare/v6.0.1...v6.1.0) (2017-05-15)
|
||||
|
||||
### Features
|
||||
|
||||
* Add 'beforepluginsetup' event and named plugin setup events (e.g. 'pluginsetup:foo') ([#4255](https://github.com/videojs/video.js/issues/4255)) ([0a19cf0](https://github.com/videojs/video.js/commit/0a19cf0))
|
||||
* add 'playsinline' player option ([#4348](https://github.com/videojs/video.js/issues/4348)) ([8d80a58](https://github.com/videojs/video.js/commit/8d80a58))
|
||||
* Add a version class to the player ([#4320](https://github.com/videojs/video.js/issues/4320)) ([ae423df](https://github.com/videojs/video.js/commit/ae423df))
|
||||
* Add getVideoPlaybackQuality API ([#4338](https://github.com/videojs/video.js/issues/4338)) ([483e5a2](https://github.com/videojs/video.js/commit/483e5a2))
|
||||
* deprecate firstplay event ([#4353](https://github.com/videojs/video.js/issues/4353)) ([35df351](https://github.com/videojs/video.js/commit/35df351))
|
||||
* remove playbackRate blacklist for recent Android Chrome ([#4321](https://github.com/videojs/video.js/issues/4321)) ([da0f1ee](https://github.com/videojs/video.js/commit/da0f1ee))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **package:** update global to version 4.3.2 ([#4291](https://github.com/videojs/video.js/issues/4291)) ([b5c60f3](https://github.com/videojs/video.js/commit/b5c60f3))
|
||||
* only disable user-selection on sliders ([#4354](https://github.com/videojs/video.js/issues/4354)) ([cb6005e](https://github.com/videojs/video.js/commit/cb6005e))
|
||||
* Only update text track mode if changed ([#4298](https://github.com/videojs/video.js/issues/4298)) ([3087830](https://github.com/videojs/video.js/commit/3087830))
|
||||
* prevent dupe events on enabled ClickableComponents ([#4316](https://github.com/videojs/video.js/issues/4316)) ([03bab83](https://github.com/videojs/video.js/commit/03bab83)), closes [#4312](https://github.com/videojs/video.js/issues/4312)
|
||||
* TextTrackButton on Safari and iOS ([#4350](https://github.com/videojs/video.js/issues/4350)) ([3dcfa95](https://github.com/videojs/video.js/commit/3dcfa95))
|
||||
|
||||
### Chores
|
||||
|
||||
* Fix examples and docs and some links ([#4279](https://github.com/videojs/video.js/issues/4279)) ([f773c47](https://github.com/videojs/video.js/commit/f773c47))
|
||||
* typo soruce -> source ([#4307](https://github.com/videojs/video.js/issues/4307)) ([da1d861](https://github.com/videojs/video.js/commit/da1d861))
|
||||
|
||||
### Documentation
|
||||
|
||||
* **react-guide:** Use a React component as a VJS component ([#4287](https://github.com/videojs/video.js/issues/4287)) ([cff2e50](https://github.com/videojs/video.js/commit/cff2e50))
|
||||
|
||||
<a name="6.0.1"></a>
|
||||
## [6.0.1](https://github.com/videojs/video.js/compare/v6.0.0...v6.0.1) (2017-04-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* set IE_VERSION correctly for IE11 ([#4281](https://github.com/videojs/video.js/issues/4281)) ([1ea0041](https://github.com/videojs/video.js/commit/1ea0041)), closes [#4278](https://github.com/videojs/video.js/issues/4278)
|
||||
* techOrder names can be camelCased. ([#4277](https://github.com/videojs/video.js/issues/4277)) ([92e5d9f](https://github.com/videojs/video.js/commit/92e5d9f))
|
||||
|
||||
### Chores
|
||||
|
||||
* **changelog:** Update CHANGELOG with v5 changes ([#4257](https://github.com/videojs/video.js/issues/4257)) ([c20ca5c](https://github.com/videojs/video.js/commit/c20ca5c))
|
||||
* add slack travis notifications ([#4282](https://github.com/videojs/video.js/issues/4282)) ([7490a49](https://github.com/videojs/video.js/commit/7490a49))
|
||||
* gitignore all npm-debug.log.* ([#4252](https://github.com/videojs/video.js/issues/4252)) ([083f643](https://github.com/videojs/video.js/commit/083f643))
|
||||
|
||||
### Documentation
|
||||
|
||||
* **component:** Replace VolumeMenuButton with VolumePanel in component tree ([#4267](https://github.com/videojs/video.js/issues/4267)) ([02721c7](https://github.com/videojs/video.js/commit/02721c7)), closes [#4266](https://github.com/videojs/video.js/issues/4266)
|
||||
* add a Webpack usage guide ([#4261](https://github.com/videojs/video.js/issues/4261)) ([230743e](https://github.com/videojs/video.js/commit/230743e))
|
||||
* remove mentions of bower support ([#4274](https://github.com/videojs/video.js/issues/4274)) ([39fd73f](https://github.com/videojs/video.js/commit/39fd73f))
|
||||
|
||||
<a name="6.0.0"></a>
|
||||
# [6.0.0](https://github.com/videojs/video.js/compare/v5.16.0...v6.0.0) (2017-04-03)
|
||||
|
||||
@@ -183,6 +312,88 @@
|
||||
* button component will always use a button element.
|
||||
* `play()` no longer returns the player object but instead the native Promise or nothing.
|
||||
|
||||
<a name="5.19.1"></a>
|
||||
## [5.19.1](https://github.com/videojs/video.js/compare/v5.19.0...v5.19.1) (2017-03-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* not showing default text tracks over video ([#4217](https://github.com/videojs/video.js/issues/4217)) ([4653922](https://github.com/videojs/video.js/commit/4653922))
|
||||
* removeCue should work with native passed in cue ([#4209](https://github.com/videojs/video.js/issues/4209)) ([3974944](https://github.com/videojs/video.js/commit/3974944))
|
||||
|
||||
### Chores
|
||||
|
||||
* **package:** update videojs-vtt.js to 0.12.3 ([#4223](https://github.com/videojs/video.js/issues/4223)) ([ad770fb](https://github.com/videojs/video.js/commit/ad770fb))
|
||||
|
||||
<a name="5.19.0"></a>
|
||||
# [5.19.0](https://github.com/videojs/video.js/compare/v5.18.4...v5.19.0) (2017-03-15)
|
||||
|
||||
### Features
|
||||
|
||||
* Make pause on open optional for ModalDialog via options ([#4187](https://github.com/videojs/video.js/issues/4187)) ([4ec3b56](https://github.com/videojs/video.js/commit/4ec3b56))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make load progress buffered regions height 100% ([#4191](https://github.com/videojs/video.js/issues/4191)) ([398c6e9](https://github.com/videojs/video.js/commit/398c6e9))
|
||||
* make sure audio track hides with one item ([#4203](https://github.com/videojs/video.js/issues/4203)) ([c069655](https://github.com/videojs/video.js/commit/c069655))
|
||||
|
||||
<a name="5.18.4"></a>
|
||||
## [5.18.4](https://github.com/videojs/video.js/compare/v5.18.3...v5.18.4) (2017-03-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **vttjs:** wait till tech el in DOM before loading vttjs ([#4176](https://github.com/videojs/video.js/issues/4176)) ([ad86eec](https://github.com/videojs/video.js/commit/ad86eec))
|
||||
|
||||
<a name="5.18.3"></a>
|
||||
## [5.18.3](https://github.com/videojs/video.js/compare/v5.18.2...v5.18.3) (2017-03-06)
|
||||
|
||||
<a name="5.18.1"></a>
|
||||
## [5.18.1](https://github.com/videojs/video.js/compare/v5.18.0...v5.18.1) (2017-03-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cues:** only copy cue props that don't exist ([#4146](https://github.com/videojs/video.js/issues/4146)) ([de08669](https://github.com/videojs/video.js/commit/de08669))
|
||||
* cue-points with a startTime of 0 ([#4148](https://github.com/videojs/video.js/issues/4148)) ([e7d4b47](https://github.com/videojs/video.js/commit/e7d4b47))
|
||||
* make sure that cues copy over their id ([#4154](https://github.com/videojs/video.js/issues/4154)) ([072c277](https://github.com/videojs/video.js/commit/072c277))
|
||||
* **MenuButton:** Unify behavior of showing/hiding ([#3993](https://github.com/videojs/video.js/issues/3993)) ([4367c69](https://github.com/videojs/video.js/commit/4367c69))
|
||||
* **playback rate menu:** playback rate menu items should be selectable ([#4150](https://github.com/videojs/video.js/issues/4150)) ([288edd1](https://github.com/videojs/video.js/commit/288edd1))
|
||||
|
||||
### Chores
|
||||
|
||||
* **build:** lint errors only and silence webpack ([#4153](https://github.com/videojs/video.js/issues/4153)) ([b1ca344](https://github.com/videojs/video.js/commit/b1ca344))
|
||||
* **package:** update video-js-swf to 5.3.0 ([#4161](https://github.com/videojs/video.js/issues/4161)) ([2bcfe21](https://github.com/videojs/video.js/commit/2bcfe21))
|
||||
|
||||
<a name="5.18.0"></a>
|
||||
# [5.18.0](https://github.com/videojs/video.js/compare/v5.17.0...v5.18.0) (2017-02-27)
|
||||
|
||||
### Features
|
||||
|
||||
* focus play toggle from Big Play Btn on play ([#4132](https://github.com/videojs/video.js/issues/4132)) ([dcc615a](https://github.com/videojs/video.js/commit/dcc615a)), closes [#2729](https://github.com/videojs/video.js/issues/2729)
|
||||
* update videojs-vtt.js and wrap native cues in TextTrack ([#4131](https://github.com/videojs/video.js/issues/4131)) ([3d4aebc](https://github.com/videojs/video.js/commit/3d4aebc)), closes [#4093](https://github.com/videojs/video.js/issues/4093)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **sass:** import path has cwd once again ([#4076](https://github.com/videojs/video.js/issues/4076)) ([c02c6c6](https://github.com/videojs/video.js/commit/c02c6c6))
|
||||
* addChild instance names should be toTitleCased ([#4117](https://github.com/videojs/video.js/issues/4117)) ([fa97309](https://github.com/videojs/video.js/commit/fa97309))
|
||||
* make mergeOptions behave the same across browsers ([#4090](https://github.com/videojs/video.js/issues/4090)) ([ce19ed5](https://github.com/videojs/video.js/commit/ce19ed5))
|
||||
* synchronously shim vtt.js when possible ([#4082](https://github.com/videojs/video.js/issues/4082)) ([b5727a6](https://github.com/videojs/video.js/commit/b5727a6))
|
||||
|
||||
<a name="5.17.0"></a>
|
||||
# [5.17.0](https://github.com/videojs/video.js/compare/v5.16.0...v5.17.0) (2017-02-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Patch a memory leak caused by un-removed track listener(s). ([#3975](https://github.com/videojs/video.js/issues/3975)) ([bca44c0](https://github.com/videojs/video.js/commit/bca44c0))
|
||||
* remove title attribute on menu items, fixes [#3699](https://github.com/videojs/video.js/issues/3699) ([#4009](https://github.com/videojs/video.js/issues/4009)) ([91874a3](https://github.com/videojs/video.js/commit/91874a3))
|
||||
|
||||
### Chores
|
||||
|
||||
* change accessibility test in grunt.js to remove unnecessary warning message. ([#4008](https://github.com/videojs/video.js/issues/4008)) ([daad492](https://github.com/videojs/video.js/commit/daad492))
|
||||
* **package:** update swf to 5.2.0 ([#4040](https://github.com/videojs/video.js/issues/4040)) ([dab893b](https://github.com/videojs/video.js/commit/dab893b))
|
||||
|
||||
### Documentation
|
||||
|
||||
* minor fix to currentTime() comment: "setting" not "getting" ([#3944](https://github.com/videojs/video.js/issues/3944)) ([6578ed9](https://github.com/videojs/video.js/commit/6578ed9))
|
||||
|
||||
<a name="5.16.0"></a>
|
||||
# [5.16.0](https://github.com/videojs/video.js/compare/v5.15.1...v5.16.0) (2017-01-12)
|
||||
|
||||
|
||||
@@ -261,7 +261,8 @@ git reset --hard upstream/master
|
||||
|
||||
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.
|
||||
Releases in video.js are done on npm and GitHub and eventually posted on the CDN. These
|
||||
are the instructions for the npm/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.
|
||||
|
||||
|
||||
+3
-3
@@ -22,11 +22,11 @@
|
||||
Thanks to the awesome folks over at [Fastly][fastly], there's a free, CDN hosted version of Video.js that anyone can use. Add these tags to your document's `<head>`:
|
||||
|
||||
```html
|
||||
<link href="//vjs.zencdn.net/5.11/video-js.min.css" rel="stylesheet">
|
||||
<script src="//vjs.zencdn.net/5.11/video.min.js"></script>
|
||||
<link href="//vjs.zencdn.net/5.19/video-js.min.css" rel="stylesheet">
|
||||
<script src="//vjs.zencdn.net/5.19/video.min.js"></script>
|
||||
```
|
||||
|
||||
> For the latest URLs, check out the [Getting Started][getting-started] page on our website.
|
||||
> For the latest version of video.js and URLs to use, check out the [Getting Started][getting-started] page on our website.
|
||||
|
||||
Next, using Video.js is as simple as creating a `<video>` element, but with an additional `data-setup` attribute. At a minimum, this attribute must have a value of `'{}'`, but it can include any Video.js [options][options] - just make sure it contains valid JSON!
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
const fs = require('fs');
|
||||
const zlib = require('zlib');
|
||||
const Promise = require('bluebird');
|
||||
const klawSync = require('klaw-sync');
|
||||
const filesize = require('filesize');
|
||||
const Table = require('cli-table');
|
||||
|
||||
const files = klawSync('dist/', {
|
||||
ignore: ['examples', 'lang', 'font', 'ie8', '*.zip', '*.gz'],
|
||||
nodir: true
|
||||
});
|
||||
|
||||
Promise.all(files.map(gzipAndStat))
|
||||
.then(mapFiles)
|
||||
.then(function(files) {
|
||||
logTable(files);
|
||||
|
||||
return files;
|
||||
})
|
||||
.then(cleanup)
|
||||
.catch(function(err) {
|
||||
console.error(err.stack);
|
||||
});
|
||||
|
||||
function cleanup(files) {
|
||||
files.forEach(function(file) {
|
||||
fs.unlinkSync('dist/' + file[0] + '.gz');
|
||||
});
|
||||
}
|
||||
|
||||
function mapFiles(files) {
|
||||
return files.map(function(file) {
|
||||
const path = file[0].path;
|
||||
const fileStat = file[0].stats;
|
||||
const gzStat = file[1];
|
||||
return [file[0].path.split('dist/')[1], filesize(fileStat.size), filesize(gzStat.size)];
|
||||
});
|
||||
}
|
||||
|
||||
function gzipAndStat(file) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const readStream = fs.createReadStream(file.path);
|
||||
const writeStream = fs.createWriteStream(file.path + '.gz');
|
||||
const gzip = zlib.createGzip();
|
||||
readStream.pipe(gzip).pipe(writeStream).on('close', function() {
|
||||
const gzStat = fs.statSync(file.path + '.gz');
|
||||
|
||||
resolve([file, gzStat]);
|
||||
})
|
||||
.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
function logTable(files) {
|
||||
const table = new Table({
|
||||
head: ['filename', 'size', 'gzipped'],
|
||||
colAligns: ['left', 'right', 'right'],
|
||||
style: {
|
||||
border: ['white']
|
||||
}
|
||||
});
|
||||
|
||||
table.push.apply(table, files);
|
||||
console.log(table.toString());
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
var unified = require('unified');
|
||||
var markdown = require('remark-parse');
|
||||
var stringify = require('remark-stringify');
|
||||
var fs = require('fs');
|
||||
|
||||
module.exports = function() {
|
||||
var processor = unified()
|
||||
.use(markdown, {commonmark: true})
|
||||
.use(stringify);
|
||||
|
||||
var ast = processor.parse(fs.readFileSync('./CHANGELOG.md'));
|
||||
|
||||
var changelog = [];
|
||||
changelog.push(processor.stringify(ast.children[0]));
|
||||
|
||||
// start at 1 so we get the first anchor tag
|
||||
// and can break on the second
|
||||
for (var i = 1; i < ast.children.length; i++) {
|
||||
var item = processor.stringify(ast.children[i]);
|
||||
|
||||
if (/^<a name="/.test(item)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (/^###/.test(item)) {
|
||||
item = '\n' + item + '\n';
|
||||
}
|
||||
|
||||
changelog.push(item);
|
||||
}
|
||||
|
||||
return changelog.join('\n');
|
||||
};
|
||||
+25
-1
@@ -5,7 +5,31 @@ var apiPath = path.join(__dirname, '..', 'docs', 'api');
|
||||
var replacements = [
|
||||
{find: /\/docs\/guides\/(.+)\.md/g, replace: 'tutorial-$1.html'},
|
||||
{find: /tutorial-tech.html/g, replace: 'tutorial-tech_.html'},
|
||||
{find: /\/docs\/guides\//g, replace: '#'}
|
||||
{find: /\/docs\/guides\//g, replace: '#'},
|
||||
{find: /(\<h[1-6] id="(?:.*)?)video-js(.*)?"\>/g, replace: '$1videojs$2">'},
|
||||
{find: /(\<h[1-6] id="(?:.*)?)don-t(.*)?"\>/g, replace: '$1dont$2">'},
|
||||
{find: /(\<h[1-6] id="(?:.*)?)node-js(.*)?"\>/g, replace: '$1nodejs$2">'},
|
||||
{find: /(\<h[1-6] id="(?:.*)?)vtt-js(.*)?"\>/g, replace: '$1vttjs$2">'},
|
||||
{find: /(\<h[1-6] id=")-(.*)("\>)/g, replace: '$1$2$3'},
|
||||
{find: /(\<h[1-6] id=")(.*)-("\>)/g, replace: '$1$2$3'},
|
||||
{find: /(\<h[1-6] id=".*)-docs-guides-.*-md("\>)/g, replace: '$1$2'},
|
||||
// replace all children with children-1
|
||||
{find: /\<h3 id="children"\>/g, replace: '<h3 id="children-1">'},
|
||||
// remove the -1 from the first item
|
||||
{find: /\<h3 id="children-1"\>/, replace: '<h3 id="children">'},
|
||||
{find: '<h4 id="nativecontrolsfortouch">', replace: '<h4 id="nativecontrolsfortouch-1">'},
|
||||
{find: '<h3 id="videojs-(audio|video)track">', replace: '<h3 id="videojs$1track">'},
|
||||
{find: '<h3 id="text-tracks">', replace: '<h3 id="text-tracks-1">'},
|
||||
{find: '<h2 id="q-how-can-i-hide-the-links-to-my-video-subtitles-audio-tracks">',
|
||||
replace: '<h2 id="q-how-can-i-hide-the-links-to-my-videosubtitlesaudiotracks">'},
|
||||
{find: '<h3 id="dispose-http-docs-videojs-com-player-html-dispose">',
|
||||
replace: '<h3 id="dispose">'},
|
||||
{find: '<h4 id="effect-on-player-width-and-player-height">',
|
||||
replace: '<h4 id="effect-on-playerwidth-and-playerheight">'},
|
||||
{find: '<h4 id="i-want-to-have-a-single-source-and-dont-care-about-live-adaptive-streaming">',
|
||||
replace: '<h4 id="i-want-to-have-a-single-source-and-dont-care-about-liveadaptive-streaming">'},
|
||||
{find: '<h2 id="api-docs-api">', replace: '<h2 id="api-docs">'},
|
||||
{find: '<h2 id="guides-docs-guides">', replace: '<h2 id="guides">'}
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
var ghrelease = require('gh-release');
|
||||
var currentChangelog = require('./current-changelog.js');
|
||||
var safeParse = require('safe-json-parse/tuple');
|
||||
var pkg = require('../package.json')
|
||||
var minimist = require('minimist');
|
||||
|
||||
var args = minimist(process.argv.slice(2), {
|
||||
boolean: ['prerelease'],
|
||||
default: {
|
||||
prerelease: false
|
||||
},
|
||||
alias: {
|
||||
p: 'prerelease'
|
||||
}
|
||||
}
|
||||
|
||||
var options = {
|
||||
owner: 'videojs',
|
||||
repo: 'video.js',
|
||||
body: currentChangelog(),
|
||||
assets: ['./dist/video-js-'+pkg.version+'.zip'],
|
||||
endpoint: 'https://api.github.com',
|
||||
auth: {
|
||||
username: process.env.VJS_GITHUB_USER,
|
||||
password: process.env.VJS_GITHUB_TOKEN
|
||||
}
|
||||
};
|
||||
|
||||
var tuple = safeParse(process.env.npm_config_argv);
|
||||
var npmargs = tuple[0] ? [] : tuple[1].cooked;
|
||||
|
||||
if (args.prerelease || npmargs.some(function(arg) { return /next/.test(arg); })) {
|
||||
options.prerelease = true;
|
||||
}
|
||||
|
||||
ghrelease(options, function(err, result) {
|
||||
if (err) {
|
||||
console.log('Unable to publish release to github');
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log('Publish release to github!');
|
||||
console.log(result);
|
||||
}
|
||||
});
|
||||
+20
-8
@@ -408,8 +408,7 @@ module.exports = function(grunt) {
|
||||
],
|
||||
dev: [
|
||||
'shell:babel',
|
||||
'browserify:watch',
|
||||
'browserify:watchnovtt',
|
||||
'shell:rollupwatch',
|
||||
'browserify:tests',
|
||||
'watch:skin',
|
||||
'watch:lang',
|
||||
@@ -448,6 +447,24 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
shell: {
|
||||
rollup: {
|
||||
command: 'npm run rollup',
|
||||
options: {
|
||||
preferLocal: true
|
||||
}
|
||||
},
|
||||
rollupall: {
|
||||
command: 'npm run rollup -- --no-progress && npm run rollup-minify -- --no-progress',
|
||||
options: {
|
||||
preferLocal: true
|
||||
}
|
||||
},
|
||||
rollupwatch: {
|
||||
command: 'npm run rollup-dev',
|
||||
optoins: {
|
||||
preferLocal: true
|
||||
}
|
||||
},
|
||||
babel: {
|
||||
command: 'npm run babel -- --watch',
|
||||
options: {
|
||||
@@ -509,12 +526,7 @@ module.exports = function(grunt) {
|
||||
'shell:lint',
|
||||
'clean:build',
|
||||
|
||||
'babel:es5',
|
||||
'browserify:build',
|
||||
'browserify:buildnovtt',
|
||||
'usebanner:novtt',
|
||||
'usebanner:vtt',
|
||||
'uglify',
|
||||
'shell:rollupall',
|
||||
|
||||
'skin',
|
||||
'version:css',
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import { rollup } from 'rollup';
|
||||
import duration from 'humanize-duration';
|
||||
import watch from 'rollup-watch';
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import json from 'rollup-plugin-json';
|
||||
import filesize from 'rollup-plugin-filesize';
|
||||
import progress from 'rollup-plugin-progress';
|
||||
import ignore from 'rollup-plugin-ignore';
|
||||
import uglify from 'rollup-plugin-uglify';
|
||||
import minimist from 'minimist';
|
||||
import _ from 'lodash';
|
||||
import pkg from '../package.json';
|
||||
import fs from 'fs';
|
||||
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
boolean: ['watch', 'minify', 'progress'],
|
||||
default: {
|
||||
progress: true
|
||||
},
|
||||
alias: {
|
||||
w: 'watch',
|
||||
m: 'minify',
|
||||
p: 'progress'
|
||||
}
|
||||
});
|
||||
|
||||
const compiledLicense = _.template(fs.readFileSync('./build/license-header.txt', 'utf8'));
|
||||
const bannerData = _.pick(pkg, ['version', 'copyright']);
|
||||
|
||||
const primedResolve = resolve({
|
||||
jsnext: true,
|
||||
main: true,
|
||||
browser: true
|
||||
});
|
||||
const primedCjs = commonjs({
|
||||
sourceMap: false
|
||||
});
|
||||
const primedBabel = babel({
|
||||
babelrc: false,
|
||||
exclude: 'node_modules/**',
|
||||
presets: [
|
||||
'es3',
|
||||
['es2015', {
|
||||
loose: true,
|
||||
modules: false
|
||||
}]
|
||||
],
|
||||
plugins: ['external-helpers']
|
||||
});
|
||||
|
||||
const es = {
|
||||
options: {
|
||||
entry: 'src/js/video.js',
|
||||
plugins: [
|
||||
json(),
|
||||
primedBabel,
|
||||
args.progress ? progress() : {},
|
||||
filesize()
|
||||
],
|
||||
onwarn(warning) {
|
||||
if (warning.code === 'UNUSED_EXTERNAL_IMPORT' ||
|
||||
warning.code === 'UNRESOLVED_IMPORT') {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(warning.message);
|
||||
},
|
||||
legacy: true
|
||||
},
|
||||
banner: compiledLicense(Object.assign({includesVtt: true}, bannerData)),
|
||||
format: 'es',
|
||||
dest: 'dist/video.es.js'
|
||||
};
|
||||
|
||||
const cjs = Object.assign({}, es, {
|
||||
format: 'cjs',
|
||||
dest: 'dist/video.cjs.js'
|
||||
});
|
||||
|
||||
const umd = {
|
||||
options: {
|
||||
entry: 'src/js/video.js',
|
||||
plugins: [
|
||||
primedResolve,
|
||||
json(),
|
||||
primedCjs,
|
||||
primedBabel,
|
||||
args.progress ? progress() : {},
|
||||
filesize()
|
||||
],
|
||||
legacy: true
|
||||
},
|
||||
banner: compiledLicense(Object.assign({includesVtt: true}, bannerData)),
|
||||
format: 'umd',
|
||||
dest: 'dist/video.js'
|
||||
};
|
||||
|
||||
const minifiedUmd = Object.assign({}, _.cloneDeep(umd), {
|
||||
dest: 'dist/video.min.js'
|
||||
});
|
||||
|
||||
minifiedUmd.options.plugins.splice(4, 0, uglify({
|
||||
preserveComments: 'some',
|
||||
screwIE8: false,
|
||||
mangle: true,
|
||||
compress: {
|
||||
/* eslint-disable camelcase */
|
||||
sequences: true,
|
||||
dead_code: true,
|
||||
conditionals: true,
|
||||
booleans: true,
|
||||
unused: true,
|
||||
if_return: true,
|
||||
join_vars: true,
|
||||
drop_console: true
|
||||
/* eslint-enable camelcase */
|
||||
}
|
||||
}));
|
||||
|
||||
const novttUmd = Object.assign({}, _.cloneDeep(umd), {
|
||||
banner: compiledLicense(Object.assign({includesVtt: false}, bannerData)),
|
||||
dest: 'dist/alt/video.novtt.js'
|
||||
});
|
||||
|
||||
novttUmd.options.plugins.unshift(ignore(['videojs-vtt.js']));
|
||||
|
||||
const minifiedNovttUmd = Object.assign({}, _.cloneDeep(minifiedUmd), {
|
||||
banner: compiledLicense(Object.assign({includesVtt: false}, bannerData)),
|
||||
dest: 'dist/alt/video.novtt.min.js'
|
||||
});
|
||||
|
||||
minifiedNovttUmd.options.plugins.unshift(ignore(['videojs-vtt.js']));
|
||||
|
||||
function runRollup({options, format, dest, banner}) {
|
||||
rollup(options)
|
||||
.then(function(bundle) {
|
||||
bundle.write({
|
||||
format,
|
||||
dest,
|
||||
banner,
|
||||
moduleName: 'videojs',
|
||||
sourceMap: false
|
||||
});
|
||||
}, function(err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
if (!args.watch) {
|
||||
if (args.minify) {
|
||||
runRollup(minifiedUmd);
|
||||
runRollup(minifiedNovttUmd);
|
||||
} else {
|
||||
runRollup(es);
|
||||
runRollup(cjs);
|
||||
runRollup(umd);
|
||||
runRollup(novttUmd);
|
||||
}
|
||||
} else {
|
||||
const props = ['format', 'dest', 'banner'];
|
||||
const watchers = [
|
||||
['es', watch({rollup},
|
||||
Object.assign({},
|
||||
es.options,
|
||||
_.pick(es, props)))],
|
||||
['cjs', watch({rollup},
|
||||
Object.assign({},
|
||||
cjs.options,
|
||||
_.pick(cjs, props)))],
|
||||
['umd', watch({rollup},
|
||||
Object.assign({moduleName: 'videojs'},
|
||||
umd.options,
|
||||
_.pick(umd, props)))],
|
||||
['novtt', watch({rollup},
|
||||
Object.assign({moduleName: 'videojs'},
|
||||
novttUmd.options,
|
||||
_.pick(novttUmd, props)))]
|
||||
];
|
||||
|
||||
watchers.forEach(function([type, watcher]) {
|
||||
watcher.on('event', (details) => {
|
||||
if (details.code === 'BUILD_START') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Bundling ${type}...`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (details.code === 'BUILD_END') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Bundled ${type} in %s`, duration(details.duration));
|
||||
return;
|
||||
}
|
||||
|
||||
if (details.code === 'ERROR') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(details.error.toString());
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -7,7 +7,6 @@ if (tuple[0]) {
|
||||
}
|
||||
|
||||
var sh = require('shelljs');
|
||||
var version = process.env.npm_package_version;
|
||||
var prereleaseType = npm_config_argv['remain'][0];
|
||||
var approvedTypes = {
|
||||
'major': 1,
|
||||
|
||||
@@ -1,32 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<title>Video.js Text Descriptions, Chapters & Captions Example</title>
|
||||
|
||||
<link href="../../video-js.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<script src="../../video.js"></script>
|
||||
|
||||
<!-- Set the location of the flash SWF -->
|
||||
<script>
|
||||
videojs.setGlobalOptions({
|
||||
flash: {
|
||||
swf: '../../video-js.swf'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<link href="http://vjs.zencdn.net/5.19/video-js.css" rel="stylesheet">
|
||||
<script src="http://vjs.zencdn.net/ie8/1.1/videojs-ie8.min.js"></script>
|
||||
<script src="http://vjs.zencdn.net/5.19/video.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p style="background-color:#eee; border: 1px solid #777; padding: 10px; font-size: .8em; line-height: 1.5em; font-family: Verdana, sans-serif;">This page demonstrates a text descriptions track (intended primarily for blind and visually impaired consumers of visual media)</p>
|
||||
|
||||
<!-- NOTE: we have to disable native Text Track support for the HTML5 tech,
|
||||
since even HTML5 video players with native Text Track support
|
||||
don't currently support 'description' text tracks in any
|
||||
useful way! -->
|
||||
useful way! Currently this means that iOS will not display
|
||||
ANY text tracks -->
|
||||
<video id="example_video_1" class="video-js vjs-default-skin" controls preload="none" width="640" height="360"
|
||||
data-setup='{ "html5" : { "nativeTextTracks" : false } }'>
|
||||
data-setup='{ "html5" : { "nativeTextTracks" : false } }'
|
||||
poster="http://d2zihajmogu5jn.cloudfront.net/elephantsdream/poster.png">
|
||||
|
||||
<source src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/ed_hd.mp4" type="video/mp4">
|
||||
<source src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/ed_hd.ogg" type="video/ogg">
|
||||
|
||||
@@ -42,5 +36,7 @@
|
||||
|
||||
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
|
||||
</video>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Index of video.js examples</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Index of video.js examples</h1>
|
||||
<ul>
|
||||
<li><a href="simple-embed">Video.js HTML5 video player simple example</a></li>
|
||||
<li><a href="elephantsdream">Elephants Dream video with text descriptions, chapters & captions example</a></li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,15 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<title>Video.js | HTML5 Video Player</title>
|
||||
<link href="http://vjs.zencdn.net/5.0.2/video-js.css" rel="stylesheet">
|
||||
<script src="http://vjs.zencdn.net/ie8/1.1.0/videojs-ie8.min.js"></script>
|
||||
<script src="http://vjs.zencdn.net/5.0.2/video.js"></script>
|
||||
<link href="http://vjs.zencdn.net/5.19/video-js.css" rel="stylesheet">
|
||||
<script src="http://vjs.zencdn.net/ie8/1.1/videojs-ie8.min.js"></script>
|
||||
<script src="http://vjs.zencdn.net/5.19/video.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<video id="example_video_1" class="video-js vjs-default-skin" controls preload="none" width="640" height="264" poster="http://vjs.zencdn.net/v/oceans.png" data-setup="{}">
|
||||
@@ -22,6 +20,7 @@
|
||||
<!-- Tracks need an ending tag thanks to IE9 -->
|
||||
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
|
||||
</video>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -292,7 +292,7 @@ Player
|
||||
├── BigPlayButton
|
||||
├─┬ ControlBar
|
||||
│ ├── PlayToggle
|
||||
│ ├── VolumeMenuButton
|
||||
│ ├── VolumePanel
|
||||
│ ├── CurrentTimeDisplay (hidden by default)
|
||||
│ ├── TimeDivider (hidden by default)
|
||||
│ ├── DurationDisplay (hidden by default)
|
||||
|
||||
+52
-49
@@ -59,13 +59,15 @@ video.js is an extendable framework/library around the native video element. It
|
||||
|
||||
## Q: How do I install video.js?
|
||||
|
||||
Currently video.js can be installed using bower, npm, serving a release file from
|
||||
Currently video.js can be installed using npm, serving a release file from
|
||||
a github tag, or even using a CDN hosted version. For information on doing any of those
|
||||
see the [install guide][install-guide].
|
||||
|
||||
## Q: Is video.js on bower?
|
||||
|
||||
Yes! See the [install guide][install-guide] for more information.
|
||||
Versions prior to video.js 6 do support bower, however, as of video.js 6, bower is no
|
||||
longer officially supported. Please see https://github.com/videojs/video.js/issues/4012
|
||||
for more information.
|
||||
|
||||
## Q: What do video.js version numbers mean?
|
||||
|
||||
@@ -266,67 +268,68 @@ Yes! Please [submit an issue or open a pull request][pr-issue-question] if this
|
||||
|
||||
Yes! Please [submit an issue or open a pull request][pr-issue-question] if this does not work.
|
||||
|
||||
Be sure to use `require('!style-loader!css-loader!video.js/dist/video-js.css')` to inject video.js CSS.
|
||||
We have a short guide that deals with small configurations that you will need to do. [Webpack and Videojs Configuration][webpack-guide].
|
||||
|
||||
## Q: Does video.js work with react?
|
||||
|
||||
Yes! See [ReactJS integration example][react-guide].
|
||||
|
||||
[reduced-test-case]: https://css-tricks.com/reduced-test-cases/
|
||||
|
||||
[react-guide]: /docs/guides/react.md
|
||||
|
||||
[plugin-guide]: /docs/guides/plugins.md
|
||||
|
||||
[install-guide]: http://videojs.com/getting-started/
|
||||
|
||||
[troubleshooting]: /docs/guides/troubleshooting.md
|
||||
|
||||
[video-tracks]: /docs/guides/video-tracks.md
|
||||
|
||||
[audio-tracks]: /docs/guides/audio-tracks.md
|
||||
|
||||
[text-tracks]: /docs/guides/text-tracks.md
|
||||
|
||||
[debug-guide]: /docs/guides/debugging.md
|
||||
|
||||
[pr-issue-question]: #q-i-think-i-found-a-bug-with-videojs-or-i-want-to-add-a-feature-what-should-i-do
|
||||
|
||||
[hls]: http://github.com/videojs/videojs-contrib-hls
|
||||
|
||||
[flash]: https://github.com/videojs/videojs-flash
|
||||
|
||||
[dash]: http://github.com/videojs/videojs-contrib-dash
|
||||
|
||||
[eme]: https://github.com/videojs/videojs-contrib-eme
|
||||
|
||||
[generator]: https://github.com/videojs/generator-videojs-plugin
|
||||
|
||||
[youtube]: https://github.com/videojs/videojs-youtube
|
||||
|
||||
[vimeo]: https://github.com/videojs/videojs-vimeo
|
||||
|
||||
[ads]: https://github.com/videojs/videojs-contrib-ads
|
||||
|
||||
[pr-template]: http://github.com/videojs/video.js/blob/master/.github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
[issue-template]: http://github.com/videojs/video.js/blob/master/.github/ISSUE_TEMPLATE.md
|
||||
|
||||
[plugin-list]: http://videojs.com/plugins
|
||||
|
||||
[skins-list]: https://github.com/videojs/video.js/wiki/Skins
|
||||
[audio-tracks]: /docs/guides/audio-tracks.md
|
||||
|
||||
[contributing-issues]: http://github.com/videojs/video.js/blob/master/CONTRIBUTING.md#filing-issues
|
||||
|
||||
[contributing-prs]: http://github.com/videojs/video.js/blob/master/CONTRIBUTING.md#contributing-code
|
||||
|
||||
[dash]: http://github.com/videojs/videojs-contrib-dash
|
||||
|
||||
[debug-guide]: /docs/guides/debugging.md
|
||||
|
||||
[eme]: https://github.com/videojs/videojs-contrib-eme
|
||||
|
||||
[flash]: https://github.com/videojs/videojs-flash
|
||||
|
||||
[generator]: https://github.com/videojs/generator-videojs-plugin
|
||||
|
||||
[hls]: http://github.com/videojs/videojs-contrib-hls
|
||||
|
||||
[install-guide]: http://videojs.com/getting-started/
|
||||
|
||||
[issue-template]: http://github.com/videojs/video.js/blob/master/.github/ISSUE_TEMPLATE.md
|
||||
|
||||
[npm-keywords]: https://docs.npmjs.com/files/package.json#keywords
|
||||
|
||||
[plugin-guide]: /docs/guides/plugins.md
|
||||
|
||||
[plugin-list]: http://videojs.com/plugins
|
||||
|
||||
[pr-issue-question]: #q-i-think-i-found-a-bug-with-videojs-or-i-want-to-add-a-feature-what-should-i-do
|
||||
|
||||
[pr-template]: http://github.com/videojs/video.js/blob/master/.github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
[react-guide]: /docs/guides/react.md
|
||||
|
||||
[reduced-test-case]: https://css-tricks.com/reduced-test-cases/
|
||||
|
||||
[semver]: http://semver.org/
|
||||
|
||||
[skins-list]: https://github.com/videojs/video.js/wiki/Skins
|
||||
|
||||
[starter-example]: http://jsbin.com/axedog/edit?html,output
|
||||
|
||||
[text-tracks]: /docs/guides/text-tracks.md
|
||||
|
||||
[troubleshooting]: /docs/guides/troubleshooting.md
|
||||
|
||||
[video-tracks]: /docs/guides/video-tracks.md
|
||||
|
||||
[vimeo]: https://github.com/videojs/videojs-vimeo
|
||||
|
||||
[vjs-issues]: https://github.com/videojs/video.js/issues
|
||||
|
||||
[vjs-prs]: https://github.com/videojs/video.js/pulls
|
||||
|
||||
[npm-keywords]: https://docs.npmjs.com/files/package.json#keywords
|
||||
|
||||
[semver]: http://semver.org/
|
||||
|
||||
[starter-example]: http://jsbin.com/axedog/edit?html,output
|
||||
[webpack-guide]: /docs/guides/webpack.md
|
||||
|
||||
[youtube]: https://github.com/videojs/videojs-youtube
|
||||
|
||||
@@ -318,7 +318,7 @@ The `children` options can also be passed as an `Object`. In this case, it is us
|
||||
videojs('my-player', {
|
||||
children: {
|
||||
controlBar: {
|
||||
fullscreenControl: false
|
||||
fullscreenToggle: false
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -333,7 +333,7 @@ Components can be given custom options via the _lower-camel-case variant of the
|
||||
```js
|
||||
videojs('my-player', {
|
||||
controlBar: {
|
||||
fullscreenControl: false
|
||||
fullscreenToggle: false
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@@ -176,6 +176,14 @@ Like components, advanced plugins offer an implementation of events. This includ
|
||||
|
||||
By offering a built-in events system, advanced plugins offer a wider range of options for code structure with a pattern familiar to most web developers.
|
||||
|
||||
##### Extra Event Data
|
||||
|
||||
All events triggered by plugins include an additional data object as a second argument. This object has three properties:
|
||||
|
||||
- `name`: The name of the plugin (e.g. `"examplePlugin"`) as a string.
|
||||
- `plugin`: The plugin constructor (e.g. `ExamplePlugin`).
|
||||
- `instance`: The plugin constructor instance.
|
||||
|
||||
#### Statefulness
|
||||
|
||||
A new concept introduced for advanced plugins is _statefulness_. This is similar to React components' `state` property and `setState` method.
|
||||
@@ -307,6 +315,19 @@ player.examplePlugin({customClass: 'example-class'});
|
||||
|
||||
These two methods are functionally identical - use whichever you prefer!
|
||||
|
||||
### Plugin Setup Events
|
||||
|
||||
Occasionally, a use-case arises where some code needs to wait for a plugin to be initialized. As of Video.js 6, this can be achieved by listening for `pluginsetup` events on the player.
|
||||
|
||||
For any given plugin initialization, there are four events to be aware of:
|
||||
|
||||
- `beforepluginsetup`: Triggered immediately before any plugin is initialized.
|
||||
- `beforepluginsetup:examplePlugin` Triggered immediately before the `examplePlugin` is initialized.
|
||||
- `pluginsetup`: Triggered after any plugin is initialized.
|
||||
- `pluginsetup:examplePlugin`: Triggered after he `examplePlugin` is initialized.
|
||||
|
||||
These events work for both basic and advanced plugins. They are triggered on the player and each includes an object of [extra event data](#extra-event-data) as a second argument to its listeners.
|
||||
|
||||
## References
|
||||
|
||||
* [Player API][api-player]
|
||||
|
||||
@@ -54,3 +54,94 @@ return <VideoPlayer { ...videoJsOptions } />
|
||||
Dont forget to include the video.js CSS, located at `video.js/dist/video-js.css`.
|
||||
|
||||
[options]: /docs/guides/options.md
|
||||
|
||||
|
||||
## Using a React Component as a Video JS Component
|
||||
|
||||
```jsx
|
||||
/**
|
||||
* EpisodeList.js
|
||||
*
|
||||
* This is just a plain ol' React component.
|
||||
* the vjsComponent methods, player methods etc. are available via
|
||||
* the vjsComponent prop (`this.props.vjsComponent`)
|
||||
*/
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
class EpisodeList extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>{this.props.body}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* vjsEpisodeList.js
|
||||
*
|
||||
* Here is where we register a Video JS Component and
|
||||
* mount the React component to it when the player is ready.
|
||||
*/
|
||||
import EpisodeList from './EpisodeList';
|
||||
import ReactDOM from 'react-dom';
|
||||
import videojs from 'video.js';
|
||||
|
||||
const vjsComponent = videojs.getComponent('Component');
|
||||
|
||||
class vjsEpisodeList extends vjsComponent {
|
||||
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
/* Bind the current class context to the mount method */
|
||||
this.mount = this.mount.bind(this);
|
||||
|
||||
/* When player is ready, call method to mount React component */
|
||||
player.ready(() => {
|
||||
this.mount();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* We will render out the React EpisodeList component into the DOM element
|
||||
* generated automatically by the VideoJS createEl() method.
|
||||
*
|
||||
* We fetch that generated element using `this.el()`, a method provided by the
|
||||
* vjsComponent class that this class is extending.
|
||||
*/
|
||||
mount() {
|
||||
ReactDOM.render(<EpisodeList vjsComponent={this} body="Episodes" />, this.el() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure to register the vjsComponent so Video JS knows it exists
|
||||
*/
|
||||
vjsComponent.registerComponent('vjsEpisodeList', vjsEpisodeList);
|
||||
|
||||
export default vjsEpisodeList;
|
||||
|
||||
|
||||
/**
|
||||
* VideoPlayer.js
|
||||
* Check the above example for how to integrate the rest of this class.
|
||||
*/
|
||||
|
||||
// ...
|
||||
componentDidMount() {
|
||||
// instantiate video.js
|
||||
this.player = videojs(this.videoNode, this.props, function onPlayerReady() {
|
||||
console.log('onPlayerReady', this)
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetch the controlBar component and add the new vjsEpisodeList component as a child
|
||||
* You can pass options here if desired in the second object.
|
||||
*/
|
||||
this.player.getChild('controlBar').addChild('vjsEpisodeList', {});
|
||||
}
|
||||
// ...
|
||||
```
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
## Getting Video.js
|
||||
|
||||
Video.js is officially available via CDN, npm, and Bower.
|
||||
Video.js is officially available via CDN and npm.
|
||||
|
||||
Video.js works out of the box with not only HTML `<script>` and `<link>` tags, but also all major bundlers/packagers/builders, such as Browserify, Node, WebPack, etc.
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Using Webpack with Video.js
|
||||
|
||||
video.js, and the playback technologies such as videojs-contrib-hls all work in a Webpack based build environment. Here are several configuration changes specific to Webpack that will get you up and running.
|
||||
|
||||
## Video.js CSS:
|
||||
To add the CSS that the player requires, simply add
|
||||
`require('!style-loader!css-loader!video.js/dist/video-js.css')` to the file where the player is also included or initialized.
|
||||
|
||||
## Handling .eot files in Webpack
|
||||
In addition to this, you may run into a problem where Webpack does not know how to load .eot files required for IE8 support by default. This can be solved by installing the file-loader and url-loader packages. Install them by running:
|
||||
`npm install --save-dev file-loader url-loader`
|
||||
|
||||
With both packages installed, simply add the following to you webpack.config file in the 'loaders' section:
|
||||
```
|
||||
{
|
||||
loader: 'url-loader?limit=100000',
|
||||
test: /\.(png|woff|woff2|eot|ttf|svg)$/
|
||||
}
|
||||
```
|
||||
|
||||
## Using Webpack with videojs-contrib-hls
|
||||
Import the HLS library with a line such as:
|
||||
`import * as HLS from 'videojs-contrib-hls';`
|
||||
|
||||
In order to use the tech, we must also introduce webworkers with the package 'webworkify-webpack-dropin', run:
|
||||
`npm install --save-dev webworkify-webpack-dropin`
|
||||
|
||||
To utilize this in your page, simply create an alias in your webpack.config.js file with:
|
||||
```
|
||||
resolve: {
|
||||
alias: {
|
||||
webworkify: 'webworkify-webpack-dropin'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Source maps that use the 'eval' tag are not compatible with webworkify, so this may need to be changed also. Source maps such as 'cheap-eval-module-source-map' should be changed to 'cheap-source-map' or anything else that fits your build without using 'eval' source maps.
|
||||
@@ -13,7 +13,6 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
## Status of translations
|
||||
|
||||
<!-- START langtable -->
|
||||
|
||||
| Language file | Missing translations |
|
||||
| ----------------------- | ----------------------------------------------------------------------------------- |
|
||||
| ar.json (missing 50) | Audio Player |
|
||||
@@ -61,7 +60,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -119,7 +118,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -177,7 +176,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -235,7 +234,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -293,7 +292,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -351,19 +350,12 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
| | End of dialog window. |
|
||||
| de.json (missing 8) | Audio Player |
|
||||
| | Video Player |
|
||||
| | Progress Bar |
|
||||
| | progress bar timing: currentTime={1} duration={2} |
|
||||
| | Volume Level |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | End of dialog window. |
|
||||
| de.json (Complete) | |
|
||||
| el.json (missing 44) | Audio Player |
|
||||
| | Video Player |
|
||||
| | Replay |
|
||||
@@ -403,7 +395,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -460,7 +452,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -518,7 +510,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -576,12 +568,69 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
| | End of dialog window. |
|
||||
| fr.json (Complete) | |
|
||||
| gl.json (missing 57) | Audio Player |
|
||||
| | Video Player |
|
||||
| | Replay |
|
||||
| | Progress Bar |
|
||||
| | progress bar timing: currentTime={1} duration={2} |
|
||||
| | Descriptions |
|
||||
| | descriptions off |
|
||||
| | Audio Track |
|
||||
| | Volume Level |
|
||||
| | The media is encrypted and we do not have the keys to decrypt it. |
|
||||
| | Close |
|
||||
| | Close Modal Dialog |
|
||||
| | 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 |
|
||||
| | captions settings |
|
||||
| | subtitles settings |
|
||||
| | descriptions settings |
|
||||
| | Text |
|
||||
| | White |
|
||||
| | Black |
|
||||
| | Red |
|
||||
| | Green |
|
||||
| | Blue |
|
||||
| | Yellow |
|
||||
| | Magenta |
|
||||
| | Cyan |
|
||||
| | Background |
|
||||
| | Window |
|
||||
| | Transparent |
|
||||
| | Semi-Transparent |
|
||||
| | Opaque |
|
||||
| | Font Size |
|
||||
| | Text Edge Style |
|
||||
| | None |
|
||||
| | Raised |
|
||||
| | Depressed |
|
||||
| | Uniform |
|
||||
| | Dropshadow |
|
||||
| | Font Family |
|
||||
| | Proportional Sans-Serif |
|
||||
| | Monospace Sans-Serif |
|
||||
| | Proportional Serif |
|
||||
| | Monospace Serif |
|
||||
| | Casual |
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
| | End of dialog window. |
|
||||
| hr.json (missing 58) | Audio Player |
|
||||
| | Video Player |
|
||||
| | Replay |
|
||||
@@ -635,7 +684,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -693,7 +742,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -751,7 +800,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -809,7 +858,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -867,7 +916,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -925,7 +974,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -972,7 +1021,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -1030,7 +1079,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -1081,7 +1130,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -1139,7 +1188,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -1182,7 +1231,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -1226,11 +1275,12 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
| | End of dialog window. |
|
||||
| sk.json (Complete) | |
|
||||
| sr.json (missing 58) | Audio Player |
|
||||
| | Video Player |
|
||||
| | Replay |
|
||||
@@ -1284,7 +1334,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -1342,7 +1392,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -1353,7 +1403,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | progress bar timing: currentTime={1} duration={2} |
|
||||
| | Volume Level |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | End of dialog window. |
|
||||
| uk.json (missing 44) | Audio Player |
|
||||
| | Video Player |
|
||||
@@ -1394,7 +1444,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -1437,30 +1487,17 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
| | End of dialog window. |
|
||||
| zh-CN.json (missing 57) | Audio Player |
|
||||
| zh-CN.json (missing 44) | Audio Player |
|
||||
| | Video Player |
|
||||
| | Replay |
|
||||
| | Progress Bar |
|
||||
| | progress bar timing: currentTime={1} duration={2} |
|
||||
| | Descriptions |
|
||||
| | descriptions off |
|
||||
| | Audio Track |
|
||||
| | Volume Level |
|
||||
| | Play Video |
|
||||
| | Close |
|
||||
| | Close Modal Dialog |
|
||||
| | 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 |
|
||||
| | captions settings |
|
||||
| | subtitles settings |
|
||||
| | descriptions settings |
|
||||
@@ -1494,7 +1531,7 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
@@ -1538,10 +1575,9 @@ This default value is hardcoded as a default to the localize method in the SeekB
|
||||
| | Script |
|
||||
| | Small Caps |
|
||||
| | Reset |
|
||||
| | all settings to the default values |
|
||||
| | restore all settings to the default values |
|
||||
| | Done |
|
||||
| | Caption Settings Dialog |
|
||||
| | Beginning of dialog window. Escape will cancel and close the window. |
|
||||
| | End of dialog window. |
|
||||
|
||||
<!-- END langtable -->
|
||||
|
||||
+9
-1
@@ -70,7 +70,15 @@
|
||||
"Casual": "Zwanglos",
|
||||
"Script": "Schreibeschrift",
|
||||
"Small Caps": "Small-Caps",
|
||||
"Reset": "Zurücksetzen",
|
||||
"restore all settings to the default values": "Alle Einstellungen auf die Standardwerte zurücksetzen",
|
||||
"Done": "Fertig",
|
||||
"Caption Settings Dialog": "Einstellungsdialog für Untertitel",
|
||||
"Beginning of dialog window. Escape will cancel and close the window.": "Anfang des Dialogfensters. Esc bricht ab und schließt das Fenster."
|
||||
"Beginning of dialog window. Escape will cancel and close the window.": "Anfang des Dialogfensters. Esc bricht ab und schließt das Fenster.",
|
||||
"End of dialog window.": "Ende des Dialogfensters.",
|
||||
"Audio Player": "Audio-Player",
|
||||
"Video Player": "Video-Player",
|
||||
"Progress Bar": "Forschrittsbalken",
|
||||
"progress bar timing: currentTime={1} duration={2}": "{1} von {2}",
|
||||
"Volume Level": "Lautstärkestufe"
|
||||
}
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@
|
||||
"Script": "Script",
|
||||
"Small Caps": "Small Caps",
|
||||
"Reset": "Reset",
|
||||
"all settings to the default values": "all settings to the default values",
|
||||
"restore all settings to the default values": "restore all settings to the default values",
|
||||
"Done": "Done",
|
||||
"Caption Settings Dialog": "Caption Settings Dialog",
|
||||
"Beginning of dialog window. Escape will cancel and close the window.": "Beginning of dialog window. Escape will cancel and close the window.",
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@
|
||||
"Script": "Scripte",
|
||||
"Small Caps": "Petites capitales",
|
||||
"Reset": "Réinitialiser",
|
||||
"all settings to the default values": "tous les paramètres aux valeurs par défaut",
|
||||
"restore all settings to the default values": "Restaurer tous les paramètres aux valeurs par défaut",
|
||||
"Done": "Terminé",
|
||||
"Caption Settings Dialog": "Boîte de dialogue des paramètres des sous-titres transcrits",
|
||||
"Beginning of dialog window. Escape will cancel and close the window.": "Début de la fenêtre de dialogue. La touche d'échappement annulera et fermera la fenêtre.",
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"Play": "Reprodución",
|
||||
"Play Video": "Reprodución Vídeo",
|
||||
"Pause": "Pausa",
|
||||
"Current Time": "Tempo reproducido",
|
||||
"Duration Time": "Duración total",
|
||||
"Remaining Time": "Tempo restante",
|
||||
"Stream Type": "Tipo de secuencia",
|
||||
"LIVE": "DIRECTO",
|
||||
"Loaded": "Cargado",
|
||||
"Progress": "Progreso",
|
||||
"Fullscreen": "Pantalla completa",
|
||||
"Non-Fullscreen": "Pantalla non completa",
|
||||
"Mute": "Silenciar",
|
||||
"Unmute": "Non silenciado",
|
||||
"Playback Rate": "Velocidade de reprodución",
|
||||
"Subtitles": "Subtítulos",
|
||||
"subtitles off": "Subtítulos desactivados",
|
||||
"Captions": "Subtítulos con lenda",
|
||||
"captions off": "Subtítulos con lenda desactivados",
|
||||
"Chapters": "Capítulos",
|
||||
"You aborted the media playback": "Interrompeches a reprodución do vídeo.",
|
||||
"A network error caused the media download to fail part-way.": "Un erro de rede interrompeu a descarga do vídeo.",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Non se puido cargar o vídeo debido a un fallo de rede ou do servidor ou porque o formato é incompatible.",
|
||||
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "A reproducción de vídeo interrompeuse por un problema de corrupción de datos ou porque o vídeo precisa funcións que o teu navegador non ofrece.",
|
||||
"No compatible source was found for this media.": "Non se atopou ningunha fonte compatible con este vídeo."
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"Audio Player": "Zvukový prehrávač",
|
||||
"Video Player": "Video prehrávač",
|
||||
"Play": "Prehrať",
|
||||
"Pause": "Pozastaviť",
|
||||
"Replay": "Prehrať znova",
|
||||
"Current Time": "Aktuálny čas",
|
||||
"Duration Time": "Čas trvania",
|
||||
"Remaining Time": "Zostávajúci čas",
|
||||
"Stream Type": "Typ stopy",
|
||||
"LIVE": "NAŽIVO",
|
||||
"Loaded": "Načítané",
|
||||
"Progress": "Priebeh",
|
||||
"Progress Bar": "Ukazovateľ priebehu",
|
||||
"progress bar timing: currentTime={1} duration={2}": "časovanie ukazovateľa priebehu: currentTime={1} duration={2}",
|
||||
"Fullscreen": "Režim celej obrazovky",
|
||||
"Non-Fullscreen": "Režim normálnej obrazovky",
|
||||
"Mute": "Stlmiť",
|
||||
"Unmute": "Zrušiť stlmenie",
|
||||
"Playback Rate": "Rýchlosť prehrávania",
|
||||
"Subtitles": "Titulky",
|
||||
"subtitles off": "titulky vypnuté",
|
||||
"Captions": "Popisky",
|
||||
"captions off": "popisky vypnuté",
|
||||
"Chapters": "Kapitoly",
|
||||
"Descriptions": "Opisy",
|
||||
"descriptions off": "opisy vypnuté",
|
||||
"Audio Track": "Zvuková stopa",
|
||||
"Volume Level": "Úroveň hlasitosti",
|
||||
"You aborted the media playback": "Prerušili ste prehrávanie",
|
||||
"A network error caused the media download to fail part-way.": "Sťahovanie súboru bolo zrušené pre chybu na sieti.",
|
||||
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Súbor sa nepodarilo načítať pre chybu servera, sieťového pripojenia, alebo je formát súboru nepodporovaný.",
|
||||
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Prehrávanie súboru bolo prerušené pre poškodené dáta, alebo súbor používa vlastnosti, ktoré váš prehliadač nepodporuje.",
|
||||
"No compatible source was found for this media.": "Nebol nájdený žiaden kompatibilný zdroj pre tento súbor.",
|
||||
"The media is encrypted and we do not have the keys to decrypt it.": "Súbor je zašifrovaný a nie je k dispozícii kľúč na rozšifrovanie.",
|
||||
"Play Video": "Prehrať video",
|
||||
"Close": "Zatvoriť",
|
||||
"Close Modal Dialog": "Zatvoriť modálne okno",
|
||||
"Modal Window": "Modálne okno",
|
||||
"This is a modal window": "Toto je modálne okno",
|
||||
"This modal can be closed by pressing the Escape key or activating the close button.": "Toto modálne okno je možné zatvoriť stlačením klávesy Escape, alebo aktivovaním tlačidla na zatvorenie.",
|
||||
", opens captions settings dialog": ", otvorí okno nastavení popiskov",
|
||||
", opens subtitles settings dialog": ", otvorí okno nastavení titulkov",
|
||||
", opens descriptions settings dialog": ", otvorí okno nastavení opisov",
|
||||
", selected": ", označené",
|
||||
"captions settings": "nastavenia popiskov",
|
||||
"subtitles settings": "nastavenia titulkov",
|
||||
"descriptions settings": "nastavenia opisov",
|
||||
"Text": "Text",
|
||||
"White": "Biela",
|
||||
"Black": "Čierna",
|
||||
"Red": "Červená",
|
||||
"Green": "Zelená",
|
||||
"Blue": "Modrá",
|
||||
"Yellow": "Žltá",
|
||||
"Magenta": "Ružová",
|
||||
"Cyan": "Tyrkysová",
|
||||
"Background": "Pozadie",
|
||||
"Window": "Okno",
|
||||
"Transparent": "Priesvitné",
|
||||
"Semi-Transparent": "Polopriesvitné",
|
||||
"Opaque": "Plné",
|
||||
"Font Size": "Veľkosť písma",
|
||||
"Text Edge Style": "Typ okrajov písma",
|
||||
"None": "Žiadne",
|
||||
"Raised": "Zvýšené",
|
||||
"Depressed": "Znížené",
|
||||
"Uniform": "Pravidelné",
|
||||
"Dropshadow": "S tieňom",
|
||||
"Font Family": "Typ písma",
|
||||
"Proportional Sans-Serif": "Proporčné bezpätkové",
|
||||
"Monospace Sans-Serif": "Pravidelné, bezpätkové",
|
||||
"Proportional Serif": "Proporčné pätkové",
|
||||
"Monospace Serif": "Pravidelné pätkové",
|
||||
"Casual": "Bežné",
|
||||
"Script": "Písané",
|
||||
"Small Caps": "Malé kapitálky",
|
||||
"Reset": "Resetovať",
|
||||
"restore all settings to the default values": "všetky nastavenia na základné hodnoty",
|
||||
"Done": "Hotovo",
|
||||
"Caption Settings Dialog": "Okno nastavení popiskov",
|
||||
"Beginning of dialog window. Escape will cancel and close the window.": "Začiatok okna. Klávesa Escape zruší a zavrie okno.",
|
||||
"End of dialog window.": "Koniec okna."
|
||||
}
|
||||
+17
-4
@@ -12,16 +12,29 @@
|
||||
"Non-Fullscreen": "退出全屏",
|
||||
"Mute": "静音",
|
||||
"Unmute": "取消静音",
|
||||
"Playback Rate": "播放码率",
|
||||
"Playback Rate": "播放速度",
|
||||
"Subtitles": "字幕",
|
||||
"subtitles off": "字幕关闭",
|
||||
"subtitles off": "关闭字幕",
|
||||
"Captions": "内嵌字幕",
|
||||
"captions off": "内嵌字幕关闭",
|
||||
"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": ", 选择"
|
||||
}
|
||||
|
||||
+37
-9
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"version": "6.0.0",
|
||||
"main": "./es5/video.js",
|
||||
"version": "6.2.3",
|
||||
"main": "./dist/video.cjs.js",
|
||||
"module": "./dist/video.es.js",
|
||||
"style": "./dist/video-js.css",
|
||||
"copyright": "Copyright Brightcove, Inc. <https://www.brightcove.com/>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -18,6 +19,11 @@
|
||||
"scripts": {
|
||||
"changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s",
|
||||
"build": "grunt dist",
|
||||
"rollup-all": "npm run rollup && npm run rollup-minify",
|
||||
"rollup": "babel-node build/rollup.js",
|
||||
"rollup-minify": "babel-node build/rollup.js --minify",
|
||||
"rollup-dev": "babel-node build/rollup.js --watch",
|
||||
"assets": "node build/assets.js",
|
||||
"change": "grunt chg-add",
|
||||
"clean": "grunt clean",
|
||||
"grunt": "grunt",
|
||||
@@ -33,6 +39,7 @@
|
||||
"docs:fix": "remark --output -- './**/*.md'",
|
||||
"babel": "babel src/js -d es5",
|
||||
"prepublish": "not-in-install && run-p docs:api build || in-install",
|
||||
"publish": "node build/gh-release.js",
|
||||
"prepush": "npm run lint -- --errors",
|
||||
"version": "node build/version.js && git add CHANGELOG.md"
|
||||
},
|
||||
@@ -42,33 +49,37 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.9.2",
|
||||
"global": "4.3.0",
|
||||
"global": "4.3.2",
|
||||
"safe-json-parse": "4.0.0",
|
||||
"tsml": "1.0.1",
|
||||
"videojs-font": "2.0.0",
|
||||
"videojs-ie8": "1.1.2",
|
||||
"videojs-vtt.js": "0.12.3",
|
||||
"videojs-vtt.js": "0.12.4",
|
||||
"xhr": "2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aliasify": "^2.1.0",
|
||||
"babel-cli": "^6.11.4",
|
||||
"babel-plugin-inline-json": "^1.1.1",
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-plugin-transform-runtime": "^6.9.0",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"babel-preset-es3": "^1.0.1",
|
||||
"babel-register": "^6.9.0",
|
||||
"babelify": "^7.3.0",
|
||||
"blanket": "^1.1.6",
|
||||
"bluebird": "^3.5.0",
|
||||
"browserify-derequire": "^0.9.4",
|
||||
"browserify-istanbul": "^2.0.0",
|
||||
"browserify-versionify": "^1.0.4",
|
||||
"bundle-collapser": "^1.2.1",
|
||||
"chg": "^0.3.2",
|
||||
"cli-table": "^0.3.1",
|
||||
"conventional-changelog-cli": "^1.2.0",
|
||||
"conventional-changelog-videojs": "^3.0.0",
|
||||
"es5-shim": "^4.1.3",
|
||||
"es6-shim": "^0.35.1",
|
||||
"filesize": "^3.5.6",
|
||||
"gh-release": "^3.0.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-accessibility": "^5.0.0",
|
||||
"grunt-babel": "^6.0.0",
|
||||
@@ -80,7 +91,7 @@
|
||||
"grunt-contrib-concat": "^1.0.1",
|
||||
"grunt-contrib-connect": "~1.0.2",
|
||||
"grunt-contrib-copy": "^1.0.0",
|
||||
"grunt-contrib-cssmin": "~1.0.2",
|
||||
"grunt-contrib-cssmin": "~2.2.0",
|
||||
"grunt-contrib-uglify": "^2.0.0",
|
||||
"grunt-contrib-watch": "~1.0.0",
|
||||
"grunt-coveralls": "^1.0.0",
|
||||
@@ -92,7 +103,8 @@
|
||||
"grunt-version": "~1.1.1",
|
||||
"grunt-videojs-languages": "0.0.4",
|
||||
"grunt-zip": "0.17.1",
|
||||
"husky": "^0.13.1",
|
||||
"humanize-duration": "^3.10.0",
|
||||
"husky": "^0.14.1",
|
||||
"in-publish": "^2.0.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"jsdoc": "^3.4.2",
|
||||
@@ -108,33 +120,49 @@
|
||||
"karma-qunit": "^1.2.0",
|
||||
"karma-safari-launcher": "^1.0.0",
|
||||
"karma-sinon": "^1.0.5",
|
||||
"klaw-sync": "^1.1.2",
|
||||
"load-grunt-tasks": "^3.1.0",
|
||||
"lodash": "^4.16.6",
|
||||
"markdown-table": "^1.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"npm-run": "^4.1.0",
|
||||
"npm-run-all": "^4.0.2",
|
||||
"proxyquireify": "^3.0.0",
|
||||
"qunitjs": "1.23.1",
|
||||
"remark-cli": "^3.0.0",
|
||||
"remark-lint": "^6.0.0",
|
||||
"remark-parse": "^3.0.1",
|
||||
"remark-stringify": "^3.0.1",
|
||||
"remark-toc": "^4.0.0",
|
||||
"remark-validate-links": "^6.0.0",
|
||||
"replace": "^0.3.0",
|
||||
"rollup": "^0.45.2",
|
||||
"rollup-plugin-babel": "^2.7.1",
|
||||
"rollup-plugin-commonjs": "^8.0.2",
|
||||
"rollup-plugin-filesize": "^1.2.1",
|
||||
"rollup-plugin-ignore": "^1.0.3",
|
||||
"rollup-plugin-json": "^2.1.1",
|
||||
"rollup-plugin-node-resolve": "^3.0.0",
|
||||
"rollup-plugin-progress": "^0.2.1",
|
||||
"rollup-plugin-uglify": "^1.0.2",
|
||||
"rollup-watch": "^4.0.0",
|
||||
"shelljs": "^0.7.5",
|
||||
"sinon": "^1.16.1",
|
||||
"time-grunt": "^1.1.1",
|
||||
"tui-jsdoc-template": "^1.1.0",
|
||||
"uglify-js": "~2.8.8",
|
||||
"unified": "^6.1.5",
|
||||
"videojs-doc-generator": "0.0.1",
|
||||
"videojs-flash": "^1.0.0-RC.0",
|
||||
"videojs-flash": "^2.0.0",
|
||||
"videojs-standard": "^6.0.1",
|
||||
"webpack": "^2.3.0"
|
||||
"webpack": "^1.15.0"
|
||||
},
|
||||
"vjsstandard": {
|
||||
"ignore": [
|
||||
"**/Gruntfile.js",
|
||||
"**/es5/**",
|
||||
"**/build/**",
|
||||
"!build/rollup.js",
|
||||
"**/dist/**",
|
||||
"**/docs/**",
|
||||
"**/lang/**",
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
<title>Video.js Sandbox</title>
|
||||
|
||||
<!-- Add ES5 shim and sham for IE8 -->
|
||||
<script src="../build/temp/ie8/videojs-ie8.js"></script>
|
||||
<script src="../dist/ie8/videojs-ie8.js"></script>
|
||||
|
||||
<!-- Load the source files -->
|
||||
<link href="../build/temp/video-js.css" rel="stylesheet" type="text/css">
|
||||
<script src="../build/temp/video.js"></script>
|
||||
<script src="../dist/video.js"></script>
|
||||
<script src="../node_modules/videojs-flash/dist/videojs-flash.js"></script>
|
||||
|
||||
<!-- Set the location of the flash SWF -->
|
||||
<script>
|
||||
videojs.options.flash.swf = '../build/temp/video-js.swf';
|
||||
videojs.options.flash.swf = '../node_modules/videojs-flash/node_modules/videojs-swf/dist/video-js.swf';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -5,25 +5,29 @@
|
||||
<title>Video.js Text Descriptions, Chapters & Captions Example</title>
|
||||
|
||||
<!-- Add ES5 shim and sham for IE8 -->
|
||||
<script src="../build/temp/ie8/videojs-ie8.js"></script>
|
||||
<script src="../dist/ie8/videojs-ie8.js"></script>
|
||||
|
||||
<!-- Load the source files -->
|
||||
<link href="../build/temp/video-js.css" rel="stylesheet" type="text/css">
|
||||
<script src="../build/temp/video.js"></script>
|
||||
<script src="../dist/video.js"></script>
|
||||
|
||||
<!-- Set the location of the flash SWF -->
|
||||
<script>
|
||||
videojs.options.flash.swf = '../build/temp/video-js.swf';
|
||||
videojs.options.flash.swf = '../node_modules/videojs-flash/node_modules/videojs-swf/dist/video-js.swf';
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- NOTE: we have to disable native Text Track support for the HTML5 tech,
|
||||
since even HTML5 video players with native Text Track support
|
||||
don't currently support 'description' text tracks in any
|
||||
useful way! -->
|
||||
useful way! Currently this means that iOS will not display
|
||||
ANY text tracks -->
|
||||
<video id="example_video_1" class="video-js vjs-default-skin" controls preload="none" width="640" height="360"
|
||||
data-setup='{ "html5" : { "nativeTextTracks" : false } }'>
|
||||
data-setup='{ "html5" : { "nativeTextTracks" : false } }'
|
||||
poster="http://d2zihajmogu5jn.cloudfront.net/elephantsdream/poster.png">
|
||||
|
||||
<source src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/ed_hd.mp4" type="video/mp4">
|
||||
<source src="//d2zihajmogu5jn.cloudfront.net/elephantsdream/ed_hd.ogg" type="video/ogg">
|
||||
|
||||
@@ -39,5 +43,6 @@
|
||||
|
||||
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
|
||||
</video>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
<title>Video.js Sandbox</title>
|
||||
|
||||
<!-- Add ES5 shim and sham for IE8 -->
|
||||
<script src="../build/temp/ie8/videojs-ie8.js"></script>
|
||||
<script src="../dist/ie8/videojs-ie8.js"></script>
|
||||
|
||||
<!-- Load the source files -->
|
||||
<link href="../build/temp/video-js.css" rel="stylesheet" type="text/css">
|
||||
<script src="../build/temp/video.js"></script>
|
||||
<script src="../dist/video.js"></script>
|
||||
<script src="../node_modules/videojs-flash/dist/videojs-flash.js"></script>
|
||||
|
||||
<!-- Set the location of the flash SWF -->
|
||||
<script>
|
||||
videojs.options.flash.swf = '../build/temp/video-js.swf';
|
||||
videojs.options.flash.swf = '../node_modules/videojs-flash/node_modules/videojs-swf/dist/video-js.swf';
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
<title>Video.js Sandbox</title>
|
||||
|
||||
<!-- Add ES5 shim and sham for IE8 -->
|
||||
<script src="../build/temp/ie8/videojs-ie8.js"></script>
|
||||
<script src="../dist/ie8/videojs-ie8.js"></script>
|
||||
|
||||
<!-- Load the source files -->
|
||||
<link href="../build/temp/video-js.css" rel="stylesheet" type="text/css">
|
||||
<script src="../build/temp/video.js"></script>
|
||||
<script src="../dist/video.js"></script>
|
||||
<script src="../node_modules/videojs-flash/dist/videojs-flash.js"></script>
|
||||
|
||||
<!-- Set the location of the flash SWF -->
|
||||
<script>
|
||||
videojs.options.flash.swf = '../build/temp/video-js.swf';
|
||||
videojs.options.flash.swf = '../node_modules/videojs-flash/node_modules/videojs-swf/dist/video-js.swf';
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
<title>VideoJS Languages Demo</title>
|
||||
|
||||
<!-- Add ES5 shim and sham for IE8 -->
|
||||
<script src="../build/temp/ie8/videojs-ie8.js"></script>
|
||||
<script src="../dist/ie8/videojs-ie8.js"></script>
|
||||
|
||||
<!-- Load the source files -->
|
||||
<link href="../build/temp/video-js.css" rel="stylesheet" type="text/css">
|
||||
<script src="../build/temp/video.js"></script>
|
||||
<script src="../dist/video.js"></script>
|
||||
|
||||
<!-- Set the location of the flash SWF -->
|
||||
<script>
|
||||
videojs.options.flash.swf = '../build/temp/video-js.swf';
|
||||
videojs.options.flash.swf = '../node_modules/videojs-flash/node_modules/videojs-swf/dist/video-js.swf';
|
||||
</script>
|
||||
|
||||
<!-- Add support for Spanish 'es' -->
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
<title>Video.js Plugin Example</title>
|
||||
|
||||
<!-- Add ES5 shim and sham for IE8 -->
|
||||
<script src="../build/temp/ie8/videojs-ie8.js"></script>
|
||||
<script src="../dist/ie8/videojs-ie8.js"></script>
|
||||
|
||||
<!-- Load the source files -->
|
||||
<link href="../build/temp/video-js.css" rel="stylesheet" type="text/css">
|
||||
<script src="../build/temp/video.js"></script>
|
||||
<script src="../dist/video.js"></script>
|
||||
|
||||
<!-- Set the location of the flash SWF -->
|
||||
<script>
|
||||
videojs.options.flash.swf = '../build/temp/video-js.swf';
|
||||
videojs.options.flash.swf = '../node_modules/videojs-flash/node_modules/videojs-swf/dist/video-js.swf';
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
// Avoiding helvetica: issue #376
|
||||
font-family: $text-font-family;
|
||||
|
||||
@include user-select(none);
|
||||
|
||||
// Fix for Firefox 9 fullscreen (only if it is enabled). Not needed when
|
||||
// checking fullScreenEnabled.
|
||||
&:-moz-full-screen { position: absolute; }
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
// TODO: I feel like this should be a generic menu. Research later.
|
||||
.vjs-playback-rate > .vjs-menu-button,
|
||||
.vjs-playback-rate .vjs-playback-rate-value {
|
||||
font-size: 1.5em;
|
||||
line-height: 2;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vjs-playback-rate .vjs-playback-rate-value {
|
||||
pointer-events: none;
|
||||
font-size: 1.5em;
|
||||
line-height: 2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
padding: 0;
|
||||
margin: 0 0.45em 0 0.45em;
|
||||
|
||||
@include user-select(none);
|
||||
|
||||
@include background-color-with-alpha($secondary-background-color, $secondary-background-transparency);
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class ClickableComponent extends Component {
|
||||
const localizedText = this.localize(text);
|
||||
|
||||
this.controlText_ = text;
|
||||
this.controlTextEl_.innerHTML = localizedText;
|
||||
Dom.textContent(this.controlTextEl_, localizedText);
|
||||
if (!this.nonIconControl) {
|
||||
// Set title attribute if only an icon is shown
|
||||
el.setAttribute('title', localizedText);
|
||||
@@ -141,28 +141,30 @@ class ClickableComponent extends Component {
|
||||
* Enable this `Component`s element.
|
||||
*/
|
||||
enable() {
|
||||
this.removeClass('vjs-disabled');
|
||||
this.el_.setAttribute('aria-disabled', 'false');
|
||||
if (typeof this.tabIndex_ !== 'undefined') {
|
||||
this.el_.setAttribute('tabIndex', this.tabIndex_);
|
||||
if (!this.enabled_) {
|
||||
this.enabled_ = true;
|
||||
this.removeClass('vjs-disabled');
|
||||
this.el_.setAttribute('aria-disabled', 'false');
|
||||
if (typeof this.tabIndex_ !== 'undefined') {
|
||||
this.el_.setAttribute('tabIndex', this.tabIndex_);
|
||||
}
|
||||
this.on(['tap', 'click'], this.handleClick);
|
||||
this.on('focus', this.handleFocus);
|
||||
this.on('blur', this.handleBlur);
|
||||
}
|
||||
this.on('tap', this.handleClick);
|
||||
this.on('click', this.handleClick);
|
||||
this.on('focus', this.handleFocus);
|
||||
this.on('blur', this.handleBlur);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable this `Component`s element.
|
||||
*/
|
||||
disable() {
|
||||
this.enabled_ = false;
|
||||
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(['tap', 'click'], this.handleClick);
|
||||
this.off('focus', this.handleFocus);
|
||||
this.off('blur', this.handleBlur);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class Component {
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* The key/value store of player options.
|
||||
#
|
||||
*
|
||||
* @param {Object[]} [options.children]
|
||||
* An array of children objects to intialize this component with. Children objects have
|
||||
* a name property that will be used if more than one component of the same type needs to be
|
||||
|
||||
@@ -70,6 +70,26 @@ class OffTextTrackMenuItem extends TextTrackMenuItem {
|
||||
this.selected(selected);
|
||||
}
|
||||
|
||||
handleSelectedLanguageChange(event) {
|
||||
const tracks = this.player().textTracks();
|
||||
let allHidden = true;
|
||||
|
||||
for (let i = 0, l = tracks.length; i < l; i++) {
|
||||
const track = tracks[i];
|
||||
|
||||
if ((['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1) && track.mode === 'showing') {
|
||||
allHidden = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allHidden) {
|
||||
this.player_.cache_.selectedLanguage = {
|
||||
enabled: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
|
||||
|
||||
@@ -26,10 +26,6 @@ class TextTrackButton extends TrackButton {
|
||||
options.tracks = player.textTracks();
|
||||
|
||||
super(player, options);
|
||||
|
||||
if (!Array.isArray(this.kinds_)) {
|
||||
this.kinds_ = [this.kind_];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,11 +57,16 @@ class TextTrackButton extends TrackButton {
|
||||
|
||||
const tracks = this.player_.textTracks();
|
||||
|
||||
if (!Array.isArray(this.kinds_)) {
|
||||
this.kinds_ = [this.kind_];
|
||||
}
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
const track = tracks[i];
|
||||
|
||||
// only add tracks that are of an appropriate kind and have a label
|
||||
if (this.kinds_.indexOf(track.kind) > -1) {
|
||||
|
||||
const item = new TrackMenuItem(this.player_, {
|
||||
track,
|
||||
// MenuItem is selectable
|
||||
|
||||
@@ -29,17 +29,20 @@ class TextTrackMenuItem extends MenuItem {
|
||||
|
||||
// Modify options for parent MenuItem class's init.
|
||||
options.label = track.label || track.language || 'Unknown';
|
||||
options.selected = track.default || track.mode === 'showing';
|
||||
options.selected = track.mode === 'showing';
|
||||
|
||||
super(player, options);
|
||||
|
||||
this.track = track;
|
||||
const changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
const selectedLanguageChangeHandler = Fn.bind(this, this.handleSelectedLanguageChange);
|
||||
|
||||
player.on(['loadstart', 'texttrackchange'], changeHandler);
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
|
||||
this.on('dispose', function() {
|
||||
tracks.removeEventListener('change', changeHandler);
|
||||
tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
|
||||
});
|
||||
|
||||
// iOS7 doesn't dispatch change events to TextTrackLists when an
|
||||
@@ -101,8 +104,10 @@ class TextTrackMenuItem extends MenuItem {
|
||||
const track = tracks[i];
|
||||
|
||||
if (track === this.track && (kinds.indexOf(track.kind) > -1)) {
|
||||
track.mode = 'showing';
|
||||
} else {
|
||||
if (track.mode !== 'showing') {
|
||||
track.mode = 'showing';
|
||||
}
|
||||
} else if (track.mode !== 'disabled') {
|
||||
track.mode = 'disabled';
|
||||
}
|
||||
}
|
||||
@@ -120,6 +125,25 @@ class TextTrackMenuItem extends MenuItem {
|
||||
this.selected(this.track.mode === 'showing');
|
||||
}
|
||||
|
||||
handleSelectedLanguageChange(event) {
|
||||
if (this.track.mode === 'showing') {
|
||||
const selectedLanguage = this.player_.cache_.selectedLanguage;
|
||||
|
||||
// Don't replace the kind of track across the same language
|
||||
if (selectedLanguage && selectedLanguage.enabled &&
|
||||
selectedLanguage.language === this.track.language &&
|
||||
selectedLanguage.kind !== this.track.kind) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.player_.cache_.selectedLanguage = {
|
||||
enabled: true,
|
||||
language: this.track.language,
|
||||
kind: this.track.kind
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
/**
|
||||
* @file current-time-display.js
|
||||
*/
|
||||
import document from 'global/document';
|
||||
import Component from '../../component.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import {bind, throttle} from '../../utils/fn.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
|
||||
/**
|
||||
@@ -23,8 +25,8 @@ class CurrentTimeDisplay extends Component {
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'timeupdate', this.updateContent);
|
||||
this.throttledUpdateContent = throttle(bind(this, this.updateContent), 25);
|
||||
this.on(player, 'timeupdate', this.throttledUpdateContent);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,18 +41,34 @@ class CurrentTimeDisplay extends Component {
|
||||
});
|
||||
|
||||
this.contentEl_ = Dom.createEl('div', {
|
||||
className: 'vjs-current-time-display',
|
||||
// label the current time for screen reader users
|
||||
innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00'
|
||||
className: 'vjs-current-time-display'
|
||||
}, {
|
||||
// tell screen readers not to automatically read the time as it changes
|
||||
'aria-live': 'off'
|
||||
});
|
||||
}, Dom.createEl('span', {
|
||||
className: 'vjs-control-text',
|
||||
textContent: this.localize('Current Time')
|
||||
}));
|
||||
|
||||
this.updateTextNode_();
|
||||
el.appendChild(this.contentEl_);
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the "current time" text node with new content using the
|
||||
* contents of the `formattedTime_` property.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateTextNode_() {
|
||||
if (this.textNode_) {
|
||||
this.contentEl_.removeChild(this.textNode_);
|
||||
}
|
||||
this.textNode_ = document.createTextNode(` ${this.formattedTime_ || '0:00'}`);
|
||||
this.contentEl_.appendChild(this.textNode_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current time display
|
||||
*
|
||||
@@ -62,12 +80,11 @@ class CurrentTimeDisplay extends Component {
|
||||
updateContent(event) {
|
||||
// Allows for smooth scrubbing, when player can't keep up.
|
||||
const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
const localizedText = this.localize('Current Time');
|
||||
const formattedTime = formatTime(time, this.player_.duration());
|
||||
|
||||
if (formattedTime !== this.formattedTime_) {
|
||||
this.formattedTime_ = formattedTime;
|
||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`;
|
||||
this.requestAnimationFrame(this.updateTextNode_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
/**
|
||||
* @file duration-display.js
|
||||
*/
|
||||
import document from 'global/document';
|
||||
import Component from '../../component.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import {bind, throttle} from '../../utils/fn.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
|
||||
/**
|
||||
@@ -24,13 +26,17 @@ class DurationDisplay extends Component {
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'durationchange', this.updateContent);
|
||||
this.throttledUpdateContent = throttle(bind(this, this.updateContent), 25);
|
||||
|
||||
// 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);
|
||||
this.on(player, [
|
||||
'durationchange',
|
||||
|
||||
// Also listen for timeupdate and loadedmetadata because removing those
|
||||
// listeners could have broken dependent applications/libraries. These
|
||||
// can likely be removed for 7.0.
|
||||
'loadedmetadata',
|
||||
'timeupdate'
|
||||
], this.throttledUpdateContent);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,18 +51,34 @@ class DurationDisplay extends Component {
|
||||
});
|
||||
|
||||
this.contentEl_ = Dom.createEl('div', {
|
||||
className: 'vjs-duration-display',
|
||||
// label the duration time for screen reader users
|
||||
innerHTML: `<span class="vjs-control-text">${this.localize('Duration Time')}</span> 0:00`
|
||||
className: 'vjs-duration-display'
|
||||
}, {
|
||||
// tell screen readers not to automatically read the time as it changes
|
||||
'aria-live': 'off'
|
||||
});
|
||||
}, Dom.createEl('span', {
|
||||
className: 'vjs-control-text',
|
||||
textContent: this.localize('Duration Time')
|
||||
}));
|
||||
|
||||
this.updateTextNode_();
|
||||
el.appendChild(this.contentEl_);
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the "current time" text node with new content using the
|
||||
* contents of the `formattedTime_` property.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateTextNode_() {
|
||||
if (this.textNode_) {
|
||||
this.contentEl_.removeChild(this.textNode_);
|
||||
}
|
||||
this.textNode_ = document.createTextNode(` ${this.formattedTime_ || '0:00'}`);
|
||||
this.contentEl_.appendChild(this.textNode_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update duration time display.
|
||||
*
|
||||
@@ -73,14 +95,10 @@ class DurationDisplay extends Component {
|
||||
|
||||
if (duration && this.duration_ !== duration) {
|
||||
this.duration_ = duration;
|
||||
const localizedText = this.localize('Duration Time');
|
||||
const formattedTime = formatTime(duration);
|
||||
|
||||
// label the duration time for screen reader users
|
||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> ${formattedTime}`;
|
||||
this.formattedTime_ = formatTime(duration);
|
||||
this.requestAnimationFrame(this.updateTextNode_);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component.registerComponent('DurationDisplay', DurationDisplay);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
/**
|
||||
* @file remaining-time-display.js
|
||||
*/
|
||||
import document from 'global/document';
|
||||
import Component from '../../component.js';
|
||||
import * as Dom from '../../utils/dom.js';
|
||||
import {bind, throttle} from '../../utils/fn.js';
|
||||
import formatTime from '../../utils/format-time.js';
|
||||
|
||||
/**
|
||||
@@ -23,9 +25,8 @@ class RemainingTimeDisplay extends Component {
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.on(player, 'timeupdate', this.updateContent);
|
||||
this.on(player, 'durationchange', this.updateContent);
|
||||
this.throttledUpdateContent = throttle(bind(this, this.updateContent), 25);
|
||||
this.on(player, ['timeupdate', 'durationchange'], this.throttledUpdateContent);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,18 +41,34 @@ class RemainingTimeDisplay extends Component {
|
||||
});
|
||||
|
||||
this.contentEl_ = Dom.createEl('div', {
|
||||
className: 'vjs-remaining-time-display',
|
||||
// label the remaining time for screen reader users
|
||||
innerHTML: `<span class="vjs-control-text">${this.localize('Remaining Time')}</span> -0:00`
|
||||
className: 'vjs-remaining-time-display'
|
||||
}, {
|
||||
// tell screen readers not to automatically read the time as it changes
|
||||
'aria-live': 'off'
|
||||
});
|
||||
}, Dom.createEl('span', {
|
||||
className: 'vjs-control-text',
|
||||
textContent: this.localize('Remaining Time')
|
||||
}));
|
||||
|
||||
this.updateTextNode_();
|
||||
el.appendChild(this.contentEl_);
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the "remaining time" text node with new content using the
|
||||
* contents of the `formattedTime_` property.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateTextNode_() {
|
||||
if (this.textNode_) {
|
||||
this.contentEl_.removeChild(this.textNode_);
|
||||
}
|
||||
this.textNode_ = document.createTextNode(` -${this.formattedTime_ || '0:00'}`);
|
||||
this.contentEl_.appendChild(this.textNode_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update remaining time display.
|
||||
*
|
||||
@@ -63,20 +80,14 @@ class RemainingTimeDisplay extends Component {
|
||||
*/
|
||||
updateContent(event) {
|
||||
if (this.player_.duration()) {
|
||||
const localizedText = this.localize('Remaining Time');
|
||||
const formattedTime = formatTime(this.player_.remainingTime());
|
||||
|
||||
if (formattedTime !== this.formattedTime_) {
|
||||
this.formattedTime_ = formattedTime;
|
||||
this.contentEl_.innerHTML = `<span class="vjs-control-text">${localizedText}</span> -${formattedTime}`;
|
||||
this.requestAnimationFrame(this.updateTextNode_);
|
||||
}
|
||||
}
|
||||
|
||||
// Allows for smooth scrubbing, when player can't keep up.
|
||||
// var time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
|
||||
// this.contentEl_.innerHTML = vjs.formatTime(time, this.player_.duration());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
|
||||
|
||||
+72
-21
@@ -4,6 +4,7 @@
|
||||
// Subclasses Component
|
||||
import Component from './component.js';
|
||||
|
||||
import {version} from '../../package.json';
|
||||
import document from 'global/document';
|
||||
import window from 'global/window';
|
||||
import tsml from 'tsml';
|
||||
@@ -14,7 +15,7 @@ import * as Fn from './utils/fn.js';
|
||||
import * as Guid from './utils/guid.js';
|
||||
import * as browser from './utils/browser.js';
|
||||
import log from './utils/log.js';
|
||||
import toTitleCase from './utils/to-title-case.js';
|
||||
import toTitleCase, { titleCaseEquals } from './utils/to-title-case.js';
|
||||
import { createTimeRange } from './utils/time-ranges.js';
|
||||
import { bufferedPercent } from './utils/buffer.js';
|
||||
import * as stylesheet from './utils/stylesheet.js';
|
||||
@@ -147,7 +148,7 @@ const TECH_EVENTS_RETRIGGER = [
|
||||
/**
|
||||
* Fires when the browser has loaded the current frame of the audio/video.
|
||||
*
|
||||
* @event player#loadeddata
|
||||
* @event Player#loadeddata
|
||||
* @type {event}
|
||||
*/
|
||||
/**
|
||||
@@ -163,7 +164,7 @@ const TECH_EVENTS_RETRIGGER = [
|
||||
/**
|
||||
* Fires when the current playback position has changed.
|
||||
*
|
||||
* @event player#timeupdate
|
||||
* @event Player#timeupdate
|
||||
* @type {event}
|
||||
*/
|
||||
/**
|
||||
@@ -179,7 +180,7 @@ const TECH_EVENTS_RETRIGGER = [
|
||||
/**
|
||||
* Fires when the playing speed of the audio/video is changed
|
||||
*
|
||||
* @event player#ratechange
|
||||
* @event Player#ratechange
|
||||
* @type {event}
|
||||
*/
|
||||
/**
|
||||
@@ -211,7 +212,7 @@ const TECH_EVENTS_RETRIGGER = [
|
||||
/**
|
||||
* Fires when the volume has been changed
|
||||
*
|
||||
* @event player#volumechange
|
||||
* @event Player#volumechange
|
||||
* @type {event}
|
||||
*/
|
||||
/**
|
||||
@@ -227,7 +228,7 @@ const TECH_EVENTS_RETRIGGER = [
|
||||
/**
|
||||
* Fires when the text track has been changed
|
||||
*
|
||||
* @event player#texttrackchange
|
||||
* @event Player#texttrackchange
|
||||
* @type {event}
|
||||
*/
|
||||
/**
|
||||
@@ -442,6 +443,11 @@ class Player extends Component {
|
||||
// Make player easily findable by ID
|
||||
Player.players[this.id_] = this;
|
||||
|
||||
// Add a major version class to aid css in plugins
|
||||
const majorVersion = version.split('.')[0];
|
||||
|
||||
this.addClass(`vjs-v${majorVersion}`);
|
||||
|
||||
// When the player is first initialized, trigger activity so components
|
||||
// like the control bar show themselves if needed
|
||||
this.userActive(true);
|
||||
@@ -855,6 +861,7 @@ class Player extends Component {
|
||||
'playerId': this.id(),
|
||||
'techId': `${this.id()}_${titleTechName}_api`,
|
||||
'autoplay': this.options_.autoplay,
|
||||
'playsinline': this.options_.playsinline,
|
||||
'preload': this.options_.preload,
|
||||
'loop': this.options_.loop,
|
||||
'muted': this.options_.muted,
|
||||
@@ -1282,7 +1289,8 @@ class Player extends Component {
|
||||
*
|
||||
* @fires Player#firstplay
|
||||
* @listens Tech#firstplay
|
||||
* @deprecated As of 6.0 passing the `starttime` option to the player will be deprecated
|
||||
* @deprecated As of 6.0 firstplay event is deprecated.
|
||||
* @deprecated As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
|
||||
* @private
|
||||
*/
|
||||
handleTechFirstPlay_() {
|
||||
@@ -1300,6 +1308,7 @@ class Player extends Component {
|
||||
* reason to prevent playback, use `myPlayer.one('play');` instead.
|
||||
*
|
||||
* @event Player#firstplay
|
||||
* @deprecated As of 6.0 firstplay event is deprecated.
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('firstplay');
|
||||
@@ -1738,10 +1747,11 @@ class Player extends Component {
|
||||
*/
|
||||
duration(seconds) {
|
||||
if (seconds === undefined) {
|
||||
return this.cache_.duration || 0;
|
||||
// return NaN if the duration is not known
|
||||
return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
|
||||
}
|
||||
|
||||
seconds = parseFloat(seconds) || 0;
|
||||
seconds = parseFloat(seconds);
|
||||
|
||||
// Standardize on Inifity for signaling video is live
|
||||
if (seconds < 0) {
|
||||
@@ -2237,17 +2247,19 @@ class Player extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* The source function updates the video source
|
||||
* There are three types of variables you can pass as the argument.
|
||||
* **URL string**: A URL to the the video file. Use this method if you are sure
|
||||
* the current playback technology (HTML5/Flash) can support the source you
|
||||
* provide. Currently only MP4 files can be used in both HTML5 and Flash.
|
||||
* Get or set the video source.
|
||||
*
|
||||
* @param {Tech~SourceObject|Tech~SourceObject[]} [source]
|
||||
* One SourceObject or an array of SourceObjects
|
||||
* @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
|
||||
* A SourceObject, an array of SourceObjects, or a string referencing
|
||||
* a URL to a media source. It is _highly recommended_ that an object
|
||||
* or array of objects is used here, so that source selection
|
||||
* algorithms can take the `type` into account.
|
||||
*
|
||||
* @return {string}
|
||||
* The current video source when getting
|
||||
* If not provided, this method acts as a getter.
|
||||
*
|
||||
* @return {string|undefined}
|
||||
* If the `source` argument is missing, returns the current source
|
||||
* URL. Otherwise, returns nothing/undefined.
|
||||
*/
|
||||
src(source) {
|
||||
// getter usage
|
||||
@@ -2326,7 +2338,7 @@ class Player extends Component {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sourceTech.tech !== this.techName_) {
|
||||
if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
|
||||
this.changingSrc_ = true;
|
||||
|
||||
// load this technology with the chosen source
|
||||
@@ -2466,6 +2478,31 @@ class Player extends Component {
|
||||
return this.techGet_('autoplay', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or unset the playsinline attribute.
|
||||
* Playsinline tells the browser that non-fullscreen playback is preferred.
|
||||
*
|
||||
* @param {boolean} [value]
|
||||
* - true means that we should try to play inline by default
|
||||
* - false means that we should use the browser's default playback mode,
|
||||
* which in most cases is inline. iOS Safari is a notable exception
|
||||
* and plays fullscreen by default.
|
||||
*
|
||||
* @return {string|Player}
|
||||
* - the current value of playsinline
|
||||
* - the player when setting
|
||||
*
|
||||
* @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
|
||||
*/
|
||||
playsinline(value) {
|
||||
if (value !== undefined) {
|
||||
this.techCall_('setPlaysinline', value);
|
||||
this.options_.playsinline = value;
|
||||
return this;
|
||||
}
|
||||
return this.techGet_('playsinline');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the loop attribute on the video element.
|
||||
*
|
||||
@@ -2528,7 +2565,7 @@ class Player extends Component {
|
||||
* asynchronous way. We want the poster component to use this
|
||||
* poster source so that it covers up the tech's controls.
|
||||
* (YouTube's play button). However we only want to use this
|
||||
* soruce if the player user hasn't set a poster through
|
||||
* source if the player user hasn't set a poster through
|
||||
* the normal APIs.
|
||||
*
|
||||
* @fires Player#posterchange
|
||||
@@ -2995,6 +3032,20 @@ class Player extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets available media playback quality metrics as specified by the W3C's Media
|
||||
* Playback Quality API.
|
||||
*
|
||||
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
|
||||
*
|
||||
* @return {Object|undefined}
|
||||
* An object with supported media playback quality metrics or undefined if there
|
||||
* is no tech or the tech does not support it.
|
||||
*/
|
||||
getVideoPlaybackQuality() {
|
||||
return this.techGet_('getVideoPlaybackQuality');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get video width
|
||||
*
|
||||
@@ -3218,7 +3269,7 @@ class Player extends Component {
|
||||
* @return {TextTrackList}
|
||||
* The current remote text track list
|
||||
*
|
||||
* @method Player.prototype.textTracks
|
||||
* @method Player.prototype.remoteTextTracks
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
+63
-8
@@ -75,6 +75,27 @@ const markPluginAsActive = (player, name) => {
|
||||
player[PLUGIN_CACHE_KEY][name] = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers a pair of plugin setup events.
|
||||
*
|
||||
* @private
|
||||
* @param {Player} player
|
||||
* A Video.js player instance.
|
||||
*
|
||||
* @param {Plugin~PluginEventHash} hash
|
||||
* A plugin event hash.
|
||||
*
|
||||
* @param {Boolean} [before]
|
||||
* If true, prefixes the event name with "before". In other words,
|
||||
* use this to trigger "beforepluginsetup" instead of "pluginsetup".
|
||||
*/
|
||||
const triggerSetupEvent = (player, hash, before) => {
|
||||
const eventName = (before ? 'before' : '') + 'pluginsetup';
|
||||
|
||||
player.trigger(eventName, hash);
|
||||
player.trigger(eventName + ':' + hash.name, hash);
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a basic plugin function and returns a wrapper function which marks
|
||||
* on the player that the plugin has been activated.
|
||||
@@ -91,15 +112,20 @@ const markPluginAsActive = (player, name) => {
|
||||
*/
|
||||
const createBasicPlugin = function(name, plugin) {
|
||||
const basicPluginWrapper = function() {
|
||||
|
||||
// We trigger the "beforepluginsetup" and "pluginsetup" events on the player
|
||||
// regardless, but we want the hash to be consistent with the hash provided
|
||||
// for advanced plugins.
|
||||
//
|
||||
// The only potentially counter-intuitive thing here is the `instance` in
|
||||
// the "pluginsetup" event is the value returned by the `plugin` function.
|
||||
triggerSetupEvent(this, {name, plugin, instance: null}, true);
|
||||
|
||||
const instance = plugin.apply(this, arguments);
|
||||
|
||||
markPluginAsActive(this, name);
|
||||
triggerSetupEvent(this, {name, plugin, instance});
|
||||
|
||||
// We trigger the "pluginsetup" event on the player regardless, but we want
|
||||
// the hash to be consistent with the hash provided for advanced plugins.
|
||||
// The only potentially counter-intuitive thing here is the `instance` is the
|
||||
// value returned by the `plugin` function.
|
||||
this.trigger('pluginsetup', {name, plugin, instance});
|
||||
return instance;
|
||||
};
|
||||
|
||||
@@ -133,11 +159,15 @@ const createPluginFactory = (name, PluginSubClass) => {
|
||||
PluginSubClass.prototype.name = name;
|
||||
|
||||
return function(...args) {
|
||||
triggerSetupEvent(this, {name, plugin: PluginSubClass, instance: null}, true);
|
||||
|
||||
const instance = new PluginSubClass(...[this, ...args]);
|
||||
|
||||
// The plugin is replaced by a function that returns the current instance.
|
||||
this[name] = () => instance;
|
||||
|
||||
triggerSetupEvent(this, instance.getEventHash());
|
||||
|
||||
return instance;
|
||||
};
|
||||
};
|
||||
@@ -147,7 +177,10 @@ const createPluginFactory = (name, PluginSubClass) => {
|
||||
*
|
||||
* @mixes module:evented~EventedMixin
|
||||
* @mixes module:stateful~StatefulMixin
|
||||
* @fires Player#beforepluginsetup
|
||||
* @fires Player#beforepluginsetup:$name
|
||||
* @fires Player#pluginsetup
|
||||
* @fires Player#pluginsetup:$name
|
||||
* @listens Player#dispose
|
||||
* @throws {Error}
|
||||
* If attempting to instantiate the base {@link Plugin} class
|
||||
@@ -164,12 +197,12 @@ class Plugin {
|
||||
* A Video.js player instance.
|
||||
*/
|
||||
constructor(player) {
|
||||
this.player = player;
|
||||
|
||||
if (this.constructor === Plugin) {
|
||||
throw new Error('Plugin must be sub-classed; not directly instantiated.');
|
||||
}
|
||||
|
||||
this.player = player;
|
||||
|
||||
// Make this object evented, but remove the added `trigger` method so we
|
||||
// use the prototype version instead.
|
||||
evented(this);
|
||||
@@ -184,7 +217,6 @@ class Plugin {
|
||||
|
||||
// If the player is disposed, dispose the plugin.
|
||||
player.on('dispose', this.dispose);
|
||||
player.trigger('pluginsetup', this.getEventHash());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,6 +464,21 @@ Player.prototype.hasPlugin = function(name) {
|
||||
|
||||
export default Plugin;
|
||||
|
||||
/**
|
||||
* Signals that a plugin is about to be set up on a player.
|
||||
*
|
||||
* @event Player#beforepluginsetup
|
||||
* @type {Plugin~PluginEventHash}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a plugin is about to be set up on a player - by name. The name
|
||||
* is the name of the plugin.
|
||||
*
|
||||
* @event Player#beforepluginsetup:$name
|
||||
* @type {Plugin~PluginEventHash}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a plugin has just been set up on a player.
|
||||
*
|
||||
@@ -439,6 +486,14 @@ export default Plugin;
|
||||
* @type {Plugin~PluginEventHash}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a plugin has just been set up on a player - by name. The name
|
||||
* is the name of the plugin.
|
||||
*
|
||||
* @event Player#pluginsetup:$name
|
||||
* @type {Plugin~PluginEventHash}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Plugin~PluginEventHash
|
||||
*
|
||||
|
||||
+96
-20
@@ -234,7 +234,7 @@ class Html5 extends Tech {
|
||||
}
|
||||
|
||||
// Update specific tag settings, in case they were overridden
|
||||
const settingsAttrs = ['autoplay', 'preload', 'loop', 'muted'];
|
||||
const settingsAttrs = ['autoplay', 'preload', 'loop', 'muted', 'playsinline'];
|
||||
|
||||
for (let i = settingsAttrs.length - 1; i >= 0; i--) {
|
||||
const attr = settingsAttrs[i];
|
||||
@@ -368,24 +368,26 @@ class Html5 extends Tech {
|
||||
// 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('timeupdate', checkProgress);
|
||||
if (
|
||||
this.el_.duration === Infinity &&
|
||||
browser.IS_ANDROID &&
|
||||
browser.IS_CHROME &&
|
||||
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('timeupdate', checkProgress);
|
||||
}
|
||||
};
|
||||
|
||||
this.on('timeupdate', checkProgress);
|
||||
return NaN;
|
||||
}
|
||||
this.on('timeupdate', checkProgress);
|
||||
return NaN;
|
||||
}
|
||||
return this.el_.duration || NaN;
|
||||
}
|
||||
@@ -430,9 +432,12 @@ class Html5 extends Tech {
|
||||
};
|
||||
|
||||
const beginFn = function() {
|
||||
this.one('webkitendfullscreen', endFn);
|
||||
if ('webkitPresentationMode' in this.el_ &&
|
||||
this.el_.webkitPresentationMode !== 'picture-in-picture') {
|
||||
this.one('webkitendfullscreen', endFn);
|
||||
|
||||
this.trigger('fullscreenchange', { isFullscreen: true });
|
||||
this.trigger('fullscreenchange', { isFullscreen: true });
|
||||
}
|
||||
};
|
||||
|
||||
this.on('webkitbeginfullscreen', beginFn);
|
||||
@@ -667,6 +672,77 @@ class Html5 extends Tech {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of `playsinline` from the media element. `playsinline` indicates
|
||||
* to the browser that non-fullscreen playback is preferred when fullscreen
|
||||
* playback is the native default, such as in iOS Safari.
|
||||
*
|
||||
* @method Html5#playsinline
|
||||
* @return {boolean}
|
||||
* - The value of `playsinline` from the media element.
|
||||
* - True indicates that the media should play inline.
|
||||
* - False indicates that the media should not play inline.
|
||||
*
|
||||
* @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
|
||||
*/
|
||||
playsinline() {
|
||||
return this.el_.hasAttribute('playsinline');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of `playsinline` from the media element. `playsinline` indicates
|
||||
* to the browser that non-fullscreen playback is preferred when fullscreen
|
||||
* playback is the native default, such as in iOS Safari.
|
||||
*
|
||||
* @method Html5#setPlaysinline
|
||||
* @param {boolean} playsinline
|
||||
* - True indicates that the media should play inline.
|
||||
* - False indicates that the media should not play inline.
|
||||
*
|
||||
* @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
|
||||
*/
|
||||
setPlaysinline(value) {
|
||||
if (value) {
|
||||
this.el_.setAttribute('playsinline', 'playsinline');
|
||||
} else {
|
||||
this.el_.removeAttribute('playsinline');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets available media playback quality metrics as specified by the W3C's Media
|
||||
* Playback Quality API.
|
||||
*
|
||||
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
|
||||
*
|
||||
* @return {Object}
|
||||
* An object with supported media playback quality metrics
|
||||
*/
|
||||
getVideoPlaybackQuality() {
|
||||
if (typeof this.el().getVideoPlaybackQuality === 'function') {
|
||||
return this.el().getVideoPlaybackQuality();
|
||||
}
|
||||
|
||||
const videoPlaybackQuality = {};
|
||||
|
||||
if (typeof this.el().webkitDroppedFrameCount !== 'undefined' &&
|
||||
typeof this.el().webkitDecodedFrameCount !== 'undefined') {
|
||||
videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
|
||||
videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
|
||||
}
|
||||
|
||||
if (window.performance && typeof window.performance.now === 'function') {
|
||||
videoPlaybackQuality.creationTime = window.performance.now();
|
||||
} else if (window.performance &&
|
||||
window.performance.timing &&
|
||||
typeof window.performance.timing.navigationStart === 'number') {
|
||||
videoPlaybackQuality.creationTime =
|
||||
window.Date.now() - window.performance.timing.navigationStart;
|
||||
}
|
||||
|
||||
return videoPlaybackQuality;
|
||||
}
|
||||
}
|
||||
|
||||
/* HTML5 Support Testing ---------------------------------------------------- */
|
||||
@@ -761,7 +837,7 @@ Html5.canControlVolume = function() {
|
||||
Html5.canControlPlaybackRate = function() {
|
||||
// Playback rate API is implemented in Android Chrome, but doesn't do anything
|
||||
// https://github.com/videojs/video.js/issues/3180
|
||||
if (browser.IS_ANDROID && browser.IS_CHROME) {
|
||||
if (browser.IS_ANDROID && browser.IS_CHROME && browser.CHROME_VERSION < 58) {
|
||||
return false;
|
||||
}
|
||||
// IE will error if Windows Media Player not installed #3315
|
||||
|
||||
+32
-3
@@ -14,6 +14,7 @@ import document from 'global/document';
|
||||
import {isPlain} from '../utils/obj';
|
||||
import * as TRACK_TYPES from '../tracks/track-types';
|
||||
import toTitleCase from '../utils/to-title-case';
|
||||
import vtt from 'videojs-vtt.js';
|
||||
|
||||
/**
|
||||
* An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
|
||||
@@ -516,7 +517,6 @@ class Tech extends Component {
|
||||
// signals that the Tech is ready at which point Tech.el_ is part of the DOM
|
||||
// before inserting the WebVTT script
|
||||
if (document.body.contains(this.el())) {
|
||||
const vtt = require('videojs-vtt.js');
|
||||
|
||||
// load via require if available and vtt.js script location was not passed in
|
||||
// as an option. novtt builds will turn the above require call into an empty object
|
||||
@@ -530,7 +530,7 @@ class Tech extends Component {
|
||||
// passed in
|
||||
const script = document.createElement('script');
|
||||
|
||||
script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.12.3/vtt.min.js';
|
||||
script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.12.4/vtt.min.js';
|
||||
script.onload = () => {
|
||||
/**
|
||||
* Fired when vtt.js is loaded.
|
||||
@@ -700,7 +700,7 @@ class Tech extends Component {
|
||||
|
||||
if (manualCleanup !== true) {
|
||||
// create the TextTrackList if it doesn't exist
|
||||
this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
|
||||
this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track));
|
||||
}
|
||||
|
||||
return htmlTrackElement;
|
||||
@@ -721,6 +721,21 @@ class Tech extends Component {
|
||||
this.autoRemoteTextTracks_.removeTrack(track);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets available media playback quality metrics as specified by the W3C's Media
|
||||
* Playback Quality API.
|
||||
*
|
||||
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
|
||||
*
|
||||
* @return {Object}
|
||||
* An object with supported media playback quality metrics
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
getVideoPlaybackQuality() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* A method to set a poster from a `Tech`.
|
||||
*
|
||||
@@ -728,6 +743,20 @@ class Tech extends Component {
|
||||
*/
|
||||
setPoster() {}
|
||||
|
||||
/**
|
||||
* A method to check for the presence of the 'playsinine' <video> attribute.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
playsinline() {}
|
||||
|
||||
/**
|
||||
* A method to set or unset the 'playsinine' <video> attribute.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
setPlaysinline() {}
|
||||
|
||||
/*
|
||||
* Check if the tech can support the given mime-type.
|
||||
*
|
||||
|
||||
@@ -19,7 +19,7 @@ import document from 'global/document';
|
||||
*/
|
||||
const disableOthers = function(list, track) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (track.id === list[i].id) {
|
||||
if (!Object.keys(list[i]).length || track.id === list[i].id) {
|
||||
continue;
|
||||
}
|
||||
// another audio track is enabled, disable it
|
||||
|
||||
@@ -92,6 +92,7 @@ class TextTrackDisplay extends Component {
|
||||
|
||||
player.on('loadstart', Fn.bind(this, this.toggleDisplay));
|
||||
player.on('texttrackchange', Fn.bind(this, this.updateDisplay));
|
||||
player.on('loadstart', Fn.bind(this, this.preselectTrack));
|
||||
|
||||
// This used to be called during player init, but was causing an error
|
||||
// if a track should show by default and the display hadn't loaded yet.
|
||||
@@ -111,33 +112,66 @@ class TextTrackDisplay extends Component {
|
||||
this.player_.addRemoteTextTrack(tracks[i], true);
|
||||
}
|
||||
|
||||
const modes = {captions: 1, subtitles: 1};
|
||||
const trackList = this.player_.textTracks();
|
||||
let firstDesc;
|
||||
let firstCaptions;
|
||||
this.preselectTrack();
|
||||
}));
|
||||
}
|
||||
|
||||
for (let i = 0; i < trackList.length; i++) {
|
||||
const track = trackList[i];
|
||||
/**
|
||||
* Preselect a track following this precedence:
|
||||
* - matches the previously selected {@link TextTrack}'s language and kind
|
||||
* - matches the previously selected {@link TextTrack}'s language only
|
||||
* - is the first default captions track
|
||||
* - is the first default descriptions track
|
||||
*
|
||||
* @listens Player#loadstart
|
||||
*/
|
||||
preselectTrack() {
|
||||
const modes = {captions: 1, subtitles: 1};
|
||||
const trackList = this.player_.textTracks();
|
||||
const userPref = this.player_.cache_.selectedLanguage;
|
||||
let firstDesc;
|
||||
let firstCaptions;
|
||||
let preferredTrack;
|
||||
|
||||
if (track.default) {
|
||||
if (track.kind === 'descriptions' && !firstDesc) {
|
||||
firstDesc = track;
|
||||
} else if (track.kind in modes && !firstCaptions) {
|
||||
firstCaptions = track;
|
||||
}
|
||||
for (let i = 0; i < trackList.length; i++) {
|
||||
const track = trackList[i];
|
||||
|
||||
if (userPref && userPref.enabled &&
|
||||
userPref.language === track.language) {
|
||||
// Always choose the track that matches both language and kind
|
||||
if (track.kind === userPref.kind) {
|
||||
preferredTrack = track;
|
||||
// or choose the first track that matches language
|
||||
} else if (!preferredTrack) {
|
||||
preferredTrack = track;
|
||||
}
|
||||
|
||||
// clear everything if offTextTrackMenuItem was clicked
|
||||
} else if (userPref && !userPref.enabled) {
|
||||
preferredTrack = null;
|
||||
firstDesc = null;
|
||||
firstCaptions = null;
|
||||
|
||||
} else if (track.default) {
|
||||
if (track.kind === 'descriptions' && !firstDesc) {
|
||||
firstDesc = track;
|
||||
} else if (track.kind in modes && !firstCaptions) {
|
||||
firstCaptions = track;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We want to show the first default track but captions and subtitles
|
||||
// take precedence over descriptions.
|
||||
// So, display the first default captions or subtitles track
|
||||
// and otherwise the first default descriptions track.
|
||||
if (firstCaptions) {
|
||||
firstCaptions.mode = 'showing';
|
||||
} else if (firstDesc) {
|
||||
firstDesc.mode = 'showing';
|
||||
}
|
||||
}));
|
||||
// The preferredTrack matches the user preference and takes
|
||||
// precendence over all the other tracks.
|
||||
// So, display the preferredTrack before the first default track
|
||||
// and the subtitles/captions track before the descriptions track
|
||||
if (preferredTrack) {
|
||||
preferredTrack.mode = 'showing';
|
||||
} else if (firstCaptions) {
|
||||
firstCaptions.mode = 'showing';
|
||||
} else if (firstDesc) {
|
||||
firstDesc.mode = 'showing';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,6 +61,14 @@ class TextTrackList extends TrackList {
|
||||
track.addEventListener('modechange', Fn.bind(this, function() {
|
||||
this.trigger('change');
|
||||
}));
|
||||
|
||||
const nonLanguageTextTrackKind = ['metadata', 'chapters'];
|
||||
|
||||
if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
|
||||
track.addEventListener('modechange', Fn.bind(this, function() {
|
||||
this.trigger('selectedlanguagechange');
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
export default TextTrackList;
|
||||
|
||||
@@ -299,8 +299,9 @@ class TextTrackSettings extends ModalDialog {
|
||||
* @param {string} key
|
||||
* Configuration key to use during creation.
|
||||
*
|
||||
* @return {Element}
|
||||
* The DOM element that gets created.
|
||||
* @return {string}
|
||||
* An HTML string.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createElSelect_(key, legendId = '', type = 'label') {
|
||||
@@ -308,101 +309,94 @@ class TextTrackSettings extends ModalDialog {
|
||||
const id = config.id.replace('%s', this.id_);
|
||||
|
||||
return [
|
||||
createEl(type, {
|
||||
id,
|
||||
className: type === 'label' ? 'vjs-label' : '',
|
||||
textContent: this.localize(config.label)
|
||||
}, {
|
||||
}),
|
||||
createEl('select', {}, {
|
||||
'aria-labelledby': `${legendId} ${id}`
|
||||
}, config.options.map(o => {
|
||||
`<${type} id="${id}" class="${type === 'label' ? 'vjs-label' : ''}">`,
|
||||
this.localize(config.label),
|
||||
`</${type}>`,
|
||||
`<select aria-labelledby="${legendId} ${id}">`
|
||||
].
|
||||
concat(config.options.map(o => {
|
||||
const optionId = id + '-' + o[1];
|
||||
|
||||
return createEl('option', {
|
||||
id: optionId,
|
||||
textContent: this.localize(o[1]),
|
||||
value: o[0]
|
||||
}, {
|
||||
'aria-labelledby': `${legendId} ${id} ${optionId}`
|
||||
});
|
||||
}))
|
||||
];
|
||||
return [
|
||||
`<option id="${optionId}" value="${o[0]}" `,
|
||||
`aria-labelledby="${legendId} ${id} ${optionId}">`,
|
||||
this.localize(o[1]),
|
||||
'</option>'
|
||||
].join('');
|
||||
})).
|
||||
concat('</select>').join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create foreground color element for the component
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created.
|
||||
* @return {string}
|
||||
* An HTML string.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createElFgColor_() {
|
||||
const legend = createEl('legend', {
|
||||
id: `captions-text-legend-${this.id_}`,
|
||||
textContent: this.localize('Text')
|
||||
});
|
||||
const legendId = `captions-text-legend-${this.id_}`;
|
||||
|
||||
const select = this.createElSelect_('color', legend.id);
|
||||
|
||||
const opacity = createEl('span', {
|
||||
className: 'vjs-text-opacity vjs-opacity'
|
||||
}, undefined, this.createElSelect_('textOpacity', legend.id));
|
||||
|
||||
return createEl('fieldset', {
|
||||
className: 'vjs-fg-color vjs-track-setting'
|
||||
}, undefined, [legend].concat(select, opacity));
|
||||
return [
|
||||
'<fieldset class="vjs-fg-color vjs-track-setting">',
|
||||
`<legend id="${legendId}">`,
|
||||
this.localize('Text'),
|
||||
'</legend>',
|
||||
this.createElSelect_('color', legendId),
|
||||
'<span class="vjs-text-opacity vjs-opacity">',
|
||||
this.createElSelect_('textOpacity', legendId),
|
||||
'</span>',
|
||||
'</fieldset>'
|
||||
].join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create background color element for the component
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created
|
||||
* @return {string}
|
||||
* An HTML string.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createElBgColor_() {
|
||||
const legend = createEl('legend', {
|
||||
id: `captions-background-${this.id_}`,
|
||||
textContent: this.localize('Background')
|
||||
});
|
||||
const legendId = `captions-background-${this.id_}`;
|
||||
|
||||
const select = this.createElSelect_('backgroundColor', legend.id);
|
||||
|
||||
const opacity = createEl('span', {
|
||||
className: 'vjs-bg-opacity vjs-opacity'
|
||||
}, undefined, this.createElSelect_('backgroundOpacity', legend.id));
|
||||
|
||||
return createEl('fieldset', {
|
||||
className: 'vjs-bg-color vjs-track-setting'
|
||||
}, undefined, [legend].concat(select, opacity));
|
||||
return [
|
||||
'<fieldset class="vjs-bg-color vjs-track-setting">',
|
||||
`<legend id="${legendId}">`,
|
||||
this.localize('Background'),
|
||||
'</legend>',
|
||||
this.createElSelect_('backgroundColor', legendId),
|
||||
'<span class="vjs-bg-opacity vjs-opacity">',
|
||||
this.createElSelect_('backgroundOpacity', legendId),
|
||||
'</span>',
|
||||
'</fieldset>'
|
||||
].join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create window color element for the component
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created
|
||||
* @return {string}
|
||||
* An HTML string.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createElWinColor_() {
|
||||
const legend = createEl('legend', {
|
||||
id: `captions-window-${this.id_}`,
|
||||
textContent: this.localize('Window')
|
||||
});
|
||||
const legendId = `captions-window-${this.id_}`;
|
||||
|
||||
const select = this.createElSelect_('windowColor', legend.id);
|
||||
|
||||
const opacity = createEl('span', {
|
||||
className: 'vjs-window-opacity vjs-opacity'
|
||||
}, undefined, this.createElSelect_('windowOpacity', legend.id));
|
||||
|
||||
return createEl('fieldset', {
|
||||
className: 'vjs-window-color vjs-track-setting'
|
||||
}, undefined, [legend].concat(select, opacity));
|
||||
return [
|
||||
'<fieldset class="vjs-window-color vjs-track-setting">',
|
||||
`<legend id="${legendId}">`,
|
||||
this.localize('Window'),
|
||||
'</legend>',
|
||||
this.createElSelect_('windowColor', legendId),
|
||||
'<span class="vjs-window-opacity vjs-opacity">',
|
||||
this.createElSelect_('windowOpacity', legendId),
|
||||
'</span>',
|
||||
'</fieldset>'
|
||||
].join('');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,12 +409,13 @@ class TextTrackSettings extends ModalDialog {
|
||||
*/
|
||||
createElColors_() {
|
||||
return createEl('div', {
|
||||
className: 'vjs-track-settings-colors'
|
||||
}, undefined, [
|
||||
this.createElFgColor_(),
|
||||
this.createElBgColor_(),
|
||||
this.createElWinColor_()
|
||||
]);
|
||||
className: 'vjs-track-settings-colors',
|
||||
innerHTML: [
|
||||
this.createElFgColor_(),
|
||||
this.createElBgColor_(),
|
||||
this.createElWinColor_()
|
||||
].join('')
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,21 +427,20 @@ class TextTrackSettings extends ModalDialog {
|
||||
* @private
|
||||
*/
|
||||
createElFont_() {
|
||||
const fontPercent = createEl('fieldset', {
|
||||
className: 'vjs-font-percent vjs-track-setting'
|
||||
}, undefined, this.createElSelect_('fontPercent', '', 'legend'));
|
||||
|
||||
const edgeStyle = createEl('fieldset', {
|
||||
className: 'vjs-edge-style vjs-track-setting'
|
||||
}, undefined, this.createElSelect_('edgeStyle', '', 'legend'));
|
||||
|
||||
const fontFamily = createEl('fieldset', {
|
||||
className: 'vjs-font-family vjs-track-setting'
|
||||
}, undefined, this.createElSelect_('fontFamily', '', 'legend'));
|
||||
|
||||
return createEl('div', {
|
||||
className: 'vjs-track-settings-font'
|
||||
}, undefined, [fontPercent, edgeStyle, fontFamily]);
|
||||
className: 'vjs-track-settings-font">',
|
||||
innerHTML: [
|
||||
'<fieldset class="vjs-font-percent vjs-track-setting">',
|
||||
this.createElSelect_('fontPercent', '', 'legend'),
|
||||
'</fieldset>',
|
||||
'<fieldset class="vjs-edge-style vjs-track-setting">',
|
||||
this.createElSelect_('edgeStyle', '', 'legend'),
|
||||
'</fieldset>',
|
||||
'<fieldset class="vjs-font-family vjs-track-setting">',
|
||||
this.createElSelect_('fontFamily', '', 'legend'),
|
||||
'</fieldset>'
|
||||
].join('')
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,30 +453,17 @@ class TextTrackSettings extends ModalDialog {
|
||||
*/
|
||||
createElControls_() {
|
||||
const defaultsDescription = this.localize('restore all settings to the default values');
|
||||
const defaultsButton = createEl('button', {
|
||||
className: 'vjs-default-button',
|
||||
title: defaultsDescription,
|
||||
innerHTML: `${this.localize('Reset')}<span class='vjs-control-text'> ${defaultsDescription}</span>`
|
||||
});
|
||||
|
||||
const doneButton = createEl('button', {
|
||||
className: 'vjs-done-button',
|
||||
textContent: this.localize('Done')
|
||||
});
|
||||
|
||||
return createEl('div', {
|
||||
className: 'vjs-track-settings-controls'
|
||||
}, undefined, [defaultsButton, doneButton]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the component's DOM element
|
||||
*
|
||||
* @return {Element}
|
||||
* The element that was created.
|
||||
*/
|
||||
createEl() {
|
||||
return super.createEl();
|
||||
className: 'vjs-track-settings-controls',
|
||||
innerHTML: [
|
||||
`<button class="vjs-default-button" title="${defaultsDescription}">`,
|
||||
this.localize('Reset'),
|
||||
`<span class="vjs-control-text"> ${defaultsDescription}</span>`,
|
||||
'</button>',
|
||||
`<button class="vjs-done-button">${this.localize('Done')}</button>`
|
||||
].join('')
|
||||
});
|
||||
}
|
||||
|
||||
content() {
|
||||
|
||||
@@ -253,6 +253,7 @@ class TextTrack extends Track {
|
||||
* @type {EventTarget~Event}
|
||||
*/
|
||||
this.trigger('modechange');
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import document from 'global/document';
|
||||
*/
|
||||
const disableOthers = function(list, track) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (track.id === list[i].id) {
|
||||
if (!Object.keys(list[i]).length || track.id === list[i].id) {
|
||||
continue;
|
||||
}
|
||||
// another video track is enabled, disable it
|
||||
|
||||
@@ -62,10 +62,26 @@ export const IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebki
|
||||
export const IS_FIREFOX = (/Firefox/i).test(USER_AGENT);
|
||||
export const IS_EDGE = (/Edge/i).test(USER_AGENT);
|
||||
export const IS_CHROME = !IS_EDGE && (/Chrome/i).test(USER_AGENT);
|
||||
export const CHROME_VERSION = (function() {
|
||||
const match = USER_AGENT.match(/Chrome\/(\d+)/);
|
||||
|
||||
if (match && match[1]) {
|
||||
return parseFloat(match[1]);
|
||||
}
|
||||
return null;
|
||||
}());
|
||||
export const IS_IE8 = (/MSIE\s8\.0/).test(USER_AGENT);
|
||||
export const IE_VERSION = (function(result) {
|
||||
return result && parseFloat(result[1]);
|
||||
}((/MSIE\s(\d+)\.\d/).exec(USER_AGENT)));
|
||||
export const IE_VERSION = (function() {
|
||||
const result = (/MSIE\s(\d+)\.\d/).exec(USER_AGENT);
|
||||
let version = result && parseFloat(result[1]);
|
||||
|
||||
if (!version && (/Trident\/7.0/i).test(USER_AGENT) && (/rv:11.0/).test(USER_AGENT)) {
|
||||
// IE 11 has a different user agent string than other IE versions
|
||||
version = 11.0;
|
||||
}
|
||||
|
||||
return version;
|
||||
}());
|
||||
|
||||
export const IS_SAFARI = (/Safari/i).test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
|
||||
export const IS_ANY_SAFARI = IS_SAFARI || IS_IOS;
|
||||
|
||||
@@ -202,6 +202,33 @@ export function fixEvent(event) {
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether passive event listeners are supported
|
||||
*/
|
||||
let _supportsPassive = false;
|
||||
|
||||
(function() {
|
||||
try {
|
||||
const opts = Object.defineProperty({}, 'passive', {
|
||||
get() {
|
||||
_supportsPassive = true;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('test', null, opts);
|
||||
} catch (e) {
|
||||
// disregard
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* Touch events Chrome expects to be passive
|
||||
*/
|
||||
const passiveEvents = [
|
||||
'touchstart',
|
||||
'touchmove'
|
||||
];
|
||||
|
||||
/**
|
||||
* Add an event listener to element
|
||||
* It stores the handler function in a separate cache object
|
||||
@@ -273,7 +300,13 @@ export function on(elem, type, fn) {
|
||||
|
||||
if (data.handlers[type].length === 1) {
|
||||
if (elem.addEventListener) {
|
||||
elem.addEventListener(type, data.dispatcher, false);
|
||||
let options = false;
|
||||
|
||||
if (_supportsPassive &&
|
||||
passiveEvents.indexOf(type) > -1) {
|
||||
options = {passive: true};
|
||||
}
|
||||
elem.addEventListener(type, data.dispatcher, options);
|
||||
} else if (elem.attachEvent) {
|
||||
elem.attachEvent('on' + type, data.dispatcher);
|
||||
}
|
||||
|
||||
@@ -21,3 +21,19 @@ function toTitleCase(string) {
|
||||
}
|
||||
|
||||
export default toTitleCase;
|
||||
|
||||
/**
|
||||
* Compares the TitleCase versions of the two strings for equality.
|
||||
*
|
||||
* @param {string} str1
|
||||
* The first string to compare
|
||||
*
|
||||
* @param {string} str2
|
||||
* The second string to compare
|
||||
*
|
||||
* @return {boolean}
|
||||
* Whether the TitleCase versions of the strings are equal
|
||||
*/
|
||||
export function titleCaseEquals(str1, str2) {
|
||||
return toTitleCase(str1) === toTitleCase(str2);
|
||||
}
|
||||
|
||||
+3
-4
@@ -2,6 +2,7 @@
|
||||
* @file video.js
|
||||
* @module videojs
|
||||
*/
|
||||
import {version} from '../../package.json';
|
||||
import window from 'global/window';
|
||||
import document from 'global/document';
|
||||
import * as setup from './setup';
|
||||
@@ -225,7 +226,7 @@ setup.autoSetupTimeout(1, videojs);
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
videojs.VERSION = require('../../package.json').version;
|
||||
videojs.VERSION = version;
|
||||
|
||||
/**
|
||||
* The global options object. These are the settings that take effect
|
||||
@@ -722,7 +723,5 @@ videojs.dom = Dom;
|
||||
*/
|
||||
videojs.url = Url;
|
||||
|
||||
// We use Node-style module.exports here instead of ES6 because it is more
|
||||
// compatible with different module systems.
|
||||
module.exports = videojs;
|
||||
export default videojs;
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ QUnit.test('should be able to access expected player API methods', function(asse
|
||||
assert.ok(player.userActive, 'userActive exists');
|
||||
assert.ok(player.usingNativeControls, 'usingNativeControls exists');
|
||||
assert.ok(player.isFullscreen, 'isFullscreen exists');
|
||||
assert.ok(player.getVideoPlaybackQuality, 'getVideoPlaybackQuality exists');
|
||||
|
||||
// Track methods
|
||||
assert.ok(player.audioTracks, 'audioTracks exists');
|
||||
|
||||
@@ -10,7 +10,7 @@ module.exports = function(config) {
|
||||
// Compling tests here
|
||||
files: [
|
||||
'../build/temp/video-js.css',
|
||||
'../build/temp/ie8/videojs-ie8.min.js',
|
||||
'../build/temp/ie8/videojs-ie8.js',
|
||||
'../test/globals-shim.js',
|
||||
'../test/unit/**/*.js',
|
||||
'../build/temp/browserify.js',
|
||||
@@ -124,7 +124,7 @@ module.exports = function(config) {
|
||||
'ie8_bs'
|
||||
];
|
||||
} else {
|
||||
settings.browsers = ['Firefox'];
|
||||
settings.browsers = ['chrome_travis'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,11 @@ module.exports = function(config) {
|
||||
|
||||
function getCustomLaunchers(){
|
||||
return {
|
||||
chrome_travis: {
|
||||
base: 'Chrome',
|
||||
flags: ['--no-sandbox']
|
||||
},
|
||||
|
||||
chrome_bs: {
|
||||
base: 'BrowserStack',
|
||||
browser: 'chrome',
|
||||
|
||||
@@ -71,3 +71,25 @@ QUnit.test('handleClick should not be triggered when disabled', function() {
|
||||
testClickableComponent.dispose();
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('handleClick should not be triggered more than once when enabled', function() {
|
||||
let clicks = 0;
|
||||
|
||||
class TestClickableComponent extends ClickableComponent {
|
||||
handleClick() {
|
||||
clicks++;
|
||||
}
|
||||
}
|
||||
|
||||
const player = TestHelpers.makePlayer({});
|
||||
const testClickableComponent = new TestClickableComponent(player);
|
||||
const el = testClickableComponent.el();
|
||||
|
||||
testClickableComponent.enable();
|
||||
// Click should still be handled just once
|
||||
Events.trigger(el, 'click');
|
||||
QUnit.equal(clicks, 1, 'no additional click handler when already enabled ClickableComponent has been enabled again');
|
||||
|
||||
testClickableComponent.dispose();
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
@@ -791,6 +791,35 @@ QUnit.test('should restore attributes from the original video tag when creating
|
||||
assert.equal(el.getAttribute('webkit-playsinline'), '', 'webkit-playsinline attribute was set properly');
|
||||
});
|
||||
|
||||
if (Html5.isSupported()) {
|
||||
QUnit.test('player.playsinline() should be able to get/set playsinline attribute', function(assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const video = document.createElement('video');
|
||||
const player = TestHelpers.makePlayer({techOrder: ['html5']}, video);
|
||||
|
||||
// test setter
|
||||
assert.ok(!player.tech_.el().hasAttribute('playsinline'), 'playsinline has not yet been added');
|
||||
|
||||
player.playsinline(true);
|
||||
|
||||
assert.ok(player.tech_.el().hasAttribute('playsinline'), 'playsinline attribute added');
|
||||
|
||||
player.playsinline(false);
|
||||
|
||||
assert.ok(!player.tech_.el().hasAttribute('playsinline'), 'playsinline attribute removed');
|
||||
|
||||
// test getter
|
||||
player.tech_.el().setAttribute('playsinline', 'playsinline');
|
||||
|
||||
assert.ok(player.playsinline(), 'correctly detects playsinline attribute');
|
||||
|
||||
player.tech_.el().removeAttribute('playsinline');
|
||||
|
||||
assert.ok(!player.playsinline(), 'correctly detects absence of playsinline attribute');
|
||||
});
|
||||
}
|
||||
|
||||
QUnit.test('if tag exists and movingMediaElementInDOM, re-use the tag', function(assert) {
|
||||
// simulate attributes stored from the original tag
|
||||
const tag = Dom.createEl('video');
|
||||
@@ -1564,6 +1593,26 @@ QUnit.test('src selects tech based on middleware', function(assert) {
|
||||
|
||||
});
|
||||
|
||||
QUnit.test('src_ does not call loadTech is name is titleCaseEquals', function(assert) {
|
||||
let loadTechCalled = 0;
|
||||
const playerProxy = {
|
||||
selectSource() {
|
||||
return {
|
||||
tech: 'html5'
|
||||
};
|
||||
},
|
||||
techName_: 'Html5',
|
||||
ready() {},
|
||||
loadTech_() {
|
||||
loadTechCalled++;
|
||||
}
|
||||
};
|
||||
|
||||
Player.prototype.src_.call(playerProxy);
|
||||
|
||||
assert.equal(loadTechCalled, 0, 'loadTech was not called');
|
||||
});
|
||||
|
||||
QUnit.test('options: plugins', function(assert) {
|
||||
const optionsSpy = sinon.spy();
|
||||
|
||||
@@ -1597,3 +1646,40 @@ QUnit.test('options: plugins', function(assert) {
|
||||
player.dispose();
|
||||
Plugin.deregisterPlugin('foo');
|
||||
});
|
||||
|
||||
QUnit.test('should add a class with major version', function(assert) {
|
||||
const majorVersion = require('../../package.json').version.split('.')[0];
|
||||
const player = TestHelpers.makePlayer();
|
||||
|
||||
assert.ok(player.hasClass('vjs-v' + majorVersion), 'the version class should be added to the player');
|
||||
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('player.duration() returns NaN if player.cache_.duration is undefined', function(assert) {
|
||||
const player = TestHelpers.makePlayer();
|
||||
|
||||
player.cache_.duration = undefined;
|
||||
assert.ok(Number.isNaN(player.duration()), 'returned NaN for unkown duration');
|
||||
});
|
||||
|
||||
QUnit.test('player.duration() returns player.cache_.duration if it is defined', function(assert) {
|
||||
const player = TestHelpers.makePlayer();
|
||||
|
||||
player.cache_.duration = 200;
|
||||
assert.equal(player.duration(), 200, 'returned correct integer duration');
|
||||
player.cache_.duration = 942;
|
||||
assert.equal(player.duration(), 942, 'returned correct integer duration');
|
||||
});
|
||||
|
||||
QUnit.test('player.duration() sets the value of player.cache_.duration', function(assert) {
|
||||
const player = TestHelpers.makePlayer();
|
||||
|
||||
// set an arbitrary initial cached duration value for testing the setter functionality
|
||||
player.cache_.duration = 1;
|
||||
|
||||
player.duration(NaN);
|
||||
assert.ok(Number.isNaN(player.duration()), 'duration() set and get NaN duration value');
|
||||
player.duration(200);
|
||||
assert.equal(player.duration(), 200, 'duration() set and get integer duration value');
|
||||
});
|
||||
|
||||
@@ -67,24 +67,35 @@ QUnit.test('setup', function(assert) {
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('"pluginsetup" event', function(assert) {
|
||||
QUnit.test('all "pluginsetup" events', function(assert) {
|
||||
const setupSpy = sinon.spy();
|
||||
const events = [
|
||||
'beforepluginsetup',
|
||||
'beforepluginsetup:mock',
|
||||
'pluginsetup',
|
||||
'pluginsetup:mock'
|
||||
];
|
||||
|
||||
this.player.on('pluginsetup', setupSpy);
|
||||
this.player.on(events, setupSpy);
|
||||
|
||||
const instance = this.player.mock();
|
||||
const event = setupSpy.firstCall.args[0];
|
||||
const hash = setupSpy.firstCall.args[1];
|
||||
|
||||
assert.strictEqual(setupSpy.callCount, 1, 'the "pluginsetup" event was triggered');
|
||||
assert.strictEqual(event.type, 'pluginsetup', 'the event has the correct type');
|
||||
assert.strictEqual(event.target, this.player.el_, 'the event has the correct target');
|
||||
events.forEach((type, i) => {
|
||||
const event = setupSpy.getCall(i).args[0];
|
||||
const hash = setupSpy.getCall(i).args[1];
|
||||
|
||||
assert.deepEqual(hash, {
|
||||
name: 'mock',
|
||||
instance,
|
||||
plugin: this.MockPlugin
|
||||
}, 'the event hash object is correct');
|
||||
assert.strictEqual(event.type, type, `the "${type}" event was triggered`);
|
||||
assert.strictEqual(event.target, this.player.el_, 'the event has the correct target');
|
||||
|
||||
assert.deepEqual(hash, {
|
||||
name: 'mock',
|
||||
|
||||
// The "before" events have a `null` instance and the others have the
|
||||
// return value of the plugin factory.
|
||||
instance: i < 2 ? null : instance,
|
||||
plugin: this.MockPlugin
|
||||
}, 'the event hash object is correct');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('defaultState static property is used to populate state', function(assert) {
|
||||
|
||||
@@ -47,23 +47,35 @@ QUnit.test('setup', function(assert) {
|
||||
assert.ok(this.player.hasPlugin('basic'), 'player has the plugin available');
|
||||
});
|
||||
|
||||
QUnit.test('"pluginsetup" event', function(assert) {
|
||||
QUnit.test('all "pluginsetup" events', function(assert) {
|
||||
const setupSpy = sinon.spy();
|
||||
const events = [
|
||||
'beforepluginsetup',
|
||||
'beforepluginsetup:basic',
|
||||
'pluginsetup',
|
||||
'pluginsetup:basic'
|
||||
];
|
||||
|
||||
this.player.on('pluginsetup', setupSpy);
|
||||
this.player.on(events, setupSpy);
|
||||
|
||||
const instance = this.player.basic();
|
||||
const event = setupSpy.firstCall.args[0];
|
||||
const hash = setupSpy.firstCall.args[1];
|
||||
|
||||
assert.strictEqual(setupSpy.callCount, 1, 'the "pluginsetup" event was triggered');
|
||||
assert.strictEqual(event.type, 'pluginsetup', 'the event has the correct type');
|
||||
events.forEach((type, i) => {
|
||||
const event = setupSpy.getCall(i).args[0];
|
||||
const hash = setupSpy.getCall(i).args[1];
|
||||
|
||||
assert.deepEqual(hash, {
|
||||
name: 'basic',
|
||||
instance,
|
||||
plugin: this.basic
|
||||
}, 'the event hash object is correct');
|
||||
assert.strictEqual(event.type, type, `the "${type}" event was triggered`);
|
||||
assert.strictEqual(event.target, this.player.el_, 'the event has the correct target');
|
||||
|
||||
assert.deepEqual(hash, {
|
||||
name: 'basic',
|
||||
|
||||
// The "before" events have a `null` instance and the others have the
|
||||
// return value of the plugin factory.
|
||||
instance: i < 2 ? null : instance,
|
||||
plugin: this.basic
|
||||
}, 'the event hash object is correct');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('properties are copied', function(assert) {
|
||||
|
||||
@@ -7,12 +7,13 @@ QUnit.test('should set options from data-setup even if autoSetup is not called b
|
||||
const el = TestHelpers.makeTag();
|
||||
|
||||
el.setAttribute('data-setup',
|
||||
'{"controls": true, "autoplay": false, "preload": "auto"}');
|
||||
'{"controls": true, "autoplay": false, "preload": "auto", "playsinline": true}');
|
||||
|
||||
const player = TestHelpers.makePlayer({}, el);
|
||||
|
||||
assert.ok(player.options_.controls === true);
|
||||
assert.ok(player.options_.autoplay === false);
|
||||
assert.ok(player.options_.preload === 'auto');
|
||||
assert.ok(player.options_.playsinline === true);
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
@@ -50,6 +50,19 @@ QUnit.module('HTML5', {
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('should be able to set playsinline attribute', function(assert) {
|
||||
assert.expect(2);
|
||||
|
||||
tech.createEl();
|
||||
tech.setPlaysinline(true);
|
||||
|
||||
assert.ok(tech.el().hasAttribute('playsinline'), 'playsinline attribute was added');
|
||||
|
||||
tech.setPlaysinline(false);
|
||||
|
||||
assert.ok(!tech.el().hasAttribute('playsinline'), 'playsinline attribute was removed');
|
||||
});
|
||||
|
||||
QUnit.test('should detect whether the volume can be changed', function(assert) {
|
||||
|
||||
if (!{}.__defineSetter__) {
|
||||
@@ -100,6 +113,32 @@ QUnit.test('test defaultPlaybackRate', function(assert) {
|
||||
assert.strictEqual(tech.defaultPlaybackRate(), 0.75, 'can be changed from the API');
|
||||
});
|
||||
|
||||
QUnit.test('blacklist playbackRate support on older verisons of Chrome on Android', function(assert) {
|
||||
if (!Html5.canControlPlaybackRate()) {
|
||||
assert.ok(true, 'playbackRate is not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset playbackrate - Firefox's rounding of playbackRate causes the rate not to change in canControlPlaybackRate() after a few instances
|
||||
Html5.TEST_VID.playbackRate = 1;
|
||||
|
||||
const oldIsAndroid = browser.IS_ANDROID;
|
||||
const oldIsChrome = browser.IS_CHROME;
|
||||
const oldChromeVersion = browser.CHROME_VERSION;
|
||||
|
||||
browser.IS_ANDROID = true;
|
||||
browser.IS_CHROME = true;
|
||||
browser.CHROME_VERSION = 50;
|
||||
assert.strictEqual(Html5.canControlPlaybackRate(), false, 'canControlPlaybackRate should return false on older Chrome');
|
||||
|
||||
browser.CHROME_VERSION = 58;
|
||||
assert.strictEqual(Html5.canControlPlaybackRate(), true, 'canControlPlaybackRate should return true on newer Chrome');
|
||||
|
||||
browser.IS_ANDROID = oldIsAndroid;
|
||||
browser.IS_CHROME = oldIsChrome;
|
||||
browser.CHROME_VERSION = oldChromeVersion;
|
||||
});
|
||||
|
||||
QUnit.test('test volume', function(assert) {
|
||||
if (!Html5.canControlVolume()) {
|
||||
assert.ok(true, 'Volume is not supported');
|
||||
@@ -665,3 +704,83 @@ test('When Android Chrome reports Infinity duration with currentTime 0, return N
|
||||
browser.IS_CHROME = oldIsChrome;
|
||||
tech.el_ = oldEl;
|
||||
});
|
||||
|
||||
QUnit.test('supports getting available media playback quality metrics', function(assert) {
|
||||
const origPerformance = window.performance;
|
||||
const origDate = window.Date;
|
||||
const oldEl = tech.el_;
|
||||
const videoPlaybackQuality = {
|
||||
creationTime: 1,
|
||||
corruptedVideoFrames: 2,
|
||||
droppedVideoFrames: 3,
|
||||
totalVideoFrames: 5
|
||||
};
|
||||
|
||||
tech.el_ = {
|
||||
getVideoPlaybackQuality: () => videoPlaybackQuality
|
||||
};
|
||||
assert.deepEqual(tech.getVideoPlaybackQuality(),
|
||||
videoPlaybackQuality,
|
||||
'uses native implementation when supported');
|
||||
|
||||
tech.el_ = {
|
||||
webkitDroppedFrameCount: 1,
|
||||
webkitDecodedFrameCount: 2
|
||||
};
|
||||
window.performance = {
|
||||
now: () => 4
|
||||
};
|
||||
assert.deepEqual(tech.getVideoPlaybackQuality(),
|
||||
{ droppedVideoFrames: 1, totalVideoFrames: 2, creationTime: 4 },
|
||||
'uses webkit prefixed metrics and performance.now when supported');
|
||||
|
||||
tech.el_ = {
|
||||
webkitDroppedFrameCount: 1,
|
||||
webkitDecodedFrameCount: 2
|
||||
};
|
||||
window.Date = {
|
||||
now: () => 10
|
||||
};
|
||||
window.performance = {
|
||||
timing: {
|
||||
navigationStart: 3
|
||||
}
|
||||
};
|
||||
assert.deepEqual(tech.getVideoPlaybackQuality(),
|
||||
{ droppedVideoFrames: 1, totalVideoFrames: 2, creationTime: 7 },
|
||||
'uses webkit prefixed metrics and Date.now() - navigationStart when ' +
|
||||
'supported');
|
||||
|
||||
tech.el_ = {};
|
||||
window.performance = void 0;
|
||||
assert.deepEqual(tech.getVideoPlaybackQuality(), {}, 'empty object when not supported');
|
||||
|
||||
window.performance = {
|
||||
now: () => 5
|
||||
};
|
||||
assert.deepEqual(tech.getVideoPlaybackQuality(),
|
||||
{ creationTime: 5 },
|
||||
'only creation time when it\'s the only piece available');
|
||||
|
||||
window.performance = {
|
||||
timing: {
|
||||
navigationStart: 3
|
||||
}
|
||||
};
|
||||
assert.deepEqual(tech.getVideoPlaybackQuality(),
|
||||
{ creationTime: 7 },
|
||||
'only creation time when it\'s the only piece available');
|
||||
|
||||
tech.el_ = {
|
||||
getVideoPlaybackQuality: () => videoPlaybackQuality,
|
||||
webkitDroppedFrameCount: 1,
|
||||
webkitDecodedFrameCount: 2
|
||||
};
|
||||
assert.deepEqual(tech.getVideoPlaybackQuality(),
|
||||
videoPlaybackQuality,
|
||||
'prefers native implementation when supported');
|
||||
|
||||
tech.el_ = oldEl;
|
||||
window.performance = origPerformance;
|
||||
window.Date = origDate;
|
||||
});
|
||||
|
||||
@@ -214,11 +214,14 @@ QUnit.test('switching sources should clear all remote tracks that are added with
|
||||
|
||||
const tech = new MyTech();
|
||||
|
||||
tech.triggerReady();
|
||||
|
||||
// set the initial source
|
||||
tech.setSource({src: 'foo.mp4', type: 'mp4'});
|
||||
|
||||
// default value for manualCleanup is true
|
||||
tech.addRemoteTextTrack({});
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.equal(warning,
|
||||
'Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js',
|
||||
@@ -226,6 +229,7 @@ QUnit.test('switching sources should clear all remote tracks that are added with
|
||||
|
||||
// should be automatically cleaned up when source changes
|
||||
tech.addRemoteTextTrack({}, false);
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.equal(tech.textTracks().length, 2, 'should have two text tracks at the start');
|
||||
assert.equal(tech.remoteTextTrackEls().length,
|
||||
@@ -238,6 +242,7 @@ QUnit.test('switching sources should clear all remote tracks that are added with
|
||||
|
||||
// change source to force cleanup of auto remote text tracks
|
||||
tech.setSource({src: 'bar.mp4', type: 'mp4'});
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.equal(tech.textTracks().length,
|
||||
1,
|
||||
@@ -601,3 +606,9 @@ QUnit.test('setSource after previous setSource should dispose source handler onc
|
||||
assert.equal(disposeCount, 2, 'did dispose for third setSource');
|
||||
|
||||
});
|
||||
|
||||
QUnit.test('returns an empty object for getVideoPlaybackQuality', function(assert) {
|
||||
const tech = new Tech();
|
||||
|
||||
assert.deepEqual(tech.getVideoPlaybackQuality(), {}, 'returns an empty object');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
/* eslint-env qunit */
|
||||
import Html5 from '../../../src/js/tech/html5.js';
|
||||
import Component from '../../../src/js/component.js';
|
||||
|
||||
import * as browser from '../../../src/js/utils/browser.js';
|
||||
import TestHelpers from '../test-helpers.js';
|
||||
import document from 'global/document';
|
||||
import sinon from 'sinon';
|
||||
|
||||
QUnit.module('Text Track Display', {
|
||||
beforeEach(assert) {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
},
|
||||
afterEach(assert) {
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
const getMenuItemByLanguage = function(items, language) {
|
||||
for (let i = items.length - 1; i > 0; i--) {
|
||||
const captionMenuItem = items[i];
|
||||
const trackLanguage = captionMenuItem.track.language;
|
||||
|
||||
if (trackLanguage && trackLanguage === language) {
|
||||
return captionMenuItem;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QUnit.test('if native text tracks are not supported, create a texttrackdisplay', function(assert) {
|
||||
const oldTestVid = Html5.TEST_VID;
|
||||
const oldIsFirefox = browser.IS_FIREFOX;
|
||||
const oldTextTrackDisplay = Component.getComponent('TextTrackDisplay');
|
||||
const tag = document.createElement('video');
|
||||
const track1 = document.createElement('track');
|
||||
const track2 = document.createElement('track');
|
||||
|
||||
track1.kind = 'captions';
|
||||
track1.label = 'en';
|
||||
track1.language = 'English';
|
||||
track1.src = 'en.vtt';
|
||||
tag.appendChild(track1);
|
||||
|
||||
track2.kind = 'captions';
|
||||
track2.label = 'es';
|
||||
track2.language = 'Spanish';
|
||||
track2.src = 'es.vtt';
|
||||
tag.appendChild(track2);
|
||||
|
||||
Html5.TEST_VID = {
|
||||
textTracks: []
|
||||
};
|
||||
|
||||
browser.IS_FIREFOX = true;
|
||||
|
||||
const fakeTTDSpy = sinon.spy();
|
||||
|
||||
class FakeTTD extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
fakeTTDSpy();
|
||||
}
|
||||
}
|
||||
|
||||
Component.registerComponent('TextTrackDisplay', FakeTTD);
|
||||
|
||||
const player = TestHelpers.makePlayer({}, tag);
|
||||
|
||||
assert.strictEqual(fakeTTDSpy.callCount, 1, 'text track display was created');
|
||||
|
||||
Html5.TEST_VID = oldTestVid;
|
||||
browser.IS_FIREFOX = oldIsFirefox;
|
||||
Component.registerComponent('TextTrackDisplay', oldTextTrackDisplay);
|
||||
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('shows the default caption track first', function(assert) {
|
||||
const player = TestHelpers.makePlayer();
|
||||
const track1 = {
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'en.vtt',
|
||||
default: true
|
||||
};
|
||||
const track2 = {
|
||||
kind: 'captions',
|
||||
label: 'Spanish',
|
||||
language: 'es',
|
||||
src: 'es.vtt'
|
||||
};
|
||||
|
||||
// Add the text tracks
|
||||
const englishTrack = player.addRemoteTextTrack(track1).track;
|
||||
const spanishTrack = player.addRemoteTextTrack(track2).track;
|
||||
|
||||
// Make sure the ready handler runs
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.ok(englishTrack.mode === 'showing', 'English track should be showing');
|
||||
assert.ok(spanishTrack.mode === 'disabled', 'Spanish track should not be showing');
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
if (!Html5.supportsNativeTextTracks()) {
|
||||
QUnit.test('selectedlanguagechange is triggered by a track mode change', function(assert) {
|
||||
const player = TestHelpers.makePlayer();
|
||||
const track1 = {
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'en.vtt'
|
||||
};
|
||||
const spy = sinon.spy();
|
||||
const selectedLanguageHandler = function(event) {
|
||||
spy();
|
||||
};
|
||||
const englishTrack = player.addRemoteTextTrack(track1).track;
|
||||
|
||||
player.textTracks().addEventListener('selectedlanguagechange', selectedLanguageHandler);
|
||||
englishTrack.mode = 'showing';
|
||||
|
||||
assert.strictEqual(spy.callCount, 1, 'selectedlanguagechange event was fired');
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test("if user-selected language is unavailable, don't pick a track to show", function(assert) {
|
||||
// The video has no default language but has ‘English’ captions only
|
||||
const player = TestHelpers.makePlayer();
|
||||
const track1 = {
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'en.vtt'
|
||||
};
|
||||
const captionsButton = player.controlBar.getChild('SubsCapsButton');
|
||||
|
||||
player.src({type: 'video/mp4', src: 'http://google.com'});
|
||||
// manualCleanUp = true by default
|
||||
const englishTrack = player.addRemoteTextTrack(track1).track;
|
||||
|
||||
// Force 'es' as user-selected track
|
||||
player.cache_.selectedLanguage = { language: 'es', kind: 'captions' };
|
||||
|
||||
this.clock.tick(1);
|
||||
player.play();
|
||||
|
||||
assert.ok(!captionsButton.hasClass('vjs-hidden'), 'The captions button is shown');
|
||||
assert.ok(englishTrack.mode === 'disabled', 'English track should be disabled');
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('the user-selected language takes priority over default language', function(assert) {
|
||||
// The video has ‘English’ captions as default, but has ‘Spanish’ captions also
|
||||
const player = TestHelpers.makePlayer();
|
||||
const track1 = {
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'en.vtt',
|
||||
default: true
|
||||
};
|
||||
const track2 = {
|
||||
kind: 'captions',
|
||||
label: 'Spanish',
|
||||
language: 'es',
|
||||
src: 'es.vtt'
|
||||
};
|
||||
|
||||
player.src({type: 'video/mp4', src: 'http://google.com'});
|
||||
// manualCleanUp = true by default
|
||||
const englishTrack = player.addRemoteTextTrack(track1).track;
|
||||
const spanishTrack = player.addRemoteTextTrack(track2).track;
|
||||
|
||||
// Force 'es' as user-selected track
|
||||
player.cache_.selectedLanguage = { enabled: true, language: 'es', kind: 'captions' };
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.ok(spanishTrack.mode === 'showing', 'Spanish captions should be shown');
|
||||
assert.ok(englishTrack.mode === 'disabled', 'English captions should be hidden');
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test("matching both the selectedLanguage's language and kind takes priority over just matching the language", function(assert) {
|
||||
const player = TestHelpers.makePlayer();
|
||||
const track1 = {
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'en.vtt'
|
||||
};
|
||||
const track2 = {
|
||||
kind: 'subtitles',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'en.vtt'
|
||||
};
|
||||
|
||||
player.src({type: 'video/mp4', src: 'http://google.com'});
|
||||
// manualCleanUp = true by default
|
||||
const captionTrack = player.addRemoteTextTrack(track1).track;
|
||||
const subsTrack = player.addRemoteTextTrack(track2).track;
|
||||
|
||||
// Force English captions as user-selected track
|
||||
player.cache_.selectedLanguage = { enabled: true, language: 'en', kind: 'captions' };
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.ok(captionTrack.mode === 'showing', 'Captions track should be preselected');
|
||||
assert.ok(subsTrack.mode === 'disabled', 'Subtitles track should remain disabled');
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('the user-selected language is used for subsequent source changes', function(assert) {
|
||||
// Start with two captions tracks: English and Spanish
|
||||
const player = TestHelpers.makePlayer();
|
||||
const track1 = {
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'en.vtt'
|
||||
};
|
||||
const track2 = {
|
||||
kind: 'captions',
|
||||
label: 'Spanish',
|
||||
language: 'es',
|
||||
src: 'es.vtt'
|
||||
};
|
||||
const tracks = player.tech_.remoteTextTracks();
|
||||
const captionsButton = player.controlBar.getChild('SubsCapsButton');
|
||||
let esCaptionMenuItem;
|
||||
let enCaptionMenuItem;
|
||||
|
||||
player.src({type: 'video/mp4', src: 'http://google.com'});
|
||||
// manualCleanUp = true by default
|
||||
player.addRemoteTextTrack(track1);
|
||||
player.addRemoteTextTrack(track2);
|
||||
|
||||
// Keep track of menu items
|
||||
esCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'es');
|
||||
enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en');
|
||||
|
||||
// The user chooses Spanish
|
||||
player.play();
|
||||
esCaptionMenuItem.trigger('click');
|
||||
|
||||
// Track mode changes on user-selection
|
||||
assert.ok(esCaptionMenuItem.track.mode === 'showing',
|
||||
'Spanish should be showing after selection');
|
||||
assert.ok(enCaptionMenuItem.track.mode === 'disabled',
|
||||
'English should be disabled after selecting Spanish');
|
||||
assert.deepEqual(player.cache_.selectedLanguage,
|
||||
{ enabled: true, language: 'es', kind: 'captions' });
|
||||
|
||||
// Switch source and remove old tracks
|
||||
player.tech_.src({type: 'video/mp4', src: 'http://example.com'});
|
||||
while (tracks.length > 0) {
|
||||
player.removeRemoteTextTrack(tracks[0]);
|
||||
}
|
||||
// Add tracks for the new source
|
||||
// change the kind of track to subtitles
|
||||
track1.kind = 'subtitles';
|
||||
track2.kind = 'subtitles';
|
||||
const englishTrack = player.addRemoteTextTrack(track1).track;
|
||||
const spanishTrack = player.addRemoteTextTrack(track2).track;
|
||||
|
||||
// Make sure player ready handler runs
|
||||
this.clock.tick(1);
|
||||
|
||||
// Keep track of menu items
|
||||
esCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'es');
|
||||
enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en');
|
||||
|
||||
// The user-selection should have persisted
|
||||
assert.ok(esCaptionMenuItem.track.mode === 'showing',
|
||||
'Spanish should remain showing');
|
||||
assert.ok(enCaptionMenuItem.track.mode === 'disabled',
|
||||
'English should remain disabled');
|
||||
assert.deepEqual(player.cache_.selectedLanguage,
|
||||
{ enabled: true, language: 'es', kind: 'captions' });
|
||||
|
||||
assert.ok(spanishTrack.mode === 'showing', 'Spanish track remains showing');
|
||||
assert.ok(englishTrack.mode === 'disabled', 'English track remains disabled');
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('the user-selected language is cleared on turning off captions', function(assert) {
|
||||
const player = TestHelpers.makePlayer();
|
||||
const track1 = {
|
||||
kind: 'captions',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
src: 'en.vtt'
|
||||
};
|
||||
const captionsButton = player.controlBar.getChild('SubsCapsButton');
|
||||
// we know the postition of the OffTextTrackMenuItem
|
||||
const offMenuItem = captionsButton.items[1];
|
||||
|
||||
player.src({type: 'video/mp4', src: 'http://google.com'});
|
||||
// manualCleanUp = true by default
|
||||
const englishTrack = player.addRemoteTextTrack(track1).track;
|
||||
// Keep track of menu items
|
||||
const enCaptionMenuItem = getMenuItemByLanguage(captionsButton.items, 'en');
|
||||
|
||||
// Select English initially
|
||||
player.play();
|
||||
enCaptionMenuItem.trigger('click');
|
||||
|
||||
assert.deepEqual(player.cache_.selectedLanguage,
|
||||
{ enabled: true, language: 'en', kind: 'captions' }, 'English track is selected');
|
||||
assert.ok(englishTrack.mode === 'showing', 'English track should be showing');
|
||||
|
||||
// Select the off button
|
||||
offMenuItem.trigger('click');
|
||||
|
||||
assert.deepEqual(player.cache_.selectedLanguage,
|
||||
{ enabled: false }, 'selectedLanguage is cleared');
|
||||
assert.ok(englishTrack.mode === 'disabled', 'English track is disabled');
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import TextTrack from '../../../src/js/tracks/text-track.js';
|
||||
import TextTrackDisplay from '../../../src/js/tracks/text-track-display.js';
|
||||
import Html5 from '../../../src/js/tech/html5.js';
|
||||
import Tech from '../../../src/js/tech/tech.js';
|
||||
import Component from '../../../src/js/component.js';
|
||||
|
||||
import * as browser from '../../../src/js/utils/browser.js';
|
||||
import TestHelpers from '../test-helpers.js';
|
||||
@@ -221,54 +220,6 @@ QUnit.test('update texttrack buttons on removetrack or addtrack', function(asser
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('if native text tracks are not supported, create a texttrackdisplay', function(assert) {
|
||||
const oldTestVid = Html5.TEST_VID;
|
||||
const oldIsFirefox = browser.IS_FIREFOX;
|
||||
const oldTextTrackDisplay = Component.getComponent('TextTrackDisplay');
|
||||
const tag = document.createElement('video');
|
||||
const track1 = document.createElement('track');
|
||||
const track2 = document.createElement('track');
|
||||
|
||||
track1.kind = 'captions';
|
||||
track1.label = 'en';
|
||||
track1.language = 'English';
|
||||
track1.src = 'en.vtt';
|
||||
tag.appendChild(track1);
|
||||
|
||||
track2.kind = 'captions';
|
||||
track2.label = 'es';
|
||||
track2.language = 'Spanish';
|
||||
track2.src = 'es.vtt';
|
||||
tag.appendChild(track2);
|
||||
|
||||
Html5.TEST_VID = {
|
||||
textTracks: []
|
||||
};
|
||||
|
||||
browser.IS_FIREFOX = true;
|
||||
|
||||
const fakeTTDSpy = sinon.spy();
|
||||
|
||||
class FakeTTD extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
fakeTTDSpy();
|
||||
}
|
||||
}
|
||||
|
||||
Component.registerComponent('TextTrackDisplay', FakeTTD);
|
||||
|
||||
const player = TestHelpers.makePlayer({}, tag);
|
||||
|
||||
assert.strictEqual(fakeTTDSpy.callCount, 1, 'text track display was created');
|
||||
|
||||
Html5.TEST_VID = oldTestVid;
|
||||
browser.IS_FIREFOX = oldIsFirefox;
|
||||
Component.registerComponent('TextTrackDisplay', oldTextTrackDisplay);
|
||||
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('emulated tracks are always used, except in safari', function(assert) {
|
||||
const oldTestVid = Html5.TEST_VID;
|
||||
const oldIsAnySafari = browser.IS_ANY_SAFARI;
|
||||
@@ -577,3 +528,55 @@ QUnit.test('removeRemoteTextTrack should be able to take both a track and the re
|
||||
'the track element was removed correctly');
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
if (Html5.isSupported()) {
|
||||
QUnit.test('auto remove tracks should not clean up tracks added while source is being added', function(assert) {
|
||||
const player = TestHelpers.makePlayer({
|
||||
techOrder: ['html5'],
|
||||
html5: {
|
||||
nativeTextTracks: false
|
||||
}
|
||||
});
|
||||
|
||||
const track = {
|
||||
kind: 'kind',
|
||||
src: 'src',
|
||||
language: 'language',
|
||||
label: 'label',
|
||||
default: 'default'
|
||||
};
|
||||
|
||||
player.src({src: 'example.mp4', type: 'video/mp4'});
|
||||
player.addRemoteTextTrack(track, false);
|
||||
|
||||
this.clock.tick(1);
|
||||
assert.equal(player.textTracks().length, 1, 'we have one text track');
|
||||
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('auto remove tracks added right before a source change will be cleaned up', function(assert) {
|
||||
const player = TestHelpers.makePlayer({
|
||||
techOrder: ['html5'],
|
||||
html5: {
|
||||
nativeTextTracks: false
|
||||
}
|
||||
});
|
||||
|
||||
const track = {
|
||||
kind: 'kind',
|
||||
src: 'src',
|
||||
language: 'language',
|
||||
label: 'label',
|
||||
default: 'default'
|
||||
};
|
||||
|
||||
player.addRemoteTextTrack(track, false);
|
||||
player.src({src: 'example.mp4', type: 'video/mp4'});
|
||||
|
||||
this.clock.tick(1);
|
||||
assert.equal(player.textTracks().length, 0, 'we do not have any tracks left');
|
||||
|
||||
player.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-env qunit */
|
||||
import toTitleCase from '../../../src/js/utils/to-title-case.js';
|
||||
import toTitleCase, { titleCaseEquals } from '../../../src/js/utils/to-title-case.js';
|
||||
|
||||
QUnit.module('to-title-case');
|
||||
|
||||
@@ -8,3 +8,15 @@ QUnit.test('should make a string start with an uppercase letter', function(asser
|
||||
|
||||
assert.ok(foo === 'Bar');
|
||||
});
|
||||
|
||||
QUnit.test('titleCaseEquals compares whether the TitleCase of two strings is equal', function(assert) {
|
||||
assert.ok(titleCaseEquals('foo', 'foo'), 'foo equals foo');
|
||||
assert.ok(titleCaseEquals('foo', 'Foo'), 'foo equals Foo');
|
||||
assert.ok(titleCaseEquals('Foo', 'foo'), 'Foo equals foo');
|
||||
assert.ok(titleCaseEquals('Foo', 'Foo'), 'Foo equals Foo');
|
||||
|
||||
assert.ok(titleCaseEquals('fooBar', 'fooBar'), 'fooBar equals fooBar');
|
||||
assert.notOk(titleCaseEquals('fooBAR', 'fooBar'), 'fooBAR does not equal fooBar');
|
||||
assert.notOk(titleCaseEquals('foobar', 'fooBar'), 'foobar does not equal fooBar');
|
||||
assert.notOk(titleCaseEquals('fooBar', 'FOOBAR'), 'fooBar does not equal fooBAR');
|
||||
});
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário