Comparar commits
64 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 66c0cf5ac6 | |||
| 0f8de5204e | |||
| 23c7f263c6 | |||
| 424d021517 | |||
| 935ec1e854 | |||
| 08aad15c04 | |||
| d90c94584d | |||
| b314268c2b | |||
| a145dd287f | |||
| 9ca9989494 | |||
| 2576eda2f7 | |||
| 8599c8ede0 | |||
| c17c003771 | |||
| a1763dcc56 | |||
| 3f7e215e7b | |||
| 0d725dfec7 | |||
| f194ca56eb | |||
| 0d6072078d | |||
| 3faea9fc63 | |||
| 69179673e1 | |||
| 823f6c7a47 | |||
| 946f84b4de | |||
| 6cf7327fd4 | |||
| ab110ff191 | |||
| c970474e10 | |||
| b855bfb9de | |||
| 3fe22e93b0 | |||
| eb05ff7f00 | |||
| 207730ed6f | |||
| 969d32551b | |||
| ad770fb196 | |||
| 3974944007 | |||
| 465392215c | |||
| 6cf4ef8545 | |||
| c1ed22c39f | |||
| aa7e94ba10 | |||
| c069655bc5 | |||
| 398c6e9dd0 | |||
| 4ec3b56b45 | |||
| d2b4f1c5b9 | |||
| ad86eec723 | |||
| ed378986a5 | |||
| d2180b0ef8 | |||
| 841d135db8 | |||
| 5d91a878fb | |||
| 2bcfe2156d | |||
| 4367c69752 | |||
| 072c27746f | |||
| 288edd1473 | |||
| b1ca34494d | |||
| e7d4b47744 | |||
| de08669d6c | |||
| 5c3be8bf7e | |||
| d838cd8c33 | |||
| 3d4aebc633 | |||
| dcc615a28d | |||
| fa97309a78 | |||
| b5727a62da | |||
| ce19ed5294 | |||
| c02c6c63dd | |||
| dab893bb5a | |||
| 91874a3f7e | |||
| daad4923c4 | |||
| bca44c0f17 |
@@ -13,6 +13,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=
|
||||
|
||||
+153
@@ -1,3 +1,156 @@
|
||||
<a name="5.20.4"></a>
|
||||
## [5.20.4](https://github.com/videojs/video.js/compare/v5.20.3...v5.20.4) (2017-10-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Make boolean attributes set and check both the associated property and the attribute ([#4631](https://github.com/videojs/video.js/issues/4631)) ([0f8de52](https://github.com/videojs/video.js/commit/0f8de52)), closes [#4351](https://github.com/videojs/video.js/issues/4351)
|
||||
|
||||
<a name="5.20.3"></a>
|
||||
## [5.20.3](https://github.com/videojs/video.js/compare/v5.20.2...v5.20.3) (2017-08-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* tracks are disabled and cuepoints are cleared in iOS native player ([#4569](https://github.com/videojs/video.js/issues/4569)) ([424d021](https://github.com/videojs/video.js/commit/424d021))
|
||||
|
||||
### Chores
|
||||
|
||||
* **CHANGELOG:** update CHANGELOG for 5.20.2 ([#4529](https://github.com/videojs/video.js/issues/4529)) ([935ec1e](https://github.com/videojs/video.js/commit/935ec1e))
|
||||
|
||||
<a name="5.20.2"></a>
|
||||
## [5.20.2](https://github.com/videojs/video.js/compare/v5.20.1...v5.20.2) (2017-07-26)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adjust volume ranges so muted(true) and vol=0 do not use the same icons ([#4425](https://github.com/videojs/video.js/issues/4425)) ([b314268](https://github.com/videojs/video.js/commit/b314268))
|
||||
* only change focus from BPB if not a mouse click ([#4523](https://github.com/videojs/video.js/issues/4523)) ([d90c945](https://github.com/videojs/video.js/commit/d90c945))
|
||||
* Only update text track mode if changed ([#4368](https://github.com/videojs/video.js/issues/4368)) ([a1763dc](https://github.com/videojs/video.js/commit/a1763dc))
|
||||
* player.duration() should return NaN if duration is not known ([#4456](https://github.com/videojs/video.js/issues/4456)) ([2576eda](https://github.com/videojs/video.js/commit/2576eda))
|
||||
* Safari picture-in-picture triggers fullscreenchange ([#4442](https://github.com/videojs/video.js/issues/4442)) ([c17c003](https://github.com/videojs/video.js/commit/c17c003))
|
||||
* Use passive event listeners for touchstart/touchmove ([#4445](https://github.com/videojs/video.js/issues/4445)) ([8599c8e](https://github.com/videojs/video.js/commit/8599c8e)), closes [#4432](https://github.com/videojs/video.js/issues/4432)
|
||||
|
||||
### Chores
|
||||
|
||||
* add a release script to makes things easier ([#4517](https://github.com/videojs/video.js/issues/4517)) ([9ca9989](https://github.com/videojs/video.js/commit/9ca9989))
|
||||
* move imports up so bundlers wont trip up ([#4363](https://github.com/videojs/video.js/issues/4363)) ([0d725df](https://github.com/videojs/video.js/commit/0d725df))
|
||||
* **package:** update to swf 5.4.1 ([#4516](https://github.com/videojs/video.js/issues/4516)) ([a145dd2](https://github.com/videojs/video.js/commit/a145dd2))
|
||||
* **package:** update videojs-vtt.js to 0.12.4 ([#4364](https://github.com/videojs/video.js/issues/4364)) ([3f7e215](https://github.com/videojs/video.js/commit/3f7e215))
|
||||
|
||||
<a name="5.20.1"></a>
|
||||
## [5.20.1](https://github.com/videojs/video.js/compare/v5.20.0...v5.20.1) (2017-05-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* only disable user-selection on sliders ([#4355](https://github.com/videojs/video.js/issues/4355)) ([0d60720](https://github.com/videojs/video.js/commit/0d60720))
|
||||
* prevent dupe events on enabled ClickableComponents ([#4357](https://github.com/videojs/video.js/issues/4357)) ([3faea9f](https://github.com/videojs/video.js/commit/3faea9f))
|
||||
|
||||
<a name="5.20.0"></a>
|
||||
# [5.20.0](https://github.com/videojs/video.js/compare/v5.19.2...v5.20.0) (2017-05-11)
|
||||
|
||||
### Features
|
||||
|
||||
* add 'playsinline' player option ([#4325](https://github.com/videojs/video.js/issues/4325)) ([946f84b](https://github.com/videojs/video.js/commit/946f84b))
|
||||
* Add a version class to the player ([#4335](https://github.com/videojs/video.js/issues/4335)) ([b855bfb](https://github.com/videojs/video.js/commit/b855bfb))
|
||||
* Add getVideoPlaybackQuality API ([#4286](https://github.com/videojs/video.js/issues/4286)) ([c970474](https://github.com/videojs/video.js/commit/c970474))
|
||||
* remove playbackRate blacklist for recent Android Chrome ([#4336](https://github.com/videojs/video.js/issues/4336)) ([3fe22e9](https://github.com/videojs/video.js/commit/3fe22e9))
|
||||
|
||||
### Chores
|
||||
|
||||
* add slack travis notifications ([#4339](https://github.com/videojs/video.js/issues/4339)) ([ab110ff](https://github.com/videojs/video.js/commit/ab110ff))
|
||||
* **package:** pin karma to 1.3.0 for IE8 support ([#4340](https://github.com/videojs/video.js/issues/4340)) ([6cf7327](https://github.com/videojs/video.js/commit/6cf7327))
|
||||
|
||||
### Tests
|
||||
|
||||
* fix playsinline test for IE8 ([#4347](https://github.com/videojs/video.js/issues/4347)) ([823f6c7](https://github.com/videojs/video.js/commit/823f6c7))
|
||||
|
||||
<a name="5.19.2"></a>
|
||||
## [5.19.2](https://github.com/videojs/video.js/compare/v5.19.1...v5.19.2) (2017-04-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* set IE_VERSION correctly for IE11 ([#4280](https://github.com/videojs/video.js/issues/4280)) ([207730e](https://github.com/videojs/video.js/commit/207730e)), closes [#4278](https://github.com/videojs/video.js/issues/4278)
|
||||
|
||||
<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)
|
||||
|
||||
|
||||
Arquivo executável
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Release Type?"
|
||||
read -rp "> " rtype
|
||||
|
||||
echo grunt version:${rtype}
|
||||
grunt version:${rtype}
|
||||
version=$(./build/bin/version)
|
||||
|
||||
echo New version is $version
|
||||
|
||||
# echo grunt chg-release:$version
|
||||
# grunt chg-release:$version
|
||||
npm run changelog
|
||||
|
||||
echo git commit -am "v$version"
|
||||
git commit -am "v$version"
|
||||
|
||||
echo git checkout temp-release-branch
|
||||
git checkout -b temp-release-branch
|
||||
|
||||
echo grunt dist
|
||||
grunt dist
|
||||
|
||||
echo git add -f dist
|
||||
git add -f dist
|
||||
|
||||
echo git commit -m "v$version dist"
|
||||
git commit -m "v$version dist"
|
||||
|
||||
echo git tag -a "v$version" -m "v$version"
|
||||
git tag -a "v$version" -m "v$version"
|
||||
|
||||
echo git show
|
||||
git show
|
||||
|
||||
|
||||
read -p "publish? " -n 1 -r
|
||||
echo # (optional) move to a new line
|
||||
if [[ $REPLY =~ ^[Nn]$ ]]
|
||||
then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo git push upstream
|
||||
git push upstream --tags
|
||||
git push origin --tags
|
||||
|
||||
echo npm publish --tag next-5
|
||||
npm publish --tag next-5
|
||||
|
||||
echo grunt github-release:prereleease
|
||||
grunt github-release:prerelease
|
||||
|
||||
echo git checkout -
|
||||
git checkout -
|
||||
|
||||
echo git branch -D temp-release-branch
|
||||
git branch -D temp-release-branch
|
||||
+4
-4
@@ -450,7 +450,7 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
lint: {
|
||||
command: 'npm run lint',
|
||||
command: 'npm run lint -- --errors',
|
||||
options: {
|
||||
preferLocal: true
|
||||
}
|
||||
@@ -468,7 +468,7 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
webpack: {
|
||||
command: 'webpack test/require/webpack.js build/temp/webpack.js',
|
||||
command: 'webpack --hide-modules test/require/webpack.js build/temp/webpack.js',
|
||||
options: {
|
||||
preferLocal: true
|
||||
}
|
||||
@@ -483,8 +483,8 @@ module.exports = function(grunt) {
|
||||
error: true
|
||||
},
|
||||
ignore: [
|
||||
// Ignore the warning about needing <optgroup> elements
|
||||
'WCAG2AA.Principle1.Guideline1_3.1_3_1.H85.2'
|
||||
// Ignore warning about contrast of the "vjs-no-js" fallback link
|
||||
'WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.BgImage'
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"version": "5.16.0",
|
||||
"version": "5.20.4",
|
||||
"keywords": [
|
||||
"videojs",
|
||||
"html5",
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"version": "5.16.0",
|
||||
"version": "5.20.4",
|
||||
"main": "./es5/video.js",
|
||||
"style": "./dist/video-js.css",
|
||||
"copyright": "Copyright Brightcove, Inc. <https://www.brightcove.com/>",
|
||||
@@ -41,8 +41,8 @@
|
||||
"tsml": "1.0.1",
|
||||
"videojs-font": "2.0.0",
|
||||
"videojs-ie8": "1.1.2",
|
||||
"videojs-swf": "5.1.0",
|
||||
"videojs-vtt.js": "0.12.1",
|
||||
"videojs-swf": "5.4.1",
|
||||
"videojs-vtt.js": "0.12.4",
|
||||
"xhr": "2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -91,7 +91,7 @@
|
||||
"grunt-zip": "0.17.1",
|
||||
"istanbul": "^0.4.5",
|
||||
"jsdoc": "^3.4.2",
|
||||
"karma": "^1.2.0",
|
||||
"karma": "1.3.0",
|
||||
"karma-browserify": "^5.1.0",
|
||||
"karma-browserstack-launcher": "^1.0.1",
|
||||
"karma-chrome-launcher": "^2.0.0",
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
.video-js .vjs-progress-holder .vjs-load-progress div {
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 0.3em;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
// updated by javascript during playback
|
||||
|
||||
@@ -5,6 +5,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,13 @@ import Component from './component.js';
|
||||
* @extends Button
|
||||
*/
|
||||
class BigPlayButton extends Button {
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.mouseused_ = false;
|
||||
|
||||
this.on('mousedown', this.handleMouseDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the default DOM `className`.
|
||||
@@ -34,7 +41,40 @@ class BigPlayButton extends Button {
|
||||
* @listens click
|
||||
*/
|
||||
handleClick(event) {
|
||||
this.player_.play();
|
||||
const playPromise = this.player_.play();
|
||||
|
||||
// exit early if clicked via the mouse
|
||||
if (this.mouseused_ && event.clientX && event.clientY) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cb = this.player_.getChild('controlBar');
|
||||
const playToggle = cb && cb.getChild('playToggle');
|
||||
|
||||
if (!playToggle) {
|
||||
this.player_.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const playFocus = () => playToggle.focus();
|
||||
|
||||
if (playPromise && playPromise.then) {
|
||||
const ignoreRejectedPlayPromise = () => {};
|
||||
|
||||
playPromise.then(playFocus, ignoreRejectedPlayPromise);
|
||||
} else {
|
||||
this.setTimeout(playFocus, 1);
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyPress(event) {
|
||||
this.mouseused_ = false;
|
||||
|
||||
super.handleKeyPress(event);
|
||||
}
|
||||
|
||||
handleMouseDown(event) {
|
||||
this.mouseused_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,11 @@ class ClickableComponent extends Component {
|
||||
|
||||
this.controlText_ = text;
|
||||
this.controlTextEl_.innerHTML = localizedText;
|
||||
el.setAttribute('title', localizedText);
|
||||
|
||||
if (!this.nonIconControl) {
|
||||
// Set title attribute if only an icon is shown
|
||||
el.setAttribute('title', localizedText);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -148,8 +152,10 @@ class ClickableComponent extends Component {
|
||||
if (typeof this.tabIndex_ !== 'undefined') {
|
||||
this.el_.setAttribute('tabIndex', this.tabIndex_);
|
||||
}
|
||||
this.on('tap', this.handleClick);
|
||||
this.on('click', this.handleClick);
|
||||
this.off(['tap', 'click'], this.handleClick);
|
||||
this.off('focus', this.handleFocus);
|
||||
this.off('blur', this.handleBlur);
|
||||
this.on(['tap', 'click'], this.handleClick);
|
||||
this.on('focus', this.handleFocus);
|
||||
this.on('blur', this.handleBlur);
|
||||
return this;
|
||||
@@ -167,8 +173,7 @@ class ClickableComponent extends Component {
|
||||
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);
|
||||
return this;
|
||||
|
||||
+15
-1
@@ -392,7 +392,7 @@ class Component {
|
||||
|
||||
// If a name wasn't used to create the component, check if we can use the
|
||||
// name function of the component
|
||||
componentName = componentName || (component.name && component.name());
|
||||
componentName = componentName || (component.name && toTitleCase(component.name()));
|
||||
|
||||
if (componentName) {
|
||||
this.childNameIndex_[componentName] = component;
|
||||
@@ -1232,6 +1232,20 @@ class Component {
|
||||
return this.currentDimension('height');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the focus to this component
|
||||
*/
|
||||
focus() {
|
||||
this.el_.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the focus from this component
|
||||
*/
|
||||
blur() {
|
||||
this.el_.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a 'tap' events when touch event support gets detected. This gets used to
|
||||
* support toggling the controls through a tap on the video. They get enabled
|
||||
|
||||
@@ -49,6 +49,9 @@ class AudioTrackButton extends TrackButton {
|
||||
* An array of menu items
|
||||
*/
|
||||
createItems(items = []) {
|
||||
// if there's only one audio track, there no point in showing it
|
||||
this.hideThreshold_ = 1;
|
||||
|
||||
const tracks = this.player_.audioTracks && this.player_.audioTracks();
|
||||
|
||||
if (!tracks) {
|
||||
|
||||
@@ -81,7 +81,7 @@ class MuteToggle extends Button {
|
||||
const vol = this.player_.volume();
|
||||
let level = 3;
|
||||
|
||||
if (vol === 0 || this.player_.muted()) {
|
||||
if (this.player_.muted()) {
|
||||
level = 0;
|
||||
} else if (vol < 0.33) {
|
||||
level = 1;
|
||||
|
||||
@@ -27,6 +27,8 @@ class PlaybackRateMenuItem extends MenuItem {
|
||||
// Modify options for parent MenuItem class's init.
|
||||
options.label = label;
|
||||
options.selected = rate === 1;
|
||||
options.selectable = true;
|
||||
|
||||
super(player, options);
|
||||
|
||||
this.label = label;
|
||||
|
||||
@@ -39,33 +39,6 @@ class CaptionsButton extends TextTrackButton {
|
||||
return `vjs-captions-button ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update caption menu items
|
||||
*
|
||||
* @param {EventTarget~Event} [event]
|
||||
* The `addtrack` or `removetrack` event that caused this function to be
|
||||
* called.
|
||||
*
|
||||
* @listens TextTrackList#addtrack
|
||||
* @listens TextTrackList#removetrack
|
||||
*/
|
||||
update(event) {
|
||||
let threshold = 2;
|
||||
|
||||
super.update();
|
||||
|
||||
// if native, then threshold is 1 because no settings button
|
||||
if (this.player().tech_ && this.player().tech_.featuresNativeTextTracks) {
|
||||
threshold = 1;
|
||||
}
|
||||
|
||||
if (this.items && this.items.length > threshold) {
|
||||
this.show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create caption menu items
|
||||
*
|
||||
@@ -77,6 +50,8 @@ class CaptionsButton extends TextTrackButton {
|
||||
|
||||
if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) {
|
||||
items.push(new CaptionSettingsMenuItem(this.player_, {kind: this.kind_}));
|
||||
|
||||
this.hideThreshold_ += 1;
|
||||
}
|
||||
|
||||
return super.createItems(items);
|
||||
|
||||
@@ -40,6 +40,7 @@ class TextTrackButton extends TrackButton {
|
||||
createItems(items = []) {
|
||||
// Add an OFF menu item to turn all tracks off
|
||||
items.push(new OffTextTrackMenuItem(this.player_, {kind: this.kind_}));
|
||||
this.hideThreshold_ += 1;
|
||||
|
||||
const tracks = this.player_.textTracks();
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ class TextTrackMenuItem extends MenuItem {
|
||||
if (tracks) {
|
||||
const changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
|
||||
player.on(['loadstart', 'texttrackchange'], changeHandler);
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
this.on('dispose', function() {
|
||||
tracks.removeEventListener('change', changeHandler);
|
||||
@@ -102,8 +103,10 @@ class TextTrackMenuItem extends MenuItem {
|
||||
}
|
||||
|
||||
if (track === this.track) {
|
||||
track.mode = 'showing';
|
||||
} else {
|
||||
if (track.mode !== 'showing') {
|
||||
track.mode = 'showing';
|
||||
}
|
||||
} else if (track.mode !== 'disabled') {
|
||||
track.mode = 'disabled';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ class ErrorDisplay extends ModalDialog {
|
||||
* @private
|
||||
*/
|
||||
ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
|
||||
pauseOnOpen: false,
|
||||
fillAlways: true,
|
||||
temporary: false,
|
||||
uncloseable: true
|
||||
|
||||
@@ -58,9 +58,9 @@ class MenuButton extends ClickableComponent {
|
||||
this.buttonPressed_ = false;
|
||||
this.el_.setAttribute('aria-expanded', 'false');
|
||||
|
||||
if (this.items && this.items.length === 0) {
|
||||
if (this.items && this.items.length <= this.hideThreshold_) {
|
||||
this.hide();
|
||||
} else if (this.items && this.items.length > 1) {
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,16 @@ class MenuButton extends ClickableComponent {
|
||||
createMenu() {
|
||||
const menu = new Menu(this.player_);
|
||||
|
||||
/**
|
||||
* Hide the menu if the number of items is less than or equal to this threshold. This defaults
|
||||
* to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
|
||||
* it here because every time we run `createMenu` we need to reset the value.
|
||||
*
|
||||
* @protected
|
||||
* @type {Number}
|
||||
*/
|
||||
this.hideThreshold_ = 0;
|
||||
|
||||
// Add a title list item to the top
|
||||
if (this.options_.title) {
|
||||
const title = Dom.createEl('li', {
|
||||
@@ -82,6 +92,8 @@ class MenuButton extends ClickableComponent {
|
||||
tabIndex: -1
|
||||
});
|
||||
|
||||
this.hideThreshold_ += 1;
|
||||
|
||||
menu.children_.unshift(title);
|
||||
Dom.insertElFirst(title, menu.contentEl());
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ class MenuItem extends ClickableComponent {
|
||||
* The element that gets created.
|
||||
*/
|
||||
createEl(type, props, attrs) {
|
||||
// The control is textual, not just an icon
|
||||
this.nonIconControl = true;
|
||||
|
||||
return super.createEl('li', assign({
|
||||
className: 'vjs-menu-item',
|
||||
innerHTML: this.localize(this.options_.label),
|
||||
|
||||
@@ -180,7 +180,7 @@ class ModalDialog extends Component {
|
||||
// playing state.
|
||||
this.wasPlaying_ = !player.paused();
|
||||
|
||||
if (this.wasPlaying_) {
|
||||
if (this.options_.pauseOnOpen && this.wasPlaying_) {
|
||||
player.pause();
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ class ModalDialog extends Component {
|
||||
this.trigger('beforemodalclose');
|
||||
this.opened_ = false;
|
||||
|
||||
if (this.wasPlaying_) {
|
||||
if (this.wasPlaying_ && this.options_.pauseOnOpen) {
|
||||
player.play();
|
||||
}
|
||||
|
||||
@@ -427,6 +427,7 @@ class ModalDialog extends Component {
|
||||
* @private
|
||||
*/
|
||||
ModalDialog.prototype.options_ = {
|
||||
pauseOnOpen: true,
|
||||
temporary: true
|
||||
};
|
||||
|
||||
|
||||
+51
-2
@@ -413,6 +413,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 = require('../../package.json').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);
|
||||
@@ -481,6 +486,9 @@ class Player extends Component {
|
||||
el = this.el_ = super.createEl('div');
|
||||
}
|
||||
|
||||
// set tabindex to -1 so we could focus on the player element
|
||||
tag.setAttribute('tabindex', '-1');
|
||||
|
||||
// Remove width/height attrs from tag so CSS can make it 100% width/height
|
||||
tag.removeAttribute('width');
|
||||
tag.removeAttribute('height');
|
||||
@@ -819,6 +827,7 @@ class Player extends Component {
|
||||
'textTracks': this.textTracks_,
|
||||
'audioTracks': this.audioTracks_,
|
||||
'autoplay': this.options_.autoplay,
|
||||
'playsinline': this.options_.playsinline,
|
||||
'preload': this.options_.preload,
|
||||
'loop': this.options_.loop,
|
||||
'muted': this.options_.muted,
|
||||
@@ -1685,10 +1694,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) {
|
||||
@@ -2377,6 +2387,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.
|
||||
*
|
||||
@@ -2985,6 +3020,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
|
||||
*
|
||||
|
||||
@@ -341,6 +341,29 @@ class Flash extends Tech {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
const videoPlaybackQuality = this.el_.vjs_getProperty('getVideoPlaybackQuality');
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Create setters and getters for attributes
|
||||
|
||||
+316
-125
@@ -131,6 +131,9 @@ class Html5 extends Tech {
|
||||
this.proxyNativeTextTracks_();
|
||||
}
|
||||
|
||||
// prevent iOS Safari from disabling metadata text tracks during native playback
|
||||
this.restoreMetadataTracksInIOSNativePlayer_();
|
||||
|
||||
// Determine if native controls should be used
|
||||
// Our goal should be to get the custom controls on mobile solid everywhere
|
||||
// so we can remove this all together. Right now this will block custom
|
||||
@@ -173,6 +176,72 @@ class Html5 extends Tech {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* When a captions track is enabled in the iOS Safari native player, all other
|
||||
* tracks are disabled (including metadata tracks), which nulls all of their
|
||||
* associated cue points. This will restore metadata tracks to their pre-fullscreen
|
||||
* state in those cases so that cue points are not needlessly lost.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
restoreMetadataTracksInIOSNativePlayer_() {
|
||||
const textTracks = this.textTracks();
|
||||
let metadataTracksPreFullscreenState;
|
||||
|
||||
// captures a snapshot of every metadata track's current state
|
||||
const takeMetadataTrackSnapshot = () => {
|
||||
metadataTracksPreFullscreenState = [];
|
||||
|
||||
for (let i = 0; i < textTracks.length; i++) {
|
||||
const track = textTracks[i];
|
||||
|
||||
if (track.kind === 'metadata') {
|
||||
metadataTracksPreFullscreenState.push({
|
||||
track,
|
||||
storedMode: track.mode
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// snapshot each metadata track's initial state, and update the snapshot
|
||||
// each time there is a track 'change' event
|
||||
takeMetadataTrackSnapshot();
|
||||
textTracks.addEventListener('change', takeMetadataTrackSnapshot);
|
||||
|
||||
const restoreTrackMode = () => {
|
||||
for (let i = 0; i < metadataTracksPreFullscreenState.length; i++) {
|
||||
const storedTrack = metadataTracksPreFullscreenState[i];
|
||||
|
||||
if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
|
||||
storedTrack.track.mode = storedTrack.storedMode;
|
||||
}
|
||||
}
|
||||
// we only want this handler to be executed on the first 'change' event
|
||||
textTracks.removeEventListener('change', restoreTrackMode);
|
||||
};
|
||||
|
||||
// when we enter fullscreen playback, stop updating the snapshot and
|
||||
// restore all track modes to their pre-fullscreen state
|
||||
this.on('webkitbeginfullscreen', () => {
|
||||
textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
|
||||
|
||||
// remove the listener before adding it just in case it wasn't previously removed
|
||||
textTracks.removeEventListener('change', restoreTrackMode);
|
||||
textTracks.addEventListener('change', restoreTrackMode);
|
||||
});
|
||||
|
||||
// start updating the snapshot again after leaving fullscreen
|
||||
this.on('webkitendfullscreen', () => {
|
||||
// remove the listener before adding it just in case it wasn't previously removed
|
||||
textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
|
||||
textTracks.addEventListener('change', takeMetadataTrackSnapshot);
|
||||
|
||||
// remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
|
||||
textTracks.removeEventListener('change', restoreTrackMode);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the `Html5` Tech's DOM element.
|
||||
*
|
||||
@@ -222,17 +291,27 @@ class Html5 extends Tech {
|
||||
el.playerId = this.options_.playerId;
|
||||
}
|
||||
|
||||
if (typeof this.options_.preload !== 'undefined') {
|
||||
Dom.setAttribute(el, 'preload', this.options_.preload);
|
||||
}
|
||||
|
||||
// Update specific tag settings, in case they were overridden
|
||||
const settingsAttrs = ['autoplay', 'preload', 'loop', 'muted'];
|
||||
// `autoplay` has to be *last* so that `muted` and `playsinline` are present
|
||||
// when iOS/Safari or other browsers attempt to autoplay.
|
||||
const settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
|
||||
|
||||
for (let i = settingsAttrs.length - 1; i >= 0; i--) {
|
||||
const attr = settingsAttrs[i];
|
||||
const overwriteAttrs = {};
|
||||
const value = this.options_[attr];
|
||||
|
||||
if (typeof this.options_[attr] !== 'undefined') {
|
||||
overwriteAttrs[attr] = this.options_[attr];
|
||||
if (typeof value !== 'undefined') {
|
||||
if (value) {
|
||||
Dom.setAttribute(el, attr, attr);
|
||||
} else {
|
||||
Dom.removeAttribute(el, attr);
|
||||
}
|
||||
el[attr] = value;
|
||||
}
|
||||
Dom.setElAttributes(el, overwriteAttrs);
|
||||
}
|
||||
|
||||
return el;
|
||||
@@ -557,9 +636,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);
|
||||
@@ -794,6 +876,40 @@ class Html5 extends Tech {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ---------------------------------------------------- */
|
||||
@@ -865,7 +981,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
|
||||
@@ -1122,7 +1238,195 @@ Html5.resetMediaElement = function(el) {
|
||||
};
|
||||
|
||||
/* Native HTML5 element property wrapping ----------------------------------- */
|
||||
// Wrap native boolean attributes with getters that check both property and attribute
|
||||
// The list is as followed:
|
||||
// muted, defaultMuted, autoplay, controls, loop, playsinline
|
||||
[
|
||||
/**
|
||||
* Get the value of `muted` from the media element. `muted` indicates
|
||||
* that the volume for the media should be set to silent. This does not actually change
|
||||
* the `volume` attribute.
|
||||
*
|
||||
* @method Html5#muted
|
||||
* @return {boolean}
|
||||
* - True if the value of `volume` should be ignored and the audio set to silent.
|
||||
* - False if the value of `volume` should be used.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
|
||||
*/
|
||||
'muted',
|
||||
|
||||
/**
|
||||
* Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
|
||||
* whether the media should start muted or not. Only changes the default state of the
|
||||
* media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
|
||||
* current state.
|
||||
*
|
||||
* @method Html5#defaultMuted
|
||||
* @return {boolean}
|
||||
* - The value of `defaultMuted` from the media element.
|
||||
* - True indicates that the media should start muted.
|
||||
* - False indicates that the media should not start muted
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
|
||||
*/
|
||||
'defaultMuted',
|
||||
|
||||
/**
|
||||
* Get the value of `autoplay` from the media element. `autoplay` indicates
|
||||
* that the media should start to play as soon as the page is ready.
|
||||
*
|
||||
* @method Html5#autoplay
|
||||
* @return {boolean}
|
||||
* - The value of `autoplay` from the media element.
|
||||
* - True indicates that the media should start as soon as the page loads.
|
||||
* - False indicates that the media should not start as soon as the page loads.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
|
||||
*/
|
||||
'autoplay',
|
||||
|
||||
/**
|
||||
* Get the value of `controls` from the media element. `controls` indicates
|
||||
* whether the native media controls should be shown or hidden.
|
||||
*
|
||||
* @method Html5#controls
|
||||
* @return {boolean}
|
||||
* - The value of `controls` from the media element.
|
||||
* - True indicates that native controls should be showing.
|
||||
* - False indicates that native controls should be hidden.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
|
||||
*/
|
||||
'controls',
|
||||
|
||||
/**
|
||||
* Get the value of `loop` from the media element. `loop` indicates
|
||||
* that the media should return to the start of the media and continue playing once
|
||||
* it reaches the end.
|
||||
*
|
||||
* @method Html5#loop
|
||||
* @return {boolean}
|
||||
* - The value of `loop` from the media element.
|
||||
* - True indicates that playback should seek back to start once
|
||||
* the end of a media is reached.
|
||||
* - False indicates that playback should not loop back to the start when the
|
||||
* end of the media is reached.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
|
||||
*/
|
||||
'loop',
|
||||
|
||||
/**
|
||||
* 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'
|
||||
].forEach(function(prop) {
|
||||
Html5.prototype[prop] = function() {
|
||||
return this.el_[prop] || this.el_.hasAttribute(prop);
|
||||
};
|
||||
});
|
||||
|
||||
// Wrap native boolean attributes with setters that set both property and attribute
|
||||
// The list is as followed:
|
||||
// setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
|
||||
// setControls is special-cased above
|
||||
[
|
||||
/**
|
||||
* Set the value of `muted` on the media element. `muted` indicates that the current
|
||||
* audio level should be silent.
|
||||
*
|
||||
* @method Html5#setMuted
|
||||
* @param {boolean} muted
|
||||
* - True if the audio should be set to silent
|
||||
* - False otherwise
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
|
||||
*/
|
||||
'muted',
|
||||
|
||||
/**
|
||||
* Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
|
||||
* audio level should be silent, but will only effect the muted level on intial playback..
|
||||
*
|
||||
* @method Html5.prototype.setDefaultMuted
|
||||
* @param {boolean} defaultMuted
|
||||
* - True if the audio should be set to silent
|
||||
* - False otherwise
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
|
||||
*/
|
||||
'defaultMuted',
|
||||
|
||||
/**
|
||||
* Set the value of `autoplay` on the media element. `autoplay` indicates
|
||||
* that the media should start to play as soon as the page is ready.
|
||||
*
|
||||
* @method Html5#setAutoplay
|
||||
* @param {boolean} autoplay
|
||||
* - True indicates that the media should start as soon as the page loads.
|
||||
* - False indicates that the media should not start as soon as the page loads.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
|
||||
*/
|
||||
'autoplay',
|
||||
|
||||
/**
|
||||
* Set the value of `loop` on the media element. `loop` indicates
|
||||
* that the media should return to the start of the media and continue playing once
|
||||
* it reaches the end.
|
||||
*
|
||||
* @method Html5#setLoop
|
||||
* @param {boolean} loop
|
||||
* - True indicates that playback should seek back to start once
|
||||
* the end of a media is reached.
|
||||
* - False indicates that playback should not loop back to the start when the
|
||||
* end of the media is reached.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
|
||||
*/
|
||||
'loop',
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
'playsinline'
|
||||
].forEach(function(prop) {
|
||||
Html5.prototype['set' + toTitleCase(prop)] = function(v) {
|
||||
this.el_[prop] = v;
|
||||
|
||||
if (v) {
|
||||
this.el_.setAttribute(prop, prop);
|
||||
} else {
|
||||
this.el_.removeAttribute(prop);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Wrap native properties with a getter
|
||||
// The list is as followed
|
||||
// paused, currentTime, buffered, volume, poster, preload, error, seeking
|
||||
// seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
|
||||
// readyState, videoWidth, videoHeight
|
||||
[
|
||||
/**
|
||||
* Get the value of `paused` from the media element. `paused` indicates whether the media element
|
||||
@@ -1174,20 +1478,6 @@ Html5.resetMediaElement = function(el) {
|
||||
*/
|
||||
'volume',
|
||||
|
||||
/**
|
||||
* Get the value of `muted` from the media element. `muted` indicates
|
||||
* that the volume for the media should be set to silent. This does not actually change
|
||||
* the `volume` attribute.
|
||||
*
|
||||
* @method Html5#muted
|
||||
* @return {boolean}
|
||||
* - True if the value of `volume` should be ignored and the audio set to silent.
|
||||
* - False if the value of `volume` should be used.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
|
||||
*/
|
||||
'muted',
|
||||
|
||||
/**
|
||||
* Get the value of `poster` from the media element. `poster` indicates
|
||||
* that the url of an image file that can/will be shown when no media data is available.
|
||||
@@ -1220,51 +1510,6 @@ Html5.resetMediaElement = function(el) {
|
||||
*/
|
||||
'preload',
|
||||
|
||||
/**
|
||||
* Get the value of `autoplay` from the media element. `autoplay` indicates
|
||||
* that the media should start to play as soon as the page is ready.
|
||||
*
|
||||
* @method Html5#autoplay
|
||||
* @return {boolean}
|
||||
* - The value of `autoplay` from the media element.
|
||||
* - True indicates that the media should start as soon as the page loads.
|
||||
* - False indicates that the media should not start as soon as the page loads.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
|
||||
*/
|
||||
'autoplay',
|
||||
|
||||
/**
|
||||
* Get the value of `controls` from the media element. `controls` indicates
|
||||
* whether the native media controls should be shown or hidden.
|
||||
*
|
||||
* @method Html5#controls
|
||||
* @return {boolean}
|
||||
* - The value of `controls` from the media element.
|
||||
* - True indicates that native controls should be showing.
|
||||
* - False indicates that native controls should be hidden.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
|
||||
*/
|
||||
'controls',
|
||||
|
||||
/**
|
||||
* Get the value of `loop` from the media element. `loop` indicates
|
||||
* that the media should return to the start of the media and continue playing once
|
||||
* it reaches the end.
|
||||
*
|
||||
* @method Html5#loop
|
||||
* @return {boolean}
|
||||
* - The value of `loop` from the media element.
|
||||
* - True indicates that playback should seek back to start once
|
||||
* the end of a media is reached.
|
||||
* - False indicates that playback should not loop back to the start when the
|
||||
* end of the media is reached.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
|
||||
*/
|
||||
'loop',
|
||||
|
||||
/**
|
||||
* Get the value of the `error` from the media element. `error` indicates any
|
||||
* MediaError that may have occured during playback. If error returns null there is no
|
||||
@@ -1320,22 +1565,6 @@ Html5.resetMediaElement = function(el) {
|
||||
*/
|
||||
'ended',
|
||||
|
||||
/**
|
||||
* Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
|
||||
* whether the media should start muted or not. Only changes the default state of the
|
||||
* media. `muted` and `defaultMuted` can have different values. `muted` indicates the
|
||||
* current state.
|
||||
*
|
||||
* @method Html5#defaultMuted
|
||||
* @return {boolean}
|
||||
* - The value of `defaultMuted` from the media element.
|
||||
* - True indicates that the media should start muted.
|
||||
* - False indicates that the media should not start muted
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
|
||||
*/
|
||||
'defaultMuted',
|
||||
|
||||
/**
|
||||
* Get the value of `playbackRate` from the media element. `playbackRate` indicates
|
||||
* the rate at which the media is currently playing back. Examples:
|
||||
@@ -1433,6 +1662,8 @@ Html5.resetMediaElement = function(el) {
|
||||
|
||||
// Wrap native properties with a setter in this format:
|
||||
// set + toTitleCase(name)
|
||||
// The list is as follows:
|
||||
// setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
|
||||
[
|
||||
/**
|
||||
* Set the value of `volume` on the media element. `volume` indicates the current
|
||||
@@ -1447,19 +1678,6 @@ Html5.resetMediaElement = function(el) {
|
||||
*/
|
||||
'volume',
|
||||
|
||||
/**
|
||||
* Set the value of `muted` on the media element. `muted` indicates the current
|
||||
* audio level should be silent.
|
||||
*
|
||||
* @method Html5#setMuted
|
||||
* @param {boolean} muted
|
||||
* - True if the audio should be set to silent
|
||||
* - False otherwise
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
|
||||
*/
|
||||
'muted',
|
||||
|
||||
/**
|
||||
* Set the value of `src` on the media element. `src` indicates the current
|
||||
* {@link Tech~SourceObject} for the media.
|
||||
@@ -1504,35 +1722,6 @@ Html5.resetMediaElement = function(el) {
|
||||
*/
|
||||
'preload',
|
||||
|
||||
/**
|
||||
* Set the value of `autoplay` on the media element. `autoplay` indicates
|
||||
* that the media should start to play as soon as the page is ready.
|
||||
*
|
||||
* @method Html5#setAutoplay
|
||||
* @param {boolean} autoplay
|
||||
* - True indicates that the media should start as soon as the page loads.
|
||||
* - False indicates that the media should not start as soon as the page loads.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
|
||||
*/
|
||||
'autoplay',
|
||||
|
||||
/**
|
||||
* Set the value of `loop` on the media element. `loop` indicates
|
||||
* that the media should return to the start of the media and continue playing once
|
||||
* it reaches the end.
|
||||
*
|
||||
* @method Html5#setLoop
|
||||
* @param {boolean} loop
|
||||
* - True indicates that playback should seek back to start once
|
||||
* the end of a media is reached.
|
||||
* - False indicates that playback should not loop back to the start when the
|
||||
* end of the media is reached.
|
||||
*
|
||||
* @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
|
||||
*/
|
||||
'loop',
|
||||
|
||||
/**
|
||||
* Set the value of `playbackRate` on the media element. `playbackRate` indicates
|
||||
* the rate at which the media should play back. Examples:
|
||||
@@ -1554,6 +1743,8 @@ Html5.resetMediaElement = function(el) {
|
||||
});
|
||||
|
||||
// wrap native functions with a function
|
||||
// The list is as follows:
|
||||
// pause, load play
|
||||
[
|
||||
/**
|
||||
* A wrapper around the media elements `pause` function. This will call the `HTML5`
|
||||
|
||||
+60
-15
@@ -529,16 +529,20 @@ class Tech extends Component {
|
||||
* @fires Tech#vttjserror
|
||||
*/
|
||||
addWebVttScript_() {
|
||||
if (!window.WebVTT && this.el().parentNode !== null && this.el().parentNode !== undefined) {
|
||||
if (window.WebVTT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initially, Tech.el_ is a child of a dummy-div wait until the Component system
|
||||
// signals that the Tech is ready at which point Tech.el_ is part of the DOM
|
||||
// before inserting the WebVTT script
|
||||
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
|
||||
// which will cause this if check to always fail.
|
||||
if (!this.options_['vtt.js'] && isPlain(vtt) && Object.keys(vtt).length > 0) {
|
||||
Object.keys(vtt).forEach(function(k) {
|
||||
window[k] = vtt[k];
|
||||
});
|
||||
this.trigger('vttjsloaded');
|
||||
return;
|
||||
}
|
||||
@@ -547,7 +551,7 @@ class Tech extends Component {
|
||||
// passed in
|
||||
const script = document.createElement('script');
|
||||
|
||||
script.src = this.options_['vtt.js'] || 'https://cdn.rawgit.com/gkatsev/vtt.js/vjs-v0.12.1/dist/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.
|
||||
@@ -574,7 +578,10 @@ class Tech extends Component {
|
||||
// we don't overwrite the injected window.WebVTT if it loads right away
|
||||
window.WebVTT = true;
|
||||
this.el().parentNode.appendChild(script);
|
||||
} else {
|
||||
this.ready(this.addWebVttScript_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -589,20 +596,17 @@ class Tech extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
this.remoteTextTracks().on('addtrack', (e) => {
|
||||
this.textTracks().addTrack_(e.track);
|
||||
});
|
||||
const remoteTracks = this.remoteTextTracks();
|
||||
const handleAddTrack = (e) => tracks.addTrack_(e.track);
|
||||
const handleRemoveTrack = (e) => tracks.removeTrack_(e.track);
|
||||
|
||||
this.remoteTextTracks().on('removetrack', (e) => {
|
||||
this.textTracks().removeTrack_(e.track);
|
||||
});
|
||||
remoteTracks.on('addtrack', handleAddTrack);
|
||||
remoteTracks.on('removetrack', handleRemoveTrack);
|
||||
|
||||
// Initially, Tech.el_ is a child of a dummy-div wait until the Component system
|
||||
// signals that the Tech is ready at which point Tech.el_ is part of the DOM
|
||||
// before inserting the WebVTT script
|
||||
this.on('ready', this.addWebVttScript_);
|
||||
this.addWebVttScript_();
|
||||
|
||||
const updateDisplay = () => this.trigger('texttrackchange');
|
||||
|
||||
const textTracksChanges = () => {
|
||||
updateDisplay();
|
||||
|
||||
@@ -618,9 +622,21 @@ class Tech extends Component {
|
||||
|
||||
textTracksChanges();
|
||||
tracks.addEventListener('change', textTracksChanges);
|
||||
tracks.addEventListener('addtrack', textTracksChanges);
|
||||
tracks.addEventListener('removetrack', textTracksChanges);
|
||||
|
||||
this.on('dispose', function() {
|
||||
remoteTracks.off('addtrack', handleAddTrack);
|
||||
remoteTracks.off('removetrack', handleRemoveTrack);
|
||||
tracks.removeEventListener('change', textTracksChanges);
|
||||
tracks.removeEventListener('addtrack', textTracksChanges);
|
||||
tracks.removeEventListener('removetrack', textTracksChanges);
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
const track = tracks[i];
|
||||
|
||||
track.removeEventListener('cuechange', updateDisplay);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -789,6 +805,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`.
|
||||
*
|
||||
@@ -796,6 +827,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.
|
||||
*
|
||||
|
||||
@@ -198,7 +198,9 @@ class TextTrack extends Track {
|
||||
});
|
||||
|
||||
if (mode !== 'disabled') {
|
||||
tt.tech_.on('timeupdate', timeupdateHandler);
|
||||
tt.tech_.ready(() => {
|
||||
tt.tech_.on('timeupdate', timeupdateHandler);
|
||||
}, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +234,9 @@ class TextTrack extends Track {
|
||||
}
|
||||
mode = newMode;
|
||||
if (mode === 'showing') {
|
||||
this.tech_.on('timeupdate', timeupdateHandler);
|
||||
this.tech_.ready(() => {
|
||||
this.tech_.on('timeupdate', timeupdateHandler);
|
||||
}, true);
|
||||
}
|
||||
/**
|
||||
* An event that fires when mode changes on this track. This allows
|
||||
@@ -328,7 +332,23 @@ class TextTrack extends Track {
|
||||
* @param {TextTrack~Cue} cue
|
||||
* The cue to add to our internal list
|
||||
*/
|
||||
addCue(cue) {
|
||||
addCue(originalCue) {
|
||||
let cue = originalCue;
|
||||
|
||||
if (window.vttjs && !(originalCue instanceof window.vttjs.VTTCue)) {
|
||||
cue = new window.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
|
||||
|
||||
for (const prop in originalCue) {
|
||||
if (!(prop in cue)) {
|
||||
cue[prop] = originalCue[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// make sure that `id` is copied over
|
||||
cue.id = originalCue.id;
|
||||
cue.originalCue_ = originalCue;
|
||||
}
|
||||
|
||||
const tracks = this.tech_.textTracks();
|
||||
|
||||
if (tracks) {
|
||||
@@ -350,20 +370,17 @@ class TextTrack extends Track {
|
||||
* The cue to remove from our internal list
|
||||
*/
|
||||
removeCue(removeCue) {
|
||||
let removed = false;
|
||||
let i = this.cues_.length;
|
||||
|
||||
for (let i = 0, l = this.cues_.length; i < l; i++) {
|
||||
while (i--) {
|
||||
const cue = this.cues_[i];
|
||||
|
||||
if (cue === removeCue) {
|
||||
if (cue === removeCue || (cue.originalCue_ && cue.originalCue_ === removeCue)) {
|
||||
this.cues_.splice(i, 1);
|
||||
removed = true;
|
||||
this.cues.setCues_(this.cues_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
this.cues.setCues_(this.cues_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -485,7 +485,7 @@ export function getElAttributes(tag) {
|
||||
// known boolean attributes
|
||||
// we can check for matching boolean properties, but older browsers
|
||||
// won't know about HTML5 boolean attributes that we still read from
|
||||
const knownBooleans = ',' + 'autoplay,controls,loop,muted,default' + ',';
|
||||
const knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
|
||||
|
||||
if (tag && tag.attributes && tag.attributes.length > 0) {
|
||||
const attrs = tag.attributes;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+18
-2
@@ -30,6 +30,22 @@
|
||||
*/
|
||||
const toString = Object.prototype.toString;
|
||||
|
||||
/**
|
||||
* Get the keys of an Object
|
||||
*
|
||||
* @param {Object}
|
||||
* The Object to get the keys from
|
||||
*
|
||||
* @return {string[]}
|
||||
* An array of the keys from the object. Returns an empty array if the
|
||||
* object passed in was invalid or had no keys.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const keys = function(object) {
|
||||
return isObject(object) ? Object.keys(object) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Array-like iteration for objects.
|
||||
*
|
||||
@@ -40,7 +56,7 @@ const toString = Object.prototype.toString;
|
||||
* The callback function which is called for each key in the object.
|
||||
*/
|
||||
export function each(object, fn) {
|
||||
Object.keys(object).forEach(key => fn(object[key], key));
|
||||
keys(object).forEach(key => fn(object[key], key));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +77,7 @@ export function each(object, fn) {
|
||||
* The final accumulated value.
|
||||
*/
|
||||
export function reduce(object, fn, initial = 0) {
|
||||
return Object.keys(object).reduce(
|
||||
return keys(object).reduce(
|
||||
(accum, key) => fn(accum, object[key], key), initial);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -7,6 +7,8 @@
|
||||
|
||||
import window from 'global/window';
|
||||
import document from 'global/document';
|
||||
import * as browser from './utils/browser.js';
|
||||
import * as Dom from './utils/dom.js';
|
||||
import * as setup from './setup';
|
||||
import * as stylesheet from './utils/stylesheet.js';
|
||||
import Component from './component';
|
||||
@@ -23,8 +25,6 @@ import VideoTrack from './tracks/video-track.js';
|
||||
import { createTimeRanges } from './utils/time-ranges.js';
|
||||
import formatTime from './utils/format-time.js';
|
||||
import log from './utils/log.js';
|
||||
import * as Dom from './utils/dom.js';
|
||||
import * as browser from './utils/browser.js';
|
||||
import * as Url from './utils/url.js';
|
||||
import {isObject} from './utils/obj';
|
||||
import computedStyle from './utils/computed-style.js';
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -99,6 +99,19 @@ QUnit.test('addChild should throw if the child does not exist', function(assert)
|
||||
|
||||
});
|
||||
|
||||
QUnit.test('addChild with instance should allow getting child correctly', function(assert) {
|
||||
const comp = new Component(getFakePlayer());
|
||||
const comp2 = new Component(getFakePlayer());
|
||||
|
||||
comp2.name = function() {
|
||||
return 'foo';
|
||||
};
|
||||
|
||||
comp.addChild(comp2);
|
||||
assert.ok(comp.getChild('foo'), 'we can get child with camelCase');
|
||||
assert.ok(comp.getChild('Foo'), 'we can get child with TitleCase');
|
||||
});
|
||||
|
||||
QUnit.test('should add a child component with title case name', function(assert) {
|
||||
const comp = new Component(getFakePlayer());
|
||||
|
||||
|
||||
@@ -208,6 +208,35 @@ QUnit.test('open() pauses playback, close() resumes', function(assert) {
|
||||
assert.strictEqual(playSpy.callCount, 1, 'player is resumed when the modal closes');
|
||||
});
|
||||
|
||||
QUnit.test('open() does not pause, close() does not play() with pauseOnOpen set to false', function(assert) {
|
||||
const playSpy = sinon.spy();
|
||||
const pauseSpy = sinon.spy();
|
||||
|
||||
// don't pause the video on modal open
|
||||
this.modal.options_.pauseOnOpen = false;
|
||||
|
||||
// Quick and dirty; make it looks like the player is playing.
|
||||
this.player.paused = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
this.player.play = function() {
|
||||
playSpy();
|
||||
};
|
||||
|
||||
this.player.pause = function() {
|
||||
pauseSpy();
|
||||
};
|
||||
|
||||
this.modal.open();
|
||||
|
||||
assert.expect(2);
|
||||
assert.strictEqual(pauseSpy.callCount, 0, 'player remains playing when the modal opens');
|
||||
|
||||
this.modal.close();
|
||||
assert.strictEqual(playSpy.callCount, 0, 'player is resumed when the modal closes');
|
||||
});
|
||||
|
||||
QUnit.test('open() hides controls, close() shows controls', function(assert) {
|
||||
this.modal.open();
|
||||
|
||||
|
||||
@@ -830,6 +830,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');
|
||||
@@ -1387,3 +1416,40 @@ QUnit.test('should not allow to register custom player when any player has been
|
||||
// reset the Player to the original value;
|
||||
videojs.registerComponent('Player', Player);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import Flash from '../../../src/js/tech/flash.js';
|
||||
import { createTimeRange } from '../../../src/js/utils/time-ranges.js';
|
||||
import document from 'global/document';
|
||||
import window from 'global/window';
|
||||
import sinon from 'sinon';
|
||||
|
||||
// fake out the <object> interaction but leave all the other logic intact
|
||||
@@ -261,3 +262,78 @@ QUnit.test('duration returns NaN, Infinity or duration according to the HTML sta
|
||||
'duration returns duration property when readyState' +
|
||||
' and duration property are both higher than 0');
|
||||
});
|
||||
|
||||
QUnit.test('getVideoPlaybackQuality API exists', function(assert) {
|
||||
const propertyCalls = [];
|
||||
const videoPlaybackQuality = { test: 'test' };
|
||||
const mockFlash = {
|
||||
el_: {
|
||||
/* eslint-disable camelcase */
|
||||
vjs_getProperty(attr) {
|
||||
propertyCalls.push(attr);
|
||||
return videoPlaybackQuality;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
}
|
||||
};
|
||||
|
||||
assert.deepEqual(Flash.prototype.getVideoPlaybackQuality.call(mockFlash),
|
||||
videoPlaybackQuality,
|
||||
'called to get property from flash');
|
||||
assert.equal(propertyCalls.length, 1, 'only one property call');
|
||||
assert.equal(propertyCalls[0],
|
||||
'getVideoPlaybackQuality',
|
||||
'called for getVideoPlaybackQuality');
|
||||
});
|
||||
|
||||
QUnit.test('getVideoPlaybackQuality uses best available creationTime', function(assert) {
|
||||
const origPerformance = window.performance;
|
||||
const origDate = window.Date;
|
||||
const videoPlaybackQuality = {};
|
||||
const mockFlash = {
|
||||
el_: {
|
||||
/* eslint-disable camelcase */
|
||||
vjs_getProperty(attr) {
|
||||
return videoPlaybackQuality;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
}
|
||||
};
|
||||
|
||||
window.performance = void 0;
|
||||
assert.notOk(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
|
||||
'no creationTime when no performance API available');
|
||||
|
||||
window.performance = {
|
||||
timing: {}
|
||||
};
|
||||
assert.notOk(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
|
||||
'no creationTime when performance API insufficient');
|
||||
|
||||
window.performance = {
|
||||
now: () => 4
|
||||
};
|
||||
assert.equal(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
|
||||
4,
|
||||
'creationTime is performance.now when available');
|
||||
|
||||
window.Date = {
|
||||
now: () => 10
|
||||
};
|
||||
window.performance = {
|
||||
timing: {
|
||||
navigationStart: 3
|
||||
}
|
||||
};
|
||||
assert.equal(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
|
||||
7,
|
||||
'creationTime uses Date.now() - navigationStart when available');
|
||||
|
||||
window.performance.now = () => 4;
|
||||
assert.equal(Flash.prototype.getVideoPlaybackQuality.call(mockFlash).creationTime,
|
||||
4,
|
||||
'creationTime prioritizes performance.now when available');
|
||||
|
||||
window.Date = origDate;
|
||||
window.performance = origPerformance;
|
||||
});
|
||||
|
||||
@@ -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__) {
|
||||
@@ -84,6 +97,32 @@ QUnit.test('test playbackRate', function(assert) {
|
||||
assert.strictEqual(tech.playbackRate(), 0.75);
|
||||
});
|
||||
|
||||
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('should export played', function(assert) {
|
||||
tech.createEl();
|
||||
assert.deepEqual(tech.played(), tech.el().played, 'returns the played attribute');
|
||||
@@ -641,3 +680,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;
|
||||
});
|
||||
|
||||
@@ -9,7 +9,10 @@ class TechFaker extends Tech {
|
||||
|
||||
constructor(options, handleReady) {
|
||||
super(options, handleReady);
|
||||
this.triggerReady();
|
||||
|
||||
if (!options || options.autoReady !== false) {
|
||||
this.triggerReady();
|
||||
}
|
||||
}
|
||||
|
||||
createEl() {
|
||||
|
||||
@@ -631,3 +631,9 @@ QUnit.test('setSource after previous setSource should dispose source handler onc
|
||||
|
||||
});
|
||||
|
||||
QUnit.test('returns an empty object for getVideoPlaybackQuality', function(assert) {
|
||||
const tech = new Tech();
|
||||
|
||||
assert.deepEqual(tech.getVideoPlaybackQuality(), {}, 'returns an empty object');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
/* eslint-env qunit */
|
||||
import HTMLTrackElement from '../../../src/js/tracks/html-track-element.js';
|
||||
import TechFaker from '../tech/tech-faker';
|
||||
|
||||
const defaultTech = {
|
||||
textTracks() {},
|
||||
on() {},
|
||||
off() {},
|
||||
currentTime() {}
|
||||
};
|
||||
|
||||
QUnit.module('HTML Track Element');
|
||||
QUnit.module('HTML Track Element', {
|
||||
beforeEach() {
|
||||
this.tech = new TechFaker();
|
||||
},
|
||||
afterEach() {
|
||||
this.tech.dispose();
|
||||
this.tech = null;
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('html track element requires a tech', function(assert) {
|
||||
assert.throws(
|
||||
@@ -31,7 +33,7 @@ QUnit.test('can create a html track element with various properties', function(a
|
||||
label,
|
||||
language,
|
||||
src,
|
||||
tech: defaultTech
|
||||
tech: this.tech
|
||||
});
|
||||
|
||||
assert.equal(typeof htmlTrackElement.default, 'undefined', 'we have a default');
|
||||
@@ -45,7 +47,7 @@ QUnit.test('can create a html track element with various properties', function(a
|
||||
|
||||
QUnit.test('defaults when items not provided', function(assert) {
|
||||
const htmlTrackElement = new HTMLTrackElement({
|
||||
tech: defaultTech
|
||||
tech: this.tech
|
||||
});
|
||||
|
||||
assert.equal(typeof htmlTrackElement.default, 'undefined', 'we have a default');
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import TextTrackList from '../../../src/js/tracks/text-track-list.js';
|
||||
import TextTrack from '../../../src/js/tracks/text-track.js';
|
||||
import EventTarget from '../../../src/js/event-target.js';
|
||||
import TechFaker from '../tech/tech-faker.js';
|
||||
|
||||
QUnit.module('Text Track List');
|
||||
QUnit.test('trigger "change" event when "modechange" is fired on a track', function(assert) {
|
||||
@@ -23,11 +24,7 @@ QUnit.test('trigger "change" event when "modechange" is fired on a track', funct
|
||||
});
|
||||
|
||||
QUnit.test('trigger "change" event when mode changes on a TextTrack', function(assert) {
|
||||
const tt = new TextTrack({
|
||||
tech: {
|
||||
on() {}
|
||||
}
|
||||
});
|
||||
const tt = new TextTrack({tech: new TechFaker()});
|
||||
const ttl = new TextTrackList([tt]);
|
||||
let changes = 0;
|
||||
const changeHandler = function() {
|
||||
|
||||
@@ -9,14 +9,16 @@ import proxyquireify from 'proxyquireify';
|
||||
import sinon from 'sinon';
|
||||
|
||||
const proxyquire = proxyquireify(require);
|
||||
const defaultTech = {
|
||||
textTracks() {},
|
||||
on() {},
|
||||
off() {},
|
||||
currentTime() {}
|
||||
};
|
||||
|
||||
QUnit.module('Text Track');
|
||||
QUnit.module('Text Track', {
|
||||
beforeEach() {
|
||||
this.tech = new TechFaker();
|
||||
},
|
||||
afterEach() {
|
||||
this.tech.dispose();
|
||||
this.tech = null;
|
||||
}
|
||||
});
|
||||
|
||||
// do baseline track testing
|
||||
TrackBaseline(TextTrack, {
|
||||
@@ -25,7 +27,7 @@ TrackBaseline(TextTrack, {
|
||||
mode: 'disabled',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
tech: defaultTech
|
||||
tech: new TechFaker()
|
||||
});
|
||||
|
||||
QUnit.test('requires a tech', function(assert) {
|
||||
@@ -39,7 +41,7 @@ QUnit.test('can create a TextTrack with a mode property', function(assert) {
|
||||
const mode = 'disabled';
|
||||
const tt = new TextTrack({
|
||||
mode,
|
||||
tech: defaultTech
|
||||
tech: this.tech
|
||||
});
|
||||
|
||||
assert.equal(tt.mode, mode, 'we have a mode');
|
||||
@@ -47,7 +49,7 @@ QUnit.test('can create a TextTrack with a mode property', function(assert) {
|
||||
|
||||
QUnit.test('defaults when items not provided', function(assert) {
|
||||
const tt = new TextTrack({
|
||||
tech: TechFaker
|
||||
tech: this.tech
|
||||
});
|
||||
|
||||
assert.equal(tt.kind, 'subtitles', 'kind defaulted to subtitles');
|
||||
@@ -58,7 +60,7 @@ QUnit.test('defaults when items not provided', function(assert) {
|
||||
|
||||
QUnit.test('kind can only be one of several options, defaults to subtitles', function(assert) {
|
||||
let tt = new TextTrack({
|
||||
tech: defaultTech,
|
||||
tech: this.tech,
|
||||
kind: 'foo'
|
||||
});
|
||||
|
||||
@@ -66,35 +68,35 @@ QUnit.test('kind can only be one of several options, defaults to subtitles', fun
|
||||
assert.notEqual(tt.kind, 'foo', 'the kind is set to subtitles, not foo');
|
||||
|
||||
tt = new TextTrack({
|
||||
tech: defaultTech,
|
||||
tech: this.tech,
|
||||
kind: 'subtitles'
|
||||
});
|
||||
|
||||
assert.equal(tt.kind, 'subtitles', 'the kind is set to subtitles');
|
||||
|
||||
tt = new TextTrack({
|
||||
tech: defaultTech,
|
||||
tech: this.tech,
|
||||
kind: 'captions'
|
||||
});
|
||||
|
||||
assert.equal(tt.kind, 'captions', 'the kind is set to captions');
|
||||
|
||||
tt = new TextTrack({
|
||||
tech: defaultTech,
|
||||
tech: this.tech,
|
||||
kind: 'descriptions'
|
||||
});
|
||||
|
||||
assert.equal(tt.kind, 'descriptions', 'the kind is set to descriptions');
|
||||
|
||||
tt = new TextTrack({
|
||||
tech: defaultTech,
|
||||
tech: this.tech,
|
||||
kind: 'chapters'
|
||||
});
|
||||
|
||||
assert.equal(tt.kind, 'chapters', 'the kind is set to chapters');
|
||||
|
||||
tt = new TextTrack({
|
||||
tech: defaultTech,
|
||||
tech: this.tech,
|
||||
kind: 'metadata'
|
||||
});
|
||||
|
||||
@@ -103,7 +105,7 @@ QUnit.test('kind can only be one of several options, defaults to subtitles', fun
|
||||
|
||||
QUnit.test('mode can only be one of several options, defaults to disabled', function(assert) {
|
||||
let tt = new TextTrack({
|
||||
tech: defaultTech,
|
||||
tech: this.tech,
|
||||
mode: 'foo'
|
||||
});
|
||||
|
||||
@@ -111,21 +113,21 @@ QUnit.test('mode can only be one of several options, defaults to disabled', func
|
||||
assert.notEqual(tt.mode, 'foo', 'the mode is set to disabld, not foo');
|
||||
|
||||
tt = new TextTrack({
|
||||
tech: defaultTech,
|
||||
tech: this.tech,
|
||||
mode: 'disabled'
|
||||
});
|
||||
|
||||
assert.equal(tt.mode, 'disabled', 'the mode is set to disabled');
|
||||
|
||||
tt = new TextTrack({
|
||||
tech: defaultTech,
|
||||
tech: this.tech,
|
||||
mode: 'hidden'
|
||||
});
|
||||
|
||||
assert.equal(tt.mode, 'hidden', 'the mode is set to hidden');
|
||||
|
||||
tt = new TextTrack({
|
||||
tech: defaultTech,
|
||||
tech: this.tech,
|
||||
mode: 'showing'
|
||||
});
|
||||
|
||||
@@ -136,7 +138,7 @@ QUnit.test('cue and activeCues are read only', function(assert) {
|
||||
const mode = 'disabled';
|
||||
const tt = new TextTrack({
|
||||
mode,
|
||||
tech: defaultTech
|
||||
tech: this.tech
|
||||
});
|
||||
|
||||
tt.cues = 'foo';
|
||||
@@ -148,7 +150,7 @@ QUnit.test('cue and activeCues are read only', function(assert) {
|
||||
|
||||
QUnit.test('mode can only be set to a few options', function(assert) {
|
||||
const tt = new TextTrack({
|
||||
tech: defaultTech
|
||||
tech: this.tech
|
||||
});
|
||||
|
||||
tt.mode = 'foo';
|
||||
@@ -173,7 +175,7 @@ QUnit.test('mode can only be set to a few options', function(assert) {
|
||||
|
||||
QUnit.test('cues and activeCues return a TextTrackCueList', function(assert) {
|
||||
const tt = new TextTrack({
|
||||
tech: defaultTech
|
||||
tech: this.tech
|
||||
});
|
||||
|
||||
assert.ok(tt.cues.getCueById, 'cues are a TextTrackCueList');
|
||||
@@ -182,7 +184,7 @@ QUnit.test('cues and activeCues return a TextTrackCueList', function(assert) {
|
||||
|
||||
QUnit.test('cues can be added and removed from a TextTrack', function(assert) {
|
||||
const tt = new TextTrack({
|
||||
tech: defaultTech
|
||||
tech: this.tech
|
||||
});
|
||||
const cues = tt.cues;
|
||||
|
||||
@@ -203,6 +205,46 @@ QUnit.test('cues can be added and removed from a TextTrack', function(assert) {
|
||||
assert.equal(cues.length, 3, 'we now have 3 cues');
|
||||
});
|
||||
|
||||
QUnit.test('original cue can be used to remove cue from cues list', function(assert) {
|
||||
const tt = new TextTrack({
|
||||
tech: this.tech
|
||||
});
|
||||
const Cue = window.VTTCue ||
|
||||
window.vttjs && window.vttjs.VTTCue ||
|
||||
window.TextTrackCue;
|
||||
|
||||
const cue1 = new Cue(0, 1, 'some-cue');
|
||||
|
||||
assert.equal(tt.cues.length, 0, 'start with zero cues');
|
||||
tt.addCue(cue1);
|
||||
assert.equal(tt.cues.length, 1, 'we have one cue');
|
||||
|
||||
tt.removeCue(cue1);
|
||||
assert.equal(tt.cues.length, 0, 'we have removed cue1');
|
||||
});
|
||||
|
||||
QUnit.test('can only remove one cue at a time', function(assert) {
|
||||
const tt = new TextTrack({
|
||||
tech: this.tech
|
||||
});
|
||||
const Cue = window.VTTCue ||
|
||||
window.vttjs && window.vttjs.VTTCue ||
|
||||
window.TextTrackCue;
|
||||
|
||||
const cue1 = new Cue(0, 1, 'some-cue');
|
||||
|
||||
assert.equal(tt.cues.length, 0, 'start with zero cues');
|
||||
tt.addCue(cue1);
|
||||
tt.addCue(cue1);
|
||||
assert.equal(tt.cues.length, 2, 'we have two cues');
|
||||
|
||||
tt.removeCue(cue1);
|
||||
assert.equal(tt.cues.length, 1, 'we have removed one instance of cue1');
|
||||
|
||||
tt.removeCue(cue1);
|
||||
assert.equal(tt.cues.length, 0, 'we have removed the other instance of cue1');
|
||||
});
|
||||
|
||||
QUnit.test('fires cuechange when cues become active and inactive', function(assert) {
|
||||
const player = TestHelpers.makePlayer();
|
||||
let changes = 0;
|
||||
@@ -242,6 +284,49 @@ QUnit.test('fires cuechange when cues become active and inactive', function(asse
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('does not fire cuechange before Tech is ready', function(assert) {
|
||||
const done = assert.async();
|
||||
const player = TestHelpers.makePlayer({techfaker: {autoReady: false}});
|
||||
let changes = 0;
|
||||
const tt = new TextTrack({
|
||||
tech: player.tech_,
|
||||
mode: 'showing'
|
||||
});
|
||||
const cuechangeHandler = function() {
|
||||
changes++;
|
||||
};
|
||||
|
||||
tt.addCue({
|
||||
id: '1',
|
||||
startTime: 0,
|
||||
endTime: 5
|
||||
});
|
||||
|
||||
tt.oncuechange = cuechangeHandler;
|
||||
tt.addEventListener('cuechange', cuechangeHandler);
|
||||
|
||||
player.tech_.currentTime = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
player.tech_.trigger('timeupdate');
|
||||
assert.equal(changes, 0, 'a cuechange event is not triggered');
|
||||
|
||||
player.tech_.on('ready', function() {
|
||||
player.tech_.currentTime = function() {
|
||||
return 0.2;
|
||||
};
|
||||
|
||||
player.tech_.trigger('timeupdate');
|
||||
|
||||
assert.equal(changes, 2, 'a cuechange event trigger addEventListener and oncuechange');
|
||||
|
||||
player.dispose();
|
||||
done();
|
||||
});
|
||||
player.tech_.triggerReady();
|
||||
});
|
||||
|
||||
QUnit.test('tracks are parsed if vttjs is loaded', function(assert) {
|
||||
const clock = sinon.useFakeTimers();
|
||||
const oldVTT = window.WebVTT;
|
||||
@@ -270,7 +355,7 @@ QUnit.test('tracks are parsed if vttjs is loaded', function(assert) {
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
const tt = new TextTrack_({
|
||||
tech: defaultTech,
|
||||
tech: this.tech,
|
||||
src: 'http://example.com'
|
||||
});
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import mergeOptions from '../../../src/js/utils/merge-options.js';
|
||||
|
||||
QUnit.module('merge-options');
|
||||
|
||||
QUnit.test('should merge options objects', function(assert) {
|
||||
const ob1 = {
|
||||
a: true,
|
||||
@@ -27,3 +28,9 @@ QUnit.test('should merge options objects', function(assert) {
|
||||
d: true
|
||||
}, 'options objects merged correctly');
|
||||
});
|
||||
|
||||
QUnit.test('should ignore non-objects', function(assert) {
|
||||
const obj = { a: 1 };
|
||||
|
||||
assert.deepEqual(mergeOptions(obj, true), obj, 'ignored non-object input');
|
||||
});
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário