videojs_rails 4.5.2 → 4.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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") %>";
|