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.
@@ -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.5';
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': 'Sorry, no compatible source and playback ' +
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.5 string
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
- vjs.obj.create = Object.create || function(obj){
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.support = {};
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} url URL of resource
1183
- * @param {Function=} onSuccess Success callback
1184
- * @param {Function=} onError Error callback
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 local, request;
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
- local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1));
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
- request.onreadystatechange = function() {
1209
- if (request.readyState === 4) {
1210
- if (request.status === 200 || local && request.status === 0) {
1211
- onSuccess(request.responseText);
1212
- } else {
1213
- if (onError) {
1214
- onError();
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
- if (onError) {
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
- // usage: log('inside coolFunc',this,arguments);
1273
- // http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
1274
- vjs.log = function(){
1275
- vjs.log.history = vjs.log.history || []; // store logs to an array for reference
1276
- vjs.log.history.push(arguments);
1277
- if(window.console){
1278
- window.console.log(Array.prototype.slice.call(arguments));
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
- var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top;
1421
+ var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top;
1286
1422
 
1287
- if (el.getBoundingClientRect && el.parentNode) {
1288
- box = el.getBoundingClientRect();
1289
- }
1423
+ if (el.getBoundingClientRect && el.parentNode) {
1424
+ box = el.getBoundingClientRect();
1425
+ }
1290
1426
 
1291
- if (!box) {
1292
- return {
1293
- left: 0,
1294
- top: 0
1295
- };
1296
- }
1427
+ if (!box) {
1428
+ return {
1429
+ left: 0,
1430
+ top: 0
1431
+ };
1432
+ }
1297
1433
 
1298
- docEl = document.documentElement;
1299
- body = document.body;
1434
+ docEl = document.documentElement;
1435
+ body = document.body;
1300
1436
 
1301
- clientLeft = docEl.clientLeft || body.clientLeft || 0;
1302
- scrollLeft = window.pageXOffset || body.scrollLeft;
1303
- left = box.left + scrollLeft - clientLeft;
1437
+ clientLeft = docEl.clientLeft || body.clientLeft || 0;
1438
+ scrollLeft = window.pageXOffset || body.scrollLeft;
1439
+ left = box.left + scrollLeft - clientLeft;
1304
1440
 
1305
- clientTop = docEl.clientTop || body.clientTop || 0;
1306
- scrollTop = window.pageYOffset || body.scrollTop;
1307
- top = box.top + scrollTop - clientTop;
1441
+ clientTop = docEl.clientTop || body.clientTop || 0;
1442
+ scrollTop = window.pageYOffset || body.scrollTop;
1443
+ top = box.top + scrollTop - clientTop;
1308
1444
 
1309
- // Android sometimes returns slightly off decimal values, so need to round
1310
- return {
1311
- left: vjs.round(left),
1312
- top: vjs.round(top)
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 options = this.options_;
1957
+ var parent, children, child, name, opts;
1799
1958
 
1800
- if (options && options['children']) {
1801
- var self = this;
1959
+ parent = this;
1960
+ children = this.options()['children'];
1802
1961
 
1803
- // Loop through components and add them to the player
1804
- vjs.obj.each(options['children'], function(name, opts){
1805
- // Allow for disabling default components
1806
- // e.g. vjs.options['children']['posterImage'] = false
1807
- if (opts === false) return;
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
- // Allow waiting to add components until a specific event is called
1810
- var tempAdd = function(){
1811
- // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.
1812
- self[name] = self.addChild(name, opts);
1813
- };
1968
+ if (typeof child == 'string') {
1969
+ name = child;
1970
+ opts = {};
1971
+ } else {
1972
+ name = child.name;
1973
+ opts = child;
1974
+ }
1814
1975
 
1815
- if (opts['loadEvent']) {
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
- // Record start time so we can detect a tap vs. "touch and hold"
2197
- touchStart = new Date().getTime();
2198
- // Reset couldBeTap tracking
2199
- couldBeTap = true;
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
- var touchstart = false;
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
- return vjs.Component.prototype.createEl.call(this, type, props);
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
- // This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520
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.el().appendChild(vjs.createEl('li', {
2912
+ menu.contentEl().appendChild(vjs.createEl('li', {
2721
2913
  className: 'vjs-menu-title',
2722
- innerHTML: vjs.capitalize(this.kind_),
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.one('progress', function(){
3476
+ if (this.tech) {
3477
+ this.tech.one('progress', function(){
3143
3478
 
3144
- // Update known progress support for this playback technology
3145
- this.features['progressEvents'] = true;
3479
+ // Update known progress support for this playback technology
3480
+ this.features['progressEvents'] = true;
3146
3481
 
3147
- // Turn off manual progress tracking
3148
- this.player_.manualProgressOff();
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.one('timeupdate', function(){
3183
- // Update known progress support for this playback technology
3184
- this.features['timeupdateEvents'] = true;
3185
- // Turn off manual progress tracking
3186
- this.player_.manualTimeUpdatesOff();
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(){ clearInterval(this.currentTimeInterval); };
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.isFullScreen()) {
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.isFullScreen_ = false;
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.isFullScreen();
3998
+ * var fullscreenOrNot = myPlayer.isFullscreen();
3632
3999
  *
3633
4000
  * // set
3634
- * myPlayer.isFullScreen(true); // tell the player it's in fullscreen
4001
+ * myPlayer.isFullscreen(true); // tell the player it's in fullscreen
3635
4002
  *
3636
- * NOTE: As of the latest HTML5 spec, isFullScreen is no longer an official
3637
- * property and instead document.fullscreenElement is used. But isFullScreen is
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.isFullScreen = function(isFS){
4011
+ vjs.Player.prototype.isFullscreen = function(isFS){
3645
4012
  if (isFS !== undefined) {
3646
- this.isFullScreen_ = isFS;
4013
+ this.isFullscreen_ = !!isFS;
3647
4014
  return this;
3648
4015
  }
3649
- return this.isFullScreen_;
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.requestFullScreen();
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.requestFullScreen = function(){
3667
- var requestFullScreen = vjs.support.requestFullScreen;
3668
- this.isFullScreen(true);
4042
+ vjs.Player.prototype.requestFullscreen = function(){
4043
+ var fsApi = vjs.browser.fullscreenAPI;
4044
+
4045
+ this.isFullscreen(true);
3669
4046
 
3670
- if (requestFullScreen) {
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, requestFullScreen.eventName, vjs.bind(this, function(e){
3680
- this.isFullScreen(document[requestFullScreen.isFullScreen]);
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.isFullScreen() === false) {
3684
- vjs.off(document, requestFullScreen.eventName, arguments.callee);
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_[requestFullScreen.requestFn]();
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.cancelFullScreen();
4096
+ * myPlayer.exitFullscreen();
3710
4097
  *
3711
4098
  * @return {vjs.Player} self
3712
4099
  */
3713
- vjs.Player.prototype.cancelFullScreen = function(){
3714
- var requestFullScreen = vjs.support.requestFullScreen;
3715
- this.isFullScreen(false);
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 (requestFullScreen) {
3719
- document[requestFullScreen.cancelFn]();
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.isFullScreen() === true) {
3751
- this.cancelFullScreen();
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
- innerHTML: this.options()['notSupportedMessage']
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
- vjs.Player.prototype.error = function(){ return this.techGet('error'); };
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 onMouseActivity, onMouseDown, mouseInProgress, onMouseUp,
4106
- activityCheck, inactivityTimeout;
4107
-
4108
- onMouseActivity = vjs.bind(this, this.reportUserActivity);
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
- onMouseActivity();
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(onMouseActivity, 250);
4576
+ mouseInProgress = setInterval(onActivity, 250);
4120
4577
  };
4121
4578
 
4122
4579
  onMouseUp = function(event) {
4123
- onMouseActivity();
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', onMouseActivity);
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', onMouseActivity);
4136
- this.on('keyup', onMouseActivity);
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_.isFullScreen()) {
4483
- this.player_.requestFullScreen();
4484
- this.el_.children[0].children[0].innerHTML = 'Non-Fullscreen'; // change the button text to "Non-Fullscreen"
4936
+ if (!this.player_.isFullscreen()) {
4937
+ this.player_.requestFullscreen();
4938
+ this.controlText_.innerHTML = 'Non-Fullscreen';
4485
4939
  } else {
4486
- this.player_.cancelFullScreen();
4487
- this.el_.children[0].children[0].innerHTML = 'Fullscreen'; // change the button to "Fullscreen"
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.trigger('loadstart');
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.player_, this.eventHandler));
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(e){
5381
- this.trigger(e);
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
- // No need for media events to bubble up.
5384
- e.stopPropagation();
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
- player.trigger('error');
6048
- vjs.log('Flash Error', err, swfID);
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
- // if (track.dflt()) {
6279
- // this.ready(vjs.bind(track, track.show));
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 curent player time
6766
- var time = this.player_.currentTime();
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.el_.appendChild(vjs.createEl('li', {
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") %>";