1
0
mirror of https://github.com/videojs/video.js.git synced 2024-11-24 08:42:25 +02:00

Merge commit 'fc72fb9' into stable

This commit is contained in:
Gary Katsevman 2016-08-08 17:54:06 -04:00
commit ae788be15f
27 changed files with 556 additions and 244 deletions

View File

@ -2,6 +2,11 @@ CHANGELOG
=========
## HEAD (Unreleased)
_(none)_
--------------------
## 5.11.0 (2016-07-22)
* @BrandonOCasey Document audio/video track usage ([view](https://github.com/videojs/video.js/pull/3295))
* @hartman Correct documentation to refer to nativeTextTracks option ([view](https://github.com/videojs/video.js/pull/3309))
* @nickygerritsen Also pass tech options to canHandleSource ([view](https://github.com/videojs/video.js/pull/3303))
@ -11,8 +16,28 @@ CHANGELOG
* @hartman Add descriptions and audio button to adaptive classes ([view](https://github.com/videojs/video.js/pull/3312))
* @MattiasBuelens Retain details from tech error ([view](https://github.com/videojs/video.js/pull/3313))
* @nickygerritsen Fix test for tooltips in IE8 ([view](https://github.com/videojs/video.js/pull/3327))
--------------------
* @mboles added loadstart event to jsdoc ([view](https://github.com/videojs/video.js/pull/3370))
* @hartman added default print styling ([view](https://github.com/videojs/video.js/pull/3304))
* @ldayananda updated videojs to not do anything if no src is set ([view](https://github.com/videojs/video.js/pull/3378))
* @nickygerritsen removed unused tracks when changing sources. Fixes #3000 ([view](https://github.com/videojs/video.js/pull/3002))
* @vit-koumar updated Flash tech to return Infinity from duration instead of -1 ([view](https://github.com/videojs/video.js/pull/3128))
* @alex-phillips added ontextdata to Flash tech ([view](https://github.com/videojs/video.js/pull/2748))
* @MattiasBuelens updated components to use durationchange only ([view](https://github.com/videojs/video.js/pull/3349))
* @misteroneill improved Logging for IE < 11 ([view](https://github.com/videojs/video.js/pull/3356))
* @vdeshpande updated control text of modal dialog ([view](https://github.com/videojs/video.js/pull/3400))
* @ldayananda fixed mouse handling on menus by using mouseleave over mouseout ([view](https://github.com/videojs/video.js/pull/3404))
* @mister-ben updated language to inherit correctly and respect the attribute on the player ([view](https://github.com/videojs/video.js/pull/3426))
* @sashyro fixed nativeControlsForTouch option ([view](https://github.com/videojs/video.js/pull/3410))
* @tbasse fixed techCall null check against tech ([view](https://github.com/videojs/video.js/pull/2676))
* @rbran100 checked src and currentSrc in handleTechReady to work around mixed content issues in chrome ([view](https://github.com/videojs/video.js/pull/3287))
* @OwenEdwards fixed caption settings dialog labels for accessibility ([view](https://github.com/videojs/video.js/pull/3281))
* @OwenEdwards removed spurious head tags in the simple-embed example ([view](https://github.com/videojs/video.js/pull/3438))
* @ntadej added a null check to errorDisplay usage ([view](https://github.com/videojs/video.js/pull/3440))
* @misteroneill fixed logging issues on IE by separating fn.apply and stringify checks ([view](https://github.com/videojs/video.js/pull/3444))
* @misteroneill fixed npm test from running coveralls locally ([view](https://github.com/videojs/video.js/pull/3449))
* @gkatsev added es6-shim to tests. Fixes Flash duration test ([view](https://github.com/videojs/video.js/pull/3453))
* @misteroneill corrects test assertions for older IEs in the log module ([view](https://github.com/videojs/video.js/pull/3454))
* @gkatsev fixed setting lang by looping through loop element variable and not constant tag ([view](https://github.com/videojs/video.js/pull/3455))
## 5.10.8 (2016-08-08)
* @gkatsev re-published to make sure that the audio button has css

View File

@ -494,7 +494,8 @@ module.exports = function(grunt) {
// Default task - build and test
grunt.registerTask('default', ['test']);
grunt.registerTask('test', ['build', 'karma:defaults']);
// The test script includes coveralls only when the TRAVIS env var is set.
grunt.registerTask('test', ['build', 'karma:defaults'].concat(process.env.TRAVIS && 'coveralls').filter(Boolean));
// Run while developing
grunt.registerTask('dev', ['build', 'connect:dev', 'concurrent:watchSandbox']);

View File

@ -1,7 +1,7 @@
{
"name": "video.js",
"description": "An HTML5 and Flash video player with a common API and skin for both.",
"version": "5.10.8",
"version": "5.11.0",
"keywords": [
"videojs",
"html5",

View File

@ -3,14 +3,11 @@
<head>
<head>
<title>Video.js | HTML5 Video Player</title>
<link href="http://vjs.zencdn.net/5.0.2/video-js.css" rel="stylesheet">
<script src="http://vjs.zencdn.net/ie8/1.1.0/videojs-ie8.min.js"></script>
<script src="http://vjs.zencdn.net/5.0.2/video.js"></script>
</head>
</head>
<body>

View File

@ -32,6 +32,7 @@ A sample dictionary for Spanish `['es']` would look as follows:
"Captions": "Subtítulos especiales",
"captions off": "Subtítulos especiales desactivados",
"Chapters": "Capítulos",
"Close Modal Dialog": "Cerca de diálogo modal",
"You aborted the video playback": "Ha interrumpido la reproducción del vídeo.",
"A network error caused the video download to fail part-way.": "Un error de red ha interrumpido la descarga del vídeo.",
"The video could not be loaded, either because the server or network failed or because the format is not supported.": "No se ha podido cargar el vídeo debido a un fallo de red o del servidor o porque el formato es incompatible.",
@ -80,6 +81,7 @@ NOTE: These need to be added after the core Video.js script.
"Captions": "Subtítulos especiales",
"captions off": "Subtítulos especiales desactivados",
"Chapters": "Capítulos",
"Close Modal Dialog": "Cerca de diálogo modal",
"You aborted the video playback": "Ha interrumpido la reproducción del vídeo.",
"A network error caused the video download to fail part-way.": "Un error de red ha interrumpido la descarga del vídeo.",
"The video could not be loaded, either because the server or network failed or because the format is not supported.": "No se ha podido cargar el vídeo debido a un fallo de red o del servidor o porque el formato es incompatible.",
@ -129,14 +131,14 @@ During a Video.js player instantiation you can force it to localize to a specifi
Determining Player Language
---------------------------
The player language is set to one of the following in descending priority
The player language is set to one of the following in descending priority:
* The language set in setup options as above
* The document language (`lang` attribute of the `html` element)
* Browser language preference
* The language specified in setup options as above
* The language specified by the closet element with a `lang` attribute. This could be the player itself or a parent element. Usually the document language is specified on the `html` tag.
* Browser language preference (the first language if more than one is configured)
* 'en'
That can be overridden after instantiation with `language('fr')`.
The player language can be change after instantiation with `language('fr')`. However localizable text will not be modified by doing this, for best results set the language beforehand.
Language selection
------------------

View File

@ -18,6 +18,7 @@
"Captions": "Captions",
"captions off": "captions off",
"Chapters": "Chapters",
"Close Modal Dialog": "Close Modal Dialog",
"Descriptions": "Descriptions",
"descriptions off": "descriptions off",
"You aborted the media playback": "You aborted the media playback",

View File

@ -1,7 +1,7 @@
{
"name": "video.js",
"description": "An HTML5 and Flash video player with a common API and skin for both.",
"version": "5.10.8",
"version": "5.11.0",
"copyright": "Copyright Brightcove, Inc. <https://www.brightcove.com/>",
"license": "Apache-2.0",
"keywords": [
@ -14,7 +14,7 @@
"homepage": "http://videojs.com",
"author": "Steve Heffernan",
"scripts": {
"test": "grunt test && if [ '$TRAVIS' ]; then grunt coveralls; fi;"
"test": "grunt test"
},
"repository": {
"type": "git",
@ -30,7 +30,7 @@
"tsml": "1.0.1",
"videojs-font": "2.0.0",
"videojs-ie8": "1.1.2",
"videojs-swf": "5.0.1",
"videojs-swf": "5.1.0",
"videojs-vtt.js": "0.12.1",
"xhr": "2.2.0"
},
@ -44,6 +44,7 @@
"chg": "^0.3.2",
"css": "^2.2.0",
"es5-shim": "^4.1.3",
"es6-shim": "^0.35.1",
"gkatsev-grunt-sass": "^1.1.1",
"grunt": "^0.4.4",
"grunt-aws-s3": "^0.12.1",

5
src/css/_print.scss Normal file
View File

@ -0,0 +1,5 @@
@media print {
.video-js > *:not(.vjs-tech):not(.vjs-poster) {
visibility:hidden;
}
}

View File

@ -6,14 +6,14 @@
color: $primary-foreground-color;
margin: 0 auto;
padding: 0.5em;
height: 15em;
height: 16em;
font-size: 12px;
width: 40em;
}
.vjs-caption-settings .vjs-tracksettings {
top: 0;
bottom: 2em;
bottom: 1em;
left: 0;
right: 0;
position: absolute;
@ -40,8 +40,9 @@
margin: 5px;
padding: 3px;
min-height: 40px;
border: none;
}
.vjs-caption-settings .vjs-tracksetting label {
.vjs-caption-settings .vjs-tracksetting label, legend {
display: block;
width: 100px;
margin-bottom: 5px;
@ -50,6 +51,8 @@
.vjs-caption-settings .vjs-tracksetting span {
display: inline;
margin-left: 5px;
vertical-align: top;
float: right;
}
.vjs-caption-settings .vjs-tracksetting > div {
@ -67,6 +70,23 @@
margin-right: 10px;
}
.vjs-caption-settings fieldset {
margin-top: 1em;
margin-left: .5em;
}
// Hide labels within fieldsets, so they are only for screen reader users
.vjs-caption-settings fieldset .vjs-label {
position: absolute;
clip: rect(1px 1px 1px 1px); /* for Internet Explorer */
clip: rect(1px, 1px, 1px, 1px);
padding: 0;
border: 0;
height: 1px;
width: 1px;
overflow: hidden;
}
.vjs-caption-settings input[type="button"] {
width: 40px;
height: 40px;

View File

@ -39,3 +39,5 @@
@import "components/adaptive";
@import "components/captions-settings";
@import "components/modal-dialog";
@import "print";

View File

@ -18,13 +18,7 @@ class DurationDisplay extends Component {
constructor(player, options){
super(player, options);
// this might need to be changed to 'durationchange' instead of 'timeupdate' eventually,
// however the durationchange event fires before this.player_.duration() is set,
// so the value cannot be written out using this method.
// Once the order of durationchange and this.player_.duration() being set is figured out,
// this can be updated.
this.on(player, 'timeupdate', this.updateContent);
this.on(player, 'loadedmetadata', this.updateContent);
this.on(player, 'durationchange', this.updateContent);
}
/**

View File

@ -19,6 +19,7 @@ class RemainingTimeDisplay extends Component {
super(player, options);
this.on(player, 'timeupdate', this.updateContent);
this.on(player, 'durationchange', this.updateContent);
}
/**

View File

@ -141,8 +141,8 @@ class MenuButton extends ClickableComponent {
* @method handleClick
*/
handleClick() {
this.one('mouseout', Fn.bind(this, function(){
this.menu.unlockShowing();
this.one(this.menu.contentEl(), 'mouseleave', Fn.bind(this, function(e){
this.unpressButton();
this.el_.blur();
}));
if (this.buttonPressed_){

View File

@ -255,7 +255,7 @@ class ModalDialog extends Component {
// content element, so temporarily change the content element.
let temp = this.contentEl_;
this.contentEl_ = this.el_;
close = this.addChild('closeButton');
close = this.addChild('closeButton', {controlText: 'Close Modal Dialog'});
this.contentEl_ = temp;
this.on(close, 'close', this.close);
}

View File

@ -92,6 +92,25 @@ class Player extends Component {
// see enableTouchActivity in Component
options.reportTouchActivity = false;
// If language is not set, get the closest lang attribute
if (!options.language) {
if (typeof tag.closest === 'function') {
let closest = tag.closest('[lang]');
if (closest) {
options.language = closest.getAttribute('lang');
}
} else {
let element = tag;
while (element && element.nodeType === 1) {
if (Dom.getElAttributes(element).hasOwnProperty('lang')) {
options.language = element.getAttribute('lang');
break;
}
element = element.parentNode;
}
}
}
// Run base component initializing with new options
super(null, options, ready);
@ -624,6 +643,7 @@ class Player extends Component {
this.on(this.tech_, 'texttrackchange', this.handleTechTextTrackChange_);
this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
this.on(this.tech_, 'textdata', this.handleTechTextData_);
this.usingNativeControls(this.techGet_('controls'));
@ -772,7 +792,7 @@ class Player extends Component {
// In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
// In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
// This fixes both issues. Need to wait for API, so it updates displays correctly
if (this.src() && this.tag && this.options_.autoplay && this.paused()) {
if ((this.src() || this.currentSrc()) && this.tag && this.options_.autoplay && this.paused()) {
try {
delete this.tag.poster; // Chrome Fix. Fixed in Chrome v16.
}
@ -1160,6 +1180,14 @@ class Player extends Component {
this.trigger('loadedmetadata');
}
handleTechTextData_() {
var data = null;
if (arguments.length > 1) {
data = arguments[1];
}
this.trigger('textdata', data);
}
/**
* Fires when the browser has loaded the current frame of the audio/video
*
@ -1238,7 +1266,7 @@ class Player extends Component {
// Otherwise call method now
} else {
try {
this.tech_[method](arg);
this.tech_ && this.tech_[method](arg);
} catch(e) {
log(e);
throw e;
@ -1292,7 +1320,15 @@ class Player extends Component {
* @method play
*/
play() {
this.techCall_('play');
// Only calls the tech's play if we already have a src loaded
if (this.src() || this.currentSrc()) {
this.techCall_('play');
} else {
this.tech_.one('loadstart', function() {
this.play();
});
}
return this;
}
@ -2234,7 +2270,9 @@ class Player extends Component {
if (err === null) {
this.error_ = err;
this.removeClass('vjs-error');
this.errorDisplay.close();
if (this.errorDisplay) {
this.errorDisplay.close();
}
return this;
}
@ -2845,7 +2883,7 @@ Player.prototype.options_ = {
'textTrackSettings'
],
language: document.getElementsByTagName('html')[0].getAttribute('lang') || navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language || 'en',
language: navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language || 'en',
// locales and their language translations
languages: {},
@ -2854,6 +2892,13 @@ Player.prototype.options_ = {
notSupportedMessage: 'No compatible source was found for this media.'
};
/**
* Fired when the user agent begins looking for media data
*
* @event loadstart
*/
Player.prototype.handleTechLoadStart_;
/**
* Fired when the player has initial duration and dimension information
*
@ -2861,6 +2906,13 @@ Player.prototype.options_ = {
*/
Player.prototype.handleLoadedMetaData_;
/**
* Fired when the player receives text data
*
* @event textdata
*/
Player.prototype.handleTextData_;
/**
* Fired when the player has downloaded data at the current playback position
*

View File

@ -229,6 +229,20 @@ class Flash extends Tech {
}
}
/**
* Get media duration
*
* @returns {Number} Media duration
*/
duration() {
if (this.readyState() === 0) {
return NaN;
} else {
let duration = this.el_.vjs_getProperty('duration');
return duration >= 0 ? duration : Infinity;
}
}
/**
* Load media into player
*
@ -312,7 +326,7 @@ class Flash extends Tech {
// Create setters and getters for attributes
const _api = Flash.prototype;
const _readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(',');
const _readOnly = 'networkState,readyState,initialTime,duration,startOffsetTime,paused,ended,videoWidth,videoHeight'.split(',');
const _readOnly = 'networkState,readyState,initialTime,startOffsetTime,paused,ended,videoWidth,videoHeight'.split(',');
function _createSetter(attr){
var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
@ -457,7 +471,7 @@ Flash.checkReady = function(tech){
// Trigger events from the swf on the player
Flash.onEvent = function(swfID, eventName){
let tech = Dom.getEl(swfID).tech;
tech.trigger(eventName);
tech.trigger(eventName, Array.prototype.slice.call(arguments, 2));
};
// Log errors from the swf

View File

@ -94,6 +94,9 @@ class Html5 extends Tech {
tl.addEventListener('change', Fn.bind(this, this[`handle${capitalType}TrackChange_`]));
tl.addEventListener('addtrack', Fn.bind(this, this[`handle${capitalType}TrackAdd_`]));
tl.addEventListener('removetrack', Fn.bind(this, this[`handle${capitalType}TrackRemove_`]));
// Remove (native) trackts that are not used anymore
this.on('loadstart', this[`removeOld${capitalType}Tracks_`]);
}
});
@ -113,9 +116,8 @@ class Html5 extends Tech {
// 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
// controls on touch enabled laptops like the Chrome Pixel
if (browser.TOUCH_ENABLED && options.nativeControlsForTouch === true ||
browser.IS_IPHONE ||
browser.IS_NATIVE_ANDROID) {
if ((browser.TOUCH_ENABLED || browser.IS_IPHONE ||
browser.IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
this.setControls(true);
}
@ -138,6 +140,11 @@ class Html5 extends Tech {
tl.removeEventListener('addtrack', this[`handle${capitalType}TrackAdd_`]);
tl.removeEventListener('removetrack', this[`handle${capitalType}TrackRemove_`]);
}
// Stop removing old text tracks
if (tl) {
this.off('loadstart', this[`removeOld${capitalType}Tracks_`]);
}
});
Html5.disposeMediaElement(this.el_);
@ -296,6 +303,9 @@ class Html5 extends Tech {
tt.addEventListener('addtrack', this.handleTextTrackAdd_);
tt.addEventListener('removetrack', this.handleTextTrackRemove_);
}
// Remove (native) texttracks that are not used anymore
this.on('loadstart', this.removeOldTextTracks_);
}
}
@ -353,6 +363,60 @@ class Html5 extends Tech {
this.audioTracks().removeTrack_(e.track);
}
/**
* This is a helper function that is used in removeOldTextTracks_, removeOldAudioTracks_ and
* removeOldVideoTracks_
* @param {Track[]} techTracks Tracks for this tech
* @param {Track[]} elTracks Tracks for the HTML5 video element
* @private
*/
removeOldTracks_(techTracks, elTracks) {
// This will loop over the techTracks and check if they are still used by the HTML5 video element
// If not, they will be removed from the emulated list
let removeTracks = [];
if (!elTracks) {
return;
}
for (let i = 0; i < techTracks.length; i++) {
let techTrack = techTracks[i];
let found = false;
for (let j = 0; j < elTracks.length; j++) {
if (elTracks[j] === techTrack) {
found = true;
break;
}
}
if (!found) {
removeTracks.push(techTrack);
}
}
for (let i = 0; i < removeTracks.length; i++) {
const track = removeTracks[i];
techTracks.removeTrack_(track);
}
}
removeOldTextTracks_() {
const techTracks = this.textTracks();
const elTracks = this.el().textTracks;
this.removeOldTracks_(techTracks, elTracks);
}
removeOldAudioTracks_() {
const techTracks = this.audioTracks();
const elTracks = this.el().audioTracks;
this.removeOldTracks_(techTracks, elTracks);
}
removeOldVideoTracks_() {
const techTracks = this.videoTracks();
const elTracks = this.el().videoTracks;
this.removeOldTracks_(techTracks, elTracks);
}
/**
* Play for html5 tech

View File

@ -67,9 +67,18 @@ class TextTrackSettings extends Component {
* @method createEl
*/
createEl() {
let uniqueId = this.id_;
let dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId;
let dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId;
return super.createEl('div', {
className: 'vjs-caption-settings vjs-modal-overlay',
innerHTML: captionOptionsMenuTemplate()
innerHTML: captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId),
tabIndex: -1
}, {
role: 'dialog',
'aria-labelledby': dialogLabelId,
'aria-describedby': dialogDescriptionId
});
}
@ -241,14 +250,19 @@ function setSelectedOption(target, value) {
target.selectedIndex = i;
}
function captionOptionsMenuTemplate() {
let template = `<div class="vjs-tracksettings">
<div class="vjs-tracksettings-colors">
<div class="vjs-fg-color vjs-tracksetting">
<label class="vjs-label">Foreground</label>
<select>
<option value="">---</option>
<option value="#FFF">White</option>
function captionOptionsMenuTemplate(uniqueId, dialogLabelId, dialogDescriptionId) {
let template = `
<div role="document">
<div role="heading" aria-level="1" id="${dialogLabelId}" class="vjs-control-text">Captions Settings Dialog</div>
<div id="${dialogDescriptionId}" class="vjs-control-text">Beginning of dialog window. Escape will cancel and close the window.</div>
<div class="vjs-tracksettings">
<div class="vjs-tracksettings-colors">
<fieldset class="vjs-fg-color vjs-tracksetting">
<legend>Text</legend>
<label class="vjs-label" for="captions-foreground-color-${uniqueId}">Color</label>
<select id="captions-foreground-color-${uniqueId}">
<option value="#FFF" selected>White</option>
<option value="#000">Black</option>
<option value="#F00">Red</option>
<option value="#0F0">Green</option>
@ -258,19 +272,19 @@ function captionOptionsMenuTemplate() {
<option value="#0FF">Cyan</option>
</select>
<span class="vjs-text-opacity vjs-opacity">
<select>
<option value="">---</option>
<option value="1">Opaque</option>
<label class="vjs-label" for="captions-foreground-opacity-${uniqueId}">Transparency</label>
<select id="captions-foreground-opacity-${uniqueId}">
<option value="1" selected>Opaque</option>
<option value="0.5">Semi-Opaque</option>
</select>
</span>
</div> <!-- vjs-fg-color -->
<div class="vjs-bg-color vjs-tracksetting">
<label class="vjs-label">Background</label>
<select>
<option value="">---</option>
</fieldset>
<fieldset class="vjs-bg-color vjs-tracksetting">
<legend>Background</legend>
<label class="vjs-label" for="captions-background-color-${uniqueId}">Color</label>
<select id="captions-background-color-${uniqueId}">
<option value="#000" selected>Black</option>
<option value="#FFF">White</option>
<option value="#000">Black</option>
<option value="#F00">Red</option>
<option value="#0F0">Green</option>
<option value="#00F">Blue</option>
@ -279,20 +293,20 @@ function captionOptionsMenuTemplate() {
<option value="#0FF">Cyan</option>
</select>
<span class="vjs-bg-opacity vjs-opacity">
<select>
<option value="">---</option>
<option value="1">Opaque</option>
<option value="0.5">Semi-Transparent</option>
<option value="0">Transparent</option>
</select>
<label class="vjs-label" for="captions-background-opacity-${uniqueId}">Transparency</label>
<select id="captions-background-opacity-${uniqueId}">
<option value="1" selected>Opaque</option>
<option value="0.5">Semi-Transparent</option>
<option value="0">Transparent</option>
</select>
</span>
</div> <!-- vjs-bg-color -->
<div class="window-color vjs-tracksetting">
<label class="vjs-label">Window</label>
<select>
<option value="">---</option>
</fieldset>
<fieldset class="window-color vjs-tracksetting">
<legend>Window</legend>
<label class="vjs-label" for="captions-window-color-${uniqueId}">Color</label>
<select id="captions-window-color-${uniqueId}">
<option value="#000" selected>Black</option>
<option value="#FFF">White</option>
<option value="#000">Black</option>
<option value="#F00">Red</option>
<option value="#0F0">Green</option>
<option value="#00F">Blue</option>
@ -301,59 +315,59 @@ function captionOptionsMenuTemplate() {
<option value="#0FF">Cyan</option>
</select>
<span class="vjs-window-opacity vjs-opacity">
<select>
<option value="">---</option>
<option value="1">Opaque</option>
<option value="0.5">Semi-Transparent</option>
<option value="0">Transparent</option>
</select>
<label class="vjs-label" for="captions-window-opacity-${uniqueId}">Transparency</label>
<select id="captions-window-opacity-${uniqueId}">
<option value="0" selected>Transparent</option>
<option value="0.5">Semi-Transparent</option>
<option value="1">Opaque</option>
</select>
</span>
</div> <!-- vjs-window-color -->
</fieldset>
</div> <!-- vjs-tracksettings-colors -->
<div class="vjs-tracksettings-font">
<div class="vjs-font-percent vjs-tracksetting">
<label class="vjs-label" for="captions-font-size-${uniqueId}">Font Size</label>
<select id="captions-font-size-${uniqueId}">
<option value="0.50">50%</option>
<option value="0.75">75%</option>
<option value="1.00" selected>100%</option>
<option value="1.25">125%</option>
<option value="1.50">150%</option>
<option value="1.75">175%</option>
<option value="2.00">200%</option>
<option value="3.00">300%</option>
<option value="4.00">400%</option>
</select>
</div>
<div class="vjs-edge-style vjs-tracksetting">
<label class="vjs-label" for="captions-edge-style-${uniqueId}">Text Edge Style</label>
<select id="captions-edge-style-${uniqueId}">
<option value="none" selected>None</option>
<option value="raised">Raised</option>
<option value="depressed">Depressed</option>
<option value="uniform">Uniform</option>
<option value="dropshadow">Dropshadow</option>
</select>
</div>
<div class="vjs-font-family vjs-tracksetting">
<label class="vjs-label" for="captions-font-family-${uniqueId}">Font Family</label>
<select id="captions-font-family-${uniqueId}">
<option value="proportionalSansSerif" selected>Proportional Sans-Serif</option>
<option value="monospaceSansSerif">Monospace Sans-Serif</option>
<option value="proportionalSerif">Proportional Serif</option>
<option value="monospaceSerif">Monospace Serif</option>
<option value="casual">Casual</option>
<option value="script">Script</option>
<option value="small-caps">Small Caps</option>
</select>
</div>
</div> <!-- vjs-tracksettings-font -->
<div class="vjs-tracksettings-controls">
<button class="vjs-default-button">Defaults</button>
<button class="vjs-done-button">Done</button>
</div>
</div> <!-- vjs-tracksettings -->
<div class="vjs-tracksettings-font">
<div class="vjs-font-percent vjs-tracksetting">
<label class="vjs-label">Font Size</label>
<select>
<option value="0.50">50%</option>
<option value="0.75">75%</option>
<option value="1.00" selected>100%</option>
<option value="1.25">125%</option>
<option value="1.50">150%</option>
<option value="1.75">175%</option>
<option value="2.00">200%</option>
<option value="3.00">300%</option>
<option value="4.00">400%</option>
</select>
</div> <!-- vjs-font-percent -->
<div class="vjs-edge-style vjs-tracksetting">
<label class="vjs-label">Text Edge Style</label>
<select>
<option value="none">None</option>
<option value="raised">Raised</option>
<option value="depressed">Depressed</option>
<option value="uniform">Uniform</option>
<option value="dropshadow">Dropshadow</option>
</select>
</div> <!-- vjs-edge-style -->
<div class="vjs-font-family vjs-tracksetting">
<label class="vjs-label">Font Family</label>
<select>
<option value="">Default</option>
<option value="monospaceSerif">Monospace Serif</option>
<option value="proportionalSerif">Proportional Serif</option>
<option value="monospaceSansSerif">Monospace Sans-Serif</option>
<option value="proportionalSansSerif">Proportional Sans-Serif</option>
<option value="casual">Casual</option>
<option value="script">Script</option>
<option value="small-caps">Small Caps</option>
</select>
</div> <!-- vjs-font-family -->
</div>
</div>
<div class="vjs-tracksettings-controls">
<button class="vjs-default-button">Defaults</button>
<button class="vjs-done-button">Done</button>
</div>`;
</div> <!-- role="document" -->`;
return template;
}

View File

@ -60,6 +60,9 @@ 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 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 TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
export const BACKGROUND_SIZE_SUPPORTED = 'backgroundSize' in document.createElement('video').style;

View File

@ -2,78 +2,96 @@
* @file log.js
*/
import window from 'global/window';
import {IE_VERSION} from './browser';
/**
* Log plain debug messages
* Log messages to the console and history based on the type of message
*
* @param {String} type
* The name of the console method to use.
* @param {Array} args
* The arguments to be passed to the matching console method.
* @param {Boolean} [stringify]
* By default, only old IEs should get console argument stringification,
* but this is exposed as a parameter to facilitate testing.
*/
const log = function(){
_logType(null, arguments);
export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 11) => {
const console = window.console;
// If there's no console then don't try to output messages, but they will
// still be stored in `log.history`.
//
// Was setting these once outside of this function, but containing them
// in the function makes it easier to test cases where console doesn't exist
// when the module is executed.
const fn = console && console[type] || function(){};
if (type !== 'log') {
// add the type to the front of the message when it's not "log"
args.unshift(type.toUpperCase() + ':');
}
// add to history
log.history.push(args);
// add console prefix after adding to history
args.unshift('VIDEOJS:');
// IEs previous to 11 log objects uselessly as "[object Object]"; so, JSONify
// objects and arrays for those less-capable browsers.
if (stringify) {
args = args.map(a => {
if (a && typeof a === 'object' || Array.isArray(a)) {
try {
return JSON.stringify(a);
} catch (x) {}
}
// Cast to string before joining, so we get null and undefined explicitly
// included in output (as we would in a modern console).
return String(a);
}).join(' ');
}
// Old IE versions do not allow .apply() for console methods (they are
// reported as objects rather than functions).
if (!fn.apply) {
fn(args);
} else {
fn[Array.isArray(args) ? 'apply' : 'call'](console, args);
}
};
/**
* Log plain debug messages
*
* @function log
*/
function log(...args) {
logByType('log', args);
}
/**
* Keep a history of log messages
*
* @type {Array}
*/
log.history = [];
/**
* Log error messages
*
* @method error
*/
log.error = function(){
_logType('error', arguments);
};
log.error = (...args) => logByType('error', args);
/**
* Log warning messages
*/
log.warn = function(){
_logType('warn', arguments);
};
/**
* Log messages to the console and history based on the type of message
*
* @param {String} type The type of message, or `null` for `log`
* @param {Object} args The args to be passed to the log
* @private
* @method _logType
* @method warn
*/
function _logType(type, args){
// convert args to an array to get array functions
let argsArray = Array.prototype.slice.call(args);
// if there's no console then don't try to output messages
// they will still be stored in log.history
// Was setting these once outside of this function, but containing them
// in the function makes it easier to test cases where console doesn't exist
let noop = function(){};
log.warn = (...args) => logByType('warn', args);
let console = window['console'] || {
'log': noop,
'warn': noop,
'error': noop
};
if (type) {
// add the type to the front of the message
argsArray.unshift(type.toUpperCase()+':');
} else {
// default to log with no prefix
type = 'log';
}
// add to history
log.history.push(argsArray);
// add console prefix after adding to history
argsArray.unshift('VIDEOJS:');
// call appropriate log function
if (console[type].apply) {
console[type].apply(console, argsArray);
} else {
// ie8 doesn't allow error.apply, but it will just join() the array anyway
console[type](argsArray.join(' '));
}
}
export default log;

View File

@ -1,3 +1,4 @@
import 'es6-shim';
import document from 'global/document';
import window from 'global/window';
import sinon from 'sinon';

View File

@ -247,7 +247,7 @@ test('should hide the poster when play is called', function() {
});
equal(player.hasStarted(), false, 'the show poster flag is true before play');
player.play();
player.tech_.trigger('play');
equal(player.hasStarted(), true, 'the show poster flag is false after play');
player.tech_.trigger('loadstart');
@ -255,7 +255,7 @@ test('should hide the poster when play is called', function() {
false,
'the resource selection algorithm sets the show poster flag to true');
player.play();
player.tech_.trigger('play');
equal(player.hasStarted(), true, 'the show poster flag is false after play');
});
@ -839,6 +839,24 @@ expect(3);
strictEqual(player.localize('Good'), 'Brilliant', 'Ignored case');
});
test('inherits language from parent element', function() {
var fixture = document.getElementById('qunit-fixture');
var oldLang = fixture.getAttribute('lang');
var player;
fixture.setAttribute('lang', 'x-test');
player = TestHelpers.makePlayer();
equal(player.language(), 'x-test', 'player inherits parent element language');
player.dispose();
if (oldLang) {
fixture.setAttribute('lang', oldLang);
} else {
fixture.removeAttribute('lang');
}
});
test('should return correct values for canPlayType', function(){
var player = TestHelpers.makePlayer();

View File

@ -1,3 +1,4 @@
import {IE_VERSION} from '../../src/js/utils/browser';
import registerPlugin from '../../src/js/plugins.js';
import Player from '../../src/js/player.js';
import TestHelpers from './test-helpers.js';
@ -169,14 +170,18 @@ test('Plugins should not get events after stopImmediatePropagation is called', f
});
test('Plugin that does not exist logs an error', function() {
// stub the global log functions
var console, log, error, origConsole;
origConsole = window['console'];
console = window['console'] = {
origConsole = window.console;
console = window.console = {
log: function(){},
warn: function(){},
error: function(){}
};
log = sinon.stub(console, 'log');
error = sinon.stub(console, 'error');
@ -190,11 +195,16 @@ test('Plugin that does not exist logs an error', function() {
});
ok(error.called, 'error was called');
equal(error.firstCall.args[2], 'Unable to find plugin:');
equal(error.firstCall.args[3], 'nonExistingPlugin');
if (IE_VERSION && IE_VERSION < 11) {
equal(error.firstCall.args[0], 'VIDEOJS: ERROR: Unable to find plugin: nonExistingPlugin');
} else {
equal(error.firstCall.args[2], 'Unable to find plugin:');
equal(error.firstCall.args[3], 'nonExistingPlugin');
}
// tear down logging stubs
log.restore();
error.restore();
window['console'] = origConsole;
window.console = origConsole;
});

View File

@ -197,6 +197,33 @@ test('play after ended seeks to the beginning', function() {
equal(seeks[0], 0, 'seeked to the beginning');
});
test('duration returns NaN, Infinity or duration according to the HTML standard', function() {
let duration = Flash.prototype.duration;
let mockedDuration = -1;
let mockedReadyState = 0;
let result;
let mockFlash = {
el_: {
vjs_getProperty() {
return mockedDuration;
}
},
readyState: function() {
return mockedReadyState;
}
};
result = duration.call(mockFlash);
ok(Number.isNaN(result), 'duration returns NaN when readyState equals 0');
mockedReadyState = 1;
result = duration.call(mockFlash);
ok(!Number.isFinite(result), 'duration returns Infinity when duration property is less then 0');
mockedDuration = 1;
result = duration.call(mockFlash);
equal(result, 1, 'duration returns duration property when readeyState and duration property are both higher than 0');
});
// fake out the <object> interaction but leave all the other logic intact
class MockFlash extends Flash {
constructor() {

View File

@ -9,6 +9,16 @@ const tracks = [{
label: 'test'
}];
const defaultSettings = {
backgroundColor: '#000',
backgroundOpacity: '1',
color: '#FFF',
fontFamily: 'proportionalSansSerif',
textOpacity: '1',
windowColor: '#000',
windowOpacity: '0'
};
q.module('Text Track Settings', {
beforeEach() {
window.localStorage.clear();
@ -20,13 +30,13 @@ test('should update settings', function() {
tracks,
persistTextTrackSettings: true
});
let newSettings = {
backgroundOpacity: '1',
textOpacity: '1',
windowOpacity: '1',
const newSettings = {
backgroundOpacity: '0.5',
textOpacity: '0.5',
windowOpacity: '0.5',
edgeStyle: 'raised',
fontFamily: 'monospaceSerif',
color: '#FFF',
color: '#F00',
backgroundColor: '#FFF',
windowColor: '#FFF',
fontPercent: 1.25
@ -35,14 +45,14 @@ test('should update settings', function() {
player.textTrackSettings.setValues(newSettings);
deepEqual(player.textTrackSettings.getValues(), newSettings, 'values are updated');
equal(player.$('.vjs-fg-color > select').selectedIndex, 1, 'fg-color is set to new value');
equal(player.$('.vjs-fg-color > select').selectedIndex, 2, 'fg-color is set to new value');
equal(player.$('.vjs-bg-color > select').selectedIndex, 1, 'bg-color is set to new value');
equal(player.$('.window-color > select').selectedIndex, 1, 'window-color is set to new value');
equal(player.$('.vjs-text-opacity > select').selectedIndex, 1, 'text-opacity is set to new value');
equal(player.$('.vjs-bg-opacity > select').selectedIndex, 1, 'bg-opacity is set to new value');
equal(player.$('.vjs-window-opacity > select').selectedIndex, 1, 'window-opacity is set to new value');
equal(player.$('.vjs-edge-style select').selectedIndex, 1, 'edge-style is set to new value');
equal(player.$('.vjs-font-family select').selectedIndex, 1, 'font-family is set to new value');
equal(player.$('.vjs-font-family select').selectedIndex, 3, 'font-family is set to new value');
equal(player.$('.vjs-font-percent select').selectedIndex, 3, 'font-percent is set to new value');
Events.trigger(player.$('.vjs-done-button'), 'click');
@ -71,8 +81,9 @@ test('should restore default settings', function() {
Events.trigger(player.$('.vjs-default-button'), 'click');
Events.trigger(player.$('.vjs-done-button'), 'click');
deepEqual(player.textTrackSettings.getValues(), {}, 'values are defaulted');
deepEqual(window.localStorage.getItem('vjs-text-track-settings'), null, 'values are saved');
deepEqual(player.textTrackSettings.getValues(), defaultSettings, 'values are defaulted');
// MikeA: need to figure out how to modify saveSettings to factor in defaults are no longer null
// deepEqual(window.localStorage.getItem('vjs-text-track-settings'), defaultSettings, 'values are saved');
equal(player.$('.vjs-fg-color > select').selectedIndex, 0, 'fg-color is set to default value');
equal(player.$('.vjs-bg-color > select').selectedIndex, 0, 'bg-color is set to default value');
@ -186,13 +197,13 @@ test('do not try to restore or save settings if persist option is not set', func
test('should restore saved settings', function() {
let player;
let newSettings = {
backgroundOpacity: '1',
textOpacity: '1',
windowOpacity: '1',
const newSettings = {
backgroundOpacity: '0.5',
textOpacity: '0.5',
windowOpacity: '0.5',
edgeStyle: 'raised',
fontFamily: 'monospaceSerif',
color: '#FFF',
color: '#F00',
backgroundColor: '#FFF',
windowColor: '#FFF',
fontPercent: 1.25
@ -212,13 +223,13 @@ test('should restore saved settings', function() {
test('should not restore saved settings', function() {
let player;
let newSettings = {
backgroundOpacity: '1',
textOpacity: '1',
windowOpacity: '1',
const newSettings = {
backgroundOpacity: '0.5',
textOpacity: '0.5',
windowOpacity: '0.5',
edgeStyle: 'raised',
fontFamily: 'monospaceSerif',
color: '#FFF',
color: '#F00',
backgroundColor: '#FFF',
windowColor: '#FFF',
fontPercent: 1.25
@ -231,7 +242,7 @@ test('should not restore saved settings', function() {
persistTextTrackSettings: false
});
deepEqual(player.textTrackSettings.getValues(), {});
deepEqual(player.textTrackSettings.getValues(), defaultSettings);
player.dispose();
});

View File

@ -339,20 +339,20 @@ test('tracks are parsed once vttjs is loaded', function() {
test('stops processing if vttjs loading errored out', function() {
const clock = sinon.useFakeTimers();
const errorSpy = sinon.spy();
const oldVTT = window.WebVTT;
let parserCreated = false;
window.WebVTT = true;
// use proxyquire to stub xhr module because IE8s XDomainRequest usage
let xhrHandler;
let errorMsg;
let TextTrack = proxyquire('../../../src/js/tracks/text-track.js', {
xhr(options, fn) {
xhrHandler = fn;
},
'../utils/log.js': {
error(msg) {
errorMsg = msg;
'default': {
error: errorSpy
}
}
});
@ -376,8 +376,11 @@ test('stops processing if vttjs loading errored out', function() {
testTech.trigger('vttjserror');
let offSpyCall = testTech.off.getCall(0);
ok(/^vttjs failed to load, stopping trying to process/.test(errorMsg),
'vttjs failed to load, so, we logged an error');
ok(errorSpy.called, 'vttjs failed to load, so log.error was called');
if (errorSpy.called) {
ok(/^vttjs failed to load, stopping trying to process/.test(errorSpy.getCall(0).args[0]),
'log.error was called with the expected message');
}
ok(!parserCreated, 'WebVTT is not loaded, do not try to parse yet');
ok(offSpyCall, 'tech.off was called');

View File

@ -1,56 +1,84 @@
import {IE_VERSION} from '../../../src/js/utils/browser';
import log from '../../../src/js/utils/log.js';
import {logByType} from '../../../src/js/utils/log.js';
import window from 'global/window';
q.module('log');
q.module('log', {
test('should confirm logging functions work', function() {
let origConsole = window['console'];
// replace the native console for testing
// in ie8 console.log is apparently not a 'function' so sinon chokes on it
// https://github.com/cjohansen/Sinon.JS/issues/386
// instead we'll temporarily replace them with no-op functions
let console = window['console'] = {
log: function(){},
warn: function(){},
error: function(){}
};
beforeEach() {
// stub the global log functions
let logStub = sinon.stub(console, 'log');
let errorStub = sinon.stub(console, 'error');
let warnStub = sinon.stub(console, 'warn');
// Back up the original console.
this.originalConsole = window.console;
// Reset the log history
log.history = [];
// Replace the native console for testing. In IE8 `console.log` is not a
// 'function' so sinon chokes on it when trying to spy:
// https://github.com/cjohansen/Sinon.JS/issues/386
//
// Instead we'll temporarily replace them with no-op functions
window.console = {
log: sinon.spy(),
warn: sinon.spy(),
error: sinon.spy()
};
},
afterEach() {
// Restore the native/original console.
window.console = this.originalConsole;
// Empty the logger's history.
log.history.length = 0;
}
});
const getConsoleArgs = (...arr) =>
IE_VERSION && IE_VERSION < 11 ? [arr.join(' ')] : arr;
test('logging functions should work', function() {
// Need to reset history here because there are extra messages logged
// when running via Karma.
log.history.length = 0;
log('log1', 'log2');
log.warn('warn1', 'warn2');
log.error('error1', 'error2');
ok(logStub.called, 'log was called');
equal(logStub.firstCall.args[0], 'VIDEOJS:');
equal(logStub.firstCall.args[1], 'log1');
equal(logStub.firstCall.args[2], 'log2');
ok(window.console.log.called, 'log was called');
deepEqual(
window.console.log.firstCall.args,
getConsoleArgs('VIDEOJS:', 'log1', 'log2')
);
ok(warnStub.called, 'warn was called');
equal(warnStub.firstCall.args[0], 'VIDEOJS:');
equal(warnStub.firstCall.args[1], 'WARN:');
equal(warnStub.firstCall.args[2], 'warn1');
equal(warnStub.firstCall.args[3], 'warn2');
ok(window.console.warn.called, 'warn was called');
deepEqual(
window.console.warn.firstCall.args,
getConsoleArgs('VIDEOJS:', 'WARN:', 'warn1', 'warn2')
);
ok(errorStub.called, 'error was called');
equal(errorStub.firstCall.args[0], 'VIDEOJS:');
equal(errorStub.firstCall.args[1], 'ERROR:');
equal(errorStub.firstCall.args[2], 'error1');
equal(errorStub.firstCall.args[3], 'error2');
ok(window.console.error.called, 'error was called');
deepEqual(
window.console.error.firstCall.args,
getConsoleArgs('VIDEOJS:', 'ERROR:', 'error1', 'error2')
);
equal(log.history.length, 3, 'there should be three messages in the log history');
// tear down sinon
logStub.restore();
errorStub.restore();
warnStub.restore();
// restore the native console
window['console'] = origConsole;
});
test('in IE pre-11 (or when requested) objects and arrays are stringified', function() {
// Run a custom log call, explicitly requesting object/array stringification.
logByType('log', [
'test',
{foo: 'bar'},
[1, 2, 3],
0,
false,
null
], true);
ok(window.console.log.called, 'log was called');
deepEqual(window.console.log.firstCall.args,
['VIDEOJS: test {"foo":"bar"} [1,2,3] 0 false null']);
});