videojs_rails 4.5.2 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +13 -5
- data/lib/videojs_rails/version.rb +1 -1
- data/readme.md +21 -0
- data/vendor/assets/fonts/vjs.eot +0 -0
- data/vendor/assets/fonts/vjs.svg +23 -60
- data/vendor/assets/fonts/vjs.ttf +0 -0
- data/vendor/assets/fonts/vjs.woff +0 -0
- data/vendor/assets/javascripts/video.js.erb +921 -275
- data/vendor/assets/stylesheets/video-js.css.erb +136 -2
- metadata +6 -7
- data/vendor/assets/images/video-js.png +0 -0
@@ -64,7 +64,7 @@ var videojs = vjs;
|
|
64
64
|
window.videojs = window.vjs = vjs;
|
65
65
|
|
66
66
|
// CDN Version. Used to target right flash swf.
|
67
|
-
vjs.CDN_VERSION = '4.
|
67
|
+
vjs.CDN_VERSION = '4.6';
|
68
68
|
vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');
|
69
69
|
|
70
70
|
/**
|
@@ -88,6 +88,11 @@ vjs.options = {
|
|
88
88
|
// defaultVolume: 0.85,
|
89
89
|
'defaultVolume': 0.00, // The freakin seaguls are driving me crazy!
|
90
90
|
|
91
|
+
// default playback rates
|
92
|
+
'playbackRates': [],
|
93
|
+
// Add playback rate selection by adding rates
|
94
|
+
// 'playbackRates': [0.5, 1, 1.5, 2],
|
95
|
+
|
91
96
|
// Included control sets
|
92
97
|
'children': {
|
93
98
|
'mediaLoader': {},
|
@@ -95,18 +100,16 @@ vjs.options = {
|
|
95
100
|
'textTrackDisplay': {},
|
96
101
|
'loadingSpinner': {},
|
97
102
|
'bigPlayButton': {},
|
98
|
-
'controlBar': {}
|
103
|
+
'controlBar': {},
|
104
|
+
'errorDisplay': {}
|
99
105
|
},
|
100
106
|
|
101
107
|
// Default message to show when a video cannot be played.
|
102
|
-
'notSupportedMessage': '
|
103
|
-
'technology were found for this video. Try using another browser ' +
|
104
|
-
'like <a href="http://bit.ly/ccMUEC">Chrome</a> or download the ' +
|
105
|
-
'latest <a href="http://adobe.ly/mwfN1">Adobe Flash Player</a>.'
|
108
|
+
'notSupportedMessage': 'No compatible source was found for this video.'
|
106
109
|
};
|
107
110
|
|
108
111
|
// Set CDN Version of swf
|
109
|
-
// The added (+) blocks the replace from changing this 4.
|
112
|
+
// The added (+) blocks the replace from changing this 4.6 string
|
110
113
|
if (vjs.CDN_VERSION !== 'GENERATED'+'_CDN_VSN') {
|
111
114
|
videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/'+vjs.CDN_VERSION+'/video-js.swf';
|
112
115
|
}
|
@@ -673,7 +676,7 @@ vjs.obj = {};
|
|
673
676
|
* @param {Object} obj Object to use as prototype
|
674
677
|
* @private
|
675
678
|
*/
|
676
|
-
|
679
|
+
vjs.obj.create = Object.create || function(obj){
|
677
680
|
//Create a new function called 'F' which is just an empty object.
|
678
681
|
function F() {}
|
679
682
|
|
@@ -1074,7 +1077,7 @@ vjs.insertFirst = function(child, parent){
|
|
1074
1077
|
* @type {Object}
|
1075
1078
|
* @private
|
1076
1079
|
*/
|
1077
|
-
vjs.
|
1080
|
+
vjs.browser = {};
|
1078
1081
|
|
1079
1082
|
/**
|
1080
1083
|
* Shorthand for document.getElementById()
|
@@ -1179,15 +1182,19 @@ vjs.createTimeRange = function(start, end){
|
|
1179
1182
|
|
1180
1183
|
/**
|
1181
1184
|
* Simple http request for retrieving external files (e.g. text tracks)
|
1182
|
-
* @param {String}
|
1183
|
-
* @param {Function
|
1184
|
-
* @param {Function=} onError
|
1185
|
+
* @param {String} url URL of resource
|
1186
|
+
* @param {Function} onSuccess Success callback
|
1187
|
+
* @param {Function=} onError Error callback
|
1188
|
+
* @param {Boolean=} withCredentials Flag which allow credentials
|
1185
1189
|
* @private
|
1186
1190
|
*/
|
1187
|
-
vjs.get = function(url, onSuccess, onError){
|
1188
|
-
var
|
1191
|
+
vjs.get = function(url, onSuccess, onError, withCredentials){
|
1192
|
+
var fileUrl, request, urlInfo, winLoc, crossOrigin;
|
1193
|
+
|
1194
|
+
onError = onError || function(){};
|
1189
1195
|
|
1190
1196
|
if (typeof XMLHttpRequest === 'undefined') {
|
1197
|
+
// Shim XMLHttpRequest for older IEs
|
1191
1198
|
window.XMLHttpRequest = function () {
|
1192
1199
|
try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) {}
|
1193
1200
|
try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) {}
|
@@ -1197,32 +1204,59 @@ vjs.get = function(url, onSuccess, onError){
|
|
1197
1204
|
}
|
1198
1205
|
|
1199
1206
|
request = new XMLHttpRequest();
|
1200
|
-
try {
|
1201
|
-
request.open('GET', url);
|
1202
|
-
} catch(e) {
|
1203
|
-
onError(e);
|
1204
|
-
}
|
1205
1207
|
|
1206
|
-
|
1208
|
+
urlInfo = vjs.parseUrl(url);
|
1209
|
+
winLoc = window.location;
|
1210
|
+
// check if url is for another domain/origin
|
1211
|
+
// ie8 doesn't know location.origin, so we won't rely on it here
|
1212
|
+
crossOrigin = (urlInfo.protocol + urlInfo.host) !== (winLoc.protocol + winLoc.host);
|
1213
|
+
|
1214
|
+
// Use XDomainRequest for IE if XMLHTTPRequest2 isn't available
|
1215
|
+
// 'withCredentials' is only available in XMLHTTPRequest2
|
1216
|
+
// Also XDomainRequest has a lot of gotchas, so only use if cross domain
|
1217
|
+
if(crossOrigin && window.XDomainRequest && !('withCredentials' in request)) {
|
1218
|
+
request = new window.XDomainRequest();
|
1219
|
+
request.onload = function() {
|
1220
|
+
onSuccess(request.responseText);
|
1221
|
+
};
|
1222
|
+
request.onerror = onError;
|
1223
|
+
// these blank handlers need to be set to fix ie9 http://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/
|
1224
|
+
request.onprogress = function() {};
|
1225
|
+
request.ontimeout = onError;
|
1207
1226
|
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1227
|
+
// XMLHTTPRequest
|
1228
|
+
} else {
|
1229
|
+
fileUrl = (urlInfo.protocol == 'file:' || winLoc.protocol == 'file:');
|
1230
|
+
|
1231
|
+
request.onreadystatechange = function() {
|
1232
|
+
if (request.readyState === 4) {
|
1233
|
+
if (request.status === 200 || fileUrl && request.status === 0) {
|
1234
|
+
onSuccess(request.responseText);
|
1235
|
+
} else {
|
1236
|
+
onError(request.responseText);
|
1215
1237
|
}
|
1216
1238
|
}
|
1239
|
+
};
|
1240
|
+
}
|
1241
|
+
|
1242
|
+
// open the connection
|
1243
|
+
try {
|
1244
|
+
// Third arg is async, or ignored by XDomainRequest
|
1245
|
+
request.open('GET', url, true);
|
1246
|
+
// withCredentials only supported by XMLHttpRequest2
|
1247
|
+
if(withCredentials) {
|
1248
|
+
request.withCredentials = true;
|
1217
1249
|
}
|
1218
|
-
}
|
1250
|
+
} catch(e) {
|
1251
|
+
onError(e);
|
1252
|
+
return;
|
1253
|
+
}
|
1219
1254
|
|
1255
|
+
// send the request
|
1220
1256
|
try {
|
1221
1257
|
request.send();
|
1222
1258
|
} catch(e) {
|
1223
|
-
|
1224
|
-
onError(e);
|
1225
|
-
}
|
1259
|
+
onError(e);
|
1226
1260
|
}
|
1227
1261
|
};
|
1228
1262
|
|
@@ -1269,48 +1303,150 @@ vjs.getAbsoluteURL = function(url){
|
|
1269
1303
|
return url;
|
1270
1304
|
};
|
1271
1305
|
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1306
|
+
|
1307
|
+
/**
|
1308
|
+
* Resolve and parse the elements of a URL
|
1309
|
+
* @param {String} url The url to parse
|
1310
|
+
* @return {Object} An object of url details
|
1311
|
+
*/
|
1312
|
+
vjs.parseUrl = function(url) {
|
1313
|
+
var div, a, addToBody, props, details;
|
1314
|
+
|
1315
|
+
props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
|
1316
|
+
|
1317
|
+
// add the url to an anchor and let the browser parse the URL
|
1318
|
+
a = vjs.createEl('a', { href: url });
|
1319
|
+
|
1320
|
+
// IE8 (and 9?) Fix
|
1321
|
+
// ie8 doesn't parse the URL correctly until the anchor is actually
|
1322
|
+
// added to the body, and an innerHTML is needed to trigger the parsing
|
1323
|
+
addToBody = (a.host === '' && a.protocol !== 'file:');
|
1324
|
+
if (addToBody) {
|
1325
|
+
div = vjs.createEl('div');
|
1326
|
+
div.innerHTML = '<a href="'+url+'"></a>';
|
1327
|
+
a = div.firstChild;
|
1328
|
+
// prevent the div from affecting layout
|
1329
|
+
div.setAttribute('style', 'display:none; position:absolute;');
|
1330
|
+
document.body.appendChild(div);
|
1331
|
+
}
|
1332
|
+
|
1333
|
+
// Copy the specific URL properties to a new object
|
1334
|
+
// This is also needed for IE8 because the anchor loses its
|
1335
|
+
// properties when it's removed from the dom
|
1336
|
+
details = {};
|
1337
|
+
for (var i = 0; i < props.length; i++) {
|
1338
|
+
details[props[i]] = a[props[i]];
|
1339
|
+
}
|
1340
|
+
|
1341
|
+
if (addToBody) {
|
1342
|
+
document.body.removeChild(div);
|
1343
|
+
}
|
1344
|
+
|
1345
|
+
return details;
|
1346
|
+
};
|
1347
|
+
|
1348
|
+
// if there's no console then don't try to output messages
|
1349
|
+
// they will still be stored in vjs.log.history
|
1350
|
+
var _noop = function(){};
|
1351
|
+
var _console = window['console'] || {
|
1352
|
+
'log': _noop,
|
1353
|
+
'warn': _noop,
|
1354
|
+
'error': _noop
|
1355
|
+
};
|
1356
|
+
|
1357
|
+
/**
|
1358
|
+
* Log messags to the console and history based on the type of message
|
1359
|
+
*
|
1360
|
+
* @param {String} type The type of message, or `null` for `log`
|
1361
|
+
* @param {[type]} args The args to be passed to the log
|
1362
|
+
* @private
|
1363
|
+
*/
|
1364
|
+
function _logType(type, args){
|
1365
|
+
// convert args to an array to get array functions
|
1366
|
+
var argsArray = Array.prototype.slice.call(args);
|
1367
|
+
|
1368
|
+
if (type) {
|
1369
|
+
// add the type to the front of the message
|
1370
|
+
argsArray.unshift(type.toUpperCase()+':');
|
1371
|
+
} else {
|
1372
|
+
// default to log with no prefix
|
1373
|
+
type = 'log';
|
1374
|
+
}
|
1375
|
+
|
1376
|
+
// add to history
|
1377
|
+
vjs.log.history.push(argsArray);
|
1378
|
+
|
1379
|
+
// add console prefix after adding to history
|
1380
|
+
argsArray.unshift('VIDEOJS:');
|
1381
|
+
|
1382
|
+
// call appropriate log function
|
1383
|
+
if (_console[type].apply) {
|
1384
|
+
_console[type].apply(_console, argsArray);
|
1385
|
+
} else {
|
1386
|
+
// ie8 doesn't allow error.apply, but it will just join() the array anyway
|
1387
|
+
_console[type](argsArray.join(' '));
|
1279
1388
|
}
|
1389
|
+
}
|
1390
|
+
|
1391
|
+
/**
|
1392
|
+
* Log plain debug messages
|
1393
|
+
*/
|
1394
|
+
vjs.log = function(){
|
1395
|
+
_logType(null, arguments);
|
1396
|
+
};
|
1397
|
+
|
1398
|
+
/**
|
1399
|
+
* Keep a history of log messages
|
1400
|
+
* @type {Array}
|
1401
|
+
*/
|
1402
|
+
vjs.log.history = [];
|
1403
|
+
|
1404
|
+
/**
|
1405
|
+
* Log error messages
|
1406
|
+
*/
|
1407
|
+
vjs.log.error = function(){
|
1408
|
+
_logType('error', arguments);
|
1409
|
+
};
|
1410
|
+
|
1411
|
+
/**
|
1412
|
+
* Log warning messages
|
1413
|
+
*/
|
1414
|
+
vjs.log.warn = function(){
|
1415
|
+
_logType('warn', arguments);
|
1280
1416
|
};
|
1281
1417
|
|
1282
1418
|
// Offset Left
|
1283
1419
|
// getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
|
1284
1420
|
vjs.findPosition = function(el) {
|
1285
|
-
|
1421
|
+
var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top;
|
1286
1422
|
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1423
|
+
if (el.getBoundingClientRect && el.parentNode) {
|
1424
|
+
box = el.getBoundingClientRect();
|
1425
|
+
}
|
1290
1426
|
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1427
|
+
if (!box) {
|
1428
|
+
return {
|
1429
|
+
left: 0,
|
1430
|
+
top: 0
|
1431
|
+
};
|
1432
|
+
}
|
1297
1433
|
|
1298
|
-
|
1299
|
-
|
1434
|
+
docEl = document.documentElement;
|
1435
|
+
body = document.body;
|
1300
1436
|
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1437
|
+
clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
1438
|
+
scrollLeft = window.pageXOffset || body.scrollLeft;
|
1439
|
+
left = box.left + scrollLeft - clientLeft;
|
1304
1440
|
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1441
|
+
clientTop = docEl.clientTop || body.clientTop || 0;
|
1442
|
+
scrollTop = window.pageYOffset || body.scrollTop;
|
1443
|
+
top = box.top + scrollTop - clientTop;
|
1308
1444
|
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1445
|
+
// Android sometimes returns slightly off decimal values, so need to round
|
1446
|
+
return {
|
1447
|
+
left: vjs.round(left),
|
1448
|
+
top: vjs.round(top)
|
1449
|
+
};
|
1314
1450
|
};
|
1315
1451
|
/**
|
1316
1452
|
* Utility functions namespace
|
@@ -1320,10 +1456,10 @@ vjs.findPosition = function(el) {
|
|
1320
1456
|
vjs.util = {};
|
1321
1457
|
|
1322
1458
|
/**
|
1323
|
-
* Merge two options objects,
|
1459
|
+
* Merge two options objects,
|
1324
1460
|
* recursively merging any plain object properties as well.
|
1325
1461
|
* Previously `deepMerge`
|
1326
|
-
*
|
1462
|
+
*
|
1327
1463
|
* @param {Object} obj1 Object to override values in
|
1328
1464
|
* @param {Object} obj2 Overriding object
|
1329
1465
|
* @return {Object} New object -- obj1 and obj2 will be untouched
|
@@ -1793,31 +1929,62 @@ vjs.Component.prototype.removeChild = function(component){
|
|
1793
1929
|
* myChildOption: true
|
1794
1930
|
* }
|
1795
1931
|
* }
|
1932
|
+
*
|
1933
|
+
* // Or when creating the component
|
1934
|
+
* var myComp = new MyComponent(player, {
|
1935
|
+
* children: {
|
1936
|
+
* myChildComponent: {
|
1937
|
+
* myChildOption: true
|
1938
|
+
* }
|
1939
|
+
* }
|
1940
|
+
* });
|
1941
|
+
*
|
1942
|
+
* The children option can also be an Array of child names or
|
1943
|
+
* child options objects (that also include a 'name' key).
|
1944
|
+
*
|
1945
|
+
* var myComp = new MyComponent(player, {
|
1946
|
+
* children: [
|
1947
|
+
* 'button',
|
1948
|
+
* {
|
1949
|
+
* name: 'button',
|
1950
|
+
* someOtherOption: true
|
1951
|
+
* }
|
1952
|
+
* ]
|
1953
|
+
* });
|
1954
|
+
*
|
1796
1955
|
*/
|
1797
1956
|
vjs.Component.prototype.initChildren = function(){
|
1798
|
-
var
|
1957
|
+
var parent, children, child, name, opts;
|
1799
1958
|
|
1800
|
-
|
1801
|
-
|
1959
|
+
parent = this;
|
1960
|
+
children = this.options()['children'];
|
1802
1961
|
|
1803
|
-
|
1804
|
-
|
1805
|
-
|
1806
|
-
|
1807
|
-
|
1962
|
+
if (children) {
|
1963
|
+
// Allow for an array of children details to passed in the options
|
1964
|
+
if (children instanceof Array) {
|
1965
|
+
for (var i = 0; i < children.length; i++) {
|
1966
|
+
child = children[i];
|
1808
1967
|
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
1968
|
+
if (typeof child == 'string') {
|
1969
|
+
name = child;
|
1970
|
+
opts = {};
|
1971
|
+
} else {
|
1972
|
+
name = child.name;
|
1973
|
+
opts = child;
|
1974
|
+
}
|
1814
1975
|
|
1815
|
-
|
1816
|
-
// this.one(opts.loadEvent, tempAdd)
|
1817
|
-
} else {
|
1818
|
-
tempAdd();
|
1976
|
+
parent[name] = parent.addChild(name, opts);
|
1819
1977
|
}
|
1820
|
-
}
|
1978
|
+
} else {
|
1979
|
+
vjs.obj.each(children, function(name, opts){
|
1980
|
+
// Allow for disabling default components
|
1981
|
+
// e.g. vjs.options['children']['posterImage'] = false
|
1982
|
+
if (opts === false) return;
|
1983
|
+
|
1984
|
+
// Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.
|
1985
|
+
parent[name] = parent.addChild(name, opts);
|
1986
|
+
});
|
1987
|
+
}
|
1821
1988
|
}
|
1822
1989
|
};
|
1823
1990
|
|
@@ -2187,35 +2354,61 @@ vjs.Component.prototype.onResize;
|
|
2187
2354
|
* @private
|
2188
2355
|
*/
|
2189
2356
|
vjs.Component.prototype.emitTapEvents = function(){
|
2190
|
-
var touchStart, touchTime, couldBeTap, noTap
|
2357
|
+
var touchStart, firstTouch, touchTime, couldBeTap, noTap,
|
2358
|
+
xdiff, ydiff, touchDistance, tapMovementThreshold;
|
2191
2359
|
|
2192
2360
|
// Track the start time so we can determine how long the touch lasted
|
2193
2361
|
touchStart = 0;
|
2362
|
+
firstTouch = null;
|
2363
|
+
|
2364
|
+
// Maximum movement allowed during a touch event to still be considered a tap
|
2365
|
+
tapMovementThreshold = 22;
|
2194
2366
|
|
2195
2367
|
this.on('touchstart', function(event) {
|
2196
|
-
//
|
2197
|
-
|
2198
|
-
|
2199
|
-
|
2368
|
+
// If more than one finger, don't consider treating this as a click
|
2369
|
+
if (event.touches.length === 1) {
|
2370
|
+
firstTouch = event.touches[0];
|
2371
|
+
// Record start time so we can detect a tap vs. "touch and hold"
|
2372
|
+
touchStart = new Date().getTime();
|
2373
|
+
// Reset couldBeTap tracking
|
2374
|
+
couldBeTap = true;
|
2375
|
+
}
|
2376
|
+
});
|
2377
|
+
|
2378
|
+
this.on('touchmove', function(event) {
|
2379
|
+
// If more than one finger, don't consider treating this as a click
|
2380
|
+
if (event.touches.length > 1) {
|
2381
|
+
couldBeTap = false;
|
2382
|
+
} else if (firstTouch) {
|
2383
|
+
// Some devices will throw touchmoves for all but the slightest of taps.
|
2384
|
+
// So, if we moved only a small distance, this could still be a tap
|
2385
|
+
xdiff = event.touches[0].pageX - firstTouch.pageX;
|
2386
|
+
ydiff = event.touches[0].pageY - firstTouch.pageY;
|
2387
|
+
touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
|
2388
|
+
if (touchDistance > tapMovementThreshold) {
|
2389
|
+
couldBeTap = false;
|
2390
|
+
}
|
2391
|
+
}
|
2200
2392
|
});
|
2201
2393
|
|
2202
2394
|
noTap = function(){
|
2203
2395
|
couldBeTap = false;
|
2204
2396
|
};
|
2205
2397
|
// TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
|
2206
|
-
this.on('touchmove', noTap);
|
2207
2398
|
this.on('touchleave', noTap);
|
2208
2399
|
this.on('touchcancel', noTap);
|
2209
2400
|
|
2210
2401
|
// When the touch ends, measure how long it took and trigger the appropriate
|
2211
2402
|
// event
|
2212
2403
|
this.on('touchend', function(event) {
|
2404
|
+
firstTouch = null;
|
2213
2405
|
// Proceed only if the touchmove/leave/cancel event didn't happen
|
2214
2406
|
if (couldBeTap === true) {
|
2215
2407
|
// Measure how long the touch lasted
|
2216
2408
|
touchTime = new Date().getTime() - touchStart;
|
2217
2409
|
// The touch needs to be quick in order to consider it a tap
|
2218
2410
|
if (touchTime < 250) {
|
2411
|
+
event.preventDefault(); // Don't let browser turn this into a click
|
2219
2412
|
this.trigger('tap');
|
2220
2413
|
// It may be good to copy the touchend event object and change the
|
2221
2414
|
// type to tap, if the other event properties aren't exact after
|
@@ -2292,23 +2485,9 @@ vjs.Button = vjs.Component.extend({
|
|
2292
2485
|
init: function(player, options){
|
2293
2486
|
vjs.Component.call(this, player, options);
|
2294
2487
|
|
2295
|
-
|
2296
|
-
this.on('touchstart', function(event) {
|
2297
|
-
// Stop click and other mouse events from triggering also
|
2298
|
-
event.preventDefault();
|
2299
|
-
touchstart = true;
|
2300
|
-
});
|
2301
|
-
this.on('touchmove', function() {
|
2302
|
-
touchstart = false;
|
2303
|
-
});
|
2304
|
-
var self = this;
|
2305
|
-
this.on('touchend', function(event) {
|
2306
|
-
if (touchstart) {
|
2307
|
-
self.onClick(event);
|
2308
|
-
}
|
2309
|
-
event.preventDefault();
|
2310
|
-
});
|
2488
|
+
this.emitTapEvents();
|
2311
2489
|
|
2490
|
+
this.on('tap', this.onClick);
|
2312
2491
|
this.on('click', this.onClick);
|
2313
2492
|
this.on('focus', this.onFocus);
|
2314
2493
|
this.on('blur', this.onBlur);
|
@@ -2316,16 +2495,34 @@ vjs.Button = vjs.Component.extend({
|
|
2316
2495
|
});
|
2317
2496
|
|
2318
2497
|
vjs.Button.prototype.createEl = function(type, props){
|
2498
|
+
var el;
|
2499
|
+
|
2319
2500
|
// Add standard Aria and Tabindex info
|
2320
2501
|
props = vjs.obj.merge({
|
2321
2502
|
className: this.buildCSSClass(),
|
2322
|
-
innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">' + (this.buttonText || 'Need Text') + '</span></div>',
|
2323
2503
|
'role': 'button',
|
2324
2504
|
'aria-live': 'polite', // let the screen reader user know that the text of the button may change
|
2325
2505
|
tabIndex: 0
|
2326
2506
|
}, props);
|
2327
2507
|
|
2328
|
-
|
2508
|
+
el = vjs.Component.prototype.createEl.call(this, type, props);
|
2509
|
+
|
2510
|
+
// if innerHTML hasn't been overridden (bigPlayButton), add content elements
|
2511
|
+
if (!props.innerHTML) {
|
2512
|
+
this.contentEl_ = vjs.createEl('div', {
|
2513
|
+
className: 'vjs-control-content'
|
2514
|
+
});
|
2515
|
+
|
2516
|
+
this.controlText_ = vjs.createEl('span', {
|
2517
|
+
className: 'vjs-control-text',
|
2518
|
+
innerHTML: this.buttonText || 'Need Text'
|
2519
|
+
});
|
2520
|
+
|
2521
|
+
this.contentEl_.appendChild(this.controlText_);
|
2522
|
+
el.appendChild(this.contentEl_);
|
2523
|
+
}
|
2524
|
+
|
2525
|
+
return el;
|
2329
2526
|
};
|
2330
2527
|
|
2331
2528
|
vjs.Button.prototype.buildCSSClass = function(){
|
@@ -2372,8 +2569,6 @@ vjs.Slider = vjs.Component.extend({
|
|
2372
2569
|
this.bar = this.getChild(this.options_['barName']);
|
2373
2570
|
this.handle = this.getChild(this.options_['handleName']);
|
2374
2571
|
|
2375
|
-
player.on(this.playerEvent, vjs.bind(this, this.update));
|
2376
|
-
|
2377
2572
|
this.on('mousedown', this.onMouseDown);
|
2378
2573
|
this.on('touchstart', this.onMouseDown);
|
2379
2574
|
this.on('focus', this.onFocus);
|
@@ -2382,10 +2577,7 @@ vjs.Slider = vjs.Component.extend({
|
|
2382
2577
|
|
2383
2578
|
this.player_.on('controlsvisible', vjs.bind(this, this.update));
|
2384
2579
|
|
2385
|
-
|
2386
|
-
// this.player_.one('timeupdate', vjs.bind(this, this.update));
|
2387
|
-
|
2388
|
-
player.ready(vjs.bind(this, this.update));
|
2580
|
+
player.on(this.playerEvent, vjs.bind(this, this.update));
|
2389
2581
|
|
2390
2582
|
this.boundEvents = {};
|
2391
2583
|
}
|
@@ -2717,9 +2909,9 @@ vjs.MenuButton.prototype.createMenu = function(){
|
|
2717
2909
|
|
2718
2910
|
// Add a title list item to the top
|
2719
2911
|
if (this.options().title) {
|
2720
|
-
menu.
|
2912
|
+
menu.contentEl().appendChild(vjs.createEl('li', {
|
2721
2913
|
className: 'vjs-menu-title',
|
2722
|
-
innerHTML: vjs.capitalize(this.
|
2914
|
+
innerHTML: vjs.capitalize(this.options().title),
|
2723
2915
|
tabindex: -1
|
2724
2916
|
}));
|
2725
2917
|
}
|
@@ -2802,6 +2994,157 @@ vjs.MenuButton.prototype.unpressButton = function(){
|
|
2802
2994
|
this.el_.setAttribute('aria-pressed', false);
|
2803
2995
|
};
|
2804
2996
|
|
2997
|
+
/**
|
2998
|
+
* Custom MediaError to mimic the HTML5 MediaError
|
2999
|
+
* @param {Number} code The media error code
|
3000
|
+
*/
|
3001
|
+
vjs.MediaError = function(code){
|
3002
|
+
if (typeof code === 'number') {
|
3003
|
+
this.code = code;
|
3004
|
+
} else if (typeof code === 'string') {
|
3005
|
+
// default code is zero, so this is a custom error
|
3006
|
+
this.message = code;
|
3007
|
+
} else if (typeof code === 'object') { // object
|
3008
|
+
vjs.obj.merge(this, code);
|
3009
|
+
}
|
3010
|
+
|
3011
|
+
if (!this.message) {
|
3012
|
+
this.message = vjs.MediaError.defaultMessages[this.code] || '';
|
3013
|
+
}
|
3014
|
+
};
|
3015
|
+
|
3016
|
+
/**
|
3017
|
+
* The error code that refers two one of the defined
|
3018
|
+
* MediaError types
|
3019
|
+
* @type {Number}
|
3020
|
+
*/
|
3021
|
+
vjs.MediaError.prototype.code = 0;
|
3022
|
+
|
3023
|
+
/**
|
3024
|
+
* An optional message to be shown with the error.
|
3025
|
+
* Message is not part of the HTML5 video spec
|
3026
|
+
* but allows for more informative custom errors.
|
3027
|
+
* @type {String}
|
3028
|
+
*/
|
3029
|
+
vjs.MediaError.prototype.message = '';
|
3030
|
+
|
3031
|
+
/**
|
3032
|
+
* An optional status code that can be set by plugins
|
3033
|
+
* to allow even more detail about the error.
|
3034
|
+
* For example the HLS plugin might provide the specific
|
3035
|
+
* HTTP status code that was returned when the error
|
3036
|
+
* occurred, then allowing a custom error overlay
|
3037
|
+
* to display more information.
|
3038
|
+
* @type {[type]}
|
3039
|
+
*/
|
3040
|
+
vjs.MediaError.prototype.status = null;
|
3041
|
+
|
3042
|
+
vjs.MediaError.errorTypes = [
|
3043
|
+
'MEDIA_ERR_CUSTOM', // = 0
|
3044
|
+
'MEDIA_ERR_ABORTED', // = 1
|
3045
|
+
'MEDIA_ERR_NETWORK', // = 2
|
3046
|
+
'MEDIA_ERR_DECODE', // = 3
|
3047
|
+
'MEDIA_ERR_SRC_NOT_SUPPORTED', // = 4
|
3048
|
+
'MEDIA_ERR_ENCRYPTED' // = 5
|
3049
|
+
];
|
3050
|
+
|
3051
|
+
vjs.MediaError.defaultMessages = {
|
3052
|
+
1: 'You aborted the video playback',
|
3053
|
+
2: 'A network error caused the video download to fail part-way.',
|
3054
|
+
3: 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.',
|
3055
|
+
4: 'The video could not be loaded, either because the server or network failed or because the format is not supported.',
|
3056
|
+
5: 'The video is encrypted and we do not have the keys to decrypt it.'
|
3057
|
+
};
|
3058
|
+
|
3059
|
+
// Add types as properties on MediaError
|
3060
|
+
// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
|
3061
|
+
for (var errNum = 0; errNum < vjs.MediaError.errorTypes.length; errNum++) {
|
3062
|
+
vjs.MediaError[vjs.MediaError.errorTypes[errNum]] = errNum;
|
3063
|
+
// values should be accessible on both the class and instance
|
3064
|
+
vjs.MediaError.prototype[vjs.MediaError.errorTypes[errNum]] = errNum;
|
3065
|
+
}
|
3066
|
+
(function(){
|
3067
|
+
var apiMap, specApi, browserApi, i;
|
3068
|
+
|
3069
|
+
/**
|
3070
|
+
* Store the browser-specifc methods for the fullscreen API
|
3071
|
+
* @type {Object|undefined}
|
3072
|
+
* @private
|
3073
|
+
*/
|
3074
|
+
vjs.browser.fullscreenAPI;
|
3075
|
+
|
3076
|
+
// browser API methods
|
3077
|
+
// map approach from Screenful.js - https://github.com/sindresorhus/screenfull.js
|
3078
|
+
apiMap = [
|
3079
|
+
// Spec: https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html
|
3080
|
+
[
|
3081
|
+
'requestFullscreen',
|
3082
|
+
'exitFullscreen',
|
3083
|
+
'fullscreenElement',
|
3084
|
+
'fullscreenEnabled',
|
3085
|
+
'fullscreenchange',
|
3086
|
+
'fullscreenerror'
|
3087
|
+
],
|
3088
|
+
// WebKit
|
3089
|
+
[
|
3090
|
+
'webkitRequestFullscreen',
|
3091
|
+
'webkitExitFullscreen',
|
3092
|
+
'webkitFullscreenElement',
|
3093
|
+
'webkitFullscreenEnabled',
|
3094
|
+
'webkitfullscreenchange',
|
3095
|
+
'webkitfullscreenerror'
|
3096
|
+
],
|
3097
|
+
// Old WebKit (Safari 5.1)
|
3098
|
+
[
|
3099
|
+
'webkitRequestFullScreen',
|
3100
|
+
'webkitCancelFullScreen',
|
3101
|
+
'webkitCurrentFullScreenElement',
|
3102
|
+
'webkitCancelFullScreen',
|
3103
|
+
'webkitfullscreenchange',
|
3104
|
+
'webkitfullscreenerror'
|
3105
|
+
],
|
3106
|
+
// Mozilla
|
3107
|
+
[
|
3108
|
+
'mozRequestFullScreen',
|
3109
|
+
'mozCancelFullScreen',
|
3110
|
+
'mozFullScreenElement',
|
3111
|
+
'mozFullScreenEnabled',
|
3112
|
+
'mozfullscreenchange',
|
3113
|
+
'mozfullscreenerror'
|
3114
|
+
],
|
3115
|
+
// Microsoft
|
3116
|
+
[
|
3117
|
+
'msRequestFullscreen',
|
3118
|
+
'msExitFullscreen',
|
3119
|
+
'msFullscreenElement',
|
3120
|
+
'msFullscreenEnabled',
|
3121
|
+
'MSFullscreenChange',
|
3122
|
+
'MSFullscreenError'
|
3123
|
+
]
|
3124
|
+
];
|
3125
|
+
|
3126
|
+
specApi = apiMap[0];
|
3127
|
+
|
3128
|
+
// determine the supported set of functions
|
3129
|
+
for (i=0; i<apiMap.length; i++) {
|
3130
|
+
// check for exitFullscreen function
|
3131
|
+
if (apiMap[i][1] in document) {
|
3132
|
+
browserApi = apiMap[i];
|
3133
|
+
break;
|
3134
|
+
}
|
3135
|
+
}
|
3136
|
+
|
3137
|
+
// map the browser API names to the spec API names
|
3138
|
+
// or leave vjs.browser.fullscreenAPI undefined
|
3139
|
+
if (browserApi) {
|
3140
|
+
vjs.browser.fullscreenAPI = {};
|
3141
|
+
|
3142
|
+
for (i=0; i<browserApi.length; i++) {
|
3143
|
+
vjs.browser.fullscreenAPI[specApi[i]] = browserApi[i];
|
3144
|
+
}
|
3145
|
+
}
|
3146
|
+
|
3147
|
+
})();
|
2805
3148
|
/**
|
2806
3149
|
* An instance of the `vjs.Player` class is created when any of the Video.js setup methods are used to initialize a video.
|
2807
3150
|
*
|
@@ -2862,6 +3205,20 @@ vjs.Player = vjs.Component.extend({
|
|
2862
3205
|
// see enableTouchActivity in Component
|
2863
3206
|
options.reportTouchActivity = false;
|
2864
3207
|
|
3208
|
+
// Make sure the event listeners are the first things to happen when
|
3209
|
+
// the player is ready. See #1208
|
3210
|
+
// If not, the tech might fire events before the listeners are attached.
|
3211
|
+
this.ready(function(){
|
3212
|
+
this.on('loadstart', this.onLoadStart);
|
3213
|
+
this.on('ended', this.onEnded);
|
3214
|
+
this.on('play', this.onPlay);
|
3215
|
+
this.on('firstplay', this.onFirstPlay);
|
3216
|
+
this.on('pause', this.onPause);
|
3217
|
+
this.on('progress', this.onProgress);
|
3218
|
+
this.on('durationchange', this.onDurationChange);
|
3219
|
+
this.on('fullscreenchange', this.onFullscreenChange);
|
3220
|
+
});
|
3221
|
+
|
2865
3222
|
// Run base component initializing with new options.
|
2866
3223
|
// Builds the element through createEl()
|
2867
3224
|
// Inits and embeds any child components in opts
|
@@ -2881,29 +3238,6 @@ vjs.Player = vjs.Component.extend({
|
|
2881
3238
|
// this.addClass('vjs-touch-enabled');
|
2882
3239
|
// }
|
2883
3240
|
|
2884
|
-
// Firstplay event implimentation. Not sold on the event yet.
|
2885
|
-
// Could probably just check currentTime==0?
|
2886
|
-
this.one('play', function(e){
|
2887
|
-
var fpEvent = { type: 'firstplay', target: this.el_ };
|
2888
|
-
// Using vjs.trigger so we can check if default was prevented
|
2889
|
-
var keepGoing = vjs.trigger(this.el_, fpEvent);
|
2890
|
-
|
2891
|
-
if (!keepGoing) {
|
2892
|
-
e.preventDefault();
|
2893
|
-
e.stopPropagation();
|
2894
|
-
e.stopImmediatePropagation();
|
2895
|
-
}
|
2896
|
-
});
|
2897
|
-
|
2898
|
-
this.on('ended', this.onEnded);
|
2899
|
-
this.on('play', this.onPlay);
|
2900
|
-
this.on('firstplay', this.onFirstPlay);
|
2901
|
-
this.on('pause', this.onPause);
|
2902
|
-
this.on('progress', this.onProgress);
|
2903
|
-
this.on('durationchange', this.onDurationChange);
|
2904
|
-
this.on('error', this.onError);
|
2905
|
-
this.on('fullscreenchange', this.onFullscreenChange);
|
2906
|
-
|
2907
3241
|
// Make player easily findable by ID
|
2908
3242
|
vjs.players[this.id_] = this;
|
2909
3243
|
|
@@ -3139,14 +3473,16 @@ vjs.Player.prototype.manualProgressOn = function(){
|
|
3139
3473
|
// In HTML5, some older versions don't support the progress event
|
3140
3474
|
// So we're assuming they don't, and turning off manual progress if they do.
|
3141
3475
|
// As opposed to doing user agent detection
|
3142
|
-
this.tech
|
3476
|
+
if (this.tech) {
|
3477
|
+
this.tech.one('progress', function(){
|
3143
3478
|
|
3144
|
-
|
3145
|
-
|
3479
|
+
// Update known progress support for this playback technology
|
3480
|
+
this.features['progressEvents'] = true;
|
3146
3481
|
|
3147
|
-
|
3148
|
-
|
3149
|
-
|
3482
|
+
// Turn off manual progress tracking
|
3483
|
+
this.player_.manualProgressOff();
|
3484
|
+
});
|
3485
|
+
}
|
3150
3486
|
};
|
3151
3487
|
|
3152
3488
|
vjs.Player.prototype.manualProgressOff = function(){
|
@@ -3179,12 +3515,14 @@ vjs.Player.prototype.manualTimeUpdatesOn = function(){
|
|
3179
3515
|
// timeupdate is also called by .currentTime whenever current time is set
|
3180
3516
|
|
3181
3517
|
// Watch for native timeupdate event
|
3182
|
-
this.tech
|
3183
|
-
|
3184
|
-
|
3185
|
-
|
3186
|
-
|
3187
|
-
|
3518
|
+
if (this.tech) {
|
3519
|
+
this.tech.one('timeupdate', function(){
|
3520
|
+
// Update known progress support for this playback technology
|
3521
|
+
this.features['timeupdateEvents'] = true;
|
3522
|
+
// Turn off manual progress tracking
|
3523
|
+
this.player_.manualTimeUpdatesOff();
|
3524
|
+
});
|
3525
|
+
}
|
3188
3526
|
};
|
3189
3527
|
|
3190
3528
|
vjs.Player.prototype.manualTimeUpdatesOff = function(){
|
@@ -3202,8 +3540,13 @@ vjs.Player.prototype.trackCurrentTime = function(){
|
|
3202
3540
|
};
|
3203
3541
|
|
3204
3542
|
// Turn off play progress tracking (when paused or dragging)
|
3205
|
-
vjs.Player.prototype.stopTrackingCurrentTime = function(){
|
3543
|
+
vjs.Player.prototype.stopTrackingCurrentTime = function(){
|
3544
|
+
clearInterval(this.currentTimeInterval);
|
3206
3545
|
|
3546
|
+
// #1002 - if the video ends right before the next timeupdate would happen,
|
3547
|
+
// the progress bar won't make it all the way to the end
|
3548
|
+
this.trigger('timeupdate');
|
3549
|
+
};
|
3207
3550
|
// /* Player event handlers (how the player reacts to certain events)
|
3208
3551
|
// ================================================================================ */
|
3209
3552
|
|
@@ -3211,7 +3554,31 @@ vjs.Player.prototype.stopTrackingCurrentTime = function(){ clearInterval(this.cu
|
|
3211
3554
|
* Fired when the user agent begins looking for media data
|
3212
3555
|
* @event loadstart
|
3213
3556
|
*/
|
3214
|
-
vjs.Player.prototype.onLoadStart
|
3557
|
+
vjs.Player.prototype.onLoadStart = function() {
|
3558
|
+
// remove any first play listeners that weren't triggered from a previous video.
|
3559
|
+
this.off('play', initFirstPlay);
|
3560
|
+
this.one('play', initFirstPlay);
|
3561
|
+
|
3562
|
+
if (this.error()) {
|
3563
|
+
this.error(null);
|
3564
|
+
}
|
3565
|
+
|
3566
|
+
vjs.removeClass(this.el_, 'vjs-has-started');
|
3567
|
+
};
|
3568
|
+
|
3569
|
+
// Need to create this outside the scope of onLoadStart so it
|
3570
|
+
// can be added and removed (to avoid piling first play listeners).
|
3571
|
+
function initFirstPlay(e) {
|
3572
|
+
var fpEvent = { type: 'firstplay', target: this.el_ };
|
3573
|
+
// Using vjs.trigger so we can check if default was prevented
|
3574
|
+
var keepGoing = vjs.trigger(this.el_, fpEvent);
|
3575
|
+
|
3576
|
+
if (!keepGoing) {
|
3577
|
+
e.preventDefault();
|
3578
|
+
e.stopPropagation();
|
3579
|
+
e.stopImmediatePropagation();
|
3580
|
+
}
|
3581
|
+
}
|
3215
3582
|
|
3216
3583
|
/**
|
3217
3584
|
* Fired when the player has initial duration and dimension information
|
@@ -3309,7 +3676,16 @@ vjs.Player.prototype.onDurationChange = function(){
|
|
3309
3676
|
// accidentally cause the stack to blow up.
|
3310
3677
|
var duration = this.techGet('duration');
|
3311
3678
|
if (duration) {
|
3679
|
+
if (duration < 0) {
|
3680
|
+
duration = Infinity;
|
3681
|
+
}
|
3312
3682
|
this.duration(duration);
|
3683
|
+
// Determine if the stream is live and propagate styles down to UI.
|
3684
|
+
if (duration === Infinity) {
|
3685
|
+
this.addClass('vjs-live');
|
3686
|
+
} else {
|
3687
|
+
this.removeClass('vjs-live');
|
3688
|
+
}
|
3313
3689
|
}
|
3314
3690
|
};
|
3315
3691
|
|
@@ -3324,21 +3700,13 @@ vjs.Player.prototype.onVolumeChange;
|
|
3324
3700
|
* @event fullscreenchange
|
3325
3701
|
*/
|
3326
3702
|
vjs.Player.prototype.onFullscreenChange = function() {
|
3327
|
-
if (this.
|
3703
|
+
if (this.isFullscreen()) {
|
3328
3704
|
this.addClass('vjs-fullscreen');
|
3329
3705
|
} else {
|
3330
3706
|
this.removeClass('vjs-fullscreen');
|
3331
3707
|
}
|
3332
3708
|
};
|
3333
3709
|
|
3334
|
-
/**
|
3335
|
-
* Fired when there is an error in playback
|
3336
|
-
* @event error
|
3337
|
-
*/
|
3338
|
-
vjs.Player.prototype.onError = function(e) {
|
3339
|
-
vjs.log('Video Error', e);
|
3340
|
-
};
|
3341
|
-
|
3342
3710
|
// /* Player API
|
3343
3711
|
// ================================================================================ */
|
3344
3712
|
|
@@ -3373,7 +3741,6 @@ vjs.Player.prototype.techCall = function(method, arg){
|
|
3373
3741
|
|
3374
3742
|
// Get calls can't wait for the tech, and sometimes don't need to.
|
3375
3743
|
vjs.Player.prototype.techGet = function(method){
|
3376
|
-
|
3377
3744
|
if (this.tech && this.tech.isReady_) {
|
3378
3745
|
|
3379
3746
|
// Flash likes to die and reload when you hide or reposition it.
|
@@ -3622,37 +3989,46 @@ vjs.Player.prototype.supportsFullScreen = function(){
|
|
3622
3989
|
* @type {Boolean}
|
3623
3990
|
* @private
|
3624
3991
|
*/
|
3625
|
-
vjs.Player.prototype.
|
3992
|
+
vjs.Player.prototype.isFullscreen_ = false;
|
3626
3993
|
|
3627
3994
|
/**
|
3628
3995
|
* Check if the player is in fullscreen mode
|
3629
3996
|
*
|
3630
3997
|
* // get
|
3631
|
-
* var fullscreenOrNot = myPlayer.
|
3998
|
+
* var fullscreenOrNot = myPlayer.isFullscreen();
|
3632
3999
|
*
|
3633
4000
|
* // set
|
3634
|
-
* myPlayer.
|
4001
|
+
* myPlayer.isFullscreen(true); // tell the player it's in fullscreen
|
3635
4002
|
*
|
3636
|
-
* NOTE: As of the latest HTML5 spec,
|
3637
|
-
* property and instead document.fullscreenElement is used. But
|
4003
|
+
* NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
|
4004
|
+
* property and instead document.fullscreenElement is used. But isFullscreen is
|
3638
4005
|
* still a valuable property for internal player workings.
|
3639
4006
|
*
|
3640
4007
|
* @param {Boolean=} isFS Update the player's fullscreen state
|
3641
4008
|
* @return {Boolean} true if fullscreen, false if not
|
3642
4009
|
* @return {vjs.Player} self, when setting
|
3643
4010
|
*/
|
3644
|
-
vjs.Player.prototype.
|
4011
|
+
vjs.Player.prototype.isFullscreen = function(isFS){
|
3645
4012
|
if (isFS !== undefined) {
|
3646
|
-
this.
|
4013
|
+
this.isFullscreen_ = !!isFS;
|
3647
4014
|
return this;
|
3648
4015
|
}
|
3649
|
-
return this.
|
4016
|
+
return this.isFullscreen_;
|
4017
|
+
};
|
4018
|
+
|
4019
|
+
/**
|
4020
|
+
* Old naming for isFullscreen()
|
4021
|
+
* @deprecated for lowercase 's' version
|
4022
|
+
*/
|
4023
|
+
vjs.Player.prototype.isFullScreen = function(isFS){
|
4024
|
+
vjs.log.warn('player.isFullScreen() has been deprecated, use player.isFullscreen() with a lowercase "s")');
|
4025
|
+
return this.isFullscreen(isFS);
|
3650
4026
|
};
|
3651
4027
|
|
3652
4028
|
/**
|
3653
4029
|
* Increase the size of the video to full screen
|
3654
4030
|
*
|
3655
|
-
* myPlayer.
|
4031
|
+
* myPlayer.requestFullscreen();
|
3656
4032
|
*
|
3657
4033
|
* In some browsers, full screen is not supported natively, so it enters
|
3658
4034
|
* "full window mode", where the video fills the browser window.
|
@@ -3663,11 +4039,12 @@ vjs.Player.prototype.isFullScreen = function(isFS){
|
|
3663
4039
|
*
|
3664
4040
|
* @return {vjs.Player} self
|
3665
4041
|
*/
|
3666
|
-
vjs.Player.prototype.
|
3667
|
-
var
|
3668
|
-
|
4042
|
+
vjs.Player.prototype.requestFullscreen = function(){
|
4043
|
+
var fsApi = vjs.browser.fullscreenAPI;
|
4044
|
+
|
4045
|
+
this.isFullscreen(true);
|
3669
4046
|
|
3670
|
-
if (
|
4047
|
+
if (fsApi) {
|
3671
4048
|
// the browser supports going fullscreen at the element level so we can
|
3672
4049
|
// take the controls fullscreen as well as the video
|
3673
4050
|
|
@@ -3676,18 +4053,18 @@ vjs.Player.prototype.requestFullScreen = function(){
|
|
3676
4053
|
// when cancelling fullscreen. Otherwise if there's multiple
|
3677
4054
|
// players on a page, they would all be reacting to the same fullscreen
|
3678
4055
|
// events
|
3679
|
-
vjs.on(document,
|
3680
|
-
this.
|
4056
|
+
vjs.on(document, fsApi.fullscreenchange, vjs.bind(this, function(e){
|
4057
|
+
this.isFullscreen(document[fsApi.fullscreenElement]);
|
3681
4058
|
|
3682
4059
|
// If cancelling fullscreen, remove event listener.
|
3683
|
-
if (this.
|
3684
|
-
vjs.off(document,
|
4060
|
+
if (this.isFullscreen() === false) {
|
4061
|
+
vjs.off(document, fsApi.fullscreenchange, arguments.callee);
|
3685
4062
|
}
|
3686
4063
|
|
3687
4064
|
this.trigger('fullscreenchange');
|
3688
4065
|
}));
|
3689
4066
|
|
3690
|
-
this.el_[
|
4067
|
+
this.el_[fsApi.requestFullscreen]();
|
3691
4068
|
|
3692
4069
|
} else if (this.tech.supportsFullScreen()) {
|
3693
4070
|
// we can't take the video.js controls fullscreen but we can go fullscreen
|
@@ -3703,20 +4080,30 @@ vjs.Player.prototype.requestFullScreen = function(){
|
|
3703
4080
|
return this;
|
3704
4081
|
};
|
3705
4082
|
|
4083
|
+
/**
|
4084
|
+
* Old naming for requestFullscreen
|
4085
|
+
* @deprecated for lower case 's' version
|
4086
|
+
*/
|
4087
|
+
vjs.Player.prototype.requestFullScreen = function(){
|
4088
|
+
vjs.log.warn('player.requestFullScreen() has been deprecated, use player.requestFullscreen() with a lowercase "s")');
|
4089
|
+
return this.requestFullscreen();
|
4090
|
+
};
|
4091
|
+
|
4092
|
+
|
3706
4093
|
/**
|
3707
4094
|
* Return the video to its normal size after having been in full screen mode
|
3708
4095
|
*
|
3709
|
-
* myPlayer.
|
4096
|
+
* myPlayer.exitFullscreen();
|
3710
4097
|
*
|
3711
4098
|
* @return {vjs.Player} self
|
3712
4099
|
*/
|
3713
|
-
vjs.Player.prototype.
|
3714
|
-
var
|
3715
|
-
this.
|
4100
|
+
vjs.Player.prototype.exitFullscreen = function(){
|
4101
|
+
var fsApi = vjs.browser.fullscreenAPI;
|
4102
|
+
this.isFullscreen(false);
|
3716
4103
|
|
3717
4104
|
// Check for browser element fullscreen support
|
3718
|
-
if (
|
3719
|
-
document[
|
4105
|
+
if (fsApi) {
|
4106
|
+
document[fsApi.exitFullscreen]();
|
3720
4107
|
} else if (this.tech.supportsFullScreen()) {
|
3721
4108
|
this.techCall('exitFullScreen');
|
3722
4109
|
} else {
|
@@ -3727,6 +4114,15 @@ vjs.Player.prototype.cancelFullScreen = function(){
|
|
3727
4114
|
return this;
|
3728
4115
|
};
|
3729
4116
|
|
4117
|
+
/**
|
4118
|
+
* Old naming for exitFullscreen
|
4119
|
+
* @deprecated for exitFullscreen
|
4120
|
+
*/
|
4121
|
+
vjs.Player.prototype.cancelFullScreen = function(){
|
4122
|
+
vjs.log.warn('player.cancelFullScreen() has been deprecated, use player.exitFullscreen()');
|
4123
|
+
return this.exitFullscreen();
|
4124
|
+
};
|
4125
|
+
|
3730
4126
|
// When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
|
3731
4127
|
vjs.Player.prototype.enterFullWindow = function(){
|
3732
4128
|
this.isFullWindow = true;
|
@@ -3747,8 +4143,8 @@ vjs.Player.prototype.enterFullWindow = function(){
|
|
3747
4143
|
};
|
3748
4144
|
vjs.Player.prototype.fullWindowOnEscKey = function(event){
|
3749
4145
|
if (event.keyCode === 27) {
|
3750
|
-
if (this.
|
3751
|
-
this.
|
4146
|
+
if (this.isFullscreen() === true) {
|
4147
|
+
this.exitFullscreen();
|
3752
4148
|
} else {
|
3753
4149
|
this.exitFullWindow();
|
3754
4150
|
}
|
@@ -3777,6 +4173,12 @@ vjs.Player.prototype.selectSource = function(sources){
|
|
3777
4173
|
var techName = vjs.capitalize(j[i]),
|
3778
4174
|
tech = window['videojs'][techName];
|
3779
4175
|
|
4176
|
+
// Check if the current tech is defined before continuing
|
4177
|
+
if (!tech) {
|
4178
|
+
vjs.log.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
|
4179
|
+
continue;
|
4180
|
+
}
|
4181
|
+
|
3780
4182
|
// Check if the browser supports this technology
|
3781
4183
|
if (tech.isSupported()) {
|
3782
4184
|
// Loop through each source object
|
@@ -3849,9 +4251,10 @@ vjs.Player.prototype.src = function(source){
|
|
3849
4251
|
this.loadTech(techName, source);
|
3850
4252
|
}
|
3851
4253
|
} else {
|
3852
|
-
this.el_.appendChild(vjs.createEl('p', {
|
3853
|
-
|
3854
|
-
}));
|
4254
|
+
// this.el_.appendChild(vjs.createEl('p', {
|
4255
|
+
// innerHTML: this.options()['notSupportedMessage']
|
4256
|
+
// }));
|
4257
|
+
this.error({ code: 4, message: this.options()['notSupportedMessage'] });
|
3855
4258
|
this.triggerReady(); // we could not find an appropriate tech, but let's still notify the delegate that this is it
|
3856
4259
|
}
|
3857
4260
|
|
@@ -4047,7 +4450,51 @@ vjs.Player.prototype.usingNativeControls = function(bool){
|
|
4047
4450
|
return this.usingNativeControls_;
|
4048
4451
|
};
|
4049
4452
|
|
4050
|
-
|
4453
|
+
/**
|
4454
|
+
* Store the current media error
|
4455
|
+
* @type {Object}
|
4456
|
+
* @private
|
4457
|
+
*/
|
4458
|
+
vjs.Player.prototype.error_ = null;
|
4459
|
+
|
4460
|
+
/**
|
4461
|
+
* Set or get the current MediaError
|
4462
|
+
* @param {*} err A MediaError or a String/Number to be turned into a MediaError
|
4463
|
+
* @return {vjs.MediaError|null} when getting
|
4464
|
+
* @return {vjs.Player} when setting
|
4465
|
+
*/
|
4466
|
+
vjs.Player.prototype.error = function(err){
|
4467
|
+
if (err === undefined) {
|
4468
|
+
return this.error_;
|
4469
|
+
}
|
4470
|
+
|
4471
|
+
// restoring to default
|
4472
|
+
if (err === null) {
|
4473
|
+
this.error_ = err;
|
4474
|
+
this.removeClass('vjs-error');
|
4475
|
+
return this;
|
4476
|
+
}
|
4477
|
+
|
4478
|
+
// error instance
|
4479
|
+
if (err instanceof vjs.MediaError) {
|
4480
|
+
this.error_ = err;
|
4481
|
+
} else {
|
4482
|
+
this.error_ = new vjs.MediaError(err);
|
4483
|
+
}
|
4484
|
+
|
4485
|
+
// fire an error event on the player
|
4486
|
+
this.trigger('error');
|
4487
|
+
|
4488
|
+
// add the vjs-error classname to the player
|
4489
|
+
this.addClass('vjs-error');
|
4490
|
+
|
4491
|
+
// log the name of the error type and any message
|
4492
|
+
// ie8 just logs "[object object]" if you just log the error object
|
4493
|
+
vjs.log.error('(CODE:'+this.error_.code+' '+vjs.MediaError.errorTypes[this.error_.code]+')', this.error_.message, this.error_);
|
4494
|
+
|
4495
|
+
return this;
|
4496
|
+
};
|
4497
|
+
|
4051
4498
|
vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
|
4052
4499
|
vjs.Player.prototype.seeking = function(){ return this.techGet('seeking'); };
|
4053
4500
|
|
@@ -4102,13 +4549,23 @@ vjs.Player.prototype.userActive = function(bool){
|
|
4102
4549
|
};
|
4103
4550
|
|
4104
4551
|
vjs.Player.prototype.listenForUserActivity = function(){
|
4105
|
-
var
|
4106
|
-
activityCheck, inactivityTimeout;
|
4107
|
-
|
4108
|
-
|
4552
|
+
var onActivity, onMouseMove, onMouseDown, mouseInProgress, onMouseUp,
|
4553
|
+
activityCheck, inactivityTimeout, lastMoveX, lastMoveY;
|
4554
|
+
|
4555
|
+
onActivity = vjs.bind(this, this.reportUserActivity);
|
4556
|
+
|
4557
|
+
onMouseMove = function(e) {
|
4558
|
+
// #1068 - Prevent mousemove spamming
|
4559
|
+
// Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
|
4560
|
+
if(e.screenX != lastMoveX || e.screenY != lastMoveY) {
|
4561
|
+
lastMoveX = e.screenX;
|
4562
|
+
lastMoveY = e.screenY;
|
4563
|
+
onActivity();
|
4564
|
+
}
|
4565
|
+
};
|
4109
4566
|
|
4110
4567
|
onMouseDown = function() {
|
4111
|
-
|
4568
|
+
onActivity();
|
4112
4569
|
// For as long as the they are touching the device or have their mouse down,
|
4113
4570
|
// we consider them active even if they're not moving their finger or mouse.
|
4114
4571
|
// So we want to continue to update that they are active
|
@@ -4116,24 +4573,24 @@ vjs.Player.prototype.listenForUserActivity = function(){
|
|
4116
4573
|
// Setting userActivity=true now and setting the interval to the same time
|
4117
4574
|
// as the activityCheck interval (250) should ensure we never miss the
|
4118
4575
|
// next activityCheck
|
4119
|
-
mouseInProgress = setInterval(
|
4576
|
+
mouseInProgress = setInterval(onActivity, 250);
|
4120
4577
|
};
|
4121
4578
|
|
4122
4579
|
onMouseUp = function(event) {
|
4123
|
-
|
4580
|
+
onActivity();
|
4124
4581
|
// Stop the interval that maintains activity if the mouse/touch is down
|
4125
4582
|
clearInterval(mouseInProgress);
|
4126
4583
|
};
|
4127
4584
|
|
4128
4585
|
// Any mouse movement will be considered user activity
|
4129
4586
|
this.on('mousedown', onMouseDown);
|
4130
|
-
this.on('mousemove',
|
4587
|
+
this.on('mousemove', onMouseMove);
|
4131
4588
|
this.on('mouseup', onMouseUp);
|
4132
4589
|
|
4133
4590
|
// Listen for keyboard navigation
|
4134
4591
|
// Shouldn't need to use inProgress interval because of key repeat
|
4135
|
-
this.on('keydown',
|
4136
|
-
this.on('keyup',
|
4592
|
+
this.on('keydown', onActivity);
|
4593
|
+
this.on('keyup', onActivity);
|
4137
4594
|
|
4138
4595
|
// Run an interval every 250 milliseconds instead of stuffing everything into
|
4139
4596
|
// the mousemove/touchmove function itself, to prevent performance degradation.
|
@@ -4172,6 +4629,20 @@ vjs.Player.prototype.listenForUserActivity = function(){
|
|
4172
4629
|
});
|
4173
4630
|
};
|
4174
4631
|
|
4632
|
+
vjs.Player.prototype.playbackRate = function(rate) {
|
4633
|
+
if (rate !== undefined) {
|
4634
|
+
this.techCall('setPlaybackRate', rate);
|
4635
|
+
return this;
|
4636
|
+
}
|
4637
|
+
|
4638
|
+
if (this.tech && this.tech.features && this.tech.features['playbackRate']) {
|
4639
|
+
return this.techGet('playbackRate');
|
4640
|
+
} else {
|
4641
|
+
return 1.0;
|
4642
|
+
}
|
4643
|
+
|
4644
|
+
};
|
4645
|
+
|
4175
4646
|
// Methods to add support for
|
4176
4647
|
// networkState: function(){ return this.techCall('networkState'); },
|
4177
4648
|
// readyState: function(){ return this.techCall('readyState'); },
|
@@ -4184,7 +4655,6 @@ vjs.Player.prototype.listenForUserActivity = function(){
|
|
4184
4655
|
// videoWidth: function(){ return this.techCall('videoWidth'); },
|
4185
4656
|
// videoHeight: function(){ return this.techCall('videoHeight'); },
|
4186
4657
|
// defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },
|
4187
|
-
// playbackRate: function(){ return this.techCall('playbackRate'); },
|
4188
4658
|
// mediaGroup: function(){ return this.techCall('mediaGroup'); },
|
4189
4659
|
// controller: function(){ return this.techCall('controller'); },
|
4190
4660
|
// defaultMuted: function(){ return this.techCall('defaultMuted'); }
|
@@ -4192,52 +4662,6 @@ vjs.Player.prototype.listenForUserActivity = function(){
|
|
4192
4662
|
// TODO
|
4193
4663
|
// currentSrcList: the array of sources including other formats and bitrates
|
4194
4664
|
// playList: array of source lists in order of playback
|
4195
|
-
|
4196
|
-
// RequestFullscreen API
|
4197
|
-
(function(){
|
4198
|
-
var prefix, requestFS, div;
|
4199
|
-
|
4200
|
-
div = document.createElement('div');
|
4201
|
-
|
4202
|
-
requestFS = {};
|
4203
|
-
|
4204
|
-
// Current W3C Spec
|
4205
|
-
// http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api
|
4206
|
-
// Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event
|
4207
|
-
// New: https://dvcs.w3.org/hg/fullscreen/raw-file/529a67b8d9f3/Overview.html
|
4208
|
-
if (div.cancelFullscreen !== undefined) {
|
4209
|
-
requestFS.requestFn = 'requestFullscreen';
|
4210
|
-
requestFS.cancelFn = 'exitFullscreen';
|
4211
|
-
requestFS.eventName = 'fullscreenchange';
|
4212
|
-
requestFS.isFullScreen = 'fullScreen';
|
4213
|
-
|
4214
|
-
// Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementations
|
4215
|
-
// that use prefixes and vary slightly from the new W3C spec. Specifically,
|
4216
|
-
// using 'exit' instead of 'cancel', and lowercasing the 'S' in Fullscreen.
|
4217
|
-
// Other browsers don't have any hints of which version they might follow yet,
|
4218
|
-
// so not going to try to predict by looping through all prefixes.
|
4219
|
-
} else {
|
4220
|
-
|
4221
|
-
if (document.mozCancelFullScreen) {
|
4222
|
-
prefix = 'moz';
|
4223
|
-
requestFS.isFullScreen = prefix + 'FullScreen';
|
4224
|
-
} else {
|
4225
|
-
prefix = 'webkit';
|
4226
|
-
requestFS.isFullScreen = prefix + 'IsFullScreen';
|
4227
|
-
}
|
4228
|
-
|
4229
|
-
if (div[prefix + 'RequestFullScreen']) {
|
4230
|
-
requestFS.requestFn = prefix + 'RequestFullScreen';
|
4231
|
-
requestFS.cancelFn = prefix + 'CancelFullScreen';
|
4232
|
-
}
|
4233
|
-
requestFS.eventName = prefix + 'fullscreenchange';
|
4234
|
-
}
|
4235
|
-
|
4236
|
-
if (document[requestFS.cancelFn]) {
|
4237
|
-
vjs.support.requestFullScreen = requestFS;
|
4238
|
-
}
|
4239
|
-
|
4240
|
-
})();
|
4241
4665
|
/**
|
4242
4666
|
* Container of main controls
|
4243
4667
|
* @param {vjs.Player|Object} player
|
@@ -4256,11 +4680,13 @@ vjs.ControlBar.prototype.options_ = {
|
|
4256
4680
|
'timeDivider': {},
|
4257
4681
|
'durationDisplay': {},
|
4258
4682
|
'remainingTimeDisplay': {},
|
4683
|
+
'liveDisplay': {},
|
4259
4684
|
'progressControl': {},
|
4260
4685
|
'fullscreenToggle': {},
|
4261
4686
|
'volumeControl': {},
|
4262
|
-
'muteToggle': {}
|
4263
|
-
// 'volumeMenuButton': {}
|
4687
|
+
'muteToggle': {},
|
4688
|
+
// 'volumeMenuButton': {},
|
4689
|
+
'playbackRateMenuButton': {}
|
4264
4690
|
}
|
4265
4691
|
};
|
4266
4692
|
|
@@ -4269,6 +4695,34 @@ vjs.ControlBar.prototype.createEl = function(){
|
|
4269
4695
|
className: 'vjs-control-bar'
|
4270
4696
|
});
|
4271
4697
|
};
|
4698
|
+
/**
|
4699
|
+
* Displays the live indicator
|
4700
|
+
* TODO - Future make it click to snap to live
|
4701
|
+
* @param {vjs.Player|Object} player
|
4702
|
+
* @param {Object=} options
|
4703
|
+
* @constructor
|
4704
|
+
*/
|
4705
|
+
vjs.LiveDisplay = vjs.Component.extend({
|
4706
|
+
init: function(player, options){
|
4707
|
+
vjs.Component.call(this, player, options);
|
4708
|
+
}
|
4709
|
+
});
|
4710
|
+
|
4711
|
+
vjs.LiveDisplay.prototype.createEl = function(){
|
4712
|
+
var el = vjs.Component.prototype.createEl.call(this, 'div', {
|
4713
|
+
className: 'vjs-live-controls vjs-control'
|
4714
|
+
});
|
4715
|
+
|
4716
|
+
this.contentEl_ = vjs.createEl('div', {
|
4717
|
+
className: 'vjs-live-display',
|
4718
|
+
innerHTML: '<span class="vjs-control-text">Stream Type </span>LIVE',
|
4719
|
+
'aria-live': 'off'
|
4720
|
+
});
|
4721
|
+
|
4722
|
+
el.appendChild(this.contentEl_);
|
4723
|
+
|
4724
|
+
return el;
|
4725
|
+
};
|
4272
4726
|
/**
|
4273
4727
|
* Button to toggle between play and pause
|
4274
4728
|
* @param {vjs.Player|Object} player
|
@@ -4479,12 +4933,12 @@ vjs.FullscreenToggle.prototype.buildCSSClass = function(){
|
|
4479
4933
|
};
|
4480
4934
|
|
4481
4935
|
vjs.FullscreenToggle.prototype.onClick = function(){
|
4482
|
-
if (!this.player_.
|
4483
|
-
this.player_.
|
4484
|
-
this.
|
4936
|
+
if (!this.player_.isFullscreen()) {
|
4937
|
+
this.player_.requestFullscreen();
|
4938
|
+
this.controlText_.innerHTML = 'Non-Fullscreen';
|
4485
4939
|
} else {
|
4486
|
-
this.player_.
|
4487
|
-
this.
|
4940
|
+
this.player_.exitFullscreen();
|
4941
|
+
this.controlText_.innerHTML = 'Fullscreen';
|
4488
4942
|
}
|
4489
4943
|
};
|
4490
4944
|
/**
|
@@ -4731,7 +5185,6 @@ vjs.VolumeBar = vjs.Slider.extend({
|
|
4731
5185
|
vjs.Slider.call(this, player, options);
|
4732
5186
|
player.on('volumechange', vjs.bind(this, this.updateARIAAttributes));
|
4733
5187
|
player.ready(vjs.bind(this, this.updateARIAAttributes));
|
4734
|
-
setTimeout(vjs.bind(this, this.update), 0); // update when elements is in DOM
|
4735
5188
|
}
|
4736
5189
|
});
|
4737
5190
|
|
@@ -4939,6 +5392,135 @@ vjs.VolumeMenuButton.prototype.createEl = function(){
|
|
4939
5392
|
});
|
4940
5393
|
};
|
4941
5394
|
vjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;
|
5395
|
+
/**
|
5396
|
+
* The component for controlling the playback rate
|
5397
|
+
*
|
5398
|
+
* @param {vjs.Player|Object} player
|
5399
|
+
* @param {Object=} options
|
5400
|
+
* @constructor
|
5401
|
+
*/
|
5402
|
+
vjs.PlaybackRateMenuButton = vjs.MenuButton.extend({
|
5403
|
+
/** @constructor */
|
5404
|
+
init: function(player, options){
|
5405
|
+
vjs.MenuButton.call(this, player, options);
|
5406
|
+
|
5407
|
+
this.updateVisibility();
|
5408
|
+
this.updateLabel();
|
5409
|
+
|
5410
|
+
player.on('loadstart', vjs.bind(this, this.updateVisibility));
|
5411
|
+
player.on('ratechange', vjs.bind(this, this.updateLabel));
|
5412
|
+
}
|
5413
|
+
});
|
5414
|
+
|
5415
|
+
vjs.PlaybackRateMenuButton.prototype.createEl = function(){
|
5416
|
+
var el = vjs.Component.prototype.createEl.call(this, 'div', {
|
5417
|
+
className: 'vjs-playback-rate vjs-menu-button vjs-control',
|
5418
|
+
innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">Playback Rate</span></div>'
|
5419
|
+
});
|
5420
|
+
|
5421
|
+
this.labelEl_ = vjs.createEl('div', {
|
5422
|
+
className: 'vjs-playback-rate-value',
|
5423
|
+
innerHTML: 1.0
|
5424
|
+
});
|
5425
|
+
|
5426
|
+
el.appendChild(this.labelEl_);
|
5427
|
+
|
5428
|
+
return el;
|
5429
|
+
};
|
5430
|
+
|
5431
|
+
// Menu creation
|
5432
|
+
vjs.PlaybackRateMenuButton.prototype.createMenu = function(){
|
5433
|
+
var menu = new vjs.Menu(this.player());
|
5434
|
+
var rates = this.player().options()['playbackRates'];
|
5435
|
+
|
5436
|
+
if (rates) {
|
5437
|
+
for (var i = rates.length - 1; i >= 0; i--) {
|
5438
|
+
menu.addChild(
|
5439
|
+
new vjs.PlaybackRateMenuItem(this.player(), { 'rate': rates[i] + 'x'})
|
5440
|
+
);
|
5441
|
+
};
|
5442
|
+
}
|
5443
|
+
|
5444
|
+
return menu;
|
5445
|
+
};
|
5446
|
+
|
5447
|
+
vjs.PlaybackRateMenuButton.prototype.updateARIAAttributes = function(){
|
5448
|
+
// Current playback rate
|
5449
|
+
this.el().setAttribute('aria-valuenow', this.player().playbackRate());
|
5450
|
+
};
|
5451
|
+
|
5452
|
+
vjs.PlaybackRateMenuButton.prototype.onClick = function(){
|
5453
|
+
// select next rate option
|
5454
|
+
var currentRate = this.player().playbackRate();
|
5455
|
+
var rates = this.player().options()['playbackRates'];
|
5456
|
+
// this will select first one if the last one currently selected
|
5457
|
+
var newRate = rates[0];
|
5458
|
+
for (var i = 0; i <rates.length ; i++) {
|
5459
|
+
if (rates[i] > currentRate) {
|
5460
|
+
newRate = rates[i];
|
5461
|
+
break;
|
5462
|
+
}
|
5463
|
+
};
|
5464
|
+
this.player().playbackRate(newRate);
|
5465
|
+
};
|
5466
|
+
|
5467
|
+
vjs.PlaybackRateMenuButton.prototype.playbackRateSupported = function(){
|
5468
|
+
return this.player().tech
|
5469
|
+
&& this.player().tech.features['playbackRate']
|
5470
|
+
&& this.player().options()['playbackRates']
|
5471
|
+
&& this.player().options()['playbackRates'].length > 0
|
5472
|
+
;
|
5473
|
+
};
|
5474
|
+
|
5475
|
+
/**
|
5476
|
+
* Hide playback rate controls when they're no playback rate options to select
|
5477
|
+
*/
|
5478
|
+
vjs.PlaybackRateMenuButton.prototype.updateVisibility = function(){
|
5479
|
+
if (this.playbackRateSupported()) {
|
5480
|
+
this.removeClass('vjs-hidden');
|
5481
|
+
} else {
|
5482
|
+
this.addClass('vjs-hidden');
|
5483
|
+
}
|
5484
|
+
};
|
5485
|
+
|
5486
|
+
/**
|
5487
|
+
* Update button label when rate changed
|
5488
|
+
*/
|
5489
|
+
vjs.PlaybackRateMenuButton.prototype.updateLabel = function(){
|
5490
|
+
if (this.playbackRateSupported()) {
|
5491
|
+
this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
|
5492
|
+
}
|
5493
|
+
};
|
5494
|
+
|
5495
|
+
/**
|
5496
|
+
* The specific menu item type for selecting a playback rate
|
5497
|
+
*
|
5498
|
+
* @constructor
|
5499
|
+
*/
|
5500
|
+
vjs.PlaybackRateMenuItem = vjs.MenuItem.extend({
|
5501
|
+
contentElType: 'button',
|
5502
|
+
/** @constructor */
|
5503
|
+
init: function(player, options){
|
5504
|
+
var label = this.label = options['rate'];
|
5505
|
+
var rate = this.rate = parseFloat(label, 10);
|
5506
|
+
|
5507
|
+
// Modify options for parent MenuItem class's init.
|
5508
|
+
options['label'] = label;
|
5509
|
+
options['selected'] = rate === 1;
|
5510
|
+
vjs.MenuItem.call(this, player, options);
|
5511
|
+
|
5512
|
+
this.player().on('ratechange', vjs.bind(this, this.update));
|
5513
|
+
}
|
5514
|
+
});
|
5515
|
+
|
5516
|
+
vjs.PlaybackRateMenuItem.prototype.onClick = function(){
|
5517
|
+
vjs.MenuItem.prototype.onClick.call(this);
|
5518
|
+
this.player().playbackRate(this.rate);
|
5519
|
+
};
|
5520
|
+
|
5521
|
+
vjs.PlaybackRateMenuItem.prototype.update = function(){
|
5522
|
+
this.selected(this.player().playbackRate() == this.rate);
|
5523
|
+
};
|
4942
5524
|
/* Poster Image
|
4943
5525
|
================================================================================ */
|
4944
5526
|
/**
|
@@ -5041,7 +5623,6 @@ vjs.LoadingSpinner = vjs.Component.extend({
|
|
5041
5623
|
// 'seeking' event
|
5042
5624
|
player.on('seeked', vjs.bind(this, this.hide));
|
5043
5625
|
|
5044
|
-
player.on('error', vjs.bind(this, this.show));
|
5045
5626
|
player.on('ended', vjs.bind(this, this.hide));
|
5046
5627
|
|
5047
5628
|
// Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.
|
@@ -5080,6 +5661,37 @@ vjs.BigPlayButton.prototype.createEl = function(){
|
|
5080
5661
|
vjs.BigPlayButton.prototype.onClick = function(){
|
5081
5662
|
this.player_.play();
|
5082
5663
|
};
|
5664
|
+
/**
|
5665
|
+
* Display that an error has occurred making the video unplayable
|
5666
|
+
* @param {vjs.Player|Object} player
|
5667
|
+
* @param {Object=} options
|
5668
|
+
* @constructor
|
5669
|
+
*/
|
5670
|
+
vjs.ErrorDisplay = vjs.Component.extend({
|
5671
|
+
init: function(player, options){
|
5672
|
+
vjs.Component.call(this, player, options);
|
5673
|
+
|
5674
|
+
this.update();
|
5675
|
+
player.on('error', vjs.bind(this, this.update));
|
5676
|
+
}
|
5677
|
+
});
|
5678
|
+
|
5679
|
+
vjs.ErrorDisplay.prototype.createEl = function(){
|
5680
|
+
var el = vjs.Component.prototype.createEl.call(this, 'div', {
|
5681
|
+
className: 'vjs-error-display'
|
5682
|
+
});
|
5683
|
+
|
5684
|
+
this.contentEl_ = vjs.createEl('div');
|
5685
|
+
el.appendChild(this.contentEl_);
|
5686
|
+
|
5687
|
+
return el;
|
5688
|
+
};
|
5689
|
+
|
5690
|
+
vjs.ErrorDisplay.prototype.update = function(){
|
5691
|
+
if (this.player().error()) {
|
5692
|
+
this.contentEl_.innerHTML = this.player().error().message;
|
5693
|
+
}
|
5694
|
+
};
|
5083
5695
|
/**
|
5084
5696
|
* @fileoverview Media Technology Controller - Base class for media playback
|
5085
5697
|
* technology controllers like Flash and HTML5
|
@@ -5234,6 +5846,7 @@ vjs.MediaTechController.prototype.features = {
|
|
5234
5846
|
|
5235
5847
|
// Resizing plugins using request fullscreen reloads the plugin
|
5236
5848
|
'fullscreenResize': false,
|
5849
|
+
'playbackRate': false,
|
5237
5850
|
|
5238
5851
|
// Optional events that we can manually mimic with timers
|
5239
5852
|
// currently not triggered by video-js-swf
|
@@ -5277,6 +5890,9 @@ vjs.Html5 = vjs.MediaTechController.extend({
|
|
5277
5890
|
// volume cannot be changed from 1 on iOS
|
5278
5891
|
this.features['volumeControl'] = vjs.Html5.canControlVolume();
|
5279
5892
|
|
5893
|
+
// just in case; or is it excessively...
|
5894
|
+
this.features['playbackRate'] = vjs.Html5.canControlPlaybackRate();
|
5895
|
+
|
5280
5896
|
// In iOS, if you move a video element in the DOM, it breaks video playback.
|
5281
5897
|
this.features['movingMediaElementInDOM'] = !vjs.IS_IOS;
|
5282
5898
|
|
@@ -5291,8 +5907,10 @@ vjs.Html5 = vjs.MediaTechController.extend({
|
|
5291
5907
|
// If the element source is already set, we may have missed the loadstart event, and want to trigger it.
|
5292
5908
|
// We don't want to set the source again and interrupt playback.
|
5293
5909
|
if (source && this.el_.currentSrc === source.src && this.el_.networkState > 0) {
|
5294
|
-
player
|
5295
|
-
|
5910
|
+
// wait for the player to be ready so the player listeners are attached
|
5911
|
+
player.ready(function(){
|
5912
|
+
player.trigger('loadstart');
|
5913
|
+
});
|
5296
5914
|
// Otherwise set the source if one was provided.
|
5297
5915
|
} else if (source) {
|
5298
5916
|
this.el_.src = source.src;
|
@@ -5370,18 +5988,26 @@ vjs.Html5.prototype.createEl = function(){
|
|
5370
5988
|
|
5371
5989
|
// Make video events trigger player events
|
5372
5990
|
// May seem verbose here, but makes other APIs possible.
|
5991
|
+
// Triggers removed using this.off when disposed
|
5373
5992
|
vjs.Html5.prototype.setupTriggers = function(){
|
5374
5993
|
for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {
|
5375
|
-
vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this
|
5994
|
+
vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this, this.eventHandler));
|
5376
5995
|
}
|
5377
5996
|
};
|
5378
|
-
// Triggers removed using this.off when disposed
|
5379
5997
|
|
5380
|
-
vjs.Html5.prototype.eventHandler = function(
|
5381
|
-
|
5998
|
+
vjs.Html5.prototype.eventHandler = function(evt){
|
5999
|
+
// In the case of an error, set the error prop on the player
|
6000
|
+
// and let the player handle triggering the event.
|
6001
|
+
if (evt.type == 'error') {
|
6002
|
+
this.player().error(this.error().code);
|
6003
|
+
|
6004
|
+
// in some cases we pass the event directly to the player
|
6005
|
+
} else {
|
6006
|
+
// No need for media events to bubble up.
|
6007
|
+
evt.bubbles = false;
|
5382
6008
|
|
5383
|
-
|
5384
|
-
|
6009
|
+
this.player().trigger(evt);
|
6010
|
+
}
|
5385
6011
|
};
|
5386
6012
|
|
5387
6013
|
vjs.Html5.prototype.useNativeControls = function(){
|
@@ -5496,6 +6122,9 @@ vjs.Html5.prototype.seeking = function(){ return this.el_.seeking; };
|
|
5496
6122
|
vjs.Html5.prototype.ended = function(){ return this.el_.ended; };
|
5497
6123
|
vjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };
|
5498
6124
|
|
6125
|
+
vjs.Html5.prototype.playbackRate = function(){ return this.el_.playbackRate; };
|
6126
|
+
vjs.Html5.prototype.setPlaybackRate = function(val){ this.el_.playbackRate = val; };
|
6127
|
+
|
5499
6128
|
/* HTML5 Support Testing ---------------------------------------------------- */
|
5500
6129
|
|
5501
6130
|
vjs.Html5.isSupported = function(){
|
@@ -5528,6 +6157,12 @@ vjs.Html5.canControlVolume = function(){
|
|
5528
6157
|
return volume !== vjs.TEST_VID.volume;
|
5529
6158
|
};
|
5530
6159
|
|
6160
|
+
vjs.Html5.canControlPlaybackRate = function(){
|
6161
|
+
var playbackRate = vjs.TEST_VID.playbackRate;
|
6162
|
+
vjs.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
|
6163
|
+
return playbackRate !== vjs.TEST_VID.playbackRate;
|
6164
|
+
};
|
6165
|
+
|
5531
6166
|
// HTML5 Feature detection and Device Fixes --------------------------------- //
|
5532
6167
|
(function() {
|
5533
6168
|
var canPlayType,
|
@@ -5928,7 +6563,6 @@ vjs.Flash.prototype.enterFullScreen = function(){
|
|
5928
6563
|
return false;
|
5929
6564
|
};
|
5930
6565
|
|
5931
|
-
|
5932
6566
|
// Create setters and getters for attributes
|
5933
6567
|
var api = vjs.Flash.prototype,
|
5934
6568
|
readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
|
@@ -6044,8 +6678,15 @@ vjs.Flash['onEvent'] = function(swfID, eventName){
|
|
6044
6678
|
// Log errors from the swf
|
6045
6679
|
vjs.Flash['onError'] = function(swfID, err){
|
6046
6680
|
var player = vjs.el(swfID)['player'];
|
6047
|
-
|
6048
|
-
|
6681
|
+
var msg = 'FLASH: '+err;
|
6682
|
+
|
6683
|
+
if (err == 'srcnotfound') {
|
6684
|
+
player.error({ code: 4, message: msg });
|
6685
|
+
|
6686
|
+
// errors we haven't categorized into the media errors
|
6687
|
+
} else {
|
6688
|
+
player.error(msg);
|
6689
|
+
}
|
6049
6690
|
};
|
6050
6691
|
|
6051
6692
|
// Flash Version Check
|
@@ -6275,9 +6916,15 @@ vjs.Player.prototype.addTextTrack = function(kind, label, language, options){
|
|
6275
6916
|
// TODO: Add a process to deterime the best track to show for the specific kind
|
6276
6917
|
// Incase there are mulitple defaulted tracks of the same kind
|
6277
6918
|
// Or the user has a set preference of a specific language that should override the default
|
6278
|
-
//
|
6279
|
-
|
6280
|
-
|
6919
|
+
// Note: The setTimeout is a workaround because with the html5 tech, the player is 'ready'
|
6920
|
+
// before it's child components (including the textTrackDisplay) have finished loading.
|
6921
|
+
if (track.dflt()) {
|
6922
|
+
this.ready(function(){
|
6923
|
+
setTimeout(function(){
|
6924
|
+
track.show();
|
6925
|
+
}, 0);
|
6926
|
+
});
|
6927
|
+
}
|
6281
6928
|
|
6282
6929
|
return track;
|
6283
6930
|
};
|
@@ -6762,8 +7409,9 @@ vjs.TextTrack.prototype.parseCueTime = function(timeText) {
|
|
6762
7409
|
vjs.TextTrack.prototype.update = function(){
|
6763
7410
|
if (this.cues_.length > 0) {
|
6764
7411
|
|
6765
|
-
// Get
|
6766
|
-
var
|
7412
|
+
// Get current player time, adjust for track offset
|
7413
|
+
var offset = this.player_.options()['trackTimeOffset'] || 0;
|
7414
|
+
var time = this.player_.currentTime() + offset;
|
6767
7415
|
|
6768
7416
|
// Check if the new time is outside the time box created by the the last update.
|
6769
7417
|
if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) {
|
@@ -7165,7 +7813,7 @@ vjs.ChaptersButton.prototype.createMenu = function(){
|
|
7165
7813
|
|
7166
7814
|
var menu = this.menu = new vjs.Menu(this.player_);
|
7167
7815
|
|
7168
|
-
menu.
|
7816
|
+
menu.contentEl().appendChild(vjs.createEl('li', {
|
7169
7817
|
className: 'vjs-menu-title',
|
7170
7818
|
innerHTML: vjs.capitalize(this.kind_),
|
7171
7819
|
tabindex: -1
|
@@ -7395,6 +8043,4 @@ vjs.plugin = function(name, init){
|
|
7395
8043
|
vjs.Player.prototype[name] = init;
|
7396
8044
|
};
|
7397
8045
|
|
7398
|
-
|
7399
|
-
|
7400
8046
|
videojs.options.flash.swf = "<%= asset_path( "video-js.swf") %>";
|