Comparar commits

..

35 Commits

Autor SHA1 Mensagem Data
Gary Katsevman 382d5aadbe v5.10.7 dist 2016-06-27 22:39:07 -04:00
Gary Katsevman 6620f2d7be v5.10.7 2016-06-27 22:38:38 -04:00
Vineet 2a76453cdd @vdeshpande fixed chapters getting duplicated each time a track is loaded. closes #3354 2016-06-27 22:32:57 -04:00
mister-ben 43551797c3 @mister-ben updated menus to use default videojs font-family. closes #3384 2016-06-27 22:24:13 -04:00
Bruno Klava b6da0a7f70 @bklava updated pt-BR language file. closes #3373 2016-06-27 22:21:53 -04:00
Matt Farmer 7f6ce63ad9 @m14t removed unused loadEvent property in ControlBar options. closes #3363 2016-06-27 22:19:26 -04:00
mister-ben 9de215fa4c @mister-ben added try catch to volume and playbackrate checks. Fixes #3315. closes #3320 2016-06-27 22:15:17 -04:00
Gary Katsevman 0808f843eb @gkatsev pinned node-sass to 3.4. closes #3401 2016-06-27 18:26:37 -04:00
Gary Katsevman 165a499271 v5.10.6 dist 2016-06-20 15:29:23 -04:00
Gary Katsevman 7da89f51a6 v5.10.6 2016-06-20 15:28:41 -04:00
Gary Katsevman 546138e433 v5.10.5 2016-06-07 12:34:07 -04:00
Laurent de Goede 4e0325013e @IJsLauw fixed unhandled exception in deleting poster on ios7. closes #3337 2016-06-07 11:53:50 -04:00
Gary Katsevman 8d5a1b1193 @gkatsev fixed minified vjs in ie8 when initialized with id string. closes #3357 2016-06-07 11:39:22 -04:00
Gary Katsevman 40cf2730b9 @gkatsev pinned dependencies to direct versions. closes #3338 2016-06-01 14:30:44 -04:00
Gary Katsevman 101f471829 v5.10.4 2016-05-31 15:35:05 -04:00
Gary Katsevman b981f254de v5.10.3 2016-05-27 18:24:33 -04:00
brandonocasey fa8fc80b83 @BrandonOCasey fixed source handlers being disposed multiple times when a video is put into the video element directly. closes #3343 2016-05-27 18:12:47 -04:00
Gary Katsevman a11f66ff4f v5.10.2 2016-05-12 14:44:36 -04:00
Gary Katsevman 82d396a1fb @gkatsev nulled out currentSource_ in setSource. closes #3314 2016-05-12 14:43:10 -04:00
Gary Katsevman 971dbaecc4 v5.10.1 2016-05-03 15:42:43 -04:00
Gary Katsevman 3aff03be84 5.10 is a broken release, update changelog 2016-05-03 15:42:11 -04:00
Gary Katsevman 9a59aee304 v5.10.0 2016-05-03 15:39:05 -04:00
jforbes 4156307792 @forbesjo add an audio track selector menu button. closes #3223 2016-05-02 18:56:44 -04:00
Greg Smith 249532ad45 @incompl clear currentSource_ after subsequent loadstarts. closes #3285 2016-05-02 18:54:34 -04:00
Owen Edwards 6296ca8538 @OwenEdwards added language attribute in HTML files for accessibility. closes #3257 2016-04-28 15:29:40 -07:00
brandonocasey 2e2dbde4b4 @BrandonOCasey added audio and video track support. closes #3173 2016-04-22 14:31:12 -04:00
Gary Katsevman 18cdf08c0e @gkatsev updated text track documentation and crossorigin warning. Fixes #1888, #1958, #2628, #3202. closes #3256 2016-04-20 15:46:49 -04:00
Gary Katsevman b931b6bde9 Merge branch 'stable' 2016-04-19 17:37:16 -04:00
Gary Katsevman cfb9621884 Merge branch 'stable' 2016-04-19 17:20:24 -04:00
Gary Katsevman 5cafd387ea revert 75116d4 adding chrome to travis (#3254) 2016-04-13 15:08:35 -04:00
Chris Auclair 55c101de0c @chrisauclair Make controls visible for accessibility reasons. closes #3237 2016-04-13 20:47:02 +02:00
Gary Katsevman f4ee2767eb @gkatsev Use fonts 2.0 that do not require wrapping codepoints. closes #3252 2016-04-13 20:43:51 +02:00
Nicky Gerritsen a53ef7d1b8 Get rid of double changelog line 2016-04-13 20:36:00 +02:00
Nicky Gerritsen 834b94385c @nickygerritsen Pass tech options to source handlers. closes #3245 2016-04-13 20:32:11 +02:00
Nicky Gerritsen f606f9df50 @nickygerritsen Pass tech options to source handlers. closes #3245 2016-04-13 20:28:09 +02:00
79 arquivos alterados com 6497 adições e 2029 exclusões
+35
Ver Arquivo
@@ -6,6 +6,41 @@ _(none)_
--------------------
## 5.10.7 (2016-06-27)
* @gkatsev pinned node-sass to 3.4 ([view](https://github.com/videojs/video.js/pull/3401))
* @mister-ben added try catch to volume and playbackrate checks. Fixes #3315 ([view](https://github.com/videojs/video.js/pull/3320))
* @m14t removed unused loadEvent property in ControlBar options ([view](https://github.com/videojs/video.js/pull/3363))
* @bklava updated pt-BR language file ([view](https://github.com/videojs/video.js/pull/3373))
* @mister-ben updated menus to use default videojs font-family ([view](https://github.com/videojs/video.js/pull/3384))
* @vdeshpande fixed chapters getting duplicated each time a track is loaded ([view](https://github.com/videojs/video.js/pull/3354))
## 5.10.6 (2016-06-20)
* @gkatsev fix not fully minified video.min.js file.
## 5.10.5 (2016-06-07)
* @gkatsev pinned dependencies to direct versions ([view](https://github.com/videojs/video.js/pull/3338))
* @gkatsev fixed minified vjs in ie8 when initialized with id string ([view](https://github.com/videojs/video.js/pull/3357))
* @IJsLauw fixed unhandled exception in deleting poster on ios7 ([view](https://github.com/videojs/video.js/pull/3337))
## 5.10.4 (2016-05-31)
* Patch release to fix dist on npm
## 5.10.3 (2016-05-27)
* @BrandonOCasey fixed source handlers being disposed multiple times when a video is put into the video element directly ([view](https://github.com/videojs/video.js/pull/3343))
## 5.10.2 (2016-05-12)
* @gkatsev nulled out currentSource_ in setSource ([view](https://github.com/videojs/video.js/pull/3314))
## 5.10.1 (2016-05-03)
* @nickygerritsen Pass tech options to source handlers ([view](https://github.com/videojs/video.js/pull/3245))
* @gkatsev Use fonts 2.0 that do not require wrapping codepoints ([view](https://github.com/videojs/video.js/pull/3252))
* @chrisauclair Make controls visible for accessibility reasons ([view](https://github.com/videojs/video.js/pull/3237))
* @gkatsev updated text track documentation and crossorigin warning. Fixes #1888, #1958, #2628, #3202 ([view](https://github.com/videojs/video.js/pull/3256))
* @BrandonOCasey added audio and video track support ([view](https://github.com/videojs/video.js/pull/3173))
* @OwenEdwards added language attribute in HTML files for accessibility ([view](https://github.com/videojs/video.js/pull/3257))
* @incompl clear currentSource_ after subsequent loadstarts ([view](https://github.com/videojs/video.js/pull/3285))
* @forbesjo add an audio track selector menu button ([view](https://github.com/videojs/video.js/pull/3223))
## 5.9.2 (2016-04-19)
* @gkatsev grouped text track errors in the console, if we can ([view](https://github.com/videojs/video.js/pull/3259))
+3 -14
Ver Arquivo
@@ -159,7 +159,7 @@ module.exports = function(grunt) {
},
skin: {
files: ['src/css/**/*'],
tasks: ['sass', 'wrapcodepoints']
tasks: ['sass']
},
jshint: {
files: ['src/**/*', 'test/unit/**/*.js', 'Gruntfile.js'],
@@ -450,6 +450,7 @@ module.exports = function(grunt) {
require('load-grunt-tasks')(grunt);
grunt.loadNpmTasks('videojs-doc-generator');
grunt.loadNpmTasks('chg');
grunt.loadNpmTasks('gkatsev-grunt-sass');
const buildDependents = [
'clean:build',
@@ -464,7 +465,6 @@ module.exports = function(grunt) {
'uglify',
'sass',
'wrapcodepoints',
'version:css',
'cssmin',
@@ -489,18 +489,7 @@ module.exports = function(grunt) {
'zip:dist'
]);
// Sass turns unicode codepoints into utf8 characters.
// We don't want that so we unwrapped them in the templates/scss.hbs file.
// After sass has generated our css file, we need to wrap the codepoints
// in quotes for it to work.
grunt.registerTask('wrapcodepoints', function() {
const sassConfig = grunt.config.get('sass.build.files');
const cssPath = Object.keys(sassConfig)[0];
const css = grunt.file.read(cssPath);
grunt.file.write(cssPath, css.replace(/(\\f\w+);/g, "'$1';"));
});
grunt.registerTask('skin', ['sass', 'wrapcodepoints']);
grunt.registerTask('skin', ['sass']);
// Default task - build and test
grunt.registerTask('default', ['test']);
+1 -1
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": "5.9.2",
"version": "5.10.7",
"keywords": [
"videojs",
"html5",
+7 -3
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+1 -1
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+1741 -369
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+11 -9
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+1 -1
Ver Arquivo
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Video.js Text Descriptions, Chapters &amp; Captions Example</title>
+1 -1
Ver Arquivo
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
BIN
Ver Arquivo
Arquivo binário não exibido.
BIN
Ver Arquivo
Arquivo binário não exibido.
BIN
Ver Arquivo
Arquivo binário não exibido.
+69 -363
Ver Arquivo
@@ -26,7 +26,7 @@ if (typeof window.HTMLVideoElement === 'undefined') {
;
// UMD (Universal Module Definition)
// see https://github.com/umdjs/umd/blob/master/templates/returnExports.js
// see https://github.com/umdjs/umd/blob/master/returnExports.js
(function (root, factory) {
'use strict';
@@ -72,7 +72,6 @@ var array_push = ArrayPrototype.push;
var array_unshift = ArrayPrototype.unshift;
var array_concat = ArrayPrototype.concat;
var call = FunctionPrototype.call;
var apply = FunctionPrototype.apply;
var max = Math.max;
var min = Math.min;
@@ -85,18 +84,19 @@ var isRegex; /* inlined from https://npmjs.com/is-regex */ var regexExec = RegEx
var isString; /* inlined from https://npmjs.com/is-string */ var strValue = String.prototype.valueOf, tryStringObject = function tryStringObject(value) { try { strValue.call(value); return true; } catch (e) { return false; } }, stringClass = '[object String]'; isString = function isString(value) { if (typeof value === 'string') { return true; } if (typeof value !== 'object') { return false; } return hasToStringTag ? tryStringObject(value) : to_string.call(value) === stringClass; };
/* inlined from http://npmjs.com/define-properties */
var supportsDescriptors = $Object.defineProperty && (function () {
try {
var obj = {};
$Object.defineProperty(obj, 'x', { enumerable: false, value: obj });
for (var _ in obj) { return false; }
return obj.x === obj;
} catch (e) { /* this is ES3 */
return false;
}
}());
var defineProperties = (function (has) {
// Define configurable, writable, and non-enumerable props
var supportsDescriptors = $Object.defineProperty && (function () {
try {
var obj = {};
$Object.defineProperty(obj, 'x', { enumerable: false, value: obj });
for (var _ in obj) { return false; }
return obj.x === obj;
} catch (e) { /* this is ES3 */
return false;
}
}());
// Define configurable, writable and non-enumerable props
// if they don't exist.
var defineProperty;
if (supportsDescriptors) {
@@ -179,6 +179,7 @@ var ES = {
// http://es5.github.com/#x9.9
/* replaceable with https://npmjs.com/package/es-abstract ES5.ToObject */
ToObject: function (o) {
/* jshint eqnull: true */
if (o == null) { // this matches both null and undefined
throw new TypeError("can't convert " + o + ' to object');
}
@@ -336,17 +337,13 @@ defineProperties(FunctionPrototype, {
});
// _Please note: Shortcuts are defined after `Function.prototype.bind` as we
// use it in defining shortcuts.
// us it in defining shortcuts.
var owns = call.bind(ObjectPrototype.hasOwnProperty);
var toStr = call.bind(ObjectPrototype.toString);
var arraySlice = call.bind(array_slice);
var arraySliceApply = apply.bind(array_slice);
var strSlice = call.bind(StringPrototype.slice);
var strSplit = call.bind(StringPrototype.split);
var strIndexOf = call.bind(StringPrototype.indexOf);
var pushCall = call.bind(array_push);
var isEnum = call.bind(ObjectPrototype.propertyIsEnumerable);
var arraySort = call.bind(ArrayPrototype.sort);
var push = call.bind(array_push);
//
// Array
@@ -400,23 +397,18 @@ var properlyBoxesContext = function properlyBoxed(method) {
// Check node 0.6.21 bug where third parameter is not boxed
var properlyBoxesNonStrict = true;
var properlyBoxesStrict = true;
var threwException = false;
if (method) {
try {
method.call('foo', function (_, __, context) {
if (typeof context !== 'object') { properlyBoxesNonStrict = false; }
});
method.call('foo', function (_, __, context) {
if (typeof context !== 'object') { properlyBoxesNonStrict = false; }
});
method.call([1], function () {
'use strict';
method.call([1], function () {
'use strict';
properlyBoxesStrict = typeof this === 'string';
}, 'x');
} catch (e) {
threwException = true;
}
properlyBoxesStrict = typeof this === 'string';
}, 'x');
}
return !!method && !threwException && properlyBoxesNonStrict && properlyBoxesStrict;
return !!method && properlyBoxesNonStrict && properlyBoxesStrict;
};
defineProperties(ArrayPrototype, {
@@ -505,7 +497,7 @@ defineProperties(ArrayPrototype, {
if (i in self) {
value = self[i];
if (typeof T === 'undefined' ? callbackfn(value, i, object) : callbackfn.call(T, value, i, object)) {
pushCall(result, value);
push(result, value);
}
}
}
@@ -758,9 +750,9 @@ defineProperties(ArrayPrototype, {
var args = arguments;
this.length = max(ES.ToInteger(this.length), 0);
if (arguments.length > 0 && typeof deleteCount !== 'number') {
args = arraySlice(arguments);
args = array_slice.call(arguments);
if (args.length < 2) {
pushCall(args, this.length - start);
push(args, this.length - start);
} else {
args[1] = ES.ToInteger(deleteCount);
}
@@ -807,7 +799,7 @@ defineProperties(ArrayPrototype, {
k += 1;
}
var items = arraySlice(arguments, 2);
var items = array_slice.call(arguments, 2);
var itemCount = items.length;
var to;
if (itemCount < actualDeleteCount) {
@@ -851,31 +843,13 @@ defineProperties(ArrayPrototype, {
}
}, !spliceWorksWithLargeSparseArrays || !spliceWorksWithSmallSparseArrays);
var originalJoin = ArrayPrototype.join;
var hasStringJoinBug;
try {
hasStringJoinBug = Array.prototype.join.call('123', ',') !== '1,2,3';
} catch (e) {
hasStringJoinBug = true;
}
if (hasStringJoinBug) {
defineProperties(ArrayPrototype, {
join: function join(separator) {
var sep = typeof separator === 'undefined' ? ',' : separator;
return originalJoin.call(isString(this) ? strSplit(this, '') : this, sep);
}
}, hasStringJoinBug);
}
var hasJoinUndefinedBug = [1, 2].join(undefined) !== '1,2';
if (hasJoinUndefinedBug) {
defineProperties(ArrayPrototype, {
join: function join(separator) {
var sep = typeof separator === 'undefined' ? ',' : separator;
return originalJoin.call(this, sep);
}
}, hasJoinUndefinedBug);
}
var originalJoin = ArrayPrototype.join;
defineProperties(ArrayPrototype, {
join: function join(separator) {
return originalJoin.call(this, typeof separator === 'undefined' ? ',' : separator);
}
}, hasJoinUndefinedBug);
var pushShim = function push(item) {
var O = ES.ToObject(this);
@@ -911,52 +885,6 @@ var pushUndefinedIsWeird = (function () {
}());
defineProperties(ArrayPrototype, { push: pushShim }, pushUndefinedIsWeird);
// ES5 15.2.3.14
// http://es5.github.io/#x15.4.4.10
// Fix boxed string bug
defineProperties(ArrayPrototype, {
slice: function (start, end) {
var arr = isString(this) ? strSplit(this, '') : this;
return arraySliceApply(arr, arguments);
}
}, splitString);
var sortIgnoresNonFunctions = (function () {
try {
[1, 2].sort(null);
[1, 2].sort({});
return true;
} catch (e) { /**/ }
return false;
}());
var sortThrowsOnRegex = (function () {
// this is a problem in Firefox 4, in which `typeof /a/ === 'function'`
try {
[1, 2].sort(/a/);
return false;
} catch (e) { /**/ }
return true;
}());
var sortIgnoresUndefined = (function () {
// applies in IE 8, for one.
try {
[1, 2].sort(undefined);
return true;
} catch (e) { /**/ }
return false;
}());
defineProperties(ArrayPrototype, {
sort: function sort(compareFn) {
if (typeof compareFn === 'undefined') {
return arraySort(this);
}
if (!isCallable(compareFn)) {
throw new TypeError('Array.prototype.sort callback must be a function');
}
return arraySort(this, compareFn);
}
}, sortIgnoresNonFunctions || !sortIgnoresUndefined || !sortThrowsOnRegex);
//
// Object
// ======
@@ -982,8 +910,7 @@ var blacklistedKeys = {
$frames: true,
$frameElement: true,
$webkitIndexedDB: true,
$webkitStorageInfo: true,
$external: true
$webkitStorageInfo: true
};
var hasAutomationEqualityBug = (function () {
/* globals window */
@@ -1048,14 +975,14 @@ defineProperties($Object, {
var skipProto = hasProtoEnumBug && isFn;
if ((isStr && hasStringEnumBug) || isArgs) {
for (var i = 0; i < object.length; ++i) {
pushCall(theKeys, $String(i));
push(theKeys, $String(i));
}
}
if (!isArgs) {
for (var name in object) {
if (!(skipProto && name === 'prototype') && owns(object, name)) {
pushCall(theKeys, $String(name));
push(theKeys, $String(name));
}
}
}
@@ -1065,7 +992,7 @@ defineProperties($Object, {
for (var j = 0; j < dontEnumsLength; j++) {
var dontEnum = dontEnums[j];
if (!(skipConstructor && dontEnum === 'constructor') && owns(object, dontEnum)) {
pushCall(theKeys, dontEnum);
push(theKeys, dontEnum);
}
}
}
@@ -1085,7 +1012,7 @@ var originalKeys = $Object.keys;
defineProperties($Object, {
keys: function keys(object) {
if (isArguments(object)) {
return originalKeys(arraySlice(object));
return originalKeys(array_slice.call(object));
} else {
return originalKeys(object);
}
@@ -1097,190 +1024,6 @@ defineProperties($Object, {
// ====
//
var hasNegativeMonthYearBug = new Date(-3509827329600292).getUTCMonth() !== 0;
var aNegativeTestDate = new Date(-1509842289600292);
var aPositiveTestDate = new Date(1449662400000);
var hasToUTCStringFormatBug = aNegativeTestDate.toUTCString() !== 'Mon, 01 Jan -45875 11:59:59 GMT';
var hasToDateStringFormatBug;
var hasToStringFormatBug;
var timeZoneOffset = aNegativeTestDate.getTimezoneOffset();
if (timeZoneOffset < -720) {
hasToDateStringFormatBug = aNegativeTestDate.toDateString() !== 'Tue Jan 02 -45875';
hasToStringFormatBug = !(/^Thu Dec 10 2015 \d\d:\d\d:\d\d GMT[-\+]\d\d\d\d(?: |$)/).test(aPositiveTestDate.toString());
} else {
hasToDateStringFormatBug = aNegativeTestDate.toDateString() !== 'Mon Jan 01 -45875';
hasToStringFormatBug = !(/^Wed Dec 09 2015 \d\d:\d\d:\d\d GMT[-\+]\d\d\d\d(?: |$)/).test(aPositiveTestDate.toString());
}
var originalGetFullYear = call.bind(Date.prototype.getFullYear);
var originalGetMonth = call.bind(Date.prototype.getMonth);
var originalGetDate = call.bind(Date.prototype.getDate);
var originalGetUTCFullYear = call.bind(Date.prototype.getUTCFullYear);
var originalGetUTCMonth = call.bind(Date.prototype.getUTCMonth);
var originalGetUTCDate = call.bind(Date.prototype.getUTCDate);
var originalGetUTCDay = call.bind(Date.prototype.getUTCDay);
var originalGetUTCHours = call.bind(Date.prototype.getUTCHours);
var originalGetUTCMinutes = call.bind(Date.prototype.getUTCMinutes);
var originalGetUTCSeconds = call.bind(Date.prototype.getUTCSeconds);
var originalGetUTCMilliseconds = call.bind(Date.prototype.getUTCMilliseconds);
var dayName = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
var monthName = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
var daysInMonth = function daysInMonth(month, year) {
return originalGetDate(new Date(year, month, 0));
};
defineProperties(Date.prototype, {
getFullYear: function getFullYear() {
if (!this || !(this instanceof Date)) {
throw new TypeError('this is not a Date object.');
}
var year = originalGetFullYear(this);
if (year < 0 && originalGetMonth(this) > 11) {
return year + 1;
}
return year;
},
getMonth: function getMonth() {
if (!this || !(this instanceof Date)) {
throw new TypeError('this is not a Date object.');
}
var year = originalGetFullYear(this);
var month = originalGetMonth(this);
if (year < 0 && month > 11) {
return 0;
}
return month;
},
getDate: function getDate() {
if (!this || !(this instanceof Date)) {
throw new TypeError('this is not a Date object.');
}
var year = originalGetFullYear(this);
var month = originalGetMonth(this);
var date = originalGetDate(this);
if (year < 0 && month > 11) {
if (month === 12) {
return date;
}
var days = daysInMonth(0, year + 1);
return (days - date) + 1;
}
return date;
},
getUTCFullYear: function getUTCFullYear() {
if (!this || !(this instanceof Date)) {
throw new TypeError('this is not a Date object.');
}
var year = originalGetUTCFullYear(this);
if (year < 0 && originalGetUTCMonth(this) > 11) {
return year + 1;
}
return year;
},
getUTCMonth: function getUTCMonth() {
if (!this || !(this instanceof Date)) {
throw new TypeError('this is not a Date object.');
}
var year = originalGetUTCFullYear(this);
var month = originalGetUTCMonth(this);
if (year < 0 && month > 11) {
return 0;
}
return month;
},
getUTCDate: function getUTCDate() {
if (!this || !(this instanceof Date)) {
throw new TypeError('this is not a Date object.');
}
var year = originalGetUTCFullYear(this);
var month = originalGetUTCMonth(this);
var date = originalGetUTCDate(this);
if (year < 0 && month > 11) {
if (month === 12) {
return date;
}
var days = daysInMonth(0, year + 1);
return (days - date) + 1;
}
return date;
}
}, hasNegativeMonthYearBug);
defineProperties(Date.prototype, {
toUTCString: function toUTCString() {
if (!this || !(this instanceof Date)) {
throw new TypeError('this is not a Date object.');
}
var day = originalGetUTCDay(this);
var date = originalGetUTCDate(this);
var month = originalGetUTCMonth(this);
var year = originalGetUTCFullYear(this);
var hour = originalGetUTCHours(this);
var minute = originalGetUTCMinutes(this);
var second = originalGetUTCSeconds(this);
return dayName[day] + ', ' +
(date < 10 ? '0' + date : date) + ' ' +
monthName[month] + ' ' +
year + ' ' +
(hour < 10 ? '0' + hour : hour) + ':' +
(minute < 10 ? '0' + minute : minute) + ':' +
(second < 10 ? '0' + second : second) + ' GMT';
}
}, hasNegativeMonthYearBug || hasToUTCStringFormatBug);
// Opera 12 has `,`
defineProperties(Date.prototype, {
toDateString: function toDateString() {
if (!this || !(this instanceof Date)) {
throw new TypeError('this is not a Date object.');
}
var day = this.getDay();
var date = this.getDate();
var month = this.getMonth();
var year = this.getFullYear();
return dayName[day] + ' ' +
monthName[month] + ' ' +
(date < 10 ? '0' + date : date) + ' ' +
year;
}
}, hasNegativeMonthYearBug || hasToDateStringFormatBug);
// can't use defineProperties here because of toString enumeration issue in IE <= 8
if (hasNegativeMonthYearBug || hasToStringFormatBug) {
Date.prototype.toString = function toString() {
if (!this || !(this instanceof Date)) {
throw new TypeError('this is not a Date object.');
}
var day = this.getDay();
var date = this.getDate();
var month = this.getMonth();
var year = this.getFullYear();
var hour = this.getHours();
var minute = this.getMinutes();
var second = this.getSeconds();
var timezoneOffset = this.getTimezoneOffset();
var hoursOffset = Math.floor(Math.abs(timezoneOffset) / 60);
var minutesOffset = Math.floor(Math.abs(timezoneOffset) % 60);
return dayName[day] + ' ' +
monthName[month] + ' ' +
(date < 10 ? '0' + date : date) + ' ' +
year + ' ' +
(hour < 10 ? '0' + hour : hour) + ':' +
(minute < 10 ? '0' + minute : minute) + ':' +
(second < 10 ? '0' + second : second) + ' GMT' +
(timezoneOffset > 0 ? '-' : '+') +
(hoursOffset < 10 ? '0' + hoursOffset : hoursOffset) +
(minutesOffset < 10 ? '0' + minutesOffset : minutesOffset);
};
if (supportsDescriptors) {
$Object.defineProperty(Date.prototype, 'toString', {
configurable: true,
enumerable: false,
writable: true
});
}
}
// ES5 15.9.5.43
// http://es5.github.com/#x15.9.5.43
// This function returns a String value represent the instance in time
@@ -1295,33 +1038,39 @@ var hasSafari51DateBug = Date.prototype.toISOString && new Date(-1).toISOString(
defineProperties(Date.prototype, {
toISOString: function toISOString() {
var result, length, value, year, month;
if (!isFinite(this)) {
throw new RangeError('Date.prototype.toISOString called on non-finite value.');
}
var year = originalGetUTCFullYear(this);
year = this.getUTCFullYear();
var month = originalGetUTCMonth(this);
month = this.getUTCMonth();
// see https://github.com/es-shims/es5-shim/issues/111
year += Math.floor(month / 12);
month = (month % 12 + 12) % 12;
// the date time string format is specified in 15.9.1.15.
var result = [month + 1, originalGetUTCDate(this), originalGetUTCHours(this), originalGetUTCMinutes(this), originalGetUTCSeconds(this)];
result = [month + 1, this.getUTCDate(), this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds()];
year = (
(year < 0 ? '-' : (year > 9999 ? '+' : '')) +
strSlice('00000' + Math.abs(year), (0 <= year && year <= 9999) ? -4 : -6)
);
for (var i = 0; i < result.length; ++i) {
// pad months, days, hours, minutes, and seconds to have two digits.
result[i] = strSlice('00' + result[i], -2);
length = result.length;
while (length--) {
value = result[length];
// pad months, days, hours, minutes, and seconds to have two
// digits.
if (value < 10) {
result[length] = '0' + value;
}
}
// pad milliseconds to have three digits.
return (
year + '-' + arraySlice(result, 0, 2).join('-') +
'T' + arraySlice(result, 2).join(':') + '.' +
strSlice('000' + originalGetUTCMilliseconds(this), -3) + 'Z'
year + '-' + array_slice.call(result, 0, 2).join('-') +
'T' + array_slice.call(result, 2).join(':') + '.' +
strSlice('000' + this.getUTCMilliseconds(), -3) + 'Z'
);
}
}, hasNegativeDateBug || hasSafari51DateBug);
@@ -1391,6 +1140,7 @@ if (doesNotParseY2KNewYear || acceptsInvalidDates || !supportsExtendedYears) {
/* global Date: true */
/* eslint-disable no-undef */
var maxSafeUnsigned32Bit = Math.pow(2, 31) - 1;
var secondsWithinMaxSafeUnsigned32Bit = Math.floor(maxSafeUnsigned32Bit / 1e3);
var hasSafariSignedIntBug = isActualNaN(new Date(1970, 0, 1, 0, 0, 0, maxSafeUnsigned32Bit + 1).getTime());
Date = (function (NativeDate) {
/* eslint-enable no-undef */
@@ -1763,7 +1513,7 @@ if (
var maxSafe32BitInt = Math.pow(2, 32) - 1;
StringPrototype.split = function (separator, limit) {
var string = String(this);
var string = this;
if (typeof separator === 'undefined' && limit === 0) {
return [];
}
@@ -1782,6 +1532,7 @@ if (
// Make `global` and avoid `lastIndex` issues by working with a copy
separator2, match, lastIndex, lastLength;
var separatorCopy = new RegExp(separator.source, flags + 'g');
string += ''; // Type-convert
if (!compliantExecNpcg) {
// Doesn't need flags gy, but they don't hurt
separator2 = new RegExp('^' + separatorCopy.source + '$(?!\\s)', flags);
@@ -1799,7 +1550,7 @@ if (
// `separatorCopy.lastIndex` is not reliable cross-browser
lastIndex = match.index + match[0].length;
if (lastIndex > lastLastIndex) {
pushCall(output, strSlice(string, lastLastIndex, match.index));
push(output, strSlice(string, lastLastIndex, match.index));
// Fix browsers whose `exec` methods don't consistently return `undefined` for
// nonparticipating capturing groups
if (!compliantExecNpcg && match.length > 1) {
@@ -1814,7 +1565,7 @@ if (
/* eslint-enable no-loop-func */
}
if (match.length > 1 && match.index < string.length) {
array_push.apply(output, arraySlice(match, 1));
array_push.apply(output, array_slice.call(match, 1));
}
lastLength = match[0].length;
lastLastIndex = lastIndex;
@@ -1829,10 +1580,10 @@ if (
}
if (lastLastIndex === string.length) {
if (lastLength || !separatorCopy.test('')) {
pushCall(output, '');
push(output, '');
}
} else {
pushCall(output, strSlice(string, lastLastIndex));
push(output, strSlice(string, lastLastIndex));
}
return output.length > splitLimit ? strSlice(output, 0, splitLimit) : output;
};
@@ -1855,7 +1606,7 @@ var str_replace = StringPrototype.replace;
var replaceReportsGroupsCorrectly = (function () {
var groups = [];
'x'.replace(/x(.)?/g, function (match, group) {
pushCall(groups, group);
push(groups, group);
});
return groups.length === 1 && typeof groups[0] === 'undefined';
}());
@@ -1873,7 +1624,7 @@ if (!replaceReportsGroupsCorrectly) {
searchValue.lastIndex = 0;
var args = searchValue.exec(match) || [];
searchValue.lastIndex = originalLastIndex;
pushCall(args, arguments[length - 2], arguments[length - 1]);
push(args, arguments[length - 2], arguments[length - 1]);
return replaceValue.apply(this, args);
};
return str_replace.call(this, searchValue, wrappedReplaceValue);
@@ -1918,7 +1669,6 @@ defineProperties(StringPrototype, {
return $String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, '');
}
}, hasTrimWhitespaceBug);
var trim = call.bind(String.prototype.trim);
var hasLastIndexBug = StringPrototype.lastIndexOf && 'abcあい'.lastIndexOf('あい', 2) !== -1;
defineProperties(StringPrototype, {
@@ -1959,26 +1709,15 @@ if (parseInt(ws + '08') !== 8 || parseInt(ws + '0x16') !== 22) {
parseInt = (function (origParseInt) {
var hexRegex = /^[\-+]?0[xX]/;
return function parseInt(str, radix) {
var string = trim(str);
var string = $String(str).trim();
var defaultedRadix = $Number(radix) || (hexRegex.test(string) ? 16 : 10);
return origParseInt(string, defaultedRadix);
};
}(parseInt));
}
// https://es5.github.io/#x15.1.2.3
if (1 / parseFloat('-0') !== -Infinity) {
/* global parseFloat: true */
parseFloat = (function (origParseFloat) {
return function parseFloat(string) {
var inputString = trim(string);
var result = origParseFloat(inputString);
return result === 0 && strSlice(inputString, 0, 1) === '-' ? -0 : result;
};
}(parseFloat));
}
if (String(new RangeError('test')) !== 'RangeError: test') {
var originalErrorToString = Error.prototype.toString;
var errorToStringShim = function toString() {
if (typeof this === 'undefined' || this === null) {
throw new TypeError("can't convert " + this + ' to object');
@@ -2007,39 +1746,6 @@ if (String(new RangeError('test')) !== 'RangeError: test') {
Error.prototype.toString = errorToStringShim;
}
if (supportsDescriptors) {
var ensureNonEnumerable = function (obj, prop) {
if (isEnum(obj, prop)) {
var desc = Object.getOwnPropertyDescriptor(obj, prop);
desc.enumerable = false;
Object.defineProperty(obj, prop, desc);
}
};
ensureNonEnumerable(Error.prototype, 'message');
if (Error.prototype.message !== '') {
Error.prototype.message = '';
}
ensureNonEnumerable(Error.prototype, 'name');
}
if (String(/a/mig) !== '/a/gim') {
var regexToString = function toString() {
var str = '/' + this.source + '/';
if (this.global) {
str += 'g';
}
if (this.ignoreCase) {
str += 'i';
}
if (this.multiline) {
str += 'm';
}
return str;
};
// can't use defineProperties here because of toString enumeration issue in IE <= 8
RegExp.prototype.toString = regexToString;
}
}));
/*!
@@ -2054,7 +1760,7 @@ if (String(/a/mig) !== '/a/gim') {
;
// UMD (Universal Module Definition)
// see https://github.com/umdjs/umd/blob/master/templates/returnExports.js
// see https://github.com/umdjs/umd/blob/master/returnExports.js
(function (root, factory) {
'use strict';
@@ -2073,10 +1779,10 @@ if (String(/a/mig) !== '/a/gim') {
}
}(this, function () {
var call = Function.call;
var call = Function.prototype.call;
var prototypeOfObject = Object.prototype;
var owns = call.bind(prototypeOfObject.hasOwnProperty);
var isEnumerable = call.bind(prototypeOfObject.propertyIsEnumerable);
var propertyIsEnumerable = call.bind(prototypeOfObject.propertyIsEnumerable);
var toStr = call.bind(prototypeOfObject.toString);
// If JS engine supports accessors creating shortcuts.
@@ -2175,7 +1881,7 @@ if (!Object.getOwnPropertyDescriptor || getOwnPropertyDescriptorFallback) {
// If object has a property then it's for sure `configurable`, and
// probably `enumerable`. Detect enumerability though.
descriptor = {
enumerable: isEnumerable(object, property),
enumerable: propertyIsEnumerable(object, property),
configurable: true
};
+1 -1
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+3 -3
Ver Arquivo
@@ -1,6 +1,6 @@
videojs.addLanguage("pt-BR",{
"Play": "Tocar",
"Pause": "Pause",
"Pause": "Pausar",
"Current Time": "Tempo",
"Duration Time": "Duração",
"Remaining Time": "Tempo Restante",
@@ -18,9 +18,9 @@ videojs.addLanguage("pt-BR",{
"Captions": "Anotações",
"captions off": "Sem Anotações",
"Chapters": "Capítulos",
"You aborted the media playback": "Você parou a execução de vídeo.",
"You aborted the media playback": "Você parou a execução do vídeo.",
"A network error caused the media download to fail part-way.": "Um erro na rede fez o vídeo parar parcialmente.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "O vídeo não pode ser carregado, ou porque houve um problema com sua rede ou pelo formato do vídeo não ser suportado.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "A Execução foi interrompida por um problema com o vídeo ou por seu navegador não dar suporte ao seu formato.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "A execução foi interrompida por um problema com o vídeo ou por seu navegador não dar suporte ao seu formato.",
"No compatible source was found for this media.": "Não foi encontrada fonte de vídeo compatível."
});
Arquivo binário não exibido.
+37 -32
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+1 -1
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+1741 -369
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+33 -15
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+11 -10
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+1 -1
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+1 -1
Ver Arquivo
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Video.js Text Descriptions, Chapters &amp; Captions Example</title>
+1 -1
Ver Arquivo
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
+148 -170
Ver Arquivo
@@ -161,175 +161,153 @@ The following is a list of official language codes.
<table border="0" cellspacing="5" cellpadding="5">
<tr>
<td>
<table>
<tr><th>ab<th><td>Abkhazian</td></tr>
<tr><th>aa<th><td>Afar</td></tr>
<tr><th>af<th><td>Afrikaans</td></tr>
<tr><th>sq<th><td>Albanian</td></tr>
<tr><th>am<th><td>Amharic</td></tr>
<tr><th>ar<th><td>Arabic</td></tr>
<tr><th>an<th><td>Aragonese</td></tr>
<tr><th>hy<th><td>Armenian</td></tr>
<tr><th>as<th><td>Assamese</td></tr>
<tr><th>ay<th><td>Aymara</td></tr>
<tr><th>az<th><td>Azerbaijani</td></tr>
<tr><th>ba<th><td>Bashkir</td></tr>
<tr><th>eu<th><td>Basque</td></tr>
<tr><th>bn<th><td>Bengali (Bangla)</td></tr>
<tr><th>dz<th><td>Bhutani</td></tr>
<tr><th>bh<th><td>Bihari</td></tr>
<tr><th>bi<th><td>Bislama</td></tr>
<tr><th>br<th><td>Breton</td></tr>
<tr><th>bg<th><td>Bulgarian</td></tr>
<tr><th>my<th><td>Burmese</td></tr>
<tr><th>be<th><td>Byelorussian (Belarusian)</td></tr>
<tr><th>km<th><td>Cambodian</td></tr>
<tr><th>ca<th><td>Catalan</td></tr>
<tr><th>zh<th><td>Chinese (Simplified)</td></tr>
<tr><th>zh<th><td>Chinese (Traditional)</td></tr>
<tr><th>co<th><td>Corsican</td></tr>
<tr><th>hr<th><td>Croatian</td></tr>
<tr><th>cs<th><td>Czech</td></tr>
<tr><th>da<th><td>Danish</td></tr>
<tr><th>nl<th><td>Dutch</td></tr>
<tr><th>en<th><td>English</td></tr>
<tr><th>eo<th><td>Esperanto</td></tr>
<tr><th>et<th><td>Estonian</td></tr>
<tr><th>fo<th><td>Faeroese</td></tr>
<tr><th>fa<th><td>Farsi</td></tr>
<tr><th>fj<th><td>Fiji</td></tr>
<tr><th>fi<th><td>Finnish</td></tr>
</table>
</td>
<td>
<table>
<tr><th>fr<th><td>French</td></tr>
<tr><th>fy<th><td>Frisian</td></tr>
<tr><th>gl<th><td>Galician</td></tr>
<tr><th>gd<th><td>Gaelic (Scottish)</td></tr>
<tr><th>gv<th><td>Gaelic (Manx)</td></tr>
<tr><th>ka<th><td>Georgian</td></tr>
<tr><th>de<th><td>German</td></tr>
<tr><th>el<th><td>Greek</td></tr>
<tr><th>kl<th><td>Greenlandic</td></tr>
<tr><th>gn<th><td>Guarani</td></tr>
<tr><th>gu<th><td>Gujarati</td></tr>
<tr><th>ht<th><td>Haitian Creole</td></tr>
<tr><th>ha<th><td>Hausa</td></tr>
<tr><th>he<th><td>Hebrew</td></tr>
<tr><th>hi<th><td>Hindi</td></tr>
<tr><th>hu<th><td>Hungarian</td></tr>
<tr><th>is<th><td>Icelandic</td></tr>
<tr><th>io<th><td>Ido</td></tr>
<tr><th>id<th><td>Indonesian</td></tr>
<tr><th>ia<th><td>Interlingua</td></tr>
<tr><th>ie<th><td>Interlingue</td></tr>
<tr><th>iu<th><td>Inuktitut</td></tr>
<tr><th>ik<th><td>Inupiak</td></tr>
<tr><th>ga<th><td>Irish</td></tr>
<tr><th>it<th><td>Italian</td></tr>
<tr><th>ja<th><td>Japanese</td></tr>
<tr><th>jv<th><td>Javanese</td></tr>
<tr><th>kn<th><td>Kannada</td></tr>
<tr><th>ks<th><td>Kashmiri</td></tr>
<tr><th>kk<th><td>Kazakh</td></tr>
<tr><th>rw<th><td>Kinyarwanda (Ruanda)</td></tr>
<tr><th>ky<th><td>Kirghiz</td></tr>
<tr><th>rn<th><td>Kirundi (Rundi)</td></tr>
<tr><th>ko<th><td>Korean</td></tr>
<tr><th>ku<th><td>Kurdish</td></tr>
<tr><th>lo<th><td>Laothian</td></tr>
<tr><th>la<th><td>Latin</td></tr>
</table>
</td>
<td>
<table>
<tr><th>lv<th><td>Latvian (Lettish)</td></tr>
<tr><th>li<th><td>Limburgish ( Limburger)</td></tr>
<tr><th>ln<th><td>Lingala</td></tr>
<tr><th>lt<th><td>Lithuanian</td></tr>
<tr><th>mk<th><td>Macedonian</td></tr>
<tr><th>mg<th><td>Malagasy</td></tr>
<tr><th>ms<th><td>Malay</td></tr>
<tr><th>ml<th><td>Malayalam</td></tr>
<tr><th>mt<th><td>Maltese</td></tr>
<tr><th>mi<th><td>Maori</td></tr>
<tr><th>mr<th><td>Marathi</td></tr>
<tr><th>mo<th><td>Moldavian</td></tr>
<tr><th>mn<th><td>Mongolian</td></tr>
<tr><th>na<th><td>Nauru</td></tr>
<tr><th>ne<th><td>Nepali</td></tr>
<tr><th>no<th><td>Norwegian</td></tr>
<tr><th>oc<th><td>Occitan</td></tr>
<tr><th>or<th><td>Oriya</td></tr>
<tr><th>om<th><td>Oromo (Afan, Galla)</td></tr>
<tr><th>ps<th><td>Pashto (Pushto)</td></tr>
<tr><th>pl<th><td>Polish</td></tr>
<tr><th>pt<th><td>Portuguese</td></tr>
<tr><th>pa<th><td>Punjabi</td></tr>
<tr><th>qu<th><td>Quechua</td></tr>
<tr><th>rm<th><td>Rhaeto-Romance</td></tr>
<tr><th>ro<th><td>Romanian</td></tr>
<tr><th>ru<th><td>Russian</td></tr>
<tr><th>sm<th><td>Samoan</td></tr>
<tr><th>sg<th><td>Sangro</td></tr>
<tr><th>sa<th><td>Sanskrit</td></tr>
<tr><th>sr<th><td>Serbian</td></tr>
<tr><th>sh<th><td>Serbo-Croatian</td></tr>
<tr><th>st<th><td>Sesotho</td></tr>
<tr><th>tn<th><td>Setswana</td></tr>
<tr><th>sn<th><td>Shona</td></tr>
<tr><th>ii<th><td>Sichuan Yi</td></tr>
<tr><th>sd<th><td>Sindhi</td></tr>
</table>
</td>
<td>
<table>
<tr><th>si<th><td>Sinhalese</td></tr>
<tr><th>ss<th><td>Siswati</td></tr>
<tr><th>sk<th><td>Slovak</td></tr>
<tr><th>sl<th><td>Slovenian</td></tr>
<tr><th>so<th><td>Somali</td></tr>
<tr><th>es<th><td>Spanish</td></tr>
<tr><th>su<th><td>Sundanese</td></tr>
<tr><th>sw<th><td>Swahili (Kiswahili)</td></tr>
<tr><th>sv<th><td>Swedish</td></tr>
<tr><th>tl<th><td>Tagalog</td></tr>
<tr><th>tg<th><td>Tajik</td></tr>
<tr><th>ta<th><td>Tamil</td></tr>
<tr><th>tt<th><td>Tatar</td></tr>
<tr><th>te<th><td>Telugu</td></tr>
<tr><th>th<th><td>Thai</td></tr>
<tr><th>bo<th><td>Tibetan</td></tr>
<tr><th>ti<th><td>Tigrinya</td></tr>
<tr><th>to<th><td>Tonga</td></tr>
<tr><th>ts<th><td>Tsonga</td></tr>
<tr><th>tr<th><td>Turkish</td></tr>
<tr><th>tk<th><td>Turkmen</td></tr>
<tr><th>tw<th><td>Twi</td></tr>
<tr><th>ug<th><td>Uighur</td></tr>
<tr><th>uk<th><td>Ukrainian</td></tr>
<tr><th>ur<th><td>Urdu</td></tr>
<tr><th>uz<th><td>Uzbek</td></tr>
<tr><th>vi<th><td>Vietnamese</td></tr>
<tr><th>vo<th><td>Volapük</td></tr>
<tr><th>wa<th><td>Wallon</td></tr>
<tr><th>cy<th><td>Welsh</td></tr>
<tr><th>wo<th><td>Wolof</td></tr>
<tr><th>xh<th><td>Xhosa</td></tr>
<tr><th>yi<th><td>Yiddish</td></tr>
<tr><th>yo<th><td>Yoruba</td></tr>
<tr><th>zu<th><td>Zulu</td></tr>
</table>
</td>
<table>
<tr><th>ab<th><td>Abkhazian</td></tr>
<tr><th>aa<th><td>Afar</td></tr>
<tr><th>af<th><td>Afrikaans</td></tr>
<tr><th>sq<th><td>Albanian</td></tr>
<tr><th>am<th><td>Amharic</td></tr>
<tr><th>ar<th><td>Arabic</td></tr>
<tr><th>an<th><td>Aragonese</td></tr>
<tr><th>hy<th><td>Armenian</td></tr>
<tr><th>as<th><td>Assamese</td></tr>
<tr><th>ay<th><td>Aymara</td></tr>
<tr><th>az<th><td>Azerbaijani</td></tr>
<tr><th>ba<th><td>Bashkir</td></tr>
<tr><th>eu<th><td>Basque</td></tr>
<tr><th>bn<th><td>Bengali (Bangla)</td></tr>
<tr><th>dz<th><td>Bhutani</td></tr>
<tr><th>bh<th><td>Bihari</td></tr>
<tr><th>bi<th><td>Bislama</td></tr>
<tr><th>br<th><td>Breton</td></tr>
<tr><th>bg<th><td>Bulgarian</td></tr>
<tr><th>my<th><td>Burmese</td></tr>
<tr><th>be<th><td>Byelorussian (Belarusian)</td></tr>
<tr><th>km<th><td>Cambodian</td></tr>
<tr><th>ca<th><td>Catalan</td></tr>
<tr><th>zh<th><td>Chinese (Simplified)</td></tr>
<tr><th>zh<th><td>Chinese (Traditional)</td></tr>
<tr><th>co<th><td>Corsican</td></tr>
<tr><th>hr<th><td>Croatian</td></tr>
<tr><th>cs<th><td>Czech</td></tr>
<tr><th>da<th><td>Danish</td></tr>
<tr><th>nl<th><td>Dutch</td></tr>
<tr><th>en<th><td>English</td></tr>
<tr><th>eo<th><td>Esperanto</td></tr>
<tr><th>et<th><td>Estonian</td></tr>
<tr><th>fo<th><td>Faeroese</td></tr>
<tr><th>fa<th><td>Farsi</td></tr>
<tr><th>fj<th><td>Fiji</td></tr>
<tr><th>fi<th><td>Finnish</td></tr>
<tr><th>fr<th><td>French</td></tr>
<tr><th>fy<th><td>Frisian</td></tr>
<tr><th>gl<th><td>Galician</td></tr>
<tr><th>gd<th><td>Gaelic (Scottish)</td></tr>
<tr><th>gv<th><td>Gaelic (Manx)</td></tr>
<tr><th>ka<th><td>Georgian</td></tr>
<tr><th>de<th><td>German</td></tr>
<tr><th>el<th><td>Greek</td></tr>
<tr><th>kl<th><td>Greenlandic</td></tr>
<tr><th>gn<th><td>Guarani</td></tr>
<tr><th>gu<th><td>Gujarati</td></tr>
<tr><th>ht<th><td>Haitian Creole</td></tr>
<tr><th>ha<th><td>Hausa</td></tr>
<tr><th>he<th><td>Hebrew</td></tr>
<tr><th>hi<th><td>Hindi</td></tr>
<tr><th>hu<th><td>Hungarian</td></tr>
<tr><th>is<th><td>Icelandic</td></tr>
<tr><th>io<th><td>Ido</td></tr>
<tr><th>id<th><td>Indonesian</td></tr>
<tr><th>ia<th><td>Interlingua</td></tr>
<tr><th>ie<th><td>Interlingue</td></tr>
<tr><th>iu<th><td>Inuktitut</td></tr>
<tr><th>ik<th><td>Inupiak</td></tr>
<tr><th>ga<th><td>Irish</td></tr>
<tr><th>it<th><td>Italian</td></tr>
<tr><th>ja<th><td>Japanese</td></tr>
<tr><th>jv<th><td>Javanese</td></tr>
<tr><th>kn<th><td>Kannada</td></tr>
<tr><th>ks<th><td>Kashmiri</td></tr>
<tr><th>kk<th><td>Kazakh</td></tr>
<tr><th>rw<th><td>Kinyarwanda (Ruanda)</td></tr>
<tr><th>ky<th><td>Kirghiz</td></tr>
<tr><th>rn<th><td>Kirundi (Rundi)</td></tr>
<tr><th>ko<th><td>Korean</td></tr>
<tr><th>ku<th><td>Kurdish</td></tr>
<tr><th>lo<th><td>Laothian</td></tr>
<tr><th>la<th><td>Latin</td></tr>
<tr><th>lv<th><td>Latvian (Lettish)</td></tr>
<tr><th>li<th><td>Limburgish ( Limburger)</td></tr>
<tr><th>ln<th><td>Lingala</td></tr>
<tr><th>lt<th><td>Lithuanian</td></tr>
<tr><th>mk<th><td>Macedonian</td></tr>
<tr><th>mg<th><td>Malagasy</td></tr>
<tr><th>ms<th><td>Malay</td></tr>
<tr><th>ml<th><td>Malayalam</td></tr>
<tr><th>mt<th><td>Maltese</td></tr>
<tr><th>mi<th><td>Maori</td></tr>
<tr><th>mr<th><td>Marathi</td></tr>
<tr><th>mo<th><td>Moldavian</td></tr>
<tr><th>mn<th><td>Mongolian</td></tr>
<tr><th>na<th><td>Nauru</td></tr>
<tr><th>ne<th><td>Nepali</td></tr>
<tr><th>no<th><td>Norwegian</td></tr>
<tr><th>oc<th><td>Occitan</td></tr>
<tr><th>or<th><td>Oriya</td></tr>
<tr><th>om<th><td>Oromo (Afan, Galla)</td></tr>
<tr><th>ps<th><td>Pashto (Pushto)</td></tr>
<tr><th>pl<th><td>Polish</td></tr>
<tr><th>pt<th><td>Portuguese</td></tr>
<tr><th>pa<th><td>Punjabi</td></tr>
<tr><th>qu<th><td>Quechua</td></tr>
<tr><th>rm<th><td>Rhaeto-Romance</td></tr>
<tr><th>ro<th><td>Romanian</td></tr>
<tr><th>ru<th><td>Russian</td></tr>
<tr><th>sm<th><td>Samoan</td></tr>
<tr><th>sg<th><td>Sangro</td></tr>
<tr><th>sa<th><td>Sanskrit</td></tr>
<tr><th>sr<th><td>Serbian</td></tr>
<tr><th>sh<th><td>Serbo-Croatian</td></tr>
<tr><th>st<th><td>Sesotho</td></tr>
<tr><th>tn<th><td>Setswana</td></tr>
<tr><th>sn<th><td>Shona</td></tr>
<tr><th>ii<th><td>Sichuan Yi</td></tr>
<tr><th>sd<th><td>Sindhi</td></tr>
<tr><th>si<th><td>Sinhalese</td></tr>
<tr><th>ss<th><td>Siswati</td></tr>
<tr><th>sk<th><td>Slovak</td></tr>
<tr><th>sl<th><td>Slovenian</td></tr>
<tr><th>so<th><td>Somali</td></tr>
<tr><th>es<th><td>Spanish</td></tr>
<tr><th>su<th><td>Sundanese</td></tr>
<tr><th>sw<th><td>Swahili (Kiswahili)</td></tr>
<tr><th>sv<th><td>Swedish</td></tr>
<tr><th>tl<th><td>Tagalog</td></tr>
<tr><th>tg<th><td>Tajik</td></tr>
<tr><th>ta<th><td>Tamil</td></tr>
<tr><th>tt<th><td>Tatar</td></tr>
<tr><th>te<th><td>Telugu</td></tr>
<tr><th>th<th><td>Thai</td></tr>
<tr><th>bo<th><td>Tibetan</td></tr>
<tr><th>ti<th><td>Tigrinya</td></tr>
<tr><th>to<th><td>Tonga</td></tr>
<tr><th>ts<th><td>Tsonga</td></tr>
<tr><th>tr<th><td>Turkish</td></tr>
<tr><th>tk<th><td>Turkmen</td></tr>
<tr><th>tw<th><td>Twi</td></tr>
<tr><th>ug<th><td>Uighur</td></tr>
<tr><th>uk<th><td>Ukrainian</td></tr>
<tr><th>ur<th><td>Urdu</td></tr>
<tr><th>uz<th><td>Uzbek</td></tr>
<tr><th>vi<th><td>Vietnamese</td></tr>
<tr><th>vo<th><td>Volapük</td></tr>
<tr><th>wa<th><td>Wallon</td></tr>
<tr><th>cy<th><td>Welsh</td></tr>
<tr><th>wo<th><td>Wolof</td></tr>
<tr><th>xh<th><td>Xhosa</td></tr>
<tr><th>yi<th><td>Yiddish</td></tr>
<tr><th>yo<th><td>Yoruba</td></tr>
<tr><th>zu<th><td>Zulu</td></tr>
</table>
</tr>
</table>
+145 -169
Ver Arquivo
@@ -1,44 +1,50 @@
Tracks
======
# Tracks
Text Tracks are a function of HTML5 video for providing time triggered text to the viewer. Video.js makes tracks work across all browsers. There are currently five types of tracks:
- **Subtitles**: Translations of the dialogue in the video for when audio is available but not understood. Subtitles are shown over the video.
- **Captions**: Transcription of the dialogue, sound effects, musical cues, and other audio information for when the viewer is deaf/hard of hearing, or the video is muted. Captions are also shown over the video.
- **Chapters**: Chapter titles that are used to create navigation within the video. Typically they're in the form of a list of chapters that the viewer can click on to go to a specific chapter.
- **Descriptions** (not supported yet): Text descriptions of what's happening in the video for when the video portion isn't available, because the viewer is blind, not using a screen, or driving and about to crash because they're trying to enjoy a video while driving. Descriptions are read by a screen reader or turned into a separate audio track.
- **Metadata** (not supported yet): Tracks that have data meant for javascript to parse and do something with. These aren't shown to the user.
- **Descriptions**: Text descriptions of what's happening in the video for when the video portion isn't available, because the viewer is blind, not using a screen, or driving and about to crash because they're trying to enjoy a video while driving. Descriptions are read by a screen reader or turned into a separate audio track.
- **Metadata**: Tracks that have data meant for javascript to parse and do something with. These aren't shown to the user.
Creating the Text File
----------------------
## Creating the Text File
Timed text requires a text file in [WebVTT](http://dev.w3.org/html5/webvtt/) format. This format defines a list of "cues" that have a start time, and end time, and text to display. [Microsoft has a builder](https://dev.modern.ie/testdrive/demos/captionmaker/) that can help you get started on the file.
When creating captions, there's also additional [caption formatting techniques] (http://www.theneitherworld.com/mcpoodle/SCC_TOOLS/DOCS/SCC_FORMAT.HTML#style) that would be good to use, like brackets around sound effects: [ sound effect ]. If you'd like a more in depth style guide for captioning, you can reference the [Captioning Key](http://www.dcmp.org/captioningkey/), but keep in mind not all features are supported by WebVTT or (more likely) the Video.js WebVTT implementation.
Adding to Video.js
------------------
## Adding to Video.js
Once you have your WebVTT file created, you can add it to Video.js using the track tag. Put your track tag after all the source elements, and before any fallback content.
```html
<video id="example_video_1" class="video-js vjs-default-skin"
<video id="example_video_1" class="video-js"
controls preload="auto" width="640" height="264"
data-setup='{"example_option":true}'>
<source src="http://video-js.zencoder.com/oceans-clip.mp4" type="video/mp4" />
<source src="http://video-js.zencoder.com/oceans-clip.webm" type="video/webm" />
<source src="http://video-js.zencoder.com/oceans-clip.ogv" type="video/ogg" />
<source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4" />
<source src="http://vjs.zencdn.net/v/oceans.webm" type="video/webm" />
<source src="http://vjs.zencdn.net/v/oceans.ogv" type="video/ogg" />
<track kind="captions" src="http://example.com/path/to/captions.vtt" srclang="en" label="English" default>
</video>
```
Subtitles from Another Domain
-----------------------------
You can also add tracks [programatically](#api).
## Subtitles from Another Domain
Because we're pulling in the text track file via Javascript, the [same-origin policy](http://en.wikipedia.org/wiki/Same_origin_policy) applies. If you'd like to have a player served from one domain,
but the text track served from another, you'll need to [enable CORS](http://enable-cors.org/) in order to do so.
In addition to enabling CORS on the server serving the text tracks, you will need to add the [`crossorigin` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) to the video element itself. This attribute has two values `anonymous` and `use-credentials`. Most users will want to use `anonymous` with cross-origin tracks.
It can be added to the video element like so:
```html
<video class="video-js" crossorigin="anonymous">
<source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
<track src="http://example.com/oceans.vtt" kind="captions" srclang="en" label="English">
</video>
```
One thing to be aware of is that in this case the video files themselves will *also* needs CORS headers applied to it. This is because some browsers apply the crossorigin attribute to the video source itself and not just the tracks and is considered a [security concern by the spec](https://html.spec.whatwg.org/multipage/embedded-content.html#security-and-privacy-considerations).
Track Attributes
----------------
## Track Attributes
Additional settings for track tags.
### kind
@@ -52,157 +58,127 @@ The default attribute can be used to have a track default to showing. Otherwise
NOTE: For chapters, default is required if you want the chapters menu to show.
### srclang
The two-letter code (valid BCP 47 language tag) for the language of the text track, for example "en" for English. Here's a list of available language codes.
The two-letter code (valid BCP 47 language tag) for the language of the text track, for example "en" for English. A list of language codes is [available here](languages.md#language-codes).
<table border="0" cellspacing="5" cellpadding="5">
<tr>
<table>
<tr><th>ab<th><td>Abkhazian</td></tr>
<tr><th>aa<th><td>Afar</td></tr>
<tr><th>af<th><td>Afrikaans</td></tr>
<tr><th>sq<th><td>Albanian</td></tr>
<tr><th>am<th><td>Amharic</td></tr>
<tr><th>ar<th><td>Arabic</td></tr>
<tr><th>an<th><td>Aragonese</td></tr>
<tr><th>hy<th><td>Armenian</td></tr>
<tr><th>as<th><td>Assamese</td></tr>
<tr><th>ay<th><td>Aymara</td></tr>
<tr><th>az<th><td>Azerbaijani</td></tr>
<tr><th>ba<th><td>Bashkir</td></tr>
<tr><th>eu<th><td>Basque</td></tr>
<tr><th>bn<th><td>Bengali (Bangla)</td></tr>
<tr><th>dz<th><td>Bhutani</td></tr>
<tr><th>bh<th><td>Bihari</td></tr>
<tr><th>bi<th><td>Bislama</td></tr>
<tr><th>br<th><td>Breton</td></tr>
<tr><th>bg<th><td>Bulgarian</td></tr>
<tr><th>my<th><td>Burmese</td></tr>
<tr><th>be<th><td>Byelorussian (Belarusian)</td></tr>
<tr><th>km<th><td>Cambodian</td></tr>
<tr><th>ca<th><td>Catalan</td></tr>
<tr><th>zh<th><td>Chinese (Simplified)</td></tr>
<tr><th>zh<th><td>Chinese (Traditional)</td></tr>
<tr><th>co<th><td>Corsican</td></tr>
<tr><th>hr<th><td>Croatian</td></tr>
<tr><th>cs<th><td>Czech</td></tr>
<tr><th>da<th><td>Danish</td></tr>
<tr><th>nl<th><td>Dutch</td></tr>
<tr><th>en<th><td>English</td></tr>
<tr><th>eo<th><td>Esperanto</td></tr>
<tr><th>et<th><td>Estonian</td></tr>
<tr><th>fo<th><td>Faeroese</td></tr>
<tr><th>fa<th><td>Farsi</td></tr>
<tr><th>fj<th><td>Fiji</td></tr>
<tr><th>fi<th><td>Finnish</td></tr>
<tr><th>fr<th><td>French</td></tr>
<tr><th>fy<th><td>Frisian</td></tr>
<tr><th>gl<th><td>Galician</td></tr>
<tr><th>gd<th><td>Gaelic (Scottish)</td></tr>
<tr><th>gv<th><td>Gaelic (Manx)</td></tr>
<tr><th>ka<th><td>Georgian</td></tr>
<tr><th>de<th><td>German</td></tr>
<tr><th>el<th><td>Greek</td></tr>
<tr><th>kl<th><td>Greenlandic</td></tr>
<tr><th>gn<th><td>Guarani</td></tr>
<tr><th>gu<th><td>Gujarati</td></tr>
<tr><th>ht<th><td>Haitian Creole</td></tr>
<tr><th>ha<th><td>Hausa</td></tr>
<tr><th>he<th><td>Hebrew</td></tr>
<tr><th>hi<th><td>Hindi</td></tr>
<tr><th>hu<th><td>Hungarian</td></tr>
<tr><th>is<th><td>Icelandic</td></tr>
<tr><th>io<th><td>Ido</td></tr>
<tr><th>id<th><td>Indonesian</td></tr>
<tr><th>ia<th><td>Interlingua</td></tr>
<tr><th>ie<th><td>Interlingue</td></tr>
<tr><th>iu<th><td>Inuktitut</td></tr>
<tr><th>ik<th><td>Inupiak</td></tr>
<tr><th>ga<th><td>Irish</td></tr>
<tr><th>it<th><td>Italian</td></tr>
<tr><th>ja<th><td>Japanese</td></tr>
<tr><th>jv<th><td>Javanese</td></tr>
<tr><th>kn<th><td>Kannada</td></tr>
<tr><th>ks<th><td>Kashmiri</td></tr>
<tr><th>kk<th><td>Kazakh</td></tr>
<tr><th>rw<th><td>Kinyarwanda (Ruanda)</td></tr>
<tr><th>ky<th><td>Kirghiz</td></tr>
<tr><th>rn<th><td>Kirundi (Rundi)</td></tr>
<tr><th>ko<th><td>Korean</td></tr>
<tr><th>ku<th><td>Kurdish</td></tr>
<tr><th>lo<th><td>Laothian</td></tr>
<tr><th>la<th><td>Latin</td></tr>
<tr><th>lv<th><td>Latvian (Lettish)</td></tr>
<tr><th>li<th><td>Limburgish ( Limburger)</td></tr>
<tr><th>ln<th><td>Lingala</td></tr>
<tr><th>lt<th><td>Lithuanian</td></tr>
<tr><th>mk<th><td>Macedonian</td></tr>
<tr><th>mg<th><td>Malagasy</td></tr>
<tr><th>ms<th><td>Malay</td></tr>
<tr><th>ml<th><td>Malayalam</td></tr>
<tr><th>mt<th><td>Maltese</td></tr>
<tr><th>mi<th><td>Maori</td></tr>
<tr><th>mr<th><td>Marathi</td></tr>
<tr><th>mo<th><td>Moldavian</td></tr>
<tr><th>mn<th><td>Mongolian</td></tr>
<tr><th>na<th><td>Nauru</td></tr>
<tr><th>ne<th><td>Nepali</td></tr>
<tr><th>no<th><td>Norwegian</td></tr>
<tr><th>oc<th><td>Occitan</td></tr>
<tr><th>or<th><td>Oriya</td></tr>
<tr><th>om<th><td>Oromo (Afan, Galla)</td></tr>
<tr><th>ps<th><td>Pashto (Pushto)</td></tr>
<tr><th>pl<th><td>Polish</td></tr>
<tr><th>pt<th><td>Portuguese</td></tr>
<tr><th>pa<th><td>Punjabi</td></tr>
<tr><th>qu<th><td>Quechua</td></tr>
<tr><th>rm<th><td>Rhaeto-Romance</td></tr>
<tr><th>ro<th><td>Romanian</td></tr>
<tr><th>ru<th><td>Russian</td></tr>
<tr><th>sm<th><td>Samoan</td></tr>
<tr><th>sg<th><td>Sangro</td></tr>
<tr><th>sa<th><td>Sanskrit</td></tr>
<tr><th>sr<th><td>Serbian</td></tr>
<tr><th>sh<th><td>Serbo-Croatian</td></tr>
<tr><th>st<th><td>Sesotho</td></tr>
<tr><th>tn<th><td>Setswana</td></tr>
<tr><th>sn<th><td>Shona</td></tr>
<tr><th>ii<th><td>Sichuan Yi</td></tr>
<tr><th>sd<th><td>Sindhi</td></tr>
<tr><th>si<th><td>Sinhalese</td></tr>
<tr><th>ss<th><td>Siswati</td></tr>
<tr><th>sk<th><td>Slovak</td></tr>
<tr><th>sl<th><td>Slovenian</td></tr>
<tr><th>so<th><td>Somali</td></tr>
<tr><th>es<th><td>Spanish</td></tr>
<tr><th>su<th><td>Sundanese</td></tr>
<tr><th>sw<th><td>Swahili (Kiswahili)</td></tr>
<tr><th>sv<th><td>Swedish</td></tr>
<tr><th>tl<th><td>Tagalog</td></tr>
<tr><th>tg<th><td>Tajik</td></tr>
<tr><th>ta<th><td>Tamil</td></tr>
<tr><th>tt<th><td>Tatar</td></tr>
<tr><th>te<th><td>Telugu</td></tr>
<tr><th>th<th><td>Thai</td></tr>
<tr><th>bo<th><td>Tibetan</td></tr>
<tr><th>ti<th><td>Tigrinya</td></tr>
<tr><th>to<th><td>Tonga</td></tr>
<tr><th>ts<th><td>Tsonga</td></tr>
<tr><th>tr<th><td>Turkish</td></tr>
<tr><th>tk<th><td>Turkmen</td></tr>
<tr><th>tw<th><td>Twi</td></tr>
<tr><th>ug<th><td>Uighur</td></tr>
<tr><th>uk<th><td>Ukrainian</td></tr>
<tr><th>ur<th><td>Urdu</td></tr>
<tr><th>uz<th><td>Uzbek</td></tr>
<tr><th>vi<th><td>Vietnamese</td></tr>
<tr><th>vo<th><td>Volapük</td></tr>
<tr><th>wa<th><td>Wallon</td></tr>
<tr><th>cy<th><td>Welsh</td></tr>
<tr><th>wo<th><td>Wolof</td></tr>
<tr><th>xh<th><td>Xhosa</td></tr>
<tr><th>yi<th><td>Yiddish</td></tr>
<tr><th>yo<th><td>Yoruba</td></tr>
<tr><th>zu<th><td>Zulu</td></tr>
</table>
</tr>
</table>
## Interacting with Text Tracks
### Showing tracks programmatically
Some of you would want to turn captions on and off programmatically rather than just forcing the user to do so themselves. This can be easily achieved by modifying the `mode` of the text tracks.
The `mode` can be one of three values `disabled`, `hidden`, and `showing`.
When a text track's `mode` is `disabled`, the track does not show on screen as the video is playing.
When the `mode` is set to `showing`, the track is visible to the viewer and updates while the video is playing.
You can change of a particular track like so:
```js
let tracks = player.textTracks();
for (let i = 0; i < tracks.length; i++) {
let track = tracks[i];
// find the captions track that's in english
if (track.kind === 'captions' && track.language === 'en') {
track.mode = 'showing';
}
}
```
### Doing something when a cue becomes active
Above, we mentioned that `mode` can also be `hidden`, what this means is that the track will update
as the video is playing but it won't be visible to the viewer. This is most useful for `metadata` text tracks.
One usecase for metadata text tracks is to have something happen when their cues become active, to do so, you listen to the `cuechange` event on the track. These events fire when the mode is `showing` as well.
Here's an example:
```js
let tracks = player.textTracks();
let metadataTrack;
for (let i = 0; i < tracks.length; i++) {
let track = tracks[i];
// find the metadata track that's labeled ads
if (track.kind === 'captions' && track.label === 'ads') {
track.mode = 'hidden';
// store it for usage outside of the loop
metadataTrack = track;
}
}
metadataTrack.addEventListener('cuechange', function() {
player.ads.startLinearAdMode();
});
```
## Emulated Text Tracks
By default, video.js will try and use native text tracks if possible and fall back to emulated text tracks if the native functionality is broken or incomplete or non-existent.
The Flash tech will always use the emulated text track functionality.
The video.js API and TextTrack objects were modeled after the w3c's specification.
video.js uses [Mozilla's vtt.js](https://github.com/mozilla/vtt.js) library to parse and display its emulated text tracks.
If you wanted to disable native text track functionality and force video.js to use emulated text tracks always, you can supply the `nativeTextTrack` option to the tech like so:
```js
let player = videojs('myvideo', {
html5: {
nativeTextTrack: false
}
});
```
### Text Track Settings
When using emulated Text Tracks, captions will have an additional item in the menu called "caption settings".
This allows the viewer of the player to change some styles of how the captions are displayed on screen.
If you don't want that, you can disable it by turning off the text track settings component and hiding the menu item like so:
```js
let player = videojs('myvideo', {
// make the text track settings dialog not initialize
textTrackSettings: false
});
```
```css
/* hide the captions settings item from the captions menu */
.vjs-texttrack-settings {
display: none;
}
```
## Text Track Precedence
In general, the Descriptions tracks is of lower precedence than captions and subtitles.
What this means for you?
* If you are using the `default` attribute, videojs will choose the first track that is marked as `default` and turn it on. If There are multiple tracks marked `default`, it will try and turn on the first `captions` or `subtitles` track *before* any `descriptions` tracks.
* This only applied to the emulated captions support, native text tracks behavior will change depending on the browser
* If you select a given track from the menu, videojs will turn off all the other tracks of the same kind. This may seem like you can have both subtitles and captions turned on at the same time but unfortuantely, at this time we only support one track being displayed at a time.
* This means that for emulated text tracks, we'll choose the first captions or subtitles track that is enabled to display.
* When native text tracks are supported, we will still disable the other tracks of the same kind but it is possible that multiple text tracks are shown.
* If a `descriptions` track is selected and subsequently a `subtitles` or `captions` track is selected, the `descriptions` track is disabled and its menu button is also disabled.
* When enabling a track programmatically, there's not much checking that videojs does.
* For emulated text tracks, when it's time to display the captions, video.js would choose the first track that's showing, again choosing `subtitles` or `captions` over `descriptions`, if necessary.
* For native text tracks, this behavior depends on the browser. Some browsers will let you have multiple text tracks but others will disable all other tracks when a new one is selected.
## API
### `player.textTracks() -> TextTrackList`
This is the main interface into the text tracks of the player.
It return a TextTrackList which lists all the tracks on the player.
### `player.remoteTextTracks() -> TextTrackList`
This is a helper method to get a list of all the tracks that were created from `track` elements or that were added to the player by the `addRemoteTextTrack` method. All these tracks are removeable from the player, where-as not all tracks from `player.textTracks()` are necessarily removeable.
### `player.remoteTextTrackEls() -> HTMLTrackElementList`
Another helper method, this is a list of all the `track` elements associated with the player. Both emulated or otherwise.
### `player.addTextTrack(String kind, [String label [, String language]]) -> TextTrack`
This is based on the [w3c spec API](http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack) and when given a kind and an optional label and language, will create a new text track for you to use.
This method is intended for purely programmatic usage of tracks and has one important limitation:
tracks created using this method *cannot* be removed. The native `addTextTrack` does not have a corresponding `removeTextTrack`, so, we actually discourage the usage of this method.
### `player.addRemoteTextTrack(Object options) -> HTMLTrackElement`
This function takes an options object that looks pretty similar to the track element and returns a HTMLTrackElement.
This object has a `track` property on it which is the actual TextTrack object.
This `TextTrack` object is equivalent to the one that can be returned from `player.addTextTrack` with the added bonus that it can be removed from the player.
Internally, video.js will either add a `<track>` element for you, or emulate that depending on whether native text tracks are supported or not.
The options available are:
* `kind`
* `label`
* `language` (also `srclang`)
* `id`
* `src`
### `player.removeRemoteTextTrack(HTMLTrackElement|TextTrack)`
This function takes either an HTMLTrackElement or a TextTrack object and removes it from the player.
+3 -3
Ver Arquivo
@@ -1,6 +1,6 @@
{
"Play": "Tocar",
"Pause": "Pause",
"Pause": "Pausar",
"Current Time": "Tempo",
"Duration Time": "Duração",
"Remaining Time": "Tempo Restante",
@@ -18,9 +18,9 @@
"Captions": "Anotações",
"captions off": "Sem Anotações",
"Chapters": "Capítulos",
"You aborted the media playback": "Você parou a execução de vídeo.",
"You aborted the media playback": "Você parou a execução do vídeo.",
"A network error caused the media download to fail part-way.": "Um erro na rede fez o vídeo parar parcialmente.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "O vídeo não pode ser carregado, ou porque houve um problema com sua rede ou pelo formato do vídeo não ser suportado.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "A Execução foi interrompida por um problema com o vídeo ou por seu navegador não dar suporte ao seu formato.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "A execução foi interrompida por um problema com o vídeo ou por seu navegador não dar suporte ao seu formato.",
"No compatible source was found for this media.": "Não foi encontrada fonte de vídeo compatível."
}
+9 -9
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": "5.9.2",
"version": "5.10.7",
"copyright": "Copyright Brightcove, Inc. <https://www.brightcove.com/>",
"license": "Apache-2.0",
"keywords": [
@@ -23,16 +23,16 @@
"main": "./dist/video.js",
"style": "./dist/video-js.css",
"dependencies": {
"global": "^4.3.0",
"lodash-compat": "^3.9.3",
"object.assign": "^4.0.1",
"safe-json-parse": "^4.0.0",
"global": "4.3.0",
"lodash-compat": "3.10.2",
"object.assign": "4.0.3",
"safe-json-parse": "4.0.0",
"tsml": "1.0.1",
"videojs-font": "1.5.1",
"videojs-font": "2.0.0",
"videojs-ie8": "1.1.2",
"videojs-swf": "5.0.1",
"videojs-vtt.js": "^0.12.1",
"xhr": "~2.2.0"
"videojs-vtt.js": "0.12.1",
"xhr": "2.2.0"
},
"devDependencies": {
"babel": "^5.2.2",
@@ -44,6 +44,7 @@
"chg": "^0.3.2",
"css": "^2.2.0",
"es5-shim": "^4.1.3",
"gkatsev-grunt-sass": "^1.1.1",
"grunt": "^0.4.4",
"grunt-aws-s3": "^0.12.1",
"grunt-banner": "^0.4.0",
@@ -64,7 +65,6 @@
"grunt-fastly": "^0.1.3",
"grunt-github-releaser": "^0.1.17",
"grunt-karma": "^0.8.3",
"grunt-sass": "^1.0.0",
"grunt-version": "~0.3.0",
"grunt-videojs-languages": "0.0.4",
"grunt-zip": "0.10.2",
+1 -1
Ver Arquivo
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Video.js Text Descriptions, Chapters &amp; Captions Example</title>
+1 -1
Ver Arquivo
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Video.js Icons Sandbox</title>
+1 -1
Ver Arquivo
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Video.js Sandbox</title>
+1 -1
Ver Arquivo
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Video.js Plugin Example</title>
-1
Ver Arquivo
@@ -1,2 +1 @@
$icon-font-path: 'font' !default;
$icon-codepoints: false !default;
+3
Ver Arquivo
@@ -0,0 +1,3 @@
.video-js .vjs-audio-button {
@extend .vjs-icon-audio;
}
+11 -2
Ver Arquivo
@@ -20,13 +20,22 @@
@include transition($trans);
}
// IE 8 hack for media queries
$ie8screen: "\\0screen";
// Video has started playing AND user is inactive
.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
visibility: hidden;
// Remain visible for screen reader and keyboard users
visibility: visible;
opacity: 0;
$trans: visibility 1.0s, opacity 1.0s;
@include transition($trans);
// Make controls hidden in IE8 for now
@media #{$ie8screen} {
visibility: hidden;
}
}
.vjs-controls-disabled .vjs-control-bar,
@@ -42,11 +51,11 @@
visibility: visible;
}
// IE8 is flakey with fonts, and you have to change the actual content to force
// fonts to show/hide properly.
// - "\9" IE8 hack didn't work for this
// Found in XP IE8 from http://modern.ie. Does not show up in "IE8 mode" in IE9
$ie8screen: "\\0screen";
.vjs-user-inactive.vjs-playing .vjs-control-bar :before {
@media #{$ie8screen} { content: ""; }
}
+1
Ver Arquivo
@@ -16,6 +16,7 @@
display: block;
padding: 0; margin: 0;
overflow: auto;
font-family: $text-font-family;
}
// prevent menus from opening while scrubbing (FF, IE)
+2 -5
Ver Arquivo
@@ -2,11 +2,7 @@
@import "private-variables";
@import "utilities";
@if $icon-codepoints {
@import "node_modules/videojs-font/scss/icons-codepoints";
} @else {
@import "node_modules/videojs-font/scss/icons";
}
@import "node_modules/videojs-font/scss/icons";
@import "components/layout";
@import "components/big-play";
@@ -39,6 +35,7 @@
@import "components/chapters";
@import "components/descriptions";
@import "components/subtitles";
@import "components/audio";
@import "components/adaptive";
@import "components/captions-settings";
@import "components/modal-dialog";
-2
Ver Arquivo
@@ -1,3 +1 @@
$icon-codepoints: true;
@import "video-js";
@@ -0,0 +1,64 @@
/**
* @file audio-track-button.js
*/
import TrackButton from '../track-button.js';
import Component from '../../component.js';
import * as Fn from '../../utils/fn.js';
import AudioTrackMenuItem from './audio-track-menu-item.js';
/**
* The base class for buttons that toggle specific text track types (e.g. subtitles)
*
* @param {Player|Object} player
* @param {Object=} options
* @extends TrackButton
* @class AudioTrackButton
*/
class AudioTrackButton extends TrackButton {
constructor(player, options = {}) {
options.tracks = player.audioTracks && player.audioTracks();
super(player, options);
this.el_.setAttribute('aria-label', 'Audio Menu');
}
/**
* Allow sub components to stack CSS class names
*
* @return {String} The constructed class name
* @method buildCSSClass
*/
buildCSSClass() {
return `vjs-audio-button ${super.buildCSSClass()}`;
}
/**
* Create a menu item for each audio track
*
* @return {Array} Array of menu items
* @method createItems
*/
createItems(items = []) {
let tracks = this.player_.audioTracks && this.player_.audioTracks();
if (!tracks) {
return items;
}
for (let i = 0; i < tracks.length; i++) {
let track = tracks[i];
items.push(new AudioTrackMenuItem(this.player_, {
// MenuItem is selectable
'selectable': true,
'track': track
}));
}
return items;
}
}
Component.registerComponent('AudioTrackButton', AudioTrackButton);
export default AudioTrackButton;
@@ -0,0 +1,71 @@
/**
* @file audio-track-menu-item.js
*/
import MenuItem from '../../menu/menu-item.js';
import Component from '../../component.js';
import * as Fn from '../../utils/fn.js';
/**
* The audio track menu item
*
* @param {Player|Object} player
* @param {Object=} options
* @extends MenuItem
* @class AudioTrackMenuItem
*/
class AudioTrackMenuItem extends MenuItem {
constructor(player, options) {
let track = options.track;
let tracks = player.audioTracks();
// Modify options for parent MenuItem class's init.
options.label = track.label || track.language || 'Unknown';
options.selected = track.enabled;
super(player, options);
this.track = track;
if (tracks) {
let changeHandler = Fn.bind(this, this.handleTracksChange);
tracks.addEventListener('change', changeHandler);
this.on('dispose', () => {
tracks.removeEventListener('change', changeHandler);
});
}
}
/**
* Handle click on audio track
*
* @method handleClick
*/
handleClick(event) {
let tracks = this.player_.audioTracks();
super.handleClick(event);
if (!tracks) return;
for (let i = 0; i < tracks.length; i++) {
let track = tracks[i];
if (track === this.track) {
track.enabled = true;
}
}
}
/**
* Handle audio track change
*
* @method handleTracksChange
*/
handleTracksChange(event) {
this.selected(this.track.enabled);
}
}
Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
export default AudioTrackMenuItem;
+2 -1
Ver Arquivo
@@ -19,6 +19,7 @@ import ChaptersButton from './text-track-controls/chapters-button.js';
import DescriptionsButton from './text-track-controls/descriptions-button.js';
import SubtitlesButton from './text-track-controls/subtitles-button.js';
import CaptionsButton from './text-track-controls/captions-button.js';
import AudioTrackButton from './audio-track-controls/audio-track-button.js';
import PlaybackRateMenuButton from './playback-rate-menu/playback-rate-menu-button.js';
import CustomControlSpacer from './spacer-controls/custom-control-spacer.js';
@@ -47,7 +48,6 @@ class ControlBar extends Component {
}
ControlBar.prototype.options_ = {
loadEvent: 'play',
children: [
'playToggle',
'volumeMenuButton',
@@ -63,6 +63,7 @@ ControlBar.prototype.options_ = {
'descriptionsButton',
'subtitlesButton',
'captionsButton',
'audioTrackButton',
'fullscreenToggle'
]
};
@@ -75,9 +75,11 @@ class ChaptersButton extends TextTrackButton {
createMenu() {
let tracks = this.player_.textTracks() || [];
let chaptersTrack;
let items = this.items = [];
let items = this.items || [];
for (let i = 0, length = tracks.length; i < length; i++) {
for (let i = tracks.length - 1; i >= 0; i--) {
// We will always choose the last track as our chaptersTrack
let track = tracks[i];
if (track['kind'] === this.kind_) {
@@ -97,6 +99,12 @@ class ChaptersButton extends TextTrackButton {
});
menu.children_.unshift(title);
Dom.insertElFirst(title, menu.contentEl());
} else {
// We will empty out the menu children each time because we want a
// fresh new menu child list each time
items.forEach(item => menu.removeChild(item));
// Empty out the ChaptersButton menu items because we no longer need them
items = [];
}
if (chaptersTrack && chaptersTrack.cues == null) {
@@ -124,17 +132,15 @@ class ChaptersButton extends TextTrackButton {
menu.addChild(mi);
}
this.addChild(menu);
}
if (this.items.length > 0) {
if (items.length > 0) {
this.show();
}
// Assigning the value of items back to this.items for next iteration
this.items = items;
return menu;
}
}
ChaptersButton.prototype.kind_ = 'chapters';
@@ -1,7 +1,7 @@
/**
* @file text-track-button.js
*/
import MenuButton from '../../menu/menu-button.js';
import TrackButton from '../track-button.js';
import Component from '../../component.js';
import * as Fn from '../../utils/fn.js';
import TextTrackMenuItem from './text-track-menu-item.js';
@@ -15,32 +15,20 @@ import OffTextTrackMenuItem from './off-text-track-menu-item.js';
* @extends MenuButton
* @class TextTrackButton
*/
class TextTrackButton extends MenuButton {
class TextTrackButton extends TrackButton {
constructor(player, options = {}){
options.tracks = player.textTracks();
constructor(player, options){
super(player, options);
let tracks = this.player_.textTracks();
if (this.items.length <= 1) {
this.hide();
}
if (!tracks) {
return;
}
let updateHandler = Fn.bind(this, this.update);
tracks.addEventListener('removetrack', updateHandler);
tracks.addEventListener('addtrack', updateHandler);
this.player_.on('dispose', function() {
tracks.removeEventListener('removetrack', updateHandler);
tracks.removeEventListener('addtrack', updateHandler);
});
}
// Create a menu item for each text track
/**
* Create a menu item for each text track
*
* @return {Array} Array of menu items
* @method createItems
*/
createItems(items=[]) {
// Add an OFF menu item to turn all tracks off
items.push(new OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ }));
+44
Ver Arquivo
@@ -0,0 +1,44 @@
/**
* @file track-button.js
*/
import MenuButton from '../menu/menu-button.js';
import Component from '../component.js';
import * as Fn from '../utils/fn.js';
/**
* The base class for buttons that toggle specific text track types (e.g. subtitles)
*
* @param {Player|Object} player
* @param {Object=} options
* @extends MenuButton
* @class TrackButton
*/
class TrackButton extends MenuButton {
constructor(player, options){
let tracks = options.tracks;
super(player, options);
if (this.items.length <= 1) {
this.hide();
}
if (!tracks) {
return;
}
let updateHandler = Fn.bind(this, this.update);
tracks.addEventListener('removetrack', updateHandler);
tracks.addEventListener('addtrack', updateHandler);
this.player_.on('dispose', function() {
tracks.removeEventListener('removetrack', updateHandler);
tracks.removeEventListener('addtrack', updateHandler);
});
}
}
Component.registerComponent('TrackButton', TrackButton);
export default TrackButton;
+6 -1
Ver Arquivo
@@ -11,7 +11,7 @@ EventTarget.prototype.on = function(type, fn) {
// Remove the addEventListener alias before calling Events.on
// so we don't get into an infinite type loop
let ael = this.addEventListener;
this.addEventListener = Function.prototype;
this.addEventListener = () => {};
Events.on(this, type, fn);
this.addEventListener = ael;
};
@@ -23,7 +23,12 @@ EventTarget.prototype.off = function(type, fn) {
EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
EventTarget.prototype.one = function(type, fn) {
// Remove the addEventListener alias before calling Events.on
// so we don't get into an infinite type loop
let ael = this.addEventListener;
this.addEventListener = () => {};
Events.one(this, type, fn);
this.addEventListener = ael;
};
EventTarget.prototype.trigger = function(event) {
+53 -8
Ver Arquivo
@@ -22,6 +22,8 @@ import safeParseTuple from 'safe-json-parse/tuple';
import assign from 'object.assign';
import mergeOptions from './utils/merge-options.js';
import textTrackConverter from './tracks/text-track-list-converter.js';
import AudioTrackList from './tracks/audio-track-list.js';
import VideoTrackList from './tracks/video-track-list.js';
// Include required child components (importing also registers them)
import MediaLoader from './tech/loader.js';
@@ -555,7 +557,9 @@ class Player extends Component {
'source': source,
'playerId': this.id(),
'techId': `${this.id()}_${techName}_api`,
'videoTracks': this.videoTracks_,
'textTracks': this.textTracks_,
'audioTracks': this.audioTracks_,
'autoplay': this.options_.autoplay,
'preload': this.options_.preload,
'loop': this.options_.loop,
@@ -648,7 +652,9 @@ class Player extends Component {
*/
unloadTech_() {
// Save the current text tracks so that we can reuse the same text tracks with the next tech
this.videoTracks_ = this.videoTracks();
this.textTracks_ = this.textTracks();
this.audioTracks_ = this.audioTracks();
this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
this.isReady_ = false;
@@ -767,7 +773,12 @@ class Player extends Component {
// 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()) {
delete this.tag.poster; // Chrome Fix. Fixed in Chrome v16.
try {
delete this.tag.poster; // Chrome Fix. Fixed in Chrome v16.
}
catch (e) {
log('deleting tag.poster throws in some browsers', e);
}
this.play();
}
}
@@ -2509,12 +2520,48 @@ class Player extends Component {
}
/**
* Text tracks are tracks of timed text events.
* Captions - text displayed over the video for the hearing impaired
* Subtitles - text displayed over the video for those who don't understand language in the video
* Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
* Descriptions - audio descriptions that are read back to the user by a screen reading device
* Get a video track list
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
*
* @return {VideoTrackList} thes current video track list
* @method videoTracks
*/
videoTracks() {
// if we have not yet loadTech_, we create videoTracks_
// these will be passed to the tech during loading
if (!this.tech_) {
this.videoTracks_ = this.videoTracks_ || new VideoTrackList();
return this.videoTracks_;
}
return this.tech_.videoTracks();
}
/**
* Get an audio track list
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
*
* @return {AudioTrackList} thes current audio track list
* @method audioTracks
*/
audioTracks() {
// if we have not yet loadTech_, we create videoTracks_
// these will be passed to the tech during loading
if (!this.tech_) {
this.audioTracks_ = this.audioTracks_ || new AudioTrackList();
return this.audioTracks_;
}
return this.tech_.audioTracks();
}
/*
* Text tracks are tracks of timed text events.
* Captions - text displayed over the video for the hearing impaired
* Subtitles - text displayed over the video for those who don't understand language in the video
* Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
* Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
*/
/**
* Get an array of associated text tracks. captions, subtitles, chapters, descriptions
@@ -2609,8 +2656,6 @@ class Player extends Component {
// initialTime: function(){ return this.techCall_('initialTime'); },
// startOffsetTime: function(){ return this.techCall_('startOffsetTime'); },
// played: function(){ return this.techCall_('played'); },
// videoTracks: function(){ return this.techCall_('videoTracks'); },
// audioTracks: function(){ return this.techCall_('audioTracks'); },
// defaultPlaybackRate: function(){ return this.techCall_('defaultPlaybackRate'); },
// defaultMuted: function(){ return this.techCall_('defaultMuted'); }
+4 -3
Ver Arquivo
@@ -95,10 +95,11 @@ function FlashRtmpDecorator(Flash) {
* Pass the source to the flash object
* Adaptive source handlers will have more complicated workflows before passing
* video data to the video element
* @param {Object} source The source object
* @param {Flash} tech The instance of the Flash tech
* @param {Object} source The source object
* @param {Flash} tech The instance of the Flash tech
* @param {Object} options The options to pass to the source
*/
Flash.rtmpSourceHandler.handleSource = function(source, tech){
Flash.rtmpSourceHandler.handleSource = function(source, tech, options){
let srcParts = Flash.streamToParts(source.src);
tech['setRtmpConnection'](srcParts.connection);
+5 -4
Ver Arquivo
@@ -312,7 +312,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,videoTracks,audioTracks,videoWidth,videoHeight'.split(',');
const _readOnly = 'networkState,readyState,initialTime,duration,startOffsetTime,paused,ended,videoWidth,videoHeight'.split(',');
function _createSetter(attr){
var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
@@ -397,10 +397,11 @@ Flash.nativeSourceHandler.canHandleSource = function(source){
* Adaptive source handlers will have more complicated workflows before passing
* video data to the video element
*
* @param {Object} source The source object
* @param {Flash} tech The instance of the Flash tech
* @param {Object} source The source object
* @param {Flash} tech The instance of the Flash tech
* @param {Object} options The options to pass to the source
*/
Flash.nativeSourceHandler.handleSource = function(source, tech){
Flash.nativeSourceHandler.handleSource = function(source, tech, options){
tech.setSrc(source.src);
};
+135 -25
Ver Arquivo
@@ -9,11 +9,14 @@ import * as Dom from '../utils/dom.js';
import * as Url from '../utils/url.js';
import * as Fn from '../utils/fn.js';
import log from '../utils/log.js';
import tsml from 'tsml';
import TextTrack from '../../../src/js/tracks/text-track.js';
import * as browser from '../utils/browser.js';
import document from 'global/document';
import window from 'global/window';
import assign from 'object.assign';
import mergeOptions from '../utils/merge-options.js';
import toTitleCase from '../utils/to-title-case.js';
/**
* HTML5 Media Controller - Wrapper for HTML5 Media API
@@ -29,6 +32,7 @@ class Html5 extends Tech {
super(options, ready);
const source = options.source;
let crossoriginTracks = false;
// Set the source if one is provided
// 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
@@ -61,6 +65,11 @@ class Html5 extends Tech {
// store HTMLTrackElement and TextTrack to remote list
this.remoteTextTrackEls().addTrackElement_(node);
this.remoteTextTracks().addTrack_(node.track);
if (!crossoriginTracks &&
!this.el_.hasAttribute('crossorigin') &&
Url.isCrossOrigin(node.src)) {
crossoriginTracks = true;
}
}
}
}
@@ -70,7 +79,30 @@ class Html5 extends Tech {
}
}
let trackTypes = ['audio', 'video'];
// ProxyNativeTextTracks
trackTypes.forEach((type) => {
let capitalType = toTitleCase(type);
if (!this[`featuresNative${capitalType}Tracks`]) {
return;
}
let tl = this.el()[`${type}Tracks`];
if (tl && tl.addEventListener) {
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_`]));
}
});
if (this.featuresNativeTextTracks) {
if (crossoriginTracks) {
log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.
This may prevent text tracks from loading.`);
}
this.handleTextTrackChange_ = Fn.bind(this, this.handleTextTrackChange);
this.handleTextTrackAdd_ = Fn.bind(this, this.handleTextTrackAdd);
this.handleTextTrackRemove_ = Fn.bind(this, this.handleTextTrackRemove);
@@ -96,25 +128,20 @@ class Html5 extends Tech {
* @method dispose
*/
dispose() {
let tt = this.el().textTracks;
let emulatedTt = this.textTracks();
// remove native event listeners
if (tt && tt.removeEventListener) {
tt.removeEventListener('change', this.handleTextTrackChange_);
tt.removeEventListener('addtrack', this.handleTextTrackAdd_);
tt.removeEventListener('removetrack', this.handleTextTrackRemove_);
}
// clearout the emulated text track list.
let i = emulatedTt.length;
while (i--) {
emulatedTt.removeTrack_(emulatedTt[i]);
}
// Un-ProxyNativeTracks
['audio', 'video', 'text'].forEach((type) => {
let capitalType = toTitleCase(type);
let tl = this.el_[`${type}Tracks`];
if (tl && tl.removeEventListener) {
tl.removeEventListener('change', this[`handle${capitalType}TrackChange_`]);
tl.removeEventListener('addtrack', this[`handle${capitalType}TrackAdd_`]);
tl.removeEventListener('removetrack', this[`handle${capitalType}TrackRemove_`]);
}
});
Html5.disposeMediaElement(this.el_);
// tech will handle clearing of the emulated track list
super.dispose();
}
@@ -290,6 +317,43 @@ class Html5 extends Tech {
this.textTracks().removeTrack_(e.track);
}
handleVideoTrackChange_(e) {
let vt = this.videoTracks();
this.videoTracks().trigger({
type: 'change',
target: vt,
currentTarget: vt,
srcElement: vt
});
}
handleVideoTrackAdd_(e) {
this.videoTracks().addTrack_(e.track);
}
handleVideoTrackRemove_(e) {
this.videoTracks().removeTrack_(e.track);
}
handleAudioTrackChange_(e) {
let audioTrackList = this.audioTracks();
this.audioTracks().trigger({
type: 'change',
target: audioTrackList,
currentTarget: audioTrackList,
srcElement: audioTrackList
});
}
handleAudioTrackAdd_(e) {
this.audioTracks().addTrack_(e.track);
}
handleAudioTrackRemove_(e) {
this.audioTracks().removeTrack_(e.track);
}
/**
* Play for html5 tech
*
@@ -903,10 +967,11 @@ Html5.nativeSourceHandler.canHandleSource = function(source){
* Adaptive source handlers will have more complicated workflows before passing
* video data to the video element
*
* @param {Object} source The source object
* @param {Html5} tech The instance of the Html5 tech
* @param {Object} source The source object
* @param {Html5} tech The instance of the Html5 tech
* @param {Object} options The options to pass to the source
*/
Html5.nativeSourceHandler.handleSource = function(source, tech){
Html5.nativeSourceHandler.handleSource = function(source, tech, options){
tech.setSrc(source.src);
};
@@ -927,9 +992,14 @@ Html5.registerSourceHandler(Html5.nativeSourceHandler);
* @return {Boolean}
*/
Html5.canControlVolume = function(){
var volume = Html5.TEST_VID.volume;
Html5.TEST_VID.volume = (volume / 2) + 0.1;
return volume !== Html5.TEST_VID.volume;
// IE will error if Windows Media Player not installed #3315
try {
var volume = Html5.TEST_VID.volume;
Html5.TEST_VID.volume = (volume / 2) + 0.1;
return volume !== Html5.TEST_VID.volume;
} catch(e) {
return false;
}
};
/*
@@ -943,9 +1013,14 @@ Html5.canControlPlaybackRate = function(){
if (browser.IS_ANDROID && browser.IS_CHROME) {
return false;
}
var playbackRate = Html5.TEST_VID.playbackRate;
Html5.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
return playbackRate !== Html5.TEST_VID.playbackRate;
// IE will error if Windows Media Player not installed #3315
try {
var playbackRate = Html5.TEST_VID.playbackRate;
Html5.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
return playbackRate !== Html5.TEST_VID.playbackRate;
} catch(e) {
return false;
}
};
/*
@@ -975,6 +1050,27 @@ Html5.supportsNativeTextTracks = function() {
return supportsTextTracks;
};
/*
* Check to see if native video tracks are supported by this browser/device
*
* @return {Boolean}
*/
Html5.supportsNativeVideoTracks = function() {
let supportsVideoTracks = !!Html5.TEST_VID.videoTracks;
return supportsVideoTracks;
};
/*
* Check to see if native audio tracks are supported by this browser/device
*
* @return {Boolean}
*/
Html5.supportsNativeAudioTracks = function() {
let supportsAudioTracks = !!Html5.TEST_VID.audioTracks;
return supportsAudioTracks;
};
/**
* An array of events available on the Html5 tech.
*
@@ -1048,6 +1144,20 @@ Html5.prototype['featuresProgressEvents'] = true;
*/
Html5.prototype['featuresNativeTextTracks'] = Html5.supportsNativeTextTracks();
/**
* Sets the tech's status on native text track support
*
* @type {Boolean}
*/
Html5.prototype['featuresNativeVideoTracks'] = Html5.supportsNativeVideoTracks();
/**
* Sets the tech's status on native audio track support
*
* @type {Boolean}
*/
Html5.prototype['featuresNativeAudioTracks'] = Html5.supportsNativeAudioTracks();
// HTML5 Feature detection and Device Fixes --------------------------------- //
let canPlayType;
const mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
+147 -17
Ver Arquivo
@@ -10,6 +10,10 @@ import HTMLTrackElementList from '../tracks/html-track-element-list';
import mergeOptions from '../utils/merge-options.js';
import TextTrack from '../tracks/text-track';
import TextTrackList from '../tracks/text-track-list';
import VideoTrack from '../tracks/video-track';
import VideoTrackList from '../tracks/video-track-list';
import AudioTrackList from '../tracks/audio-track-list';
import AudioTrack from '../tracks/audio-track';
import * as Fn from '../utils/fn.js';
import log from '../utils/log.js';
import { createTimeRange } from '../utils/time-ranges.js';
@@ -45,6 +49,8 @@ class Tech extends Component {
});
this.textTracks_ = options.textTracks;
this.videoTracks_ = options.videoTracks;
this.audioTracks_ = options.audioTracks;
// Manually track progress in cases where the browser/flash player doesn't report it.
if (!this.featuresProgressEvents) {
@@ -65,6 +71,7 @@ class Tech extends Component {
}
this.initTextTrackListeners();
this.initTrackListeners();
// Turn on component tap events
this.emitTapEvents();
@@ -218,15 +225,9 @@ class Tech extends Component {
* @method dispose
*/
dispose() {
// clear out text tracks because we can't reuse them between techs
let textTracks = this.textTracks();
if (textTracks) {
let i = textTracks.length;
while(i--) {
this.removeRemoteTextTrack(textTracks[i]);
}
}
// clear out all tracks because we can't reuse them between techs
this.clearTracks(['audio', 'video', 'text']);
// Turn off any manual progress or timeupdate tracking
if (this.manualProgress) { this.manualProgressOff(); }
@@ -236,6 +237,33 @@ class Tech extends Component {
super.dispose();
}
/**
* clear out a track list, or multiple track lists
*
* Note: Techs without source handlers should call this between
* sources for video & audio tracks, as usually you don't want
* to use them between tracks and we have no automatic way to do
* it for you
*
* @method clearTracks
* @param {Array|String} types type(s) of track lists to empty
*/
clearTracks(types) {
types = [].concat(types);
// clear out all tracks because we can't reuse them between techs
types.forEach((type) => {
let list = this[`${type}Tracks`]() || [];
let i = list.length;
while (i--) {
let track = list[i];
if (type === 'text') {
this.removeRemoteTextTrack(track);
}
list.removeTrack_(track);
}
});
}
/**
* Reset the tech. Removes all sources and resets readyState.
*
@@ -313,6 +341,32 @@ class Tech extends Component {
}));
}
/**
* Initialize audio and video track listeners
*
* @method initTrackListeners
*/
initTrackListeners() {
const trackTypes = ['video', 'audio'];
trackTypes.forEach((type) => {
let trackListChanges = () => {
this.trigger(`${type}trackchange`);
};
let tracks = this[`${type}Tracks`]();
tracks.addEventListener('removetrack', trackListChanges);
tracks.addEventListener('addtrack', trackListChanges);
this.on('dispose', () => {
tracks.removeEventListener('removetrack', trackListChanges);
tracks.removeEventListener('addtrack', trackListChanges);
});
});
}
/**
* Emulate texttracks
*
@@ -337,8 +391,10 @@ class Tech extends Component {
script.onload = null;
script.onerror = null;
});
this.el().parentNode.appendChild(script);
// but have not loaded yet and we set it to true before the inject so that
// we don't overwrite the injected window.WebVTT if it loads right away
window['WebVTT'] = true;
this.el().parentNode.appendChild(script);
}
let updateDisplay = () => this.trigger('texttrackchange');
@@ -362,6 +418,28 @@ class Tech extends Component {
});
}
/**
* Get videotracks
*
* @returns {VideoTrackList}
* @method videoTracks
*/
videoTracks() {
this.videoTracks_ = this.videoTracks_ || new VideoTrackList();
return this.videoTracks_;
}
/**
* Get audiotracklist
*
* @returns {AudioTrackList}
* @method audioTracks
*/
audioTracks() {
this.audioTracks_ = this.audioTracks_ || new AudioTrackList();
return this.audioTracks_;
}
/*
* Provide default methods for text tracks.
*
@@ -536,14 +614,31 @@ class Tech extends Component {
}
}
/*
/**
* List of associated text tracks
*
* @type {Array}
* @type {TextTrackList}
* @private
*/
Tech.prototype.textTracks_;
/**
* List of associated audio tracks
*
* @type {AudioTrackList}
* @private
*/
Tech.prototype.audioTracks_;
/**
* List of associated video tracks
*
* @type {VideoTrackList}
* @private
*/
Tech.prototype.videoTracks_;
var createTrackHelper = function(self, kind, label, language, options={}) {
let tracks = self.textTracks();
@@ -713,19 +808,54 @@ Tech.withSourceHandlers = function(_Tech){
this.disposeSourceHandler();
this.off('dispose', this.disposeSourceHandler);
this.currentSource_ = source;
this.sourceHandler_ = sh.handleSource(source, this);
// if we have a source and get another one
// then we are loading something new
// than clear all of our current tracks
if (this.currentSource_) {
this.clearTracks(['audio', 'video']);
this.currentSource_ = null;
}
if (sh !== _Tech.nativeSourceHandler) {
this.currentSource_ = source;
// Catch if someone replaced the src without calling setSource.
// If they do, set currentSource_ to null and dispose our source handler.
this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
this.one(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
}
this.sourceHandler_ = sh.handleSource(source, this, this.options_);
this.on('dispose', this.disposeSourceHandler);
return this;
};
/*
* Clean up any existing source handler
*/
_Tech.prototype.disposeSourceHandler = function(){
// On the first loadstart after setSource
_Tech.prototype.firstLoadStartListener_ = function() {
this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
};
// On successive loadstarts when setSource has not been called again
_Tech.prototype.successiveLoadStartListener_ = function() {
this.currentSource_ = null;
this.disposeSourceHandler();
this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
};
/*
* Clean up any existing source handler
*/
_Tech.prototype.disposeSourceHandler = function() {
if (this.sourceHandler_ && this.sourceHandler_.dispose) {
this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
this.sourceHandler_.dispose();
this.sourceHandler_ = null;
}
};
+113
Ver Arquivo
@@ -0,0 +1,113 @@
/**
* @file audio-track-list.js
*/
import TrackList from './track-list';
import * as browser from '../utils/browser.js';
import document from 'global/document';
/**
* anywhere we call this function we diverge from the spec
* as we only support one enabled audiotrack at a time
*
* @param {Array|AudioTrackList} list list to work on
* @param {AudioTrack} track the track to skip
*/
const disableOthers = function(list, track) {
for (let i = 0; i < list.length; i++) {
if (track.id === list[i].id) {
continue;
}
// another audio track is enabled, disable it
list[i].enabled = false;
}
};
/**
* A list of possible audio tracks. All functionality is in the
* base class Tracklist and the spec for AudioTrackList is located at:
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
*
* interface AudioTrackList : EventTarget {
* readonly attribute unsigned long length;
* getter AudioTrack (unsigned long index);
* AudioTrack? getTrackById(DOMString id);
*
* attribute EventHandler onchange;
* attribute EventHandler onaddtrack;
* attribute EventHandler onremovetrack;
* };
*
* @param {AudioTrack[]} tracks a list of audio tracks to instantiate the list with
* @extends TrackList
* @class AudioTrackList
*/
class AudioTrackList extends TrackList {
constructor(tracks = []) {
let list;
// make sure only 1 track is enabled
// sorted from last index to first index
for (let i = tracks.length - 1; i >= 0; i--) {
if (tracks[i].enabled) {
disableOthers(tracks, tracks[i]);
break;
}
}
// IE8 forces us to implement inheritance ourselves
// as it does not support Object.defineProperty properly
if (browser.IS_IE8) {
list = document.createElement('custom');
for (let prop in TrackList.prototype) {
if (prop !== 'constructor') {
list[prop] = TrackList.prototype[prop];
}
}
for (let prop in AudioTrackList.prototype) {
if (prop !== 'constructor') {
list[prop] = AudioTrackList.prototype[prop];
}
}
}
list = super(tracks, list);
list.changing_ = false;
return list;
}
addTrack_(track) {
if (track.enabled) {
disableOthers(this, track);
}
super.addTrack_(track);
// native tracks don't have this
if (!track.addEventListener) {
return;
}
track.addEventListener('enabledchange', () => {
// when we are disabling other tracks (since we don't support
// more than one track at a time) we will set changing_
// to true so that we don't trigger additional change events
if (this.changing_) {
return;
}
this.changing_ = true;
disableOthers(this, track);
this.changing_ = false;
this.trigger('change');
});
}
addTrack(track) {
this.addTrack_(track);
}
removeTrack(track) {
super.removeTrack_(track);
}
}
export default AudioTrackList;
+63
Ver Arquivo
@@ -0,0 +1,63 @@
import {AudioTrackKind} from './track-enums';
import Track from './track';
import merge from '../utils/merge-options';
import * as browser from '../utils/browser.js';
/**
* A single audio text track as defined in:
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack
*
* interface AudioTrack {
* readonly attribute DOMString id;
* readonly attribute DOMString kind;
* readonly attribute DOMString label;
* readonly attribute DOMString language;
* attribute boolean enabled;
* };
*
* @param {Object=} options Object of option names and values
* @class AudioTrack
*/
class AudioTrack extends Track {
constructor(options = {}) {
let settings = merge(options, {
kind: AudioTrackKind[options.kind] || ''
});
// on IE8 this will be a document element
// for every other browser this will be a normal object
let track = super(settings);
let enabled = false;
if (browser.IS_IE8) {
for (let prop in AudioTrack.prototype) {
if (prop !== 'constructor') {
track[prop] = AudioTrack.prototype[prop];
}
}
}
Object.defineProperty(track, 'enabled', {
get() { return enabled; },
set(newEnabled) {
// an invalid or unchanged value
if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
return;
}
enabled = newEnabled;
this.trigger('enabledchange');
}
});
// if the user sets this track to selected then
// set selected to that true value otherwise
// we keep it false
if (settings.enabled) {
track.enabled = settings.enabled;
}
track.loaded_ = true;
return track;
}
}
export default AudioTrack;
-39
Ver Arquivo
@@ -1,39 +0,0 @@
/**
* @file text-track-enums.js
*/
/**
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
*
* enum TextTrackMode { "disabled", "hidden", "showing" };
*/
const TextTrackMode = {
disabled: 'disabled',
hidden: 'hidden',
showing: 'showing'
};
/**
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackkind
*
* enum TextTrackKind {
* "subtitles",
* "captions",
* "descriptions",
* "chapters",
* "metadata"
* };
*/
const TextTrackKind = {
subtitles: 'subtitles',
captions: 'captions',
descriptions: 'descriptions',
chapters: 'chapters',
metadata: 'metadata'
};
/* jshint ignore:start */
// we ignore jshint here because it does not see
// TextTrackMode or TextTrackKind as defined here somehow...
export { TextTrackMode, TextTrackKind };
/* jshint ignore:end */
+18 -69
Ver Arquivo
@@ -1,14 +1,15 @@
/**
* @file text-track-list.js
*/
import EventTarget from '../event-target';
import TrackList from './track-list';
import * as Fn from '../utils/fn.js';
import * as browser from '../utils/browser.js';
import document from 'global/document';
/**
* A text track list as defined in:
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist
* A list of possible text tracks. All functionality is in the
* base class TrackList. The spec for TextTrackList is located at:
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist
*
* interface TextTrackList : EventTarget {
* readonly attribute unsigned long length;
@@ -20,19 +21,23 @@ import document from 'global/document';
* attribute EventHandler onremovetrack;
* };
*
* @param {Track[]} tracks A list of tracks to initialize the list with
* @extends EventTarget
* @param {TextTrack[]} tracks A list of tracks to initialize the list with
* @extends TrackList
* @class TextTrackList
*/
class TextTrackList extends EventTarget {
class TextTrackList extends TrackList {
constructor(tracks = []) {
super();
let list = this;
let list;
// IE8 forces us to implement inheritance ourselves
// as it does not support Object.defineProperty properly
if (browser.IS_IE8) {
list = document.createElement('custom');
for (let prop in TrackList.prototype) {
if (prop !== 'constructor') {
list[prop] = TrackList.prototype[prop];
}
}
for (let prop in TextTrackList.prototype) {
if (prop !== 'constructor') {
list[prop] = TextTrackList.prototype[prop];
@@ -40,54 +45,15 @@ class TextTrackList extends EventTarget {
}
}
list.tracks_ = [];
Object.defineProperty(list, 'length', {
get() {
return this.tracks_.length;
}
});
for (let i = 0; i < tracks.length; i++) {
list.addTrack_(tracks[i]);
}
if (browser.IS_IE8) {
return list;
}
list = super(tracks, list);
return list;
}
/**
* Add TextTrack from TextTrackList
*
* @param {TextTrack} track
* @method addTrack_
* @private
*/
addTrack_(track) {
let index = this.tracks_.length;
if (!('' + index in this)) {
Object.defineProperty(this, index, {
get() {
return this.tracks_[index];
}
});
}
super.addTrack_(track);
track.addEventListener('modechange', Fn.bind(this, function() {
this.trigger('change');
}));
// Do not add duplicate tracks
if (this.tracks_.indexOf(track) === -1) {
this.tracks_.push(track);
this.trigger({
track,
type: 'addtrack'
});
}
}
/**
@@ -147,21 +113,4 @@ class TextTrackList extends EventTarget {
return result;
}
}
/**
* change - One or more tracks in the track list have been enabled or disabled.
* addtrack - A track has been added to the track list.
* removetrack - A track has been removed from the track list.
*/
TextTrackList.prototype.allowedEvents_ = {
change: 'change',
addtrack: 'addtrack',
removetrack: 'removetrack'
};
// emulate attribute EventHandler support to allow for feature detection
for (let event in TextTrackList.prototype.allowedEvents_) {
TextTrackList.prototype['on' + event] = null;
}
export default TextTrackList;
+26 -60
Ver Arquivo
@@ -3,15 +3,15 @@
*/
import TextTrackCueList from './text-track-cue-list';
import * as Fn from '../utils/fn.js';
import * as Guid from '../utils/guid.js';
import * as browser from '../utils/browser.js';
import * as TextTrackEnum from './text-track-enums';
import {TextTrackKind, TextTrackMode} from './track-enums';
import log from '../utils/log.js';
import EventTarget from '../event-target';
import document from 'global/document';
import window from 'global/window';
import Track from './track.js';
import { isCrossOrigin } from '../utils/url.js';
import XHR from 'xhr';
import merge from '../utils/merge-options';
import * as browser from '../utils/browser.js';
/**
* takes a webvtt file contents and parses it into cues
@@ -54,7 +54,6 @@ const parseCues = function(srcContent, track) {
parser.flush();
};
/**
* load a track from a specifed url
*
@@ -99,7 +98,7 @@ const loadTrack = function(src, track) {
/**
* A single text track as defined in:
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack
*
* interface TextTrack : EventTarget {
* readonly attribute TextTrackKind kind;
@@ -121,21 +120,31 @@ const loadTrack = function(src, track) {
* };
*
* @param {Object=} options Object of option names and values
* @extends EventTarget
* @extends Track
* @class TextTrack
*/
class TextTrack extends EventTarget {
class TextTrack extends Track {
constructor(options = {}) {
super();
if (!options.tech) {
throw new Error('A tech was not provided.');
}
let tt = this;
let settings = merge(options, {
kind: TextTrackKind[options.kind] || 'subtitles',
language: options.language || options.srclang || ''
});
let mode = TextTrackMode[settings.mode] || 'disabled';
let default_ = settings.default;
if (settings.kind === 'metadata' || settings.kind === 'chapters') {
mode = 'hidden';
}
// on IE8 this will be a document element
// for every other browser this will be a normal object
let tt = super(settings);
tt.tech_ = settings.tech;
if (browser.IS_IE8) {
tt = document.createElement('custom');
for (let prop in TextTrack.prototype) {
if (prop !== 'constructor') {
tt[prop] = TextTrack.prototype[prop];
@@ -143,19 +152,6 @@ class TextTrack extends EventTarget {
}
}
tt.tech_ = options.tech;
let mode = TextTrackEnum.TextTrackMode[options.mode] || 'disabled';
let kind = TextTrackEnum.TextTrackKind[options.kind] || 'subtitles';
let default_ = options.default;
let label = options.label || '';
let language = options.language || options.srclang || '';
let id = options.id || 'vjs_text_track_' + Guid.newGUID();
if (kind === 'metadata' || kind === 'chapters') {
mode = 'hidden';
}
tt.cues_ = [];
tt.activeCues_ = [];
@@ -174,34 +170,6 @@ class TextTrack extends EventTarget {
tt.tech_.on('timeupdate', timeupdateHandler);
}
Object.defineProperty(tt, 'kind', {
get() {
return kind;
},
set() {}
});
Object.defineProperty(tt, 'label', {
get() {
return label;
},
set() {}
});
Object.defineProperty(tt, 'language', {
get() {
return language;
},
set() {}
});
Object.defineProperty(tt, 'id', {
get() {
return id;
},
set() {}
});
Object.defineProperty(tt, 'default', {
get() {
return default_;
@@ -214,7 +182,7 @@ class TextTrack extends EventTarget {
return mode;
},
set(newMode) {
if (!TextTrackEnum.TextTrackMode[newMode]) {
if (!TextTrackMode[newMode]) {
return;
}
mode = newMode;
@@ -282,16 +250,14 @@ class TextTrack extends EventTarget {
set() {}
});
if (options.src) {
tt.src = options.src;
loadTrack(options.src, tt);
if (settings.src) {
tt.src = settings.src;
loadTrack(settings.src, tt);
} else {
tt.loaded_ = true;
}
if (browser.IS_IE8) {
return tt;
}
return tt;
}
/**
+86
Ver Arquivo
@@ -0,0 +1,86 @@
/**
* @file track-kinds.js
*/
/**
* https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
*
* enum VideoTrackKind {
* "alternative",
* "captions",
* "main",
* "sign",
* "subtitles",
* "commentary",
* "",
* };
*/
const VideoTrackKind = {
alternative: 'alternative',
captions: 'captions',
main: 'main',
sign: 'sign',
subtitles: 'subtitles',
commentary: 'commentary',
};
/**
* https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
*
* enum AudioTrackKind {
* "alternative",
* "descriptions",
* "main",
* "main-desc",
* "translation",
* "commentary",
* "",
* };
*/
const AudioTrackKind = {
alternative: 'alternative',
descriptions: 'descriptions',
main: 'main',
'main-desc': 'main-desc',
translation: 'translation',
commentary: 'commentary',
};
/**
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackkind
*
* enum TextTrackKind {
* "subtitles",
* "captions",
* "descriptions",
* "chapters",
* "metadata"
* };
*/
const TextTrackKind = {
subtitles: 'subtitles',
captions: 'captions',
descriptions: 'descriptions',
chapters: 'chapters',
metadata: 'metadata'
};
/**
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
*
* enum TextTrackMode { "disabled", "hidden", "showing" };
*/
const TextTrackMode = {
disabled: 'disabled',
hidden: 'hidden',
showing: 'showing'
};
/* jshint ignore:start */
// we ignore jshint here because it does not see
// AudioTrackKind as defined here
export default { VideoTrackKind, AudioTrackKind, TextTrackKind, TextTrackMode };
/* jshint ignore:end */
+148
Ver Arquivo
@@ -0,0 +1,148 @@
/**
* @file track-list.js
*/
import EventTarget from '../event-target';
import * as Fn from '../utils/fn.js';
import * as browser from '../utils/browser.js';
import document from 'global/document';
/**
* Common functionaliy between Text, Audio, and Video TrackLists
* Interfaces defined in the following spec:
* @link https://html.spec.whatwg.org/multipage/embedded-content.html
*
* @param {Track[]} tracks A list of tracks to initialize the list with
* @param {Object} list the child object with inheritance done manually for ie8
* @extends EventTarget
* @class TrackList
*/
class TrackList extends EventTarget {
constructor(tracks = [], list = null) {
super();
if (!list) {
list = this;
if (browser.IS_IE8) {
list = document.createElement('custom');
for (let prop in TrackList.prototype) {
if (prop !== 'constructor') {
list[prop] = TrackList.prototype[prop];
}
}
}
}
list.tracks_ = [];
Object.defineProperty(list, 'length', {
get() {
return this.tracks_.length;
}
});
for (let i = 0; i < tracks.length; i++) {
list.addTrack_(tracks[i]);
}
return list;
}
/**
* Add a Track from TrackList
*
* @param {Mixed} track
* @method addTrack_
* @private
*/
addTrack_(track) {
let index = this.tracks_.length;
if (!('' + index in this)) {
Object.defineProperty(this, index, {
get() {
return this.tracks_[index];
}
});
}
// Do not add duplicate tracks
if (this.tracks_.indexOf(track) === -1) {
this.tracks_.push(track);
this.trigger({
track,
type: 'addtrack'
});
}
}
/**
* Remove a Track from TrackList
*
* @param {Track} rtrack track to be removed
* @method removeTrack_
* @private
*/
removeTrack_(rtrack) {
let track;
for (let i = 0, l = this.length; i < l; i++) {
if (this[i] === rtrack) {
track = this[i];
if (track.off) {
track.off();
}
this.tracks_.splice(i, 1);
break;
}
}
if (!track) {
return;
}
this.trigger({
track,
type: 'removetrack'
});
}
/**
* Get a Track from the TrackList by a tracks id
*
* @param {String} id - the id of the track to get
* @method getTrackById
* @return {Track}
* @private
*/
getTrackById(id) {
let result = null;
for (let i = 0, l = this.length; i < l; i++) {
let track = this[i];
if (track.id === id) {
result = track;
break;
}
}
return result;
}
}
/**
* change - One or more tracks in the track list have been enabled or disabled.
* addtrack - A track has been added to the track list.
* removetrack - A track has been removed from the track list.
*/
TrackList.prototype.allowedEvents_ = {
change: 'change',
addtrack: 'addtrack',
removetrack: 'removetrack'
};
// emulate attribute EventHandler support to allow for feature detection
for (let event in TrackList.prototype.allowedEvents_) {
TrackList.prototype['on' + event] = null;
}
export default TrackList;
+50
Ver Arquivo
@@ -0,0 +1,50 @@
/**
* @file track.js
*/
import * as browser from '../utils/browser.js';
import document from 'global/document';
import * as Guid from '../utils/guid.js';
import EventTarget from '../event-target';
/**
* setup the common parts of an audio, video, or text track
* @link https://html.spec.whatwg.org/multipage/embedded-content.html
*
* @param {String} type The type of track we are dealing with audio|video|text
* @param {Object=} options Object of option names and values
* @extends EventTarget
* @class Track
*/
class Track extends EventTarget {
constructor(options = {}) {
super();
let track = this;
if (browser.IS_IE8) {
track = document.createElement('custom');
for (let prop in Track.prototype) {
if (prop !== 'constructor') {
track[prop] = Track.prototype[prop];
}
}
}
let trackProps = {
id: options.id || 'vjs_track_' + Guid.newGUID(),
kind: options.kind || '',
label: options.label || '',
language: options.language || ''
};
for (let key in trackProps) {
Object.defineProperty(track, key, {
get() { return trackProps[key]; },
set() {}
});
}
return track;
}
}
export default Track;
+123
Ver Arquivo
@@ -0,0 +1,123 @@
/**
* @file video-track-list.js
*/
import TrackList from './track-list';
import * as browser from '../utils/browser.js';
import document from 'global/document';
/**
* disable other video tracks before selecting the new one
*
* @param {Array|VideoTrackList} list list to work on
* @param {VideoTrack} track the track to skip
*/
const disableOthers = function(list, track) {
for (let i = 0; i < list.length; i++) {
if (track.id === list[i].id) {
continue;
}
// another audio track is enabled, disable it
list[i].selected = false;
}
};
/**
* A list of possiblee video tracks. Most functionality is in the
* base class Tracklist and the spec for VideoTrackList is located at:
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
*
* interface VideoTrackList : EventTarget {
* readonly attribute unsigned long length;
* getter VideoTrack (unsigned long index);
* VideoTrack? getTrackById(DOMString id);
* readonly attribute long selectedIndex;
*
* attribute EventHandler onchange;
* attribute EventHandler onaddtrack;
* attribute EventHandler onremovetrack;
* };
*
* @param {VideoTrack[]} tracks a list of video tracks to instantiate the list with
# @extends TrackList
* @class VideoTrackList
*/
class VideoTrackList extends TrackList {
constructor(tracks = []) {
let list;
// make sure only 1 track is enabled
// sorted from last index to first index
for (let i = tracks.length - 1; i >= 0; i--) {
if (tracks[i].selected) {
disableOthers(tracks, tracks[i]);
break;
}
}
// IE8 forces us to implement inheritance ourselves
// as it does not support Object.defineProperty properly
if (browser.IS_IE8) {
list = document.createElement('custom');
for (let prop in TrackList.prototype) {
if (prop !== 'constructor') {
list[prop] = TrackList.prototype[prop];
}
}
for (let prop in VideoTrackList.prototype) {
if (prop !== 'constructor') {
list[prop] = VideoTrackList.prototype[prop];
}
}
}
list = super(tracks, list);
list.changing_ = false;
Object.defineProperty(list, 'selectedIndex', {
get() {
for (let i = 0; i < this.length; i++) {
if (this[i].selected) {
return i;
}
}
return -1;
},
set() {}
});
return list;
}
addTrack_(track) {
if (track.selected) {
disableOthers(this, track);
}
super.addTrack_(track);
// native tracks don't have this
if (!track.addEventListener) {
return;
}
track.addEventListener('selectedchange', () => {
if (this.changing_) {
return;
}
this.changing_ = true;
disableOthers(this, track);
this.changing_ = false;
this.trigger('change');
});
}
addTrack(track) {
this.addTrack_(track);
}
removeTrack(track) {
super.removeTrack_(track);
}
}
export default VideoTrackList;
+63
Ver Arquivo
@@ -0,0 +1,63 @@
import {VideoTrackKind} from './track-enums';
import Track from './track';
import merge from '../utils/merge-options';
import * as browser from '../utils/browser.js';
/**
* A single video text track as defined in:
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack
*
* interface VideoTrack {
* readonly attribute DOMString id;
* readonly attribute DOMString kind;
* readonly attribute DOMString label;
* readonly attribute DOMString language;
* attribute boolean selected;
* };
*
* @param {Object=} options Object of option names and values
* @class VideoTrack
*/
class VideoTrack extends Track {
constructor(options = {}) {
let settings = merge(options, {
kind: VideoTrackKind[options.kind] || ''
});
// on IE8 this will be a document element
// for every other browser this will be a normal object
let track = super(settings);
let selected = false;
if (browser.IS_IE8) {
for (let prop in VideoTrack.prototype) {
if (prop !== 'constructor') {
track[prop] = VideoTrack.prototype[prop];
}
}
}
Object.defineProperty(track, 'selected', {
get() { return selected; },
set(newSelected) {
// an invalid or unchanged value
if (typeof newSelected !== 'boolean' || newSelected === selected) {
return;
}
selected = newSelected;
this.trigger('selectedchange');
}
});
// if the user sets this track to selected then
// set selected to that true value otherwise
// we keep it false
if (settings.selected) {
track.selected = settings.selected;
}
return track;
}
}
export default VideoTrack;
+20 -2
Ver Arquivo
@@ -13,6 +13,8 @@ import plugin from './plugins.js';
import mergeOptions from '../../src/js/utils/merge-options.js';
import * as Fn from './utils/fn.js';
import TextTrack from './tracks/text-track.js';
import AudioTrack from './tracks/audio-track.js';
import VideoTrack from './tracks/video-track.js';
import assign from 'object.assign';
import { createTimeRanges } from './utils/time-ranges.js';
@@ -53,7 +55,7 @@ if (typeof HTMLVideoElement === 'undefined') {
* @mixes videojs
* @method videojs
*/
let videojs = function(id, options, ready){
function videojs(id, options, ready){
let tag; // Element of ID
// Allow for element or ID to be passed in
@@ -97,7 +99,7 @@ let videojs = function(id, options, ready){
// Element may have a player attr referring to an already created player instance.
// If not, set up a new player and return the instance.
return tag['player'] || Player.players[tag.playerId] || new Player(tag, options, ready);
};
}
// Add default styles
if (window.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
@@ -549,6 +551,22 @@ videojs.xhr = xhr;
*/
videojs.TextTrack = TextTrack;
/**
* export the AudioTrack class so that source handlers can create
* AudioTracks and then add them to the players AudioTrackList
*
* @type {Function}
*/
videojs.AudioTrack = AudioTrack;
/**
* export the VideoTrack class so that source handlers can create
* VideoTracks and then add them to the players VideoTrackList
*
* @type {Function}
*/
videojs.VideoTrack = VideoTrack;
/**
* Determines, via duck typing, whether or not a value is a DOM element.
*
+3 -1
Ver Arquivo
@@ -59,7 +59,9 @@ test('should be able to access expected player API methods', function() {
ok(player.usingNativeControls, 'usingNativeControls exists');
ok(player.isFullscreen, 'isFullscreen exists');
// TextTrack methods
// Track methods
ok(player.audioTracks, 'audioTracks exists');
ok(player.videoTracks, 'videoTracks exists');
ok(player.textTracks, 'textTracks exists');
ok(player.remoteTextTrackEls, 'remoteTextTrackEls exists');
ok(player.remoteTextTracks, 'remoteTextTracks exists');
+1 -1
Ver Arquivo
@@ -1,5 +1,5 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>VideoJS Tests</title>
+1 -2
Ver Arquivo
@@ -86,14 +86,13 @@ test('dispose removes the object element even before ready fires', function() {
// This test appears to test bad functionaly that was fixed
// so it's debateable whether or not it's useful
let dispose = Flash.prototype.dispose;
let mockFlash = {};
let mockFlash = new MockFlash();
let noop = function(){};
// Mock required functions for dispose
mockFlash.off = noop;
mockFlash.trigger = noop;
mockFlash.el_ = {};
mockFlash.textTracks = () => ([]);
dispose.call(mockFlash);
strictEqual(mockFlash.el_, null, 'swf el is nulled');
+91
Ver Arquivo
@@ -249,6 +249,97 @@ if (Html5.supportsNativeTextTracks()) {
equal(adds[2][0], rems[2][0], 'removetrack event handler removed');
});
}
if (Html5.supportsNativeAudioTracks()) {
test('add native audioTrack listeners on startup', function() {
let adds = [];
let rems = [];
let at = {
length: 0,
addEventListener: (type, fn) => adds.push([type, fn]),
removeEventListener: (type, fn) => rems.push([type, fn]),
};
let el = document.createElement('div');
el.audioTracks = at;
let htmlTech = new Html5({el});
equal(adds[0][0], 'change', 'change event handler added');
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
});
test('remove all tracks from emulated list on dispose', function() {
let adds = [];
let rems = [];
let at = {
length: 0,
addEventListener: (type, fn) => adds.push([type, fn]),
removeEventListener: (type, fn) => rems.push([type, fn]),
};
let el = document.createElement('div');
el.audioTracks = at;
let htmlTech = new Html5({el});
htmlTech.dispose();
equal(adds[0][0], 'change', 'change event handler added');
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
equal(rems[0][0], 'change', 'change event handler removed');
equal(rems[1][0], 'addtrack', 'addtrack event handler removed');
equal(rems[2][0], 'removetrack', 'removetrack event handler removed');
equal(adds[0][0], rems[0][0], 'change event handler removed');
equal(adds[1][0], rems[1][0], 'addtrack event handler removed');
equal(adds[2][0], rems[2][0], 'removetrack event handler removed');
});
}
if (Html5.supportsNativeVideoTracks()) {
test('add native videoTrack listeners on startup', function() {
let adds = [];
let rems = [];
let vt = {
length: 0,
addEventListener: (type, fn) => adds.push([type, fn]),
removeEventListener: (type, fn) => rems.push([type, fn]),
};
let el = document.createElement('div');
el.videoTracks = vt;
let htmlTech = new Html5({el});
equal(adds[0][0], 'change', 'change event handler added');
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
});
test('remove all tracks from emulated list on dispose', function() {
let adds = [];
let rems = [];
let vt = {
length: 0,
addEventListener: (type, fn) => adds.push([type, fn]),
removeEventListener: (type, fn) => rems.push([type, fn]),
};
let el = document.createElement('div');
el.videoTracks = vt;
let htmlTech = new Html5({el});
htmlTech.dispose();
equal(adds[0][0], 'change', 'change event handler added');
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
equal(rems[0][0], 'change', 'change event handler removed');
equal(rems[1][0], 'addtrack', 'addtrack event handler removed');
equal(rems[2][0], 'removetrack', 'removetrack event handler removed');
equal(adds[0][0], rems[0][0], 'change event handler removed');
equal(adds[1][0], rems[1][0], 'addtrack event handler removed');
equal(adds[2][0], rems[2][0], 'removetrack event handler removed');
});
}
test('should always return currentSource_ if set', function(){
let currentSrc = Html5.prototype.currentSrc;
equal(currentSrc.call({el_: {currentSrc:'test1'}}), 'test1', 'sould return source from element if nothing else set');
+218 -14
Ver Arquivo
@@ -7,6 +7,12 @@ import Button from '../../../src/js/button.js';
import { createTimeRange } from '../../../src/js/utils/time-ranges.js';
import extendFn from '../../../src/js/extend.js';
import MediaError from '../../../src/js/media-error.js';
import AudioTrack from '../../../src/js/tracks/audio-track';
import VideoTrack from '../../../src/js/tracks/video-track';
import TextTrack from '../../../src/js/tracks/text-track';
import AudioTrackList from '../../../src/js/tracks/audio-track-list';
import VideoTrackList from '../../../src/js/tracks/video-track-list';
import TextTrackList from '../../../src/js/tracks/text-track-list';
q.module('Media Tech', {
'setup': function() {
@@ -14,20 +20,10 @@ q.module('Media Tech', {
this.clock = sinon.useFakeTimers();
this.featuresProgessEvents = Tech.prototype['featuresProgessEvents'];
Tech.prototype['featuresProgressEvents'] = false;
Tech.prototype['featuresNativeTextTracks'] = true;
oldTextTracks = Tech.prototype.textTracks;
Tech.prototype.textTracks = function() {
return {
addEventListener: Function.prototype,
removeEventListener: Function.prototype
};
};
},
'teardown': function() {
this.clock.restore();
Tech.prototype['featuresProgessEvents'] = this.featuresProgessEvents;
Tech.prototype['featuresNativeTextTracks'] = false;
Tech.prototype.textTracks = oldTextTracks;
}
});
@@ -103,6 +99,54 @@ test('dispose() should stop time tracking', function() {
ok(true, 'no exception was thrown');
});
test('dispose() should clear all tracks that are passed when its created', function() {
var audioTracks = new AudioTrackList([new AudioTrack(), new AudioTrack()]);
var videoTracks = new VideoTrackList([new VideoTrack(), new VideoTrack()]);
var textTracks = new TextTrackList([new TextTrack({tech: {}}), new TextTrack({tech: {}})]);
equal(audioTracks.length, 2, 'should have two audio tracks at the start');
equal(videoTracks.length, 2, 'should have two video tracks at the start');
equal(textTracks.length, 2, 'should have two text tracks at the start');
var tech = new Tech({audioTracks, videoTracks, textTracks});
equal(tech.videoTracks().length, videoTracks.length, 'should hold video tracks that we passed');
equal(tech.audioTracks().length, audioTracks.length, 'should hold audio tracks that we passed');
equal(tech.textTracks().length, textTracks.length, 'should hold text tracks that we passed');
tech.dispose();
equal(audioTracks.length, 0, 'should have zero audio tracks after dispose');
equal(videoTracks.length, 0, 'should have zero video tracks after dispose');
equal(textTracks.length, 0, 'should have zero text tracks after dispose');
});
test('dispose() should clear all tracks that are added after creation', function() {
var tech = new Tech();
tech.addRemoteTextTrack({});
tech.addRemoteTextTrack({});
tech.audioTracks().addTrack_(new AudioTrack());
tech.audioTracks().addTrack_(new AudioTrack());
tech.videoTracks().addTrack_(new VideoTrack());
tech.videoTracks().addTrack_(new VideoTrack());
equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
tech.dispose();
equal(tech.audioTracks().length, 0, 'should have zero audio tracks after dispose');
equal(tech.videoTracks().length, 0, 'should have zero video tracks after dispose');
equal(tech.remoteTextTrackEls().length, 0, 'should have zero remote text tracks els');
equal(tech.remoteTextTracks().length, 0, 'should have zero remote text tracks');
equal(tech.textTracks().length, 0, 'should have zero video tracks after dispose');
});
test('should add the source handler interface to a tech', function(){
var sourceA = { src: 'foo.mp4', type: 'video/mp4' };
var sourceB = { src: 'no-support', type: 'no-support' };
@@ -147,9 +191,10 @@ test('should add the source handler interface to a tech', function(){
}
return '';
},
handleSource: function(s, t){
handleSource: function(s, t, o){
strictEqual(tech, t, 'the tech instance was passed to the source handler');
strictEqual(sourceA, s, 'the tech instance was passed to the source handler');
strictEqual(tech.options_, o, 'the tech options were passed to the source handler');
return new handlerInternalState();
}
};
@@ -161,7 +206,7 @@ test('should add the source handler interface to a tech', function(){
canHandleSource: function(source){
return ''; // no support
},
handleSource: function(source, tech){
handleSource: function(source, tech, options){
ok(false, 'handlerTwo supports nothing and should never be called');
}
};
@@ -184,13 +229,49 @@ test('should add the source handler interface to a tech', function(){
strictEqual(MyTech.canPlaySource(sourceA), 'probably', 'the Tech returned probably for the valid source');
strictEqual(MyTech.canPlaySource(sourceB), '', 'the Tech returned an empty string for the invalid source');
tech.addRemoteTextTrack({});
tech.addRemoteTextTrack({});
tech.audioTracks().addTrack_(new AudioTrack());
tech.audioTracks().addTrack_(new AudioTrack());
tech.videoTracks().addTrack_(new VideoTrack());
tech.videoTracks().addTrack_(new VideoTrack());
equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
// Pass a source through the source handler process of a tech instance
tech.setSource(sourceA);
// verify that the Tracks are still there
equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
strictEqual(tech.currentSource_, sourceA, 'sourceA was handled and stored');
ok(tech.sourceHandler_.dispose, 'the handlerOne state instance was stored');
// Pass a second source
tech.setSource(sourceA);
strictEqual(tech.currentSource_, sourceA, 'sourceA was handled and stored');
ok(tech.sourceHandler_.dispose, 'the handlerOne state instance was stored');
// verify that all the tracks were removed as we got a new source
equal(tech.audioTracks().length, 0, 'should have zero audio tracks');
equal(tech.videoTracks().length, 0, 'should have zero video tracks');
equal(tech.textTracks().length, 2, 'should have two text tracks');
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
// Check that the handler dipose method works
ok(!disposeCalled, 'dispose has not been called for the handler yet');
ok(disposeCalled, 'dispose has been called for the handler yet');
disposeCalled = false;
tech.dispose();
ok(disposeCalled, 'the handler dispose method was called when the tech was disposed');
});
@@ -261,7 +342,7 @@ test('delegates seekable to the source handler', function(){
canHandleSource: function() {
return true;
},
handleSource: function(source, tech) {
handleSource: function(source, tech, options) {
return handler;
}
});
@@ -288,3 +369,126 @@ test('Tech.isTech returns correct answers for techs and components', function()
ok(!isTech(new Button({}, {})), 'A Button instance is not a Tech');
ok(!isTech(isTech), 'A function is not a Tech');
});
test('Tech#setSource clears currentSource_ after repeated loadstart', function() {
let disposed = false;
let MyTech = extendFn(Tech);
Tech.withSourceHandlers(MyTech);
let tech = new MyTech();
var sourceHandler = {
canPlayType: function(type) {
return true;
},
canHandleSource: function(source) {
return true;
},
handleSource: function(source, tech, options) {
return {
dispose: function() {
disposed = true;
}
};
}
};
// Test registering source handlers
MyTech.registerSourceHandler(sourceHandler);
// First loadstart
tech.setSource('test');
tech.currentSource_ = 'test';
tech.trigger('loadstart');
equal(tech.currentSource_, 'test', 'Current source is test');
// Second loadstart
tech.trigger('loadstart');
equal(tech.currentSource_, null, 'Current source is null');
equal(disposed, true, 'disposed is true');
// Third loadstart
tech.currentSource_ = 'test';
tech.trigger('loadstart');
equal(tech.currentSource_, null, 'Current source is still null');
});
test('setSource after tech dispose should dispose source handler once', function(){
let MyTech = extendFn(Tech);
Tech.withSourceHandlers(MyTech);
let disposeCount = 0;
let handler = {
dispose() {
disposeCount++;
}
};
MyTech.registerSourceHandler({
canPlayType: function() {
return true;
},
canHandleSource: function() {
return true;
},
handleSource: function(source, tech, options) {
return handler;
}
});
let tech = new MyTech();
tech.setSource('test');
equal(disposeCount, 0, 'did not call sourceHandler_ dispose for initial dispose');
tech.dispose();
ok(!tech.sourceHandler_, 'sourceHandler should be unset');
equal(disposeCount, 1, 'called the source handler dispose');
// this would normally be done above tech on src after dispose
tech.el_ = tech.createEl();
tech.setSource('test');
equal(disposeCount, 1, 'did not dispose after initial setSource');
tech.setSource('test');
equal(disposeCount, 2, 'did dispose on second setSource');
});
test('setSource after previous setSource should dispose source handler once', function(){
let MyTech = extendFn(Tech);
Tech.withSourceHandlers(MyTech);
let disposeCount = 0;
let handler = {
dispose() {
disposeCount++;
}
};
MyTech.registerSourceHandler({
canPlayType: function() {
return true;
},
canHandleSource: function() {
return true;
},
handleSource: function(source, tech, options) {
return handler;
}
});
let tech = new MyTech();
tech.setSource('test');
equal(disposeCount, 0, 'did not call dispose for initial setSource');
tech.setSource('test');
equal(disposeCount, 1, 'did dispose for second setSource');
tech.setSource('test');
equal(disposeCount, 2, 'did dispose for third setSource');
});
+97
Ver Arquivo
@@ -0,0 +1,97 @@
import AudioTrackList from '../../../src/js/tracks/audio-track-list.js';
import AudioTrack from '../../../src/js/tracks/audio-track.js';
import EventTarget from '../../../src/js/event-target.js';
q.module('Audio Track List');
test('trigger "change" when "enabledchange" is fired on a track', function() {
let track = new EventTarget();
track.loaded_ = true;
let audioTrackList = new AudioTrackList([track]);
let changes = 0;
let changeHandler = function() {
changes++;
};
audioTrackList.on('change', changeHandler);
track.trigger('enabledchange');
equal(changes, 1, 'one change events for trigger');
audioTrackList.off('change', changeHandler);
audioTrackList.onchange = changeHandler;
track.trigger('enabledchange');
equal(changes, 2, 'one change events for another trigger');
});
test('only one track is ever enabled', function() {
let track = new AudioTrack({enabled: true});
let track2 = new AudioTrack({enabled: true});
let track3 = new AudioTrack({enabled: true});
let track4 = new AudioTrack();
let list = new AudioTrackList([track, track2]);
equal(track.enabled, false, 'track is disabled');
equal(track2.enabled, true, 'track2 is enabled');
track.enabled = true;
equal(track.enabled, true, 'track is enabled');
equal(track2.enabled, false, 'track2 is disabled');
list.addTrack_(track3);
equal(track.enabled, false, 'track is disabled');
equal(track2.enabled, false, 'track2 is disabled');
equal(track3.enabled, true, 'track3 is enabled');
track.enabled = true;
equal(track.enabled, true, 'track is disabled');
equal(track2.enabled, false, 'track2 is disabled');
equal(track3.enabled, false, 'track3 is disabled');
list.addTrack_(track4);
equal(track.enabled, true, 'track is enabled');
equal(track2.enabled, false, 'track2 is disabled');
equal(track3.enabled, false, 'track3 is disabled');
equal(track4.enabled, false, 'track4 is disabled');
});
test('all tracks can be disabled', function() {
let track = new AudioTrack();
let track2 = new AudioTrack();
let list = new AudioTrackList([track, track2]);
equal(track.enabled, false, 'track is disabled');
equal(track2.enabled, false, 'track2 is disabled');
track.enabled = true;
equal(track.enabled, true, 'track is enabled');
equal(track2.enabled, false, 'track2 is disabled');
track.enabled = false;
equal(track.enabled, false, 'track is disabled');
equal(track2.enabled, false, 'track2 is disabled');
});
test('trigger a change event per enabled change', function() {
let track = new AudioTrack({enabled: true});
let track2 = new AudioTrack({enabled: true});
let track3 = new AudioTrack({enabled: true});
let track4 = new AudioTrack();
let list = new AudioTrackList([track, track2]);
let change = 0;
list.on('change', () => change++);
track.enabled = true;
equal(change, 1, 'one change triggered');
list.addTrack_(track3);
equal(change, 2, 'another change triggered by adding an enabled track');
track.enabled = true;
equal(change, 3, 'another change trigger by changing enabled');
track.enabled = false;
equal(change, 4, 'another change trigger by changing enabled');
list.addTrack_(track4);
equal(change, 4, 'no change triggered by adding a disabled track');
});
+118
Ver Arquivo
@@ -0,0 +1,118 @@
import AudioTrack from '../../../src/js/tracks/audio-track.js';
import {AudioTrackKind} from '../../../src/js/tracks/track-enums.js';
import TrackBaseline from './track-baseline';
q.module('Audio Track');
// do baseline track testing
TrackBaseline(AudioTrack, {
id: '1',
language: 'en',
label: 'English',
kind: 'main',
});
test('can create an enabled propert on an AudioTrack', function() {
let enabled = true;
let track = new AudioTrack({
enabled,
});
equal(track.enabled, enabled, 'enabled value matches what we passed in');
});
test('defaults when items not provided', function() {
let track = new AudioTrack();
equal(track.kind, '', 'kind defaulted to empty string');
equal(track.enabled, false, 'enabled defaulted to true since there is one track');
equal(track.label, '', 'label defaults to empty string');
equal(track.language, '', 'language defaults to empty string');
ok(track.id.match(/vjs_track_\d{5}/), 'id defaults to vjs_track_GUID');
});
test('kind can only be one of several options, defaults to empty string', function() {
let track = new AudioTrack({
kind: 'foo'
});
equal(track.kind, '', 'the kind is set to empty string, not foo');
notEqual(track.kind, 'foo', 'the kind is set to empty string, not foo');
// loop through all possible kinds to verify
for (let key in AudioTrackKind) {
let currentKind = AudioTrackKind[key];
let track = new AudioTrack({
kind: currentKind,
});
equal(track.kind, currentKind, 'the kind is set to ' + currentKind);
}
});
test('enabled can only be instantiated to true or false, defaults to false', function() {
let track = new AudioTrack({
enabled: 'foo'
});
equal(track.enabled, false, 'the enabled value is set to false, not foo');
notEqual(track.enabled, 'foo', 'the enabled value is not set to foo');
track = new AudioTrack({
enabled: true
});
equal(track.enabled, true, 'the enabled value is set to true');
track = new AudioTrack({
enabled: false
});
equal(track.enabled, false, 'the enabled value is set to false');
});
test('enabled can only be changed to true or false', function() {
let track = new AudioTrack();
track.enabled = 'foo';
notEqual(track.enabled, 'foo', 'enabled not set to invalid value, foo');
equal(track.enabled, false, 'enabled remains on the old value, false');
track.enabled = true;
equal(track.enabled, true, 'enabled was set to true');
track.enabled = 'baz';
notEqual(track.enabled, 'baz', 'enabled not set to invalid value, baz');
equal(track.enabled, true, 'enabled remains on the old value, true');
track.enabled = false;
equal(track.enabled, false, 'enabled was set to false');
});
test('when enabled is changed enabledchange event is fired', function() {
let track = new AudioTrack({
tech: this.tech,
enabled: false
});
let eventsTriggered = 0;
track.addEventListener('enabledchange', () => {
eventsTriggered++;
});
// two events
track.enabled = true;
track.enabled = false;
equal(eventsTriggered, 2, 'two enabled changes');
// no event here
track.enabled = false;
track.enabled = false;
equal(eventsTriggered, 2, 'still two enabled changes');
// one event
track.enabled = true;
equal(eventsTriggered, 3, 'three enabled changes');
// no events
track.enabled = true;
track.enabled = true;
equal(eventsTriggered, 3, 'still three enabled changes');
});
+114
Ver Arquivo
@@ -0,0 +1,114 @@
import AudioTrack from '../../../src/js/tracks/text-track.js';
import Html5 from '../../../src/js/tech/html5.js';
import Tech from '../../../src/js/tech/tech.js';
import Component from '../../../src/js/component.js';
import * as browser from '../../../src/js/utils/browser.js';
import TestHelpers from '../test-helpers.js';
import document from 'global/document';
q.module('Tracks', {
setup() {
this.clock = sinon.useFakeTimers();
},
teardown() {
this.clock.restore();
}
});
test('Player track methods call the tech', function() {
let player;
let calls = 0;
player = TestHelpers.makePlayer();
player.tech_.audioTracks = function() {
calls++;
};
player.audioTracks();
equal(calls, 1, 'audioTrack defers to the tech');
player.dispose();
});
test('listen to remove and add track events in native audio tracks', function() {
let oldTestVid = Html5.TEST_VID;
let player;
let options;
let oldAudioTracks = Html5.prototype.audioTracks;
let events = {};
let html;
Html5.prototype.audioTracks = function() {
return {
addEventListener(type, handler) {
events[type] = true;
}
};
};
Html5.TEST_VID = {
audioTracks: []
};
player = {
// Function.prototype is a built-in no-op function.
controls() {},
ready() {},
options() {
return {};
},
addChild() {},
id() {},
el() {
return {
insertBefore() {},
appendChild() {}
};
}
};
player.player_ = player;
player.options_ = options = {};
html = new Html5(options);
ok(events.removetrack, 'removetrack listener was added');
ok(events.addtrack, 'addtrack listener was added');
Html5.TEST_VID = oldTestVid;
Html5.prototype.audioTracks = oldAudioTracks;
});
test('html5 tech supports native audio tracks if the video supports it', function() {
let oldTestVid = Html5.TEST_VID;
Html5.TEST_VID = {
audioTracks: []
};
ok(Html5.supportsNativeAudioTracks(), 'native audio tracks are supported');
Html5.TEST_VID = oldTestVid;
});
test('html5 tech does not support native audio tracks if the video does not supports it', function() {
let oldTestVid = Html5.TEST_VID;
Html5.TEST_VID = {};
ok(!Html5.supportsNativeAudioTracks(), 'native audio tracks are not supported');
Html5.TEST_VID = oldTestVid;
});
test('when switching techs, we should not get a new audio track', function() {
let player = TestHelpers.makePlayer();
player.loadTech_('TechFaker');
let firstTracks = player.audioTracks();
player.loadTech_('TechFaker');
let secondTracks = player.audioTracks();
ok(firstTracks === secondTracks, 'the tracks are equal');
});
-153
Ver Arquivo
@@ -2,155 +2,7 @@ import TextTrackList from '../../../src/js/tracks/text-track-list.js';
import TextTrack from '../../../src/js/tracks/text-track.js';
import EventTarget from '../../../src/js/event-target.js';
const genericTracks = [
{
id: '1',
addEventListener() {},
off() {}
}, {
id: '2',
addEventListener() {},
off() {}
}, {
id: '3',
addEventListener() {},
off() {}
}
];
q.module('Text Track List');
test('TextTrackList\'s length is set correctly', function() {
let ttl = new TextTrackList(genericTracks);
equal(ttl.length, genericTracks.length, 'the length is ' + genericTracks.length);
});
test('can get text tracks by id', function() {
let ttl = new TextTrackList(genericTracks);
equal(ttl.getTrackById('1').id, 1, 'id "1" has id of "1"');
equal(ttl.getTrackById('2').id, 2, 'id "2" has id of "2"');
equal(ttl.getTrackById('3').id, 3, 'id "3" has id of "3"');
ok(!ttl.getTrackById(1), 'there isn\'t an item with "numeric" id of `1`');
});
test('length is updated when new tracks are added or removed', function() {
let ttl = new TextTrackList(genericTracks);
ttl.addTrack_({id: '100', addEventListener() {}, off() {}});
equal(ttl.length, genericTracks.length + 1, 'the length is ' + (genericTracks.length + 1));
ttl.addTrack_({id: '101', addEventListener() {}, off() {}});
equal(ttl.length, genericTracks.length + 2, 'the length is ' + (genericTracks.length + 2));
ttl.removeTrack_(ttl.getTrackById('101'));
equal(ttl.length, genericTracks.length + 1, 'the length is ' + (genericTracks.length + 1));
ttl.removeTrack_(ttl.getTrackById('100'));
equal(ttl.length, genericTracks.length, 'the length is ' + genericTracks.length);
});
test('can access items by index', function() {
let ttl = new TextTrackList(genericTracks);
let i = 0;
let length = ttl.length;
expect(length);
for (; i < length; i++) {
equal(ttl[i].id, String(i + 1), 'the id of a track matches the index + 1');
}
});
test('can access new items by index', function() {
let ttl = new TextTrackList(genericTracks);
ttl.addTrack_({id: '100', addEventListener() {}});
equal(ttl[3].id, '100', 'id of item at index 3 is 100');
ttl.addTrack_({id: '101', addEventListener() {}});
equal(ttl[4].id, '101', 'id of item at index 4 is 101');
});
test('cannot access removed items by index', function() {
let ttl = new TextTrackList(genericTracks);
ttl.addTrack_({id: '100', addEventListener() {}, off() {}});
ttl.addTrack_({id: '101', addEventListener() {}, off() {}});
equal(ttl[3].id, '100', 'id of item at index 3 is 100');
equal(ttl[4].id, '101', 'id of item at index 4 is 101');
ttl.removeTrack_(ttl.getTrackById('101'));
ttl.removeTrack_(ttl.getTrackById('100'));
ok(!ttl[3], 'nothing at index 3');
ok(!ttl[4], 'nothing at index 4');
});
test('new item available at old index', function() {
let ttl = new TextTrackList(genericTracks);
ttl.addTrack_({id: '100', addEventListener() {}, off() {}});
equal(ttl[3].id, '100', 'id of item at index 3 is 100');
ttl.removeTrack_(ttl.getTrackById('100'));
ok(!ttl[3], 'nothing at index 3');
ttl.addTrack_({id: '101', addEventListener() {}});
equal(ttl[3].id, '101', 'id of new item at index 3 is now 101');
});
test('a "addtrack" event is triggered when new tracks are added', function() {
let ttl = new TextTrackList(genericTracks);
let tracks = 0;
let adds = 0;
let addHandler = function(e) {
if (e.track) {
tracks++;
}
adds++;
};
ttl.on('addtrack', addHandler);
ttl.addTrack_({id: '100', addEventListener() {}});
ttl.addTrack_({id: '101', addEventListener() {}});
ttl.off('addtrack', addHandler);
ttl.onaddtrack = addHandler;
ttl.addTrack_({id: '102', addEventListener() {}});
ttl.addTrack_({id: '103', addEventListener() {}});
equal(adds, 4, 'we got ' + adds + ' "addtrack" events');
equal(tracks, 4, 'we got a track with every event');
});
test('a "removetrack" event is triggered when tracks are removed', function() {
let ttl = new TextTrackList(genericTracks);
let tracks = 0;
let rms = 0;
let rmHandler = function(e) {
if (e.track) {
tracks++;
}
rms++;
};
ttl.on('removetrack', rmHandler);
ttl.removeTrack_(ttl.getTrackById('1'));
ttl.removeTrack_(ttl.getTrackById('2'));
ttl.off('removetrack', rmHandler);
ttl.onremovetrack = rmHandler;
ttl.removeTrack_(ttl.getTrackById('3'));
equal(rms, 3, 'we got ' + rms + ' "removetrack" events');
equal(tracks, 3, 'we got a track with every event');
});
test('trigger "change" event when "modechange" is fired on a track', function() {
let tt = new EventTarget();
let ttl = new TextTrackList([tt]);
@@ -160,15 +12,12 @@ test('trigger "change" event when "modechange" is fired on a track', function()
};
ttl.on('change', changeHandler);
tt.trigger('modechange');
ttl.off('change', changeHandler);
ttl.onchange = changeHandler;
tt.trigger('modechange');
equal(changes, 2, 'two change events should have fired');
});
@@ -185,11 +34,9 @@ test('trigger "change" event when mode changes on a TextTrack', function() {
};
ttl.on('change', changeHandler);
tt.mode = 'showing';
ttl.off('change', changeHandler);
ttl.onchange = changeHandler;
tt.mode = 'hidden';
+21 -37
Ver Arquivo
@@ -1,5 +1,7 @@
import window from 'global/window';
import EventTarget from '../../../src/js/event-target.js';
import TrackBaseline from './track-baseline';
import TechFaker from '../tech/tech-faker';
import TextTrack from '../../../src/js/tracks/text-track.js';
import TestHelpers from '../test-helpers.js';
import log from '../../../src/js/utils/log.js';
@@ -13,40 +15,38 @@ const defaultTech = {
off() {},
currentTime() {}
};
q.module('Text Track');
test('text-track requires a tech', function() {
let error = new Error('A tech was not provided.');
q.throws(() => new TextTrack(), error, 'a tech is required for text track');
// do baseline track testing
TrackBaseline(TextTrack, {
id: '1',
kind: 'subtitles',
mode: 'disabled',
label: 'English',
language: 'en',
tech: defaultTech
});
test('can create a TextTrack with various properties', function() {
let kind = 'captions';
let label = 'English';
let language = 'en';
let id = '1';
test('requires a tech', function() {
let error = new Error('A tech was not provided.');
q.throws(() => new TextTrack({}), error, 'a tech is required');
q.throws(() => new TextTrack({tech: null}), error, 'a tech is required');
});
test('can create a TextTrack with a mode property', function() {
let mode = 'disabled';
let tt = new TextTrack({
kind,
label,
language,
id,
mode,
tech: defaultTech
});
equal(tt.kind, kind, 'we have a kind');
equal(tt.label, label, 'we have a label');
equal(tt.language, language, 'we have a language');
equal(tt.id, id, 'we have a id');
equal(tt.mode, mode, 'we have a mode');
});
test('defaults when items not provided', function() {
let tt = new TextTrack({
tech: defaultTech
tech: TechFaker
});
equal(tt.kind, 'subtitles', 'kind defaulted to subtitles');
@@ -131,32 +131,16 @@ test('mode can only be one of several options, defaults to disabled', function()
equal(tt.mode, 'showing', 'the mode is set to showing');
});
test('kind, label, language, id, cue, and activeCues are read only', function() {
let kind = 'captions';
let label = 'English';
let language = 'en';
let id = '1';
test('cue and activeCues are read only', function() {
let mode = 'disabled';
let tt = new TextTrack({
kind,
label,
language,
id,
mode,
tech: defaultTech
tech: defaultTech,
});
tt.kind = 'subtitles';
tt.label = 'Spanish';
tt.language = 'es';
tt.id = '2';
tt.cues = 'foo';
tt.activeCues = 'bar';
equal(tt.kind, kind, 'kind is still set to captions');
equal(tt.label, label, 'label is still set to English');
equal(tt.language, language, 'language is still set to en');
equal(tt.id, id, 'id is still set to \'1\'');
notEqual(tt.cues, 'foo', 'cues is still original value');
notEqual(tt.activeCues, 'bar', 'activeCues is still original value');
});
+43
Ver Arquivo
@@ -0,0 +1,43 @@
import * as browser from '../../../src/js/utils/browser.js';
import document from 'global/document';
/**
* Tests baseline functionality for all tracks
*
# @param {Track} TrackClass the track class object to use for testing
# @param {Object} options the options to setup a track with
*/
const TrackBaseline = function(TrackClass, options) {
test('is setup with id, kind, label, and language', function() {
let track = new TrackClass(options);
equal(track.kind, options.kind, 'we have a kind');
equal(track.label, options.label, 'we have a label');
equal(track.language, options.language, 'we have a language');
equal(track.id, options.id, 'we have a id');
});
test('kind, label, language, id, are read only', function() {
let track = new TrackClass(options);
track.kind = 'subtitles';
track.label = 'Spanish';
track.language = 'es';
track.id = '2';
equal(track.kind, options.kind, 'we have a kind');
equal(track.label, options.label, 'we have a label');
equal(track.language, options.language, 'we have a language');
equal(track.id, options.id, 'we have an id');
});
test('returns an instance of itself on non ie8 browsers', function() {
let track = new TrackClass(options);
if (browser.IS_IE8) {
ok(track, 'returns an object on ie8');
return;
}
ok(track instanceof TrackClass, 'returns an instance');
});
};
export default TrackBaseline;
+143
Ver Arquivo
@@ -0,0 +1,143 @@
import TrackList from '../../../src/js/tracks/track-list.js';
import EventTarget from '../../../src/js/event-target.js';
const newTrack = function(id) {
return {
id,
addEventListener() {},
off() {},
};
};
q.module('Track List', {
beforeEach() {
this.tracks = [newTrack('1'), newTrack('2'), newTrack('3')];
}
});
test('TrackList\'s length is set correctly', function() {
let trackList = new TrackList(this.tracks);
equal(trackList.length, this.tracks.length, 'length is ' + this.tracks.length);
});
test('can get tracks by int and string id', function() {
let trackList = new TrackList(this.tracks);
equal(trackList.getTrackById('1').id, '1', 'id "1" has id of "1"');
equal(trackList.getTrackById('2').id, '2', 'id "2" has id of "2"');
equal(trackList.getTrackById('3').id, '3', 'id "3" has id of "3"');
});
test('length is updated when new tracks are added or removed', function() {
let trackList = new TrackList(this.tracks);
trackList.addTrack_(newTrack('100'));
equal(trackList.length, this.tracks.length + 1, 'the length is ' + (this.tracks.length + 1));
trackList.addTrack_(newTrack('101'));
equal(trackList.length, this.tracks.length + 2, 'the length is ' + (this.tracks.length + 2));
trackList.removeTrack_(trackList.getTrackById('101'));
equal(trackList.length, this.tracks.length + 1, 'the length is ' + (this.tracks.length + 1));
trackList.removeTrack_(trackList.getTrackById('100'));
equal(trackList.length, this.tracks.length, 'the length is ' + this.tracks.length);
});
test('can access items by index', function() {
let trackList = new TrackList(this.tracks);
let length = trackList.length;
expect(length);
for (let i = 0; i < length; i++) {
equal(trackList[i].id, String(i + 1), 'the id of a track matches the index + 1');
}
});
test('can access new items by index', function() {
let trackList = new TrackList(this.tracks);
trackList.addTrack_(newTrack('100'));
equal(trackList[3].id, '100', 'id of item at index 3 is 100');
trackList.addTrack_(newTrack('101'));
equal(trackList[4].id, '101', 'id of item at index 4 is 101');
});
test('cannot access removed items by index', function() {
let trackList = new TrackList(this.tracks);
trackList.addTrack_(newTrack('100'));
trackList.addTrack_(newTrack('101'));
equal(trackList[3].id, '100', 'id of item at index 3 is 100');
equal(trackList[4].id, '101', 'id of item at index 4 is 101');
trackList.removeTrack_(trackList.getTrackById('101'));
trackList.removeTrack_(trackList.getTrackById('100'));
ok(!trackList[3], 'nothing at index 3');
ok(!trackList[4], 'nothing at index 4');
});
test('new item available at old index', function() {
let trackList = new TrackList(this.tracks);
trackList.addTrack_(newTrack('100'));
equal(trackList[3].id, '100', 'id of item at index 3 is 100');
trackList.removeTrack_(trackList.getTrackById('100'));
ok(!trackList[3], 'nothing at index 3');
trackList.addTrack_(newTrack('101'));
equal(trackList[3].id, '101', 'id of new item at index 3 is now 101');
});
test('a "addtrack" event is triggered when new tracks are added', function() {
let trackList = new TrackList(this.tracks);
let tracks = 0;
let adds = 0;
let addHandler = (e) => {
if (e.track) {
tracks++;
}
adds++;
};
trackList.on('addtrack', addHandler);
trackList.addTrack_(newTrack('100'));
trackList.addTrack_(newTrack('101'));
trackList.off('addtrack', addHandler);
trackList.onaddtrack = addHandler;
trackList.addTrack_(newTrack('102'));
trackList.addTrack_(newTrack('103'));
equal(adds, 4, 'we got ' + adds + ' "addtrack" events');
equal(tracks, 4, 'we got a track with every event');
});
test('a "removetrack" event is triggered when tracks are removed', function() {
let trackList = new TrackList(this.tracks);
let tracks = 0;
let rms = 0;
let rmHandler = (e) => {
if (e.track) {
tracks++;
}
rms++;
};
trackList.on('removetrack', rmHandler);
trackList.removeTrack_(trackList.getTrackById('1'));
trackList.removeTrack_(trackList.getTrackById('2'));
trackList.off('removetrack', rmHandler);
trackList.onremovetrack = rmHandler;
trackList.removeTrack_(trackList.getTrackById('3'));
equal(rms, 3, 'we got ' + rms + ' "removetrack" events');
equal(tracks, 3, 'we got a track with every event');
});
+34
Ver Arquivo
@@ -0,0 +1,34 @@
import TechFaker from '../tech/tech-faker';
import TrackBaseline from './track-baseline';
import Track from '../../../src/js/tracks/track.js';
import * as browser from '../../../src/js/utils/browser.js';
const defaultTech = {
textTracks() {},
on() {},
off() {},
currentTime() {}
};
// do baseline track testing
q.module('Track');
TrackBaseline(Track, {
id: '1',
kind: 'subtitles',
mode: 'disabled',
label: 'English',
language: 'en',
tech: new TechFaker()
});
test('defaults when items not provided', function() {
let track = new Track({
tech: defaultTech
});
equal(track.kind, '', 'kind defaulted to empty string');
equal(track.label, '', 'label defaults to empty string');
equal(track.language, '', 'language defaults to empty string');
ok(track.id.match(/vjs_track_\d{5}/), 'id defaults to vjs_track_GUID');
});
+96
Ver Arquivo
@@ -0,0 +1,96 @@
import VideoTrackList from '../../../src/js/tracks/video-track-list.js';
import VideoTrack from '../../../src/js/tracks/video-track.js';
import EventTarget from '../../../src/js/event-target.js';
q.module('Video Track List');
test('trigger "change" when "selectedchange" is fired on a track', function() {
let track = new EventTarget();
track.loaded_ = true;
let audioTrackList = new VideoTrackList([track]);
let changes = 0;
let changeHandler = function() {
changes++;
};
audioTrackList.on('change', changeHandler);
track.trigger('selectedchange');
equal(changes, 1, 'one change events for trigger');
audioTrackList.off('change', changeHandler);
audioTrackList.onchange = changeHandler;
track.trigger('selectedchange');
equal(changes, 2, 'one change events for another trigger');
});
test('only one track is ever selected', function() {
let track = new VideoTrack({selected: true});
let track2 = new VideoTrack({selected: true});
let track3 = new VideoTrack({selected: true});
let track4 = new VideoTrack();
let list = new VideoTrackList([track, track2]);
equal(track.selected, false, 'track is unselected');
equal(track2.selected, true, 'track2 is selected');
track.selected = true;
equal(track.selected, true, 'track is selected');
equal(track2.selected, false, 'track2 is unselected');
list.addTrack_(track3);
equal(track.selected, false, 'track is unselected');
equal(track2.selected, false, 'track2 is unselected');
equal(track3.selected, true, 'track3 is selected');
track.selected = true;
equal(track.selected, true, 'track is unselected');
equal(track2.selected, false, 'track2 is unselected');
equal(track3.selected, false, 'track3 is unselected');
list.addTrack_(track4);
equal(track.selected, true, 'track is selected');
equal(track2.selected, false, 'track2 is unselected');
equal(track3.selected, false, 'track3 is unselected');
equal(track4.selected, false, 'track4 is unselected');
});
test('all tracks can be unselected', function() {
let track = new VideoTrack();
let track2 = new VideoTrack();
let list = new VideoTrackList([track, track2]);
equal(track.selected, false, 'track is unselected');
equal(track2.selected, false, 'track2 is unselected');
track.selected = true;
equal(track.selected, true, 'track is selected');
equal(track2.selected, false, 'track2 is unselected');
track.selected = false;
equal(track.selected, false, 'track is unselected');
equal(track2.selected, false, 'track2 is unselected');
});
test('trigger a change event per selected change', function() {
let track = new VideoTrack({selected: true});
let track2 = new VideoTrack({selected: true});
let track3 = new VideoTrack({selected: true});
let track4 = new VideoTrack();
let list = new VideoTrackList([track, track2]);
let change = 0;
list.on('change', () => change++);
track.selected = true;
equal(change, 1, 'one change triggered');
list.addTrack_(track3);
equal(change, 2, 'another change triggered by adding an selected track');
track.selected = true;
equal(change, 3, 'another change trigger by changing selected');
track.selected = false;
equal(change, 4, 'another change trigger by changing selected');
list.addTrack_(track4);
equal(change, 4, 'no change triggered by adding a unselected track');
});
+111
Ver Arquivo
@@ -0,0 +1,111 @@
import VideoTrack from '../../../src/js/tracks/video-track';
import VideoTrackList from '../../../src/js/tracks/video-track-list';
import {VideoTrackKind} from '../../../src/js/tracks/track-enums';
import TrackBaseline from './track-baseline';
q.module('Video Track');
// do baseline track testing
TrackBaseline(VideoTrack, {
id: '1',
language: 'en',
label: 'English',
kind: 'main',
});
test('can create an VideoTrack a selected property', function() {
let selected = true;
let track = new VideoTrack({
selected,
});
equal(track.selected, selected, 'selected value matches what we passed in');
});
test('defaults when items not provided', function() {
let track = new VideoTrack();
equal(track.kind, '', 'kind defaulted to empty string');
equal(track.selected, false, 'selected defaulted to true since there is one track');
equal(track.label, '', 'label defaults to empty string');
equal(track.language, '', 'language defaults to empty string');
ok(track.id.match(/vjs_track_\d{5}/), 'id defaults to vjs_track_GUID');
});
test('kind can only be one of several options, defaults to empty string', function() {
let track = new VideoTrack({
kind: 'foo'
});
equal(track.kind, '', 'the kind is set to empty string, not foo');
notEqual(track.kind, 'foo', 'the kind is set to empty string, not foo');
// loop through all possible kinds to verify
for (let key in VideoTrackKind) {
let currentKind = VideoTrackKind[key];
let track = new VideoTrack({kind: currentKind});
equal(track.kind, currentKind, 'the kind is set to ' + currentKind);
}
});
test('selected can only be instantiated to true or false, defaults to false', function() {
let track = new VideoTrack({
selected: 'foo'
});
equal(track.selected, false, 'the selected value is set to false, not foo');
notEqual(track.selected, 'foo', 'the selected value is not set to foo');
track = new VideoTrack({
selected: true
});
equal(track.selected, true, 'the selected value is set to true');
track = new VideoTrack({
selected: false
});
equal(track.selected, false, 'the selected value is set to false');
});
test('selected can only be changed to true or false', function() {
let track = new VideoTrack();
track.selected = 'foo';
notEqual(track.selected, 'foo', 'selected not set to invalid value, foo');
equal(track.selected, false, 'selected remains on the old value, false');
track.selected = true;
equal(track.selected, true, 'selected was set to true');
track.selected = 'baz';
notEqual(track.selected, 'baz', 'selected not set to invalid value, baz');
equal(track.selected, true, 'selected remains on the old value, true');
track.selected = false;
equal(track.selected, false, 'selected was set to false');
});
test('when selected is changed selectedchange event is fired', function() {
let track = new VideoTrack({
selected: false
});
let eventsTriggered = 0;
track.addEventListener('selectedchange', () => {
eventsTriggered++;
});
// two events
track.selected = true;
track.selected = false;
equal(eventsTriggered, 2, 'two selected changes');
// no event here
track.selected = false;
track.selected = false;
equal(eventsTriggered, 2, 'still two selected changes');
// one event
track.selected = true;
equal(eventsTriggered, 3, 'three selected changes');
});
+114
Ver Arquivo
@@ -0,0 +1,114 @@
import VideoTrack from '../../../src/js/tracks/video-track.js';
import Html5 from '../../../src/js/tech/html5.js';
import Tech from '../../../src/js/tech/tech.js';
import Component from '../../../src/js/component.js';
import * as browser from '../../../src/js/utils/browser.js';
import TestHelpers from '../test-helpers.js';
import document from 'global/document';
q.module('Video Tracks', {
setup() {
this.clock = sinon.useFakeTimers();
},
teardown() {
this.clock.restore();
}
});
test('Player track methods call the tech', function() {
let player;
let calls = 0;
player = TestHelpers.makePlayer();
player.tech_.videoTracks = function() {
calls++;
};
player.videoTracks();
equal(calls, 1, 'videoTrack defers to the tech');
player.dispose();
});
test('listen to remove and add track events in native video tracks', function() {
let oldTestVid = Html5.TEST_VID;
let player;
let options;
let oldVideoTracks = Html5.prototype.videoTracks;
let events = {};
let html;
Html5.prototype.videoTracks = function() {
return {
addEventListener(type, handler) {
events[type] = true;
}
};
};
Html5.TEST_VID = {
videoTracks: []
};
player = {
// Function.prototype is a built-in no-op function.
controls() {},
ready() {},
options() {
return {};
},
addChild() {},
id() {},
el() {
return {
insertBefore() {},
appendChild() {}
};
}
};
player.player_ = player;
player.options_ = options = {};
html = new Html5(options);
ok(events.removetrack, 'removetrack listener was added');
ok(events.addtrack, 'addtrack listener was added');
Html5.TEST_VID = oldTestVid;
Html5.prototype.videoTracks = oldVideoTracks;
});
test('html5 tech supports native video tracks if the video supports it', function() {
let oldTestVid = Html5.TEST_VID;
Html5.TEST_VID = {
videoTracks: []
};
ok(Html5.supportsNativeVideoTracks(), 'native video tracks are supported');
Html5.TEST_VID = oldTestVid;
});
test('html5 tech does not support native video tracks if the video does not supports it', function() {
let oldTestVid = Html5.TEST_VID;
Html5.TEST_VID = {};
ok(!Html5.supportsNativeVideoTracks(), 'native video tracks are not supported');
Html5.TEST_VID = oldTestVid;
});
test('when switching techs, we should not get a new video track', function() {
let player = TestHelpers.makePlayer();
player.loadTech_('TechFaker');
let firstTracks = player.videoTracks();
player.loadTech_('TechFaker');
let secondTracks = player.videoTracks();
ok(firstTracks === secondTracks, 'the tracks are equal');
});