Comparar commits

...

32 Commits

Autor SHA1 Mensagem Data
brandonocasey bf0b538e23 doc comments, cleanup 2017-10-23 11:59:39 -04:00
brandonocasey 809d6d881b better support and tests 2017-10-20 14:51:21 -04:00
brandonocasey ca210e987a sourceset 2017-10-19 17:39:01 -04:00
brandonocasey 7173228f6e up the wait time 2017-10-17 10:46:15 -04:00
brandonocasey 9a2415a9a4 get it to work on any IOS 2017-10-13 17:10:52 -04:00
brandonocasey fa3172b245 ie8 will never work 2017-10-13 14:30:10 -04:00
brandonocasey a11e657ccd fix 2017-10-12 17:36:44 -04:00
brandonocasey bd578596ed add a method for ie9/10 2017-10-12 13:27:42 -04:00
brandonocasey 905329b10b check for mutation observer 2017-10-12 12:05:37 -04:00
brandonocasey 7f67a01d8a feat: sourcechange 2017-10-12 11:53:29 -04:00
Gary Katsevman e8511a5799 chore(gh-release): no console log on success (#4657)
We've been printing the resulting value from a successful call to the ghrelease, this was useful for debugging but isn't necessary anymore.
Add the result to the error conditional with some headings.
2017-10-10 11:35:50 -07:00
Gary Katsevman 69577790eb 6.3.3 2017-10-10 13:52:45 -04:00
Brandon Casey b1de506b43 fix: a possible breaking change caused by the use of remainingTimeDisplay (#4655) 2017-10-10 11:35:46 -04:00
TaeSoo 태수 6738f765da docs(hooks): Fix Typo (#4652) 2017-10-06 18:01:54 -07:00
Pat O'Neill 8ec61bbb20 6.3.2 2017-10-04 10:37:26 -04:00
Thijs Triemstra 85a34d1b49 docs: Document how to add a version number to a plugin (#4642) 2017-10-04 10:32:18 -04:00
Pat O'Neill 4658c7bad6 fix: Fix a typo in current time display component. (#4647) 2017-10-04 10:31:10 -04:00
Pat O'Neill 8878acc040 6.3.1 2017-10-03 13:11:27 -04:00
Pat O'Neill 20f7fe991f fix: Make sure time displays use correctly-formatted time. (#4643) 2017-10-03 12:52:58 -04:00
Pat O'Neill f0d9c240fe 6.3.0 2017-10-03 11:04:07 -04:00
Brandon Casey 445eb26722 feat: Add remainingTimeDisplay method to Player (#4620) 2017-10-02 16:29:40 -04:00
Thom 5ca0992cf1 chore(lang): Update Dutch (#4588) 2017-10-02 16:08:04 -04:00
ngoisaosang ac58dbf13a chore(lang): Update Vietnamese (#4625) 2017-10-02 16:04:57 -04:00
Alex Barstow 1ac8065ea6 feat: Do not set focus in sub-menus to prevent undesirable scrolling behavior in iOS (#4607) 2017-10-02 15:29:34 -04:00
Brandon Casey f51d36b053 feat: display currentTime as duration and remainingTime as 0 on ended (#4634) 2017-10-02 11:19:29 -04:00
Brandon Casey fa6f884409 refactor: Create a base time display class, and use it (#4633) 2017-10-02 10:58:30 -04:00
Brandon Casey 335bcded98 fix: reset to a play/pause button when seeking after ended (#4614) 2017-09-20 13:45:47 -04:00
Thijs Triemstra d8ea23e0c9 docs: update player reference in advanced plugins doc (#4622) 2017-09-20 10:55:29 -04:00
Erik Demaine 9d249bb23b docs: Document playbackRates (#4602) 2017-09-19 17:44:36 -04:00
greenkeeper[bot] 7929677f6f chore(package): update remark-validate-links to version 7.0.0 (#4585) 2017-09-19 17:21:39 -04:00
greenkeeper[bot] 6cd785ab7f chore(package): update grunt-browserify to version 5.2.0 (#4578) 2017-09-19 17:21:20 -04:00
Brandon Casey edde614822 chore: alias rollup-dev to watch for development (#4615) 2017-09-14 15:09:52 -04:00
17 arquivos alterados com 1192 adições e 177 exclusões
+59
Ver Arquivo
@@ -1,3 +1,62 @@
<a name="6.3.3"></a>
## [6.3.3](https://github.com/videojs/video.js/compare/v6.3.2...v6.3.3) (2017-10-10)
### Bug Fixes
* a possible breaking change caused by the use of remainingTimeDisplay ([#4655](https://github.com/videojs/video.js/issues/4655)) ([b1de506](https://github.com/videojs/video.js/commit/b1de506))
### Documentation
* **hooks:** Fix Typo ([#4652](https://github.com/videojs/video.js/issues/4652)) ([6738f76](https://github.com/videojs/video.js/commit/6738f76))
<a name="6.3.2"></a>
## [6.3.2](https://github.com/videojs/video.js/compare/v6.3.1...v6.3.2) (2017-10-04)
### Bug Fixes
* Fix a typo in current time display component. ([#4647](https://github.com/videojs/video.js/issues/4647)) ([4658c7b](https://github.com/videojs/video.js/commit/4658c7b))
### Documentation
* Document how to add a version number to a plugin ([#4642](https://github.com/videojs/video.js/issues/4642)) ([85a34d1](https://github.com/videojs/video.js/commit/85a34d1))
<a name="6.3.1"></a>
## [6.3.1](https://github.com/videojs/video.js/compare/v6.3.0...v6.3.1) (2017-10-03)
### Bug Fixes
* Make sure time displays use correctly-formatted time. ([#4643](https://github.com/videojs/video.js/issues/4643)) ([20f7fe9](https://github.com/videojs/video.js/commit/20f7fe9))
<a name="6.3.0"></a>
# [6.3.0](https://github.com/videojs/video.js/compare/v6.2.8...v6.3.0) (2017-10-03)
### Features
* Add remainingTimeDisplay method to Player ([#4620](https://github.com/videojs/video.js/issues/4620)) ([445eb26](https://github.com/videojs/video.js/commit/445eb26))
* display currentTime as duration and remainingTime as 0 on ended ([#4634](https://github.com/videojs/video.js/issues/4634)) ([f51d36b](https://github.com/videojs/video.js/commit/f51d36b))
* Do not set focus in sub-menus to prevent undesirable scrolling behavior in iOS ([#4607](https://github.com/videojs/video.js/issues/4607)) ([1ac8065](https://github.com/videojs/video.js/commit/1ac8065))
### Bug Fixes
* reset to a play/pause button when seeking after ended ([#4614](https://github.com/videojs/video.js/issues/4614)) ([335bcde](https://github.com/videojs/video.js/commit/335bcde))
### Chores
* alias rollup-dev to watch for development ([#4615](https://github.com/videojs/video.js/issues/4615)) ([edde614](https://github.com/videojs/video.js/commit/edde614))
* **lang:** Update Dutch ([#4588](https://github.com/videojs/video.js/issues/4588)) ([5ca0992](https://github.com/videojs/video.js/commit/5ca0992))
* **lang:** Update Vietnamese ([#4625](https://github.com/videojs/video.js/issues/4625)) ([ac58dbf](https://github.com/videojs/video.js/commit/ac58dbf))
* **package:** update grunt-browserify to version 5.2.0 ([#4578](https://github.com/videojs/video.js/issues/4578)) ([6cd785a](https://github.com/videojs/video.js/commit/6cd785a))
* **package:** update remark-validate-links to version 7.0.0 ([#4585](https://github.com/videojs/video.js/issues/4585)) ([7929677](https://github.com/videojs/video.js/commit/7929677))
### Code Refactoring
* Create a base time display class, and use it ([#4633](https://github.com/videojs/video.js/issues/4633)) ([fa6f884](https://github.com/videojs/video.js/commit/fa6f884))
### Documentation
* Document playbackRates ([#4602](https://github.com/videojs/video.js/issues/4602)) ([9d249bb](https://github.com/videojs/video.js/commit/9d249bb))
* update player reference in advanced plugins doc ([#4622](https://github.com/videojs/video.js/issues/4622)) ([d8ea23e](https://github.com/videojs/video.js/commit/d8ea23e))
<a name="6.2.8"></a>
## [6.2.8](https://github.com/videojs/video.js/compare/v6.2.7...v6.2.8) (2017-09-01)
+3 -3
Ver Arquivo
@@ -35,10 +35,10 @@ if (args.prerelease || npmargs.some(function(arg) { return /next/.test(arg); }))
ghrelease(options, function(err, result) {
if (err) {
console.log('Unable to publish release to github');
console.log(err);
console.error('Unable to publish release to github');
console.error('err:', err);
console.error('result:', result);
} else {
console.log('Publish release to github!');
console.log(result);
}
});
+1 -1
Ver Arquivo
@@ -14,7 +14,7 @@ Hooks exist so that users can "hook" on to certain Video.js player lifecycle
## Current Hooks
Currently, the following hooks are avialable:
Currently, the following hooks are available:
### beforesetup
+19
Ver Arquivo
@@ -26,6 +26,7 @@
* [languages](#languages)
* [nativeControlsForTouch](#nativecontrolsfortouch)
* [notSupportedMessage](#notsupportedmessage)
* [playbackRates](#playbackrates)
* [plugins](#plugins)
* [sourceOrder](#sourceorder)
* [sources](#sources)
@@ -180,6 +181,24 @@ Explicitly set a default value for [the associated tech option](#nativecontrolsf
Allows overriding the default message that is displayed when Video.js cannot play back a media source.
### `playbackRates`
> Type: `Array`
An array of numbers strictly greater than 0, where 1 means regular speed
(100%), 0.5 means half-speed (50%), 2 means double-speed (200%), etc.
If specified, Video.js displays a control (of class `vjs-playback-rate`)
allowing the user to choose playback speed from among the array of choices.
The choices are presented in the specified order from bottom to top.
For example:
```js
videojs('my-player', {
playbackRates: [0.5, 1, 1.5, 2]
});
```
### `plugins`
> Type: `Object`
+20 -1
Ver Arquivo
@@ -225,6 +225,25 @@ The `dispose` method has several effects:
In addition, if the player is disposed, the disposal of all its advanced plugin instances will be triggered as well.
#### Version
Adding a version number to a plugin is done by defining a `VERSION` property on the plugin before registering it:
```js
ExamplePlugin.VERSION = '1.0.1';
videojs.registerPlugin('examplePlugin', ExamplePlugin);
```
Retrieve it using `videojs.getPluginVersion`:
```js
var version = videojs.getPluginVersion('examplePlugin');
console.log(version); // 1.0.1
```
Note that the [plugin generator](https://github.com/videojs/generator-videojs-plugin) already takes care of adding a version number for you.
### Advanced Example Advanced Plugin
What follows is a complete ES6 advanced plugin that logs a custom message when the player's state changes between playing and paused. It uses all the described advanced features:
@@ -251,7 +270,7 @@ class Advanced extends Plugin {
}
updateState() {
this.setState({playing: !player.paused()});
this.setState({playing: !this.player.paused()});
}
logState(changed) {
+68 -21
Ver Arquivo
@@ -1,37 +1,84 @@
{
"Audio Player": "Audiospeler",
"Video Player": "Videospeler",
"Play": "Afspelen",
"Pause": "Pauze",
"Pause": "Pauzeren",
"Replay": "Opnieuw afspelen",
"Current Time": "Huidige tijd",
"Duration Time": "Looptijd",
"Duration Time": "Tijdsduur",
"Remaining Time": "Resterende tijd",
"Stream Type": "Streamtype",
"LIVE": "LIVE",
"Loaded": "Geladen",
"Progress": "Status",
"Progress": "Voortgang",
"Progress Bar": "Voortgangsbalk",
"progress bar timing: currentTime={1} duration={2}": "{1} van {2}",
"Fullscreen": "Volledig scherm",
"Non-Fullscreen": "Geen volledig scherm",
"Mute": "Geluid uit",
"Unmute": "Geluid aan",
"Playback Rate": "Weergavesnelheid",
"Mute": "Dempen",
"Unmute": "Niet dempen",
"Playback Rate": "Afspeelsnelheid",
"Subtitles": "Ondertiteling",
"subtitles off": "ondertiteling uit",
"Captions": "Bijschriften",
"captions off": "bijschriften uit",
"Chapters": "Hoofdstukken",
"Descriptions": "Beschrijvingen",
"descriptions off": "beschrijvingen off",
"You aborted the media playback": "U hebt de mediaweergave afgebroken.",
"A network error caused the media download to fail part-way.": "De mediadownload is mislukt door een netwerkfout.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "De media kon niet worden geladen, vanwege een server- of netwerkfout of doordat het formaat niet wordt ondersteund.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "De mediaweergave is afgebroken vanwege beschadigde data of het mediabestand gebruikt functies die niet door uw browser worden ondersteund.",
"No compatible source was found for this media.": "Voor deze media is geen ondersteunde bron gevonden.",
"Play Video": "Video Afspelen",
"descriptions off": "beschrijvingen uit",
"Audio Track": "Audiospoor",
"Volume Level": "Geluidsniveau",
"You aborted the media playback": "U heeft het afspelen van de media afgebroken",
"A network error caused the media download to fail part-way.": "Een netwerkfout heeft ervoor gezorgd dat het downloaden van de media halverwege is mislukt.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "De media kon niet worden geladen, dit komt doordat of de server of het netwerk mislukt of doordat het formaat niet wordt ondersteund.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Het afspelen van de media is afgebroken door een probleem met beschadeigde gegevens of doordat de media functies gebruikt die uw browser niet ondersteund.",
"No compatible source was found for this media.": "Er is geen geschikte bron voor deze media gevonden.",
"The media is encrypted and we do not have the keys to decrypt it.": "De media is versleuteld en we hebben de sleutels niet om deze te ontsleutelen.",
"Play Video": "Video afspelen",
"Close": "Sluiten",
"Modal Window": "Modal Venster",
"This is a modal window": "Dit is een modaal venster",
"This modal can be closed by pressing the Escape key or activating the close button.": "Dit modaal venster kan gesloten worden door op Escape te drukken of de 'sluiten' knop te activeren.",
", opens captions settings dialog": ", opent bijschriften instellingen venster",
", opens subtitles settings dialog": ", opent ondertiteling instellingen venster",
", opens descriptions settings dialog": ", opent beschrijvingen instellingen venster",
", selected": ", selected"
}
"Close Modal Dialog": "Extra venster sluiten",
"Modal Window": "Extra venster",
"This is a modal window": "Dit is een extra venster",
"This modal can be closed by pressing the Escape key or activating the close button.": "Dit venster kan worden gesloten door op de Escape-toets te drukken of door de sluiten-knop te activeren.",
", opens captions settings dialog": ", opent instellingen-venster voor bijschriften",
", opens subtitles settings dialog": ", opent instellingen-venster voor ondertitelingen",
", opens descriptions settings dialog": ", opent instellingen-venster voor beschrijvingen",
", selected": ", geselecteerd",
"captions settings": "bijschriften-instellingen",
"subtitles settings": "ondertiteling-instellingen",
"descriptions settings": "beschrijvingen-instellingen",
"Text": "Tekst",
"White": "Wit",
"Black": "Zwart",
"Red": "Rood",
"Green": "Groen",
"Blue": "Blauw",
"Yellow": "Geel",
"Magenta": "Magenta",
"Cyan": "Cyaan",
"Background": "Achtergrond",
"Window": "Venster",
"Transparent": "Transparant",
"Semi-Transparent": "Semi-transparant",
"Opaque": "Ondoorzichtig",
"Font Size": "Lettergrootte",
"Text Edge Style": "Stijl tekstrand",
"None": "Geen",
"Raised": "Verhoogd",
"Depressed": "Ingedrukt",
"Uniform": "Uniform",
"Dropshadow": "Schaduw",
"Font Family": "Lettertype",
"Proportional Sans-Serif": "Proportioneel sans-serif",
"Monospace Sans-Serif": "Monospace sans-serif",
"Proportional Serif": "Proportioneel serif",
"Monospace Serif": "Monospace serif",
"Casual": "Luchtig",
"Script": "Script",
"Small Caps": "Kleine hoofdletters",
"Reset": "Herstellen",
"restore all settings to the default values": "alle instellingen naar de standaardwaarden herstellen",
"Done": "Klaar",
"Caption Settings Dialog": "Venster voor bijschriften-instellingen",
"Beginning of dialog window. Escape will cancel and close the window.": "Begin van dialoogvenster. Escape zal annuleren en het venster sluiten.",
"End of dialog window.": "Einde van dialoogvenster."
}
+51 -8
Ver Arquivo
@@ -1,4 +1,6 @@
{
"Audio Player": "Trình phát Audio",
"Video Player": "Trình phát Video",
"Play": "Phát",
"Pause": "Tạm dừng",
"Replay": "Phát lại",
@@ -9,6 +11,8 @@
"LIVE": "TRỰC TIẾP",
"Loaded": "Đã tải",
"Progress": "Tiến trình",
"Progress Bar": "Thanh tiến trình",
"progress bar timing: currentTime={1} duration={2}": "{1} của {2}",
"Fullscreen": "Toàn màn hình",
"Non-Fullscreen": "Thoát toàn màn hình",
"Mute": "Tắt tiếng",
@@ -19,23 +23,62 @@
"Captions": "Chú thích",
"captions off": "tắt chú thích",
"Chapters": "Chương",
"Close Modal Dialog": "Đóng hộp thoại",
"Descriptions": "Mô tả",
"descriptions off": "tắt mô tả",
"Audio Track": "Track âm thanh",
"Volume Level": "Mức âm lượng",
"You aborted the media playback": "Bạn đã hủy việc phát lại media.",
"A network error caused the media download to fail part-way.": "Một lỗi mạng dẫn đến việc tải media bị lỗi.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Video không tải được, mạng hay server có lỗi hoặc định dạng không được hỗ trợ.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Phát media đã bị hủy do một sai lỗi hoặc media sử dụng những tính năng trình duyệt không hỗ trợ.",
"No compatible source was found for this media.": "Không có nguồn tương thích cho media này.",
"The media is encrypted and we do not have the keys to decrypt it.": "Media đã được mã hóa và chúng tôi không thể giải mã nó.",
"The media is encrypted and we do not have the keys to decrypt it.": "Media đã được mã hóa và chúng tôi không có để giải mã nó.",
"Play Video": "Phát Video",
"Close": "Đóng",
"Close Modal Dialog": "Đóng cửa sổ",
"Modal Window": "Cửa sổ",
"This is a modal window": "Đây là một cửa sổ",
"This modal can be closed by pressing the Escape key or activating the close button.": "Cửa sổ này có thể đóng lại bằng việc nhấn phím Esc hoặc kích hoạt nút đóng.",
", opens captions settings dialog": ", mở hộp thoại thiết lập chú thích",
", opens subtitles settings dialog": ", mở hộp thoại thiết lập phụ đề",
", opens descriptions settings dialog": ", mở hộp thoại thiết lập mô tả",
", selected": ", đã chọn"
}
"This modal can be closed by pressing the Escape key or activating the close button.": "Cửa sổ này có thể thoát bằng việc nhấn phím Esc hoặc kích hoạt nút đóng.",
", opens captions settings dialog": ", mở hộp thoại cài đặt chú thích",
", opens subtitles settings dialog": ", mở hộp thoại cài đặt phụ đề",
", opens descriptions settings dialog": ", mở hộp thoại cài đặt mô tả",
", selected": ", đã chọn",
"captions settings": "cài đặt chú thích",
"subtitles settings": "cài đặt phụ đề",
"descriptions settings": "cài đặt mô tả",
"Text": "Văn bản",
"White": "Trắng",
"Black": "Đen",
"Red": "Đỏ",
"Green": "Xanh lá cây",
"Blue": "Xanh da trời",
"Yellow": "Vàng",
"Magenta": "Đỏ tươi",
"Cyan": "Lam",
"Background": "Nền",
"Window": "Cửa sổ",
"Transparent": "Trong suốt",
"Semi-Transparent": "Bán trong suốt",
"Opaque": "Mờ",
"Font Size": "Kích cỡ phông chữ",
"Text Edge Style": "Dạng viền văn bản",
"None": "None",
"Raised": "Raised",
"Depressed": "Depressed",
"Uniform": "Uniform",
"Dropshadow": "Dropshadow",
"Font Family": "Phông chữ",
"Proportional Sans-Serif": "Proportional Sans-Serif",
"Monospace Sans-Serif": "Monospace Sans-Serif",
"Proportional Serif": "Proportional Serif",
"Monospace Serif": "Monospace Serif",
"Casual": "Casual",
"Script": "Script",
"Small Caps": "Small Caps",
"Reset": "Đặt lại",
"restore all settings to the default values": "khôi phục lại tất cả các cài đặt về giá trị mặc định",
"Done": "Xong",
"Caption Settings Dialog": "Hộp thoại cài đặt chú thích",
"Beginning of dialog window. Escape will cancel and close the window.": "Bắt đầu cửa sổ hộp thoại. Esc sẽ thoát và đóng cửa sổ.",
"End of dialog window.": "Kết thúc cửa sổ hộp thoại."
}
+4 -3
Ver Arquivo
@@ -1,7 +1,7 @@
{
"name": "video.js",
"description": "An HTML5 and Flash video player with a common API and skin for both.",
"version": "6.2.8",
"version": "6.3.3",
"main": "./dist/video.cjs.js",
"style": "./dist/video-js.css",
"copyright": "Copyright Brightcove, Inc. <https://www.brightcove.com/>",
@@ -22,6 +22,7 @@
"rollup": "babel-node build/rollup.js",
"rollup-minify": "babel-node build/rollup.js --minify",
"rollup-dev": "babel-node build/rollup.js --watch",
"watch": "npm run rollup-dev",
"assets": "node build/assets.js",
"change": "grunt chg-add",
"clean": "grunt clean",
@@ -83,7 +84,7 @@
"grunt-accessibility": "^5.0.0",
"grunt-babel": "^7.0.0",
"grunt-banner": "^0.6.0",
"grunt-browserify": "5.1.0",
"grunt-browserify": "5.2.0",
"grunt-cli": "~1.2.0",
"grunt-concurrent": "^2.3.1",
"grunt-contrib-clean": "^1.0.0",
@@ -133,7 +134,7 @@
"remark-parse": "^4.0.0",
"remark-stringify": "^4.0.0",
"remark-toc": "^4.0.0",
"remark-validate-links": "^6.0.0",
"remark-validate-links": "^7.0.0",
"replace": "^0.3.0",
"rollup": "^0.47.5",
"rollup-plugin-babel": "^2.7.1",
+27 -1
Ver Arquivo
@@ -57,6 +57,26 @@ class PlayToggle extends Button {
}
}
/**
* This gets called once after the video has ended and the user seeks so that
* we can change the replay button back to a play button.
*
* @param {EventTarget~Event} [event]
* The event that caused this function to run.
*
* @listens Player#seeked
*/
handleSeeked(event) {
// remove the ended class
this.removeClass('vjs-ended');
if (this.player_.paused()) {
this.handlePause(event);
} else {
this.handlePlay(event);
}
}
/**
* Add the vjs-playing class to the element so it can change appearance.
*
@@ -66,7 +86,6 @@ class PlayToggle extends Button {
* @listens Player#play
*/
handlePlay(event) {
this.removeClass('vjs-ended');
this.removeClass('vjs-paused');
this.addClass('vjs-playing');
// change the button text to "Pause"
@@ -91,12 +110,19 @@ class PlayToggle extends Button {
/**
* Add the vjs-ended class to the element so it can change appearance
*
* @param {EventTarget~Event} [event]
* The event that caused this function to run.
*
* @listens Player#ended
*/
handleEnded(event) {
this.removeClass('vjs-playing');
this.addClass('vjs-ended');
// change the button text to "Replay"
this.controlText('Replay');
// on the next seek remove the replay button
this.one(this.player_, 'seeked', this.handleSeeked);
}
}
@@ -1,18 +1,15 @@
/**
* @file current-time-display.js
*/
import document from 'global/document';
import TimeDisplay from './time-display';
import Component from '../../component.js';
import * as Dom from '../../utils/dom.js';
import {bind, throttle} from '../../utils/fn.js';
import formatTime from '../../utils/format-time.js';
/**
* Displays the current time
*
* @extends Component
*/
class CurrentTimeDisplay extends Component {
class CurrentTimeDisplay extends TimeDisplay {
/**
* Creates an instance of this class.
@@ -25,48 +22,17 @@ class CurrentTimeDisplay extends Component {
*/
constructor(player, options) {
super(player, options);
this.throttledUpdateContent = throttle(bind(this, this.updateContent), 25);
this.on(player, 'timeupdate', this.throttledUpdateContent);
this.on(player, 'ended', this.handleEnded);
}
/**
* Create the `Component`'s DOM element
* Builds the default DOM `className`.
*
* @return {Element}
* The element that was created.
* @return {string}
* The DOM `className` for this object.
*/
createEl() {
const el = super.createEl('div', {
className: 'vjs-current-time vjs-time-control vjs-control'
});
this.contentEl_ = Dom.createEl('div', {
className: 'vjs-current-time-display'
}, {
// tell screen readers not to automatically read the time as it changes
'aria-live': 'off'
}, Dom.createEl('span', {
className: 'vjs-control-text',
textContent: this.localize('Current Time')
}));
this.updateTextNode_();
el.appendChild(this.contentEl_);
return el;
}
/**
* Updates the "current time" text node with new content using the
* contents of the `formattedTime_` property.
*
* @private
*/
updateTextNode_() {
if (this.textNode_) {
this.contentEl_.removeChild(this.textNode_);
}
this.textNode_ = document.createTextNode(` ${this.formattedTime_ || '0:00'}`);
this.contentEl_.appendChild(this.textNode_);
buildCSSClass() {
return 'vjs-current-time';
}
/**
@@ -80,15 +46,36 @@ class CurrentTimeDisplay extends Component {
updateContent(event) {
// Allows for smooth scrubbing, when player can't keep up.
const time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
const formattedTime = formatTime(time, this.player_.duration());
if (formattedTime !== this.formattedTime_) {
this.formattedTime_ = formattedTime;
this.requestAnimationFrame(this.updateTextNode_);
this.updateFormattedTime_(time);
}
/**
* When the player fires ended there should be no time left. Sadly
* this is not always the case, lets make it seem like that is the case
* for users.
*
* @param {EventTarget~Event} [event]
* The `ended` event that caused this to run.
*
* @listens Player#ended
*/
handleEnded(event) {
if (!this.player_.duration()) {
return;
}
this.updateFormattedTime_(this.player_.duration());
}
}
/**
* The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
*
* @type {string}
* @private
*/
CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
export default CurrentTimeDisplay;
@@ -1,18 +1,15 @@
/**
* @file duration-display.js
*/
import document from 'global/document';
import TimeDisplay from './time-display';
import Component from '../../component.js';
import * as Dom from '../../utils/dom.js';
import {bind, throttle} from '../../utils/fn.js';
import formatTime from '../../utils/format-time.js';
/**
* Displays the duration
*
* @extends Component
*/
class DurationDisplay extends Component {
class DurationDisplay extends TimeDisplay {
/**
* Creates an instance of this class.
@@ -26,57 +23,24 @@ class DurationDisplay extends Component {
constructor(player, options) {
super(player, options);
this.throttledUpdateContent = throttle(bind(this, this.updateContent), 25);
this.on(player, [
'durationchange',
// Also listen for timeupdate and loadedmetadata because removing those
// Also listen for timeupdate (in the parent) and loadedmetadata because removing those
// listeners could have broken dependent applications/libraries. These
// can likely be removed for 7.0.
'loadedmetadata',
'timeupdate'
'loadedmetadata'
], this.throttledUpdateContent);
}
/**
* Create the `Component`'s DOM element
* Builds the default DOM `className`.
*
* @return {Element}
* The element that was created.
* @return {string}
* The DOM `className` for this object.
*/
createEl() {
const el = super.createEl('div', {
className: 'vjs-duration vjs-time-control vjs-control'
});
this.contentEl_ = Dom.createEl('div', {
className: 'vjs-duration-display'
}, {
// tell screen readers not to automatically read the time as it changes
'aria-live': 'off'
}, Dom.createEl('span', {
className: 'vjs-control-text',
textContent: this.localize('Duration Time')
}));
this.updateTextNode_();
el.appendChild(this.contentEl_);
return el;
}
/**
* Updates the "current time" text node with new content using the
* contents of the `formattedTime_` property.
*
* @private
*/
updateTextNode_() {
if (this.textNode_) {
this.contentEl_.removeChild(this.textNode_);
}
this.textNode_ = document.createTextNode(` ${this.formattedTime_ || '0:00'}`);
this.contentEl_.appendChild(this.textNode_);
buildCSSClass() {
return 'vjs-duration';
}
/**
@@ -95,11 +59,18 @@ class DurationDisplay extends Component {
if (duration && this.duration_ !== duration) {
this.duration_ = duration;
this.formattedTime_ = formatTime(duration);
this.requestAnimationFrame(this.updateTextNode_);
this.updateFormattedTime_(duration);
}
}
}
/**
* The text that should display over the `DurationDisplay`s controls. Added to for localization.
*
* @type {string}
* @private
*/
DurationDisplay.prototype.controlText_ = 'Duration Time';
Component.registerComponent('DurationDisplay', DurationDisplay);
export default DurationDisplay;
@@ -1,18 +1,14 @@
/**
* @file remaining-time-display.js
*/
import document from 'global/document';
import TimeDisplay from './time-display';
import Component from '../../component.js';
import * as Dom from '../../utils/dom.js';
import {bind, throttle} from '../../utils/fn.js';
import formatTime from '../../utils/format-time.js';
/**
* Displays the time left in the video
*
* @extends Component
*/
class RemainingTimeDisplay extends Component {
class RemainingTimeDisplay extends TimeDisplay {
/**
* Creates an instance of this class.
@@ -25,48 +21,33 @@ class RemainingTimeDisplay extends Component {
*/
constructor(player, options) {
super(player, options);
this.throttledUpdateContent = throttle(bind(this, this.updateContent), 25);
this.on(player, ['timeupdate', 'durationchange'], this.throttledUpdateContent);
this.on(player, 'durationchange', this.throttledUpdateContent);
this.on(player, 'ended', this.handleEnded);
}
/**
* Create the `Component`'s DOM element
* Builds the default DOM `className`.
*
* @return {Element}
* The element that was created.
* @return {string}
* The DOM `className` for this object.
*/
createEl() {
const el = super.createEl('div', {
className: 'vjs-remaining-time vjs-time-control vjs-control'
});
this.contentEl_ = Dom.createEl('div', {
className: 'vjs-remaining-time-display'
}, {
// tell screen readers not to automatically read the time as it changes
'aria-live': 'off'
}, Dom.createEl('span', {
className: 'vjs-control-text',
textContent: this.localize('Remaining Time')
}));
this.updateTextNode_();
el.appendChild(this.contentEl_);
return el;
buildCSSClass() {
return 'vjs-remaining-time';
}
/**
* Updates the "remaining time" text node with new content using the
* contents of the `formattedTime_` property.
* The remaining time display prefixes numbers with a "minus" character.
*
* @param {number} time
* A numeric time, in seconds.
*
* @return {string}
* A formatted time
*
* @private
*/
updateTextNode_() {
if (this.textNode_) {
this.contentEl_.removeChild(this.textNode_);
}
this.textNode_ = document.createTextNode(` -${this.formattedTime_ || '0:00'}`);
this.contentEl_.appendChild(this.textNode_);
formatTime_(time) {
return '-' + super.formatTime_(time);
}
/**
@@ -79,16 +60,44 @@ class RemainingTimeDisplay extends Component {
* @listens Player#durationchange
*/
updateContent(event) {
if (this.player_.duration()) {
const formattedTime = formatTime(this.player_.remainingTime());
if (formattedTime !== this.formattedTime_) {
this.formattedTime_ = formattedTime;
this.requestAnimationFrame(this.updateTextNode_);
}
if (!this.player_.duration()) {
return;
}
// @deprecated We should only use remainingTimeDisplay
// as of video.js 7
if (this.player_.remainingTimeDisplay) {
this.updateFormattedTime_(this.player_.remainingTimeDisplay());
} else {
this.updateFormattedTime_(this.player_.remainingTime());
}
}
/**
* When the player fires ended there should be no time left. Sadly
* this is not always the case, lets make it seem like that is the case
* for users.
*
* @param {EventTarget~Event} [event]
* The `ended` event that caused this to run.
*
* @listens Player#ended
*/
handleEnded(event) {
if (!this.player_.duration()) {
return;
}
this.updateFormattedTime_(0);
}
}
/**
* The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
*
* @type {string}
* @private
*/
RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
export default RemainingTimeDisplay;
@@ -0,0 +1,129 @@
/**
* @file time-display.js
*/
import document from 'global/document';
import Component from '../../component.js';
import * as Dom from '../../utils/dom.js';
import {bind, throttle} from '../../utils/fn.js';
import formatTime from '../../utils/format-time.js';
/**
* Displays the time left in the video
*
* @extends Component
*/
class TimeDisplay extends Component {
/**
* Creates an instance of this class.
*
* @param {Player} player
* The `Player` that this class should be attached to.
*
* @param {Object} [options]
* The key/value store of player options.
*/
constructor(player, options) {
super(player, options);
this.throttledUpdateContent = throttle(bind(this, this.updateContent), 25);
this.on(player, 'timeupdate', this.throttledUpdateContent);
}
/**
* Create the `Component`'s DOM element
*
* @return {Element}
* The element that was created.
*/
createEl(plainName) {
const className = this.buildCSSClass();
const el = super.createEl('div', {
className: `${className} vjs-time-control vjs-control`
});
this.contentEl_ = Dom.createEl('div', {
className: `${className}-display`
}, {
// tell screen readers not to automatically read the time as it changes
'aria-live': 'off'
}, Dom.createEl('span', {
className: 'vjs-control-text',
textContent: this.localize(this.contentText_)
}));
this.updateTextNode_();
el.appendChild(this.contentEl_);
return el;
}
/**
* Updates the "remaining time" text node with new content using the
* contents of the `formattedTime_` property.
*
* @private
*/
updateTextNode_() {
if (this.textNode_) {
this.contentEl_.removeChild(this.textNode_);
}
this.textNode_ = document.createTextNode(this.formattedTime_ || '0:00');
this.contentEl_.appendChild(this.textNode_);
}
/**
* Generates a formatted time for this component to use in display.
*
* @param {number} time
* A numeric time, in seconds.
*
* @return {string}
* A formatted time
*
* @private
*/
formatTime_(time) {
return formatTime(time);
}
/**
* Updates the time display text node if it has what was passed in changed
* the formatted time.
*
* @param {number} time
* The time to update to
*
* @private
*/
updateFormattedTime_(time) {
const formattedTime = this.formatTime_(time);
if (formattedTime === this.formattedTime_) {
return;
}
this.formattedTime_ = formattedTime;
this.requestAnimationFrame(this.updateTextNode_);
}
/**
* To be filled out in the child class, should update the displayed time
* in accordance with the fact that the current time has changed.
*
* @param {EventTarget~Event} [event]
* The `timeupdate` event that caused this to run.
*
* @listens Player#timeupdate
*/
updateContent(event) {}
}
/**
* The text that should display over the `TimeDisplay`s controls. Added to for localization.
*
* @type {string}
* @private
*/
TimeDisplay.prototype.controlText_ = 'Time';
Component.registerComponent('TimeDisplay', TimeDisplay);
export default TimeDisplay;
+7 -2
Ver Arquivo
@@ -8,6 +8,7 @@ import * as Dom from '../utils/dom.js';
import * as Fn from '../utils/fn.js';
import * as Events from '../utils/events.js';
import toTitleCase from '../utils/to-title-case.js';
import { IS_IOS } from '../utils/browser.js';
import document from 'global/document';
/**
@@ -339,8 +340,12 @@ class MenuButton extends Component {
this.buttonPressed_ = true;
this.menu.lockShowing();
this.menuButton_.el_.setAttribute('aria-expanded', 'true');
// set the focus into the submenu
this.menu.focus();
// set the focus into the submenu, except on iOS where it is resulting in
// undesired scrolling behavior when the player is in an iframe
if (!IS_IOS && !Dom.isInFrame()) {
this.menu.focus();
}
}
}
+186
Ver Arquivo
@@ -458,6 +458,8 @@ class Player extends Component {
this.on('stageclick', this.handleStageClick_);
this.changingSrc_ = false;
this.watchForSourceSet_();
}
/**
@@ -822,6 +824,179 @@ class Player extends Component {
`);
}
/**
* Modify the video/audio element so that we can detect when
* the source is changed. Fires `sourceset` just after the source has changed
*
* @fires Player#sourceset
*/
watchForSourceSet_() {
// if we cannot overwrite the src property, there is no support
// iOS 7 safari for instance cannot do this.
try {
const el = document.createElement('video');
Object.defineProperty(el, 'src', {
get() {},
set() {}
});
} catch (e) {
return;
}
const el = this.el().getElementsByTagName('video')[0] || this.el().getElementsByTagName('audio')[0];
if (!el) {
if (!this.isReady_) {
this.ready(() => this.watchForSourceSet_());
}
return;
}
// we need to fire sourceset when the player is ready
// if we find that the media element had a src when it was
// given to us
if (this.tagAttributes && this.tagAttributes.src) {
this.ready(() => {
this.trigger('sourceset');
});
}
const proto = window.HTMLMediaElement.prototype;
const srcDescriptor = {
get() {
return proto.getAttribute.call(this, 'src');
},
set(v) {
return proto.setAttribute.call(this, 'src', v);
},
enumerable: true
};
// preserve getters/setters already on `el.src` if they exist
if (Object.getOwnPropertyDescriptor(el, 'src')) {
Object.assign(srcDescriptor, Object.getOwnPropertyDescriptor(el, 'src'));
} else if (Object.getOwnPropertyDescriptor(proto, 'src')) {
Object.assign(srcDescriptor, Object.getOwnPropertyDescriptor(proto, 'src'));
}
Object.defineProperty(el, 'src', {
get: srcDescriptor.get.bind(el),
set: (v) => {
const retval = srcDescriptor.set.call(el, v);
this.ready(() => this.trigger('sourceset'), true);
return retval;
},
configurable: true,
enumerable: srcDescriptor.enumerable
});
const oldSetAttribute = el.setAttribute;
el.setAttribute = (n, v) => {
const retval = oldSetAttribute.call(el, n, v);
if (n === 'src') {
this.ready(() => this.trigger('sourceset'), true);
}
return retval;
};
const oldLoad = el.load;
el.load = () => {
const retval = oldLoad.call(el);
this.ready(() => this.trigger('sourceset'), true);
return retval;
};
this.on('dispose', () => {
if (!el) {
return;
}
el.load = oldLoad.bind(el);
el.setAttribute = oldSetAttribute.bind(el);
Object.defineProperty(el, 'src', srcDescriptor);
});
// observer video tag changes, we do this when tech loads on iOS
this.on('sourceset', this.handleSourceSet_);
}
/**
* Update the internal source cache based on the `sourceset` event.
*
* @param {EventTarget~Event} e
* The sourceset event that triggered this function to run.
*/
handleSourceSet_(e) {
const el = this.el().getElementsByTagName('video')[0] || this.el().getElementsByTagName('audio')[0];
if (!el) {
return;
}
// get what the video element thinks the source is
let src = el.src;
let sources = Array.prototype.map.call(el.getElementsByTagName('source'), (sourceEl) => {
return {src: sourceEl.src, type: sourceEl.type};
});
if (!sources.length && !src) {
return;
}
if (el.getAttribute('src') === null) {
src = null;
}
if (src) {
let type;
if (sources && !type) {
// get the source type from our sources list
for (let i = 0; i < sources.length; i++) {
if (sources[i].src === src) {
type = sources[i].type;
break;
}
}
}
if (!type) {
type = 'video/mp4';
if ((/\.mp3$/).test(src)) {
type = 'audio/mpeg';
}
}
src = {src, type};
}
if (!src && sources.length) {
src = sources[0];
} else if (src && !sources.length) {
sources = [src];
}
if (!this.cache_.src || this.cache_.src !== src.src) {
this.cache_.src = src.src;
}
if (!this.cache_.source || this.cache_.source.src !== src.src) {
this.cache_.source = src;
this.cache_.sources = sources;
}
}
/**
* Load/Create an instance of playback {@link Tech} including element
* and API methods. Then append the `Tech` element in `Player` as a child.
@@ -1787,6 +1962,17 @@ class Player extends Component {
return this.duration() - this.currentTime();
}
/**
* A remaining time function that is intented to be used when
* the time is to be displayed directly to the user.
*
* @return {number}
* The rounded time remaining in seconds
*/
remainingTimeDisplay() {
return Math.floor(this.duration()) - Math.floor(this.currentTime());
}
//
// Kind of like an array of portions of the video that have been downloaded.
+17
Ver Arquivo
@@ -85,6 +85,23 @@ export function isEl(value) {
return isObject(value) && value.nodeType === 1;
}
/**
* Determines if the current DOM is embedded in an iframe.
*
* @return {boolean}
*
*/
export function isInFrame() {
// We need a try/catch here because Safari will throw errors when attempting
// to get either `parent` or `self`
try {
return window.parent !== window.self;
} catch (x) {
return true;
}
}
/**
* Creates functions to query the DOM using a given method.
*
+497
Ver Arquivo
@@ -0,0 +1,497 @@
/* eslint-env qunit */
import videojs from '../../src/js/video.js';
import document from 'global/document';
import window from 'global/window';
const wait = 100;
let qunitFn = 'module';
// if we cannot overwrite the src property, there is no support
try {
const el = document.createElement('video');
Object.defineProperty(el, 'src', {
get() {},
set() {}
});
} catch (e) {
qunitFn = 'skip';
}
const Html5 = videojs.getTech('Html5');
const oldMovingMedia = Html5.prototype.movingMediaElementInDOM;
const validateSource = function(assert, player, sources, checkMediaElSource = true) {
const tech = player.tech_;
const mediaEl = tech.el();
assert.deepEqual(player.currentSource(), sources[0], 'currentSource is correct');
assert.equal(player.src(), sources[0].src, 'src is correct');
assert.deepEqual(player.currentSources(), sources, 'currentSources is correct');
// when we are dealing with <source> elements mediaEl.src will be null
if (checkMediaElSource) {
assert.equal(mediaEl.src, sources[0].src, 'mediaEl.src is correct');
assert.equal(mediaEl.getAttribute('src'), sources[0].src, 'mediaEl attribute is correct');
assert.equal(tech.src(), sources[0].src, 'tech is correct');
}
};
QUnit[qunitFn]('sourceset', function(hooks) {
['video el', 'change video el', 'audio el', 'change audio el'].forEach((testName) => {
QUnit.module(`source before player - ${testName}`, {
beforeEach() {
if (testName === 'change video el' || testName === 'change audio el') {
Html5.prototype.movingMediaElementInDOM = false;
}
this.hook = (player) => player.on('sourceset', () => this.sourcesets++);
videojs.hook('setup', this.hook);
this.sourcesets = 0;
this.fixture = document.getElementById('qunit-fixture');
if ((/audio/i).test(testName)) {
this.mediaEl = document.createElement('audio');
this.testSrc = {
src: 'http://vjs.zencdn.net/v/oceans.mp3',
type: 'audio/mpeg'
};
} else {
this.mediaEl = document.createElement('video');
this.testSrc = {
src: 'http://vjs.zencdn.net/v/oceans.mp4',
type: 'video/mp4'
};
}
this.sourceOne = {src: 'http://example.com/one.mp4', type: 'video/mp4'};
this.sourceTwo = {src: 'http://example.com/two.mp4', type: 'video/mp4'};
if ((/audio/).test(testName)) {
this.sourceOne = {src: 'http://example.com/one.mp3', type: 'audio/mpeg'};
this.sourceTwo = {src: 'http://example.com/two.mp3', type: 'audio/mpeg'};
}
this.mediaEl.className = 'video-js';
this.fixture.appendChild(this.mediaEl);
},
afterEach(assert) {
const done = assert.async();
// reset sourceset to 0 so we can track if more happen
this.sourcesets = 0;
window.setTimeout(() => {
assert.equal(this.sourcesets, 0, 'no additional sourcesets');
this.player.dispose();
assert.equal(this.sourcesets, 0, 'no source set on dispose');
videojs.removeHook('setup', this.hook);
Html5.prototype.movingMediaElementInDOM = oldMovingMedia;
done();
}, wait);
}
});
QUnit.test('data-setup x1', function(assert) {
const done = assert.async();
this.mediaEl.setAttribute('data-setup', JSON.stringify({sources: [this.testSrc]}));
this.player = videojs(this.mediaEl);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.testSrc]);
done();
});
});
QUnit.test('data-setup, x2', function(assert) {
const done = assert.async();
this.mediaEl.setAttribute('data-setup', JSON.stringify({sources: [this.sourceOne, this.sourceTwo]}));
this.player = videojs(this.mediaEl);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne, this.sourceTwo]);
done();
});
});
QUnit.test('videojs({sources: [...]}) x1', function(assert) {
const done = assert.async();
this.player = videojs(this.mediaEl, {sources: [this.testSrc]});
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.testSrc]);
done();
});
});
QUnit.test('videojs({sources: [...]}) x2', function(assert) {
const done = assert.async();
this.player = videojs(this.mediaEl, {sources: [this.sourceOne, this.sourceTwo]});
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne, this.sourceTwo]);
done();
});
});
QUnit.test('player.src({...})', function(assert) {
const done = assert.async();
this.player = videojs(this.mediaEl);
this.player.src(this.testSrc);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.testSrc]);
done();
});
});
QUnit.test('player.src({...}) x2', function(assert) {
const done = assert.async();
this.player = videojs(this.mediaEl);
this.player.src([this.sourceOne, this.sourceTwo]);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne, this.sourceTwo]);
done();
});
});
QUnit.test('mediaEl.src = ...;', function(assert) {
const done = assert.async();
this.mediaEl.src = this.testSrc.src;
this.player = videojs(this.mediaEl);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.testSrc]);
done();
});
});
QUnit.test('mediaEl.setAttribute("src", ...)"', function(assert) {
const done = assert.async();
this.mediaEl.setAttribute('src', this.testSrc.src);
this.player = videojs(this.mediaEl);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.testSrc]);
done();
});
});
QUnit.test('<source> x1', function(assert) {
const done = assert.async();
this.source = document.createElement('source');
this.source.src = this.testSrc.src;
this.source.type = this.testSrc.type;
this.mediaEl.appendChild(this.source);
this.player = videojs(this.mediaEl);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.testSrc]);
done();
});
});
QUnit.test('<source> x2', function(assert) {
const done = assert.async();
this.source = document.createElement('source');
this.source.src = this.sourceOne.src;
this.source.type = this.sourceOne.type;
this.source2 = document.createElement('source');
this.source2.src = this.sourceTwo.src;
this.source2.type = this.sourceTwo.type;
this.mediaEl.appendChild(this.source);
this.mediaEl.appendChild(this.source2);
this.player = videojs(this.mediaEl);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne, this.sourceTwo]);
done();
});
});
QUnit.test('no source', function(assert) {
const done = assert.async();
this.player = videojs(this.mediaEl);
window.setTimeout(() => {
assert.equal(this.sourcesets, 0, 'no sourceset');
done();
}, wait);
});
QUnit.module(`source change - ${testName}`, {
beforeEach(assert) {
const done = assert.async();
if (testName === 'change video el' || testName === 'change audio el') {
Html5.prototype.movingMediaElementInDOM = false;
}
this.hook = (player) => player.on('sourceset', () => this.sourcesets++);
videojs.hook('setup', this.hook);
this.sourcesets = 0;
this.fixture = document.getElementById('qunit-fixture');
if ((/audio/i).test(testName)) {
this.mediaEl = document.createElement('audio');
this.testSrc = {
src: 'http://vjs.zencdn.net/v/oceans.mp3',
type: 'audio/mpeg'
};
} else {
this.mediaEl = document.createElement('video');
this.testSrc = {
src: 'http://vjs.zencdn.net/v/oceans.mp4',
type: 'video/mp4'
};
}
this.sourceOne = {src: 'http://example.com/one.mp4', type: 'video/mp4'};
this.sourceTwo = {src: 'http://example.com/two.mp4', type: 'video/mp4'};
if ((/audio/).test(testName)) {
this.sourceOne = {src: 'http://example.com/one.mp3', type: 'audio/mpeg'};
this.sourceTwo = {src: 'http://example.com/two.mp3', type: 'audio/mpeg'};
}
this.mediaEl.className = 'video-js';
this.mediaEl.src = this.testSrc.src;
this.fixture.appendChild(this.mediaEl);
this.player = videojs(this.mediaEl);
this.player.ready(() => {
this.mediaEl = this.player.tech_.el();
});
// intial sourceset should happen on player.ready
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.testSrc]);
done();
});
},
afterEach(assert) {
const done = assert.async();
// reset sourceset to 0 so we can track if more happen
this.sourcesets = 0;
window.setTimeout(() => {
assert.equal(this.sourcesets, 0, 'no additional sourcesets');
this.player.dispose();
assert.equal(this.sourcesets, 0, 'no source set on dispose');
videojs.removeHook('setup', this.hook);
Html5.prototype.movingMediaElementInDOM = oldMovingMedia;
done();
}, wait);
}
});
QUnit.test('player.src({...})', function(assert) {
const done = assert.async();
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.testSrc]);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne]);
done();
});
this.player.src(this.sourceOne);
});
this.player.src(this.testSrc);
});
QUnit.test('player.src({...}) x2 at the same time', function(assert) {
const done = assert.async();
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne]);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceTwo]);
done();
});
});
this.player.src(this.sourceOne);
this.player.src(this.sourceTwo);
});
QUnit.test('mediaEl.src = ...', function(assert) {
const done = assert.async();
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.testSrc]);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne]);
done();
});
this.mediaEl.src = this.sourceOne.src;
});
this.mediaEl.src = this.testSrc.src;
});
QUnit.test('mediaEl.src = ... x2 at the same time', function(assert) {
const done = assert.async();
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne]);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceTwo]);
done();
});
});
this.mediaEl.src = this.sourceOne.src;
this.mediaEl.src = this.sourceTwo.src;
});
QUnit.test('mediaEl.setAttribute("src", ...)', function(assert) {
const done = assert.async();
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.testSrc]);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne]);
done();
});
this.mediaEl.setAttribute('src', this.sourceOne.src);
});
this.mediaEl.setAttribute('src', this.testSrc.src);
});
QUnit.test('mediaEl.setAttribute("src", ...) x2 at the same time', function(assert) {
const done = assert.async();
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne]);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceTwo]);
done();
});
});
this.mediaEl.setAttribute('src', this.sourceOne.src);
this.mediaEl.setAttribute('src', this.sourceTwo.src);
});
QUnit.test('mediaEl.load()', function(assert) {
const done = assert.async();
const source = document.createElement('source');
source.src = this.testSrc.src;
source.type = this.testSrc.type;
// the only way to unset a source, so that we use the source
// elements instead
this.mediaEl.removeAttribute('src');
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.testSrc], false);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne], false);
done();
});
source.src = this.sourceOne.src;
source.type = this.sourceOne.type;
this.mediaEl.load();
});
this.mediaEl.appendChild(source);
this.mediaEl.load();
});
QUnit.test('mediaEl.load() x2 at the same time', function(assert) {
const done = assert.async();
const source = document.createElement('source');
source.src = this.sourceOne.src;
source.type = this.sourceOne.type;
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceOne], false);
this.player.one('sourceset', () => {
validateSource(assert, this.player, [this.sourceTwo], false);
done();
});
});
// the only way to unset a source, so that we use the source
// elements instead
this.mediaEl.removeAttribute('src');
this.mediaEl.appendChild(source);
this.mediaEl.load();
source.src = this.sourceTwo.src;
source.type = this.sourceTwo.type;
this.mediaEl.load();
});
QUnit.test('adding a <source> without load()', function(assert) {
const done = assert.async();
const source = document.createElement('source');
source.src = this.testSrc.src;
source.type = this.testSrc.type;
this.mediaEl.appendChild(source);
window.setTimeout(() => {
assert.equal(this.sourcesets, 1, 'does not trigger sourceset');
done();
}, wait);
});
QUnit.test('changing a <source>s src without load()', function(assert) {
const done = assert.async();
const source = document.createElement('source');
source.src = this.testSrc.src;
source.type = this.testSrc.type;
this.mediaEl.appendChild(source);
source.src = this.testSrc.src;
window.setTimeout(() => {
assert.equal(this.sourcesets, 1, 'does not trigger sourceset');
done();
}, wait);
});
});
});