videojs_rails 4.5.2 → 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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") %>";