videojs_rails 4.1.0 → 4.2.0

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.
@@ -57,7 +57,7 @@ var videojs = vjs;
57
57
  window.videojs = window.vjs = vjs;
58
58
 
59
59
  // CDN Version. Used to target right flash swf.
60
- vjs.CDN_VERSION = '4.1';
60
+ vjs.CDN_VERSION = '4.2';
61
61
  vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');
62
62
 
63
63
  /**
@@ -89,11 +89,17 @@ vjs.options = {
89
89
  'loadingSpinner': {},
90
90
  'bigPlayButton': {},
91
91
  'controlBar': {}
92
- }
92
+ },
93
+
94
+ // Default message to show when a video cannot be played.
95
+ 'notSupportedMessage': 'Sorry, no compatible source and playback ' +
96
+ 'technology were found for this video. Try using another browser ' +
97
+ 'like <a href="http://bit.ly/ccMUEC">Chrome</a> or download the ' +
98
+ 'latest <a href="http://adobe.ly/mwfN1">Adobe Flash Player</a>.'
93
99
  };
94
100
 
95
101
  // Set CDN Version of swf
96
- // The added (+) blocks the replace from changing this 4.1 string
102
+ // The added (+) blocks the replace from changing this 4.2 string
97
103
  if (vjs.CDN_VERSION !== 'GENERATED'+'_CDN_VSN') {
98
104
  videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/'+vjs.CDN_VERSION+'/video-js.swf';
99
105
  }
@@ -457,8 +463,9 @@ vjs.trigger = function(elem, event) {
457
463
  elemData.dispatcher.call(elem, event);
458
464
  }
459
465
 
460
- // Unless explicitly stopped, recursively calls this function to bubble the event up the DOM.
461
- if (parent && !event.isPropagationStopped()) {
466
+ // Unless explicitly stopped or the event does not bubble (e.g. media events)
467
+ // recursively calls this function to bubble the event up the DOM.
468
+ if (parent && !event.isPropagationStopped() && event.bubbles !== false) {
462
469
  vjs.trigger(parent, event);
463
470
 
464
471
  // If at the top of the DOM, triggers the default action unless disabled.
@@ -509,10 +516,12 @@ vjs.trigger = function(elem, event) {
509
516
  * @return {[type]}
510
517
  */
511
518
  vjs.one = function(elem, type, fn) {
512
- vjs.on(elem, type, function(){
513
- vjs.off(elem, type, arguments.callee);
519
+ var func = function(){
520
+ vjs.off(elem, type, func);
514
521
  fn.apply(this, arguments);
515
- });
522
+ };
523
+ func.guid = fn.guid = fn.guid || vjs.guid++;
524
+ vjs.on(elem, type, func);
516
525
  };
517
526
  var hasOwnProp = Object.prototype.hasOwnProperty;
518
527
 
@@ -523,9 +532,11 @@ var hasOwnProp = Object.prototype.hasOwnProperty;
523
532
  * @return {Element}
524
533
  */
525
534
  vjs.createEl = function(tagName, properties){
526
- var el = document.createElement(tagName || 'div');
535
+ var el, propName;
536
+
537
+ el = document.createElement(tagName || 'div');
527
538
 
528
- for (var propName in properties){
539
+ for (propName in properties){
529
540
  if (hasOwnProp.call(properties, propName)) {
530
541
  //el[propName] = properties[propName];
531
542
  // Not remembering why we were checking for dash
@@ -619,10 +630,9 @@ vjs.obj.merge = function(obj1, obj2){
619
630
  * @return {Object} New object. Obj1 and Obj2 will be untouched.
620
631
  */
621
632
  vjs.obj.deepMerge = function(obj1, obj2){
622
- var key, val1, val2, objDef;
623
- objDef = '[object Object]';
633
+ var key, val1, val2;
624
634
 
625
- // Make a copy of obj1 so we're not ovewriting original values.
635
+ // make a copy of obj1 so we're not ovewriting original values.
626
636
  // like prototype.options_ and all sub options objects
627
637
  obj1 = vjs.obj.copy(obj1);
628
638
 
@@ -789,15 +799,19 @@ vjs.addClass = function(element, classToAdd){
789
799
  * @param {String} classToAdd Classname to remove
790
800
  */
791
801
  vjs.removeClass = function(element, classToRemove){
802
+ var classNames, i;
803
+
792
804
  if (element.className.indexOf(classToRemove) == -1) { return; }
793
- var classNames = element.className.split(' ');
794
- // IE8 Does not support array.indexOf so using a for loop
795
- for (var i = classNames.length - 1; i >= 0; i--) {
805
+
806
+ classNames = element.className.split(' ');
807
+
808
+ // no arr.indexOf in ie8, and we don't want to add a big shim
809
+ for (i = classNames.length - 1; i >= 0; i--) {
796
810
  if (classNames[i] === classToRemove) {
797
811
  classNames.splice(i,1);
798
812
  }
799
813
  }
800
- // classNames.splice(classNames.indexOf(classToRemove),1);
814
+
801
815
  element.className = classNames.join(' ');
802
816
  };
803
817
 
@@ -859,6 +873,7 @@ vjs.IS_OLD_ANDROID = vjs.IS_ANDROID && (/webkit/i).test(vjs.USER_AGENT) && vjs.A
859
873
  vjs.IS_FIREFOX = (/Firefox/i).test(vjs.USER_AGENT);
860
874
  vjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT);
861
875
 
876
+ vjs.TOUCH_ENABLED = ('ontouchstart' in window);
862
877
 
863
878
  /**
864
879
  * Get an element's attribute values, as defined on the HTML tag
@@ -869,28 +884,28 @@ vjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT);
869
884
  * @return {Object}
870
885
  */
871
886
  vjs.getAttributeValues = function(tag){
872
- var obj = {};
887
+ var obj, knownBooleans, attrs, attrName, attrVal;
873
888
 
874
- // Known boolean attributes
875
- // We can check for matching boolean properties, but older browsers
876
- // won't know about HTML5 boolean attributes that we still read from.
877
- // Bookending with commas to allow for an easy string search.
878
- var knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';
889
+ obj = {};
890
+
891
+ // known boolean attributes
892
+ // we can check for matching boolean properties, but older browsers
893
+ // won't know about HTML5 boolean attributes that we still read from
894
+ knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';
879
895
 
880
896
  if (tag && tag.attributes && tag.attributes.length > 0) {
881
- var attrs = tag.attributes;
882
- var attrName, attrVal;
897
+ attrs = tag.attributes;
883
898
 
884
899
  for (var i = attrs.length - 1; i >= 0; i--) {
885
900
  attrName = attrs[i].name;
886
901
  attrVal = attrs[i].value;
887
902
 
888
- // Check for known booleans
889
- // The matching element property will return a value for typeof
903
+ // check for known booleans
904
+ // the matching element property will return a value for typeof
890
905
  if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) {
891
- // The value of an included boolean attribute is typically an empty string ('')
892
- // which would equal false if we just check for a false value.
893
- // We also don't want support bad code like autoplay='false'
906
+ // the value of an included boolean attribute is typically an empty
907
+ // string ('') which would equal false if we just check for a false value.
908
+ // we also don't want support bad code like autoplay='false'
894
909
  attrVal = (attrVal !== null) ? true : false;
895
910
  }
896
911
 
@@ -962,13 +977,21 @@ vjs.el = function(id){
962
977
  * @return {String} Time formatted as H:MM:SS or M:SS
963
978
  */
964
979
  vjs.formatTime = function(seconds, guide) {
965
- guide = guide || seconds; // Default to using seconds as guide
980
+ // Default to using seconds as guide
981
+ guide = guide || seconds;
966
982
  var s = Math.floor(seconds % 60),
967
983
  m = Math.floor(seconds / 60 % 60),
968
984
  h = Math.floor(seconds / 3600),
969
985
  gm = Math.floor(guide / 60 % 60),
970
986
  gh = Math.floor(guide / 3600);
971
987
 
988
+ // handle invalid times
989
+ if (isNaN(seconds) || seconds === Infinity) {
990
+ // '-' is false for all relational operators (e.g. <, >=) so this setting
991
+ // will add the minimum number of fields specified by the guide
992
+ h = m = s = '-';
993
+ }
994
+
972
995
  // Check if we need to show hours
973
996
  h = (h > 0 || gh > 0) ? h + ':' : '';
974
997
 
@@ -995,8 +1018,8 @@ vjs.unblockTextSelection = function(){ document.onselectstart = function () { re
995
1018
  * @param {String} string String to trim
996
1019
  * @return {String} Trimmed string
997
1020
  */
998
- vjs.trim = function(string){
999
- return string.toString().replace(/^\s+/, '').replace(/\s+$/, '');
1021
+ vjs.trim = function(str){
1022
+ return (str+'').replace(/^\s+|\s+$/g, '');
1000
1023
  };
1001
1024
 
1002
1025
  /**
@@ -1034,7 +1057,7 @@ vjs.createTimeRange = function(start, end){
1034
1057
  * @param {Function=} onError Error callback
1035
1058
  */
1036
1059
  vjs.get = function(url, onSuccess, onError){
1037
- var local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1));
1060
+ var local, request;
1038
1061
 
1039
1062
  if (typeof XMLHttpRequest === 'undefined') {
1040
1063
  window.XMLHttpRequest = function () {
@@ -1045,14 +1068,15 @@ vjs.get = function(url, onSuccess, onError){
1045
1068
  };
1046
1069
  }
1047
1070
 
1048
- var request = new XMLHttpRequest();
1049
-
1071
+ request = new XMLHttpRequest();
1050
1072
  try {
1051
1073
  request.open('GET', url);
1052
1074
  } catch(e) {
1053
1075
  onError(e);
1054
1076
  }
1055
1077
 
1078
+ local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1));
1079
+
1056
1080
  request.onreadystatechange = function() {
1057
1081
  if (request.readyState === 4) {
1058
1082
  if (request.status === 200 || local && request.status === 0) {
@@ -1203,6 +1227,8 @@ vjs.Component = vjs.CoreObject.extend({
1203
1227
  * Dispose of the component and all child components.
1204
1228
  */
1205
1229
  vjs.Component.prototype.dispose = function(){
1230
+ this.trigger('dispose');
1231
+
1206
1232
  // Dispose all children.
1207
1233
  if (this.children_) {
1208
1234
  for (var i = this.children_.length - 1; i >= 0; i--) {
@@ -1695,26 +1721,6 @@ vjs.Component.prototype.hide = function(){
1695
1721
  return this;
1696
1722
  };
1697
1723
 
1698
- /**
1699
- * Fade a component in using CSS
1700
- * @return {vjs.Component}
1701
- */
1702
- vjs.Component.prototype.fadeIn = function(){
1703
- this.removeClass('vjs-fade-out');
1704
- this.addClass('vjs-fade-in');
1705
- return this;
1706
- };
1707
-
1708
- /**
1709
- * Fade a component out using CSS
1710
- * @return {vjs.Component}
1711
- */
1712
- vjs.Component.prototype.fadeOut = function(){
1713
- this.removeClass('vjs-fade-in');
1714
- this.addClass('vjs-fade-out');
1715
- return this;
1716
- };
1717
-
1718
1724
  /**
1719
1725
  * Lock an item in its visible state. To be used with fadeIn/fadeOut.
1720
1726
  * @return {vjs.Component}
@@ -1739,15 +1745,8 @@ vjs.Component.prototype.unlockShowing = function(){
1739
1745
  vjs.Component.prototype.disable = function(){
1740
1746
  this.hide();
1741
1747
  this.show = function(){};
1742
- this.fadeIn = function(){};
1743
1748
  };
1744
1749
 
1745
- // TODO: Get enable working
1746
- // vjs.Component.prototype.enable = function(){
1747
- // this.fadeIn = vjs.Component.prototype.fadeIn;
1748
- // this.show = vjs.Component.prototype.show;
1749
- // };
1750
-
1751
1750
  /**
1752
1751
  * If a value is provided it will change the width of the player to that value
1753
1752
  * otherwise the width is returned
@@ -1849,6 +1848,53 @@ vjs.Component.prototype.dimension = function(widthOrHeight, num, skipListeners){
1849
1848
  // }
1850
1849
  }
1851
1850
  };
1851
+
1852
+ /**
1853
+ * Emit 'tap' events when touch events are supported. We're requireing them to
1854
+ * be enabled because otherwise every component would have this extra overhead
1855
+ * unnecessarily, on mobile devices where extra overhead is especially bad.
1856
+ *
1857
+ * This is being implemented so we can support taps on the video element
1858
+ * toggling the controls.
1859
+ */
1860
+ vjs.Component.prototype.emitTapEvents = function(){
1861
+ var touchStart, touchTime, couldBeTap, noTap;
1862
+
1863
+ // Track the start time so we can determine how long the touch lasted
1864
+ touchStart = 0;
1865
+
1866
+ this.on('touchstart', function(event) {
1867
+ // Record start time so we can detect a tap vs. "touch and hold"
1868
+ touchStart = new Date().getTime();
1869
+ // Reset couldBeTap tracking
1870
+ couldBeTap = true;
1871
+ });
1872
+
1873
+ noTap = function(){
1874
+ couldBeTap = false;
1875
+ };
1876
+ // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
1877
+ this.on('touchmove', noTap);
1878
+ this.on('touchleave', noTap);
1879
+ this.on('touchcancel', noTap);
1880
+
1881
+ // When the touch ends, measure how long it took and trigger the appropriate
1882
+ // event
1883
+ this.on('touchend', function() {
1884
+ // Proceed only if the touchmove/leave/cancel event didn't happen
1885
+ if (couldBeTap === true) {
1886
+ // Measure how long the touch lasted
1887
+ touchTime = new Date().getTime() - touchStart;
1888
+ // The touch needs to be quick in order to consider it a tap
1889
+ if (touchTime < 250) {
1890
+ this.trigger('tap');
1891
+ // It may be good to copy the touchend event object and change the
1892
+ // type to tap, if the other event properties aren't exact after
1893
+ // vjs.fixEvent runs (e.g. event.target)
1894
+ }
1895
+ }
1896
+ });
1897
+ };
1852
1898
  /* Button - Base class for all buttons
1853
1899
  ================================================================================ */
1854
1900
  /**
@@ -1863,7 +1909,9 @@ vjs.Button = vjs.Component.extend({
1863
1909
  vjs.Component.call(this, player, options);
1864
1910
 
1865
1911
  var touchstart = false;
1866
- this.on('touchstart', function() {
1912
+ this.on('touchstart', function(event) {
1913
+ // Stop click and other mouse events from triggering also
1914
+ event.preventDefault();
1867
1915
  touchstart = true;
1868
1916
  });
1869
1917
  this.on('touchmove', function() {
@@ -1875,7 +1923,6 @@ vjs.Button = vjs.Component.extend({
1875
1923
  self.onClick(event);
1876
1924
  }
1877
1925
  event.preventDefault();
1878
- event.stopPropagation();
1879
1926
  });
1880
1927
 
1881
1928
  this.on('click', this.onClick);
@@ -2282,7 +2329,7 @@ vjs.MenuButton.prototype.createMenu = function(){
2282
2329
  }));
2283
2330
  }
2284
2331
 
2285
- this.items = this.createItems();
2332
+ this.items = this['createItems']();
2286
2333
 
2287
2334
  if (this.items) {
2288
2335
  // Add menu items to the menu
@@ -2386,22 +2433,30 @@ vjs.Player = vjs.Component.extend({
2386
2433
  this.poster_ = options['poster'];
2387
2434
  // Set controls
2388
2435
  this.controls_ = options['controls'];
2389
- // Use native controls for iOS and Android by default
2390
- // until controls are more stable on those devices.
2391
- if (options['customControlsOnMobile'] !== true && (vjs.IS_IOS || vjs.IS_ANDROID)) {
2392
- tag.controls = options['controls'];
2393
- this.controls_ = false;
2394
- } else {
2395
- // Original tag settings stored in options
2396
- // now remove immediately so native controls don't flash.
2397
- tag.controls = false;
2398
- }
2436
+ // Original tag settings stored in options
2437
+ // now remove immediately so native controls don't flash.
2438
+ // May be turned back on by HTML5 tech if nativeControlsForTouch is true
2439
+ tag.controls = false;
2399
2440
 
2400
2441
  // Run base component initializing with new options.
2401
2442
  // Builds the element through createEl()
2402
2443
  // Inits and embeds any child components in opts
2403
2444
  vjs.Component.call(this, this, options, ready);
2404
2445
 
2446
+ // Update controls className. Can't do this when the controls are initially
2447
+ // set because the element doesn't exist yet.
2448
+ if (this.controls()) {
2449
+ this.addClass('vjs-controls-enabled');
2450
+ } else {
2451
+ this.addClass('vjs-controls-disabled');
2452
+ }
2453
+
2454
+ // TODO: Make this smarter. Toggle user state between touching/mousing
2455
+ // using events, since devices can have both touch and mouse events.
2456
+ // if (vjs.TOUCH_ENABLED) {
2457
+ // this.addClass('vjs-touch-enabled');
2458
+ // }
2459
+
2405
2460
  // Firstplay event implimentation. Not sold on the event yet.
2406
2461
  // Could probably just check currentTime==0?
2407
2462
  this.one('play', function(e){
@@ -2433,6 +2488,8 @@ vjs.Player = vjs.Component.extend({
2433
2488
  this[key](val);
2434
2489
  }, this);
2435
2490
  }
2491
+
2492
+ this.listenForUserActivity();
2436
2493
  }
2437
2494
  });
2438
2495
 
@@ -2448,7 +2505,9 @@ vjs.Player = vjs.Component.extend({
2448
2505
  vjs.Player.prototype.options_ = vjs.options;
2449
2506
 
2450
2507
  vjs.Player.prototype.dispose = function(){
2451
- // this.isReady_ = false;
2508
+ this.trigger('dispose');
2509
+ // prevent dispose from being called twice
2510
+ this.off('dispose');
2452
2511
 
2453
2512
  // Kill reference to this player
2454
2513
  vjs.players[this.id_] = null;
@@ -2585,12 +2644,12 @@ vjs.Player.prototype.loadTech = function(techName, source){
2585
2644
  this.player_.triggerReady();
2586
2645
 
2587
2646
  // Manually track progress in cases where the browser/flash player doesn't report it.
2588
- if (!this.features.progressEvents) {
2647
+ if (!this.features['progressEvents']) {
2589
2648
  this.player_.manualProgressOn();
2590
2649
  }
2591
2650
 
2592
2651
  // Manually track timeudpates in cases where the browser/flash player doesn't report it.
2593
- if (!this.features.timeupdateEvents) {
2652
+ if (!this.features['timeupdateEvents']) {
2594
2653
  this.player_.manualTimeUpdatesOn();
2595
2654
  }
2596
2655
  };
@@ -2655,7 +2714,7 @@ vjs.Player.prototype.manualProgressOn = function(){
2655
2714
  this.tech.one('progress', function(){
2656
2715
 
2657
2716
  // Update known progress support for this playback technology
2658
- this.features.progressEvents = true;
2717
+ this.features['progressEvents'] = true;
2659
2718
 
2660
2719
  // Turn off manual progress tracking
2661
2720
  this.player_.manualProgressOff();
@@ -2694,7 +2753,7 @@ vjs.Player.prototype.manualTimeUpdatesOn = function(){
2694
2753
  // Watch for native timeupdate event
2695
2754
  this.tech.one('timeupdate', function(){
2696
2755
  // Update known progress support for this playback technology
2697
- this.features.timeupdateEvents = true;
2756
+ this.features['timeupdateEvents'] = true;
2698
2757
  // Turn off manual progress tracking
2699
2758
  this.player_.manualTimeUpdatesOff();
2700
2759
  });
@@ -2737,6 +2796,8 @@ vjs.Player.prototype.onFirstPlay = function(){
2737
2796
  if(this.options_['starttime']){
2738
2797
  this.currentTime(this.options_['starttime']);
2739
2798
  }
2799
+
2800
+ this.addClass('vjs-has-started');
2740
2801
  };
2741
2802
 
2742
2803
  vjs.Player.prototype.onPause = function(){
@@ -2804,12 +2865,7 @@ vjs.Player.prototype.techCall = function(method, arg){
2804
2865
  // Get calls can't wait for the tech, and sometimes don't need to.
2805
2866
  vjs.Player.prototype.techGet = function(method){
2806
2867
 
2807
- // Make sure there is a tech
2808
- // if (!this.tech) {
2809
- // return;
2810
- // }
2811
-
2812
- if (this.tech.isReady_) {
2868
+ if (this.tech && this.tech.isReady_) {
2813
2869
 
2814
2870
  // Flash likes to die and reload when you hide or reposition it.
2815
2871
  // In these cases the object methods go away and we get errors.
@@ -2906,11 +2962,12 @@ vjs.Player.prototype.remainingTime = function(){
2906
2962
  vjs.Player.prototype.buffered = function(){
2907
2963
  var buffered = this.techGet('buffered'),
2908
2964
  start = 0,
2965
+ buflast = buffered.length - 1,
2909
2966
  // Default end to 0 and store in values
2910
2967
  end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;
2911
2968
 
2912
- if (buffered && buffered.length > 0 && buffered.end(0) !== end) {
2913
- end = buffered.end(0);
2969
+ if (buffered && buflast >= 0 && buffered.end(buflast) !== end) {
2970
+ end = buffered.end(buflast);
2914
2971
  // Storing values allows them be overridden by setBufferedFromProgress
2915
2972
  this.cache_.bufferEnd = end;
2916
2973
  }
@@ -3102,7 +3159,7 @@ vjs.Player.prototype.src = function(source){
3102
3159
  }
3103
3160
  } else {
3104
3161
  this.el_.appendChild(vjs.createEl('p', {
3105
- innerHTML: 'Sorry, no compatible source and playback technology were found for this video. Try using another browser like <a href="http://bit.ly/ccMUEC">Chrome</a> or download the latest <a href="http://adobe.ly/mwfN1">Adobe Flash Player</a>.'
3162
+ innerHTML: this.options()['notSupportedMessage']
3106
3163
  }));
3107
3164
  }
3108
3165
 
@@ -3207,19 +3264,188 @@ vjs.Player.prototype.controls_;
3207
3264
  * @param {Boolean} controls Set controls to showing or not
3208
3265
  * @return {Boolean} Controls are showing
3209
3266
  */
3210
- vjs.Player.prototype.controls = function(controls){
3211
- if (controls !== undefined) {
3267
+ vjs.Player.prototype.controls = function(bool){
3268
+ if (bool !== undefined) {
3269
+ bool = !!bool; // force boolean
3212
3270
  // Don't trigger a change event unless it actually changed
3213
- if (this.controls_ !== controls) {
3214
- this.controls_ = !!controls; // force boolean
3215
- this.trigger('controlschange');
3271
+ if (this.controls_ !== bool) {
3272
+ this.controls_ = bool;
3273
+ if (bool) {
3274
+ this.removeClass('vjs-controls-disabled');
3275
+ this.addClass('vjs-controls-enabled');
3276
+ this.trigger('controlsenabled');
3277
+ } else {
3278
+ this.removeClass('vjs-controls-enabled');
3279
+ this.addClass('vjs-controls-disabled');
3280
+ this.trigger('controlsdisabled');
3281
+ }
3216
3282
  }
3283
+ return this;
3217
3284
  }
3218
3285
  return this.controls_;
3219
3286
  };
3220
3287
 
3288
+ vjs.Player.prototype.usingNativeControls_;
3289
+
3290
+ /**
3291
+ * Toggle native controls on/off. Native controls are the controls built into
3292
+ * devices (e.g. default iPhone controls), Flash, or other techs
3293
+ * (e.g. Vimeo Controls)
3294
+ *
3295
+ * **This should only be set by the current tech, because only the tech knows
3296
+ * if it can support native controls**
3297
+ *
3298
+ * @param {Boolean} bool True signals that native controls are on
3299
+ * @return {vjs.Player} Returns the player
3300
+ */
3301
+ vjs.Player.prototype.usingNativeControls = function(bool){
3302
+ if (bool !== undefined) {
3303
+ bool = !!bool; // force boolean
3304
+ // Don't trigger a change event unless it actually changed
3305
+ if (this.usingNativeControls_ !== bool) {
3306
+ this.usingNativeControls_ = bool;
3307
+ if (bool) {
3308
+ this.addClass('vjs-using-native-controls');
3309
+ this.trigger('usingnativecontrols');
3310
+ } else {
3311
+ this.removeClass('vjs-using-native-controls');
3312
+ this.trigger('usingcustomcontrols');
3313
+ }
3314
+ }
3315
+ return this;
3316
+ }
3317
+ return this.usingNativeControls_;
3318
+ };
3319
+
3221
3320
  vjs.Player.prototype.error = function(){ return this.techGet('error'); };
3222
3321
  vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
3322
+ vjs.Player.prototype.seeking = function(){ return this.techGet('seeking'); };
3323
+
3324
+ // When the player is first initialized, trigger activity so components
3325
+ // like the control bar show themselves if needed
3326
+ vjs.Player.prototype.userActivity_ = true;
3327
+ vjs.Player.prototype.reportUserActivity = function(event){
3328
+ this.userActivity_ = true;
3329
+ };
3330
+
3331
+ vjs.Player.prototype.userActive_ = true;
3332
+ vjs.Player.prototype.userActive = function(bool){
3333
+ if (bool !== undefined) {
3334
+ bool = !!bool;
3335
+ if (bool !== this.userActive_) {
3336
+ this.userActive_ = bool;
3337
+ if (bool) {
3338
+ // If the user was inactive and is now active we want to reset the
3339
+ // inactivity timer
3340
+ this.userActivity_ = true;
3341
+ this.removeClass('vjs-user-inactive');
3342
+ this.addClass('vjs-user-active');
3343
+ this.trigger('useractive');
3344
+ } else {
3345
+ // We're switching the state to inactive manually, so erase any other
3346
+ // activity
3347
+ this.userActivity_ = false;
3348
+
3349
+ // Chrome/Safari/IE have bugs where when you change the cursor it can
3350
+ // trigger a mousemove event. This causes an issue when you're hiding
3351
+ // the cursor when the user is inactive, and a mousemove signals user
3352
+ // activity. Making it impossible to go into inactive mode. Specifically
3353
+ // this happens in fullscreen when we really need to hide the cursor.
3354
+ //
3355
+ // When this gets resolved in ALL browsers it can be removed
3356
+ // https://code.google.com/p/chromium/issues/detail?id=103041
3357
+ this.tech.one('mousemove', function(e){
3358
+ e.stopPropagation();
3359
+ e.preventDefault();
3360
+ });
3361
+ this.removeClass('vjs-user-active');
3362
+ this.addClass('vjs-user-inactive');
3363
+ this.trigger('userinactive');
3364
+ }
3365
+ }
3366
+ return this;
3367
+ }
3368
+ return this.userActive_;
3369
+ };
3370
+
3371
+ vjs.Player.prototype.listenForUserActivity = function(){
3372
+ var onMouseActivity, onMouseDown, mouseInProgress, onMouseUp,
3373
+ activityCheck, inactivityTimeout;
3374
+
3375
+ onMouseActivity = this.reportUserActivity;
3376
+
3377
+ onMouseDown = function() {
3378
+ onMouseActivity();
3379
+ // For as long as the they are touching the device or have their mouse down,
3380
+ // we consider them active even if they're not moving their finger or mouse.
3381
+ // So we want to continue to update that they are active
3382
+ clearInterval(mouseInProgress);
3383
+ // Setting userActivity=true now and setting the interval to the same time
3384
+ // as the activityCheck interval (250) should ensure we never miss the
3385
+ // next activityCheck
3386
+ mouseInProgress = setInterval(vjs.bind(this, onMouseActivity), 250);
3387
+ };
3388
+
3389
+ onMouseUp = function(event) {
3390
+ onMouseActivity();
3391
+ // Stop the interval that maintains activity if the mouse/touch is down
3392
+ clearInterval(mouseInProgress);
3393
+ };
3394
+
3395
+ // Any mouse movement will be considered user activity
3396
+ this.on('mousedown', onMouseDown);
3397
+ this.on('mousemove', onMouseActivity);
3398
+ this.on('mouseup', onMouseUp);
3399
+
3400
+ // Listen for keyboard navigation
3401
+ // Shouldn't need to use inProgress interval because of key repeat
3402
+ this.on('keydown', onMouseActivity);
3403
+ this.on('keyup', onMouseActivity);
3404
+
3405
+ // Consider any touch events that bubble up to be activity
3406
+ // Certain touches on the tech will be blocked from bubbling because they
3407
+ // toggle controls
3408
+ this.on('touchstart', onMouseDown);
3409
+ this.on('touchmove', onMouseActivity);
3410
+ this.on('touchend', onMouseUp);
3411
+ this.on('touchcancel', onMouseUp);
3412
+
3413
+ // Run an interval every 250 milliseconds instead of stuffing everything into
3414
+ // the mousemove/touchmove function itself, to prevent performance degradation.
3415
+ // `this.reportUserActivity` simply sets this.userActivity_ to true, which
3416
+ // then gets picked up by this loop
3417
+ // http://ejohn.org/blog/learning-from-twitter/
3418
+ activityCheck = setInterval(vjs.bind(this, function() {
3419
+ // Check to see if mouse/touch activity has happened
3420
+ if (this.userActivity_) {
3421
+ // Reset the activity tracker
3422
+ this.userActivity_ = false;
3423
+
3424
+ // If the user state was inactive, set the state to active
3425
+ this.userActive(true);
3426
+
3427
+ // Clear any existing inactivity timeout to start the timer over
3428
+ clearTimeout(inactivityTimeout);
3429
+
3430
+ // In X seconds, if no more activity has occurred the user will be
3431
+ // considered inactive
3432
+ inactivityTimeout = setTimeout(vjs.bind(this, function() {
3433
+ // Protect against the case where the inactivityTimeout can trigger just
3434
+ // before the next user activity is picked up by the activityCheck loop
3435
+ // causing a flicker
3436
+ if (!this.userActivity_) {
3437
+ this.userActive(false);
3438
+ }
3439
+ }), 2000);
3440
+ }
3441
+ }), 250);
3442
+
3443
+ // Clean up the intervals when we kill the player
3444
+ this.on('dispose', function(){
3445
+ clearInterval(activityCheck);
3446
+ clearTimeout(inactivityTimeout);
3447
+ });
3448
+ };
3223
3449
 
3224
3450
  // Methods to add support for
3225
3451
  // networkState: function(){ return this.techCall('networkState'); },
@@ -3288,61 +3514,15 @@ vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
3288
3514
  }
3289
3515
 
3290
3516
  })();
3517
+
3518
+
3291
3519
  /**
3292
3520
  * Container of main controls
3293
3521
  * @param {vjs.Player|Object} player
3294
3522
  * @param {Object=} options
3295
3523
  * @constructor
3296
3524
  */
3297
- vjs.ControlBar = vjs.Component.extend({
3298
- /** @constructor */
3299
- init: function(player, options){
3300
- vjs.Component.call(this, player, options);
3301
-
3302
- if (!player.controls()) {
3303
- this.disable();
3304
- }
3305
-
3306
- player.one('play', vjs.bind(this, function(){
3307
- var touchstart,
3308
- fadeIn = vjs.bind(this, this.fadeIn),
3309
- fadeOut = vjs.bind(this, this.fadeOut);
3310
-
3311
- this.fadeIn();
3312
-
3313
- if ( !('ontouchstart' in window) ) {
3314
- this.player_.on('mouseover', fadeIn);
3315
- this.player_.on('mouseout', fadeOut);
3316
- this.player_.on('pause', vjs.bind(this, this.lockShowing));
3317
- this.player_.on('play', vjs.bind(this, this.unlockShowing));
3318
- }
3319
-
3320
- touchstart = false;
3321
- this.player_.on('touchstart', function() {
3322
- touchstart = true;
3323
- });
3324
- this.player_.on('touchmove', function() {
3325
- touchstart = false;
3326
- });
3327
- this.player_.on('touchend', vjs.bind(this, function(event) {
3328
- var idx;
3329
- if (touchstart) {
3330
- idx = this.el().className.search('fade-in');
3331
- if (idx !== -1) {
3332
- this.fadeOut();
3333
- } else {
3334
- this.fadeIn();
3335
- }
3336
- }
3337
- touchstart = false;
3338
-
3339
- if (!this.player_.paused()) {
3340
- event.preventDefault();
3341
- }
3342
- }));
3343
- }));
3344
- }
3345
- });
3525
+ vjs.ControlBar = vjs.Component.extend();
3346
3526
 
3347
3527
  vjs.ControlBar.prototype.options_ = {
3348
3528
  loadEvent: 'play',
@@ -3365,16 +3545,7 @@ vjs.ControlBar.prototype.createEl = function(){
3365
3545
  className: 'vjs-control-bar'
3366
3546
  });
3367
3547
  };
3368
-
3369
- vjs.ControlBar.prototype.fadeIn = function(){
3370
- vjs.Component.prototype.fadeIn.call(this);
3371
- this.player_.trigger('controlsvisible');
3372
- };
3373
-
3374
- vjs.ControlBar.prototype.fadeOut = function(){
3375
- vjs.Component.prototype.fadeOut.call(this);
3376
- this.player_.trigger('controlshidden');
3377
- };/**
3548
+ /**
3378
3549
  * Button to toggle between play and pause
3379
3550
  * @param {vjs.Player|Object} player
3380
3551
  * @param {Object=} options
@@ -3484,8 +3655,9 @@ vjs.DurationDisplay.prototype.createEl = function(){
3484
3655
  };
3485
3656
 
3486
3657
  vjs.DurationDisplay.prototype.updateContent = function(){
3487
- if (this.player_.duration()) {
3488
- this.content.innerHTML = '<span class="vjs-control-text">Duration Time </span>' + vjs.formatTime(this.player_.duration()); // label the duration time for screen reader users
3658
+ var duration = this.player_.duration();
3659
+ if (duration) {
3660
+ this.content.innerHTML = '<span class="vjs-control-text">Duration Time </span>' + vjs.formatTime(duration); // label the duration time for screen reader users
3489
3661
  }
3490
3662
  };
3491
3663
 
@@ -3541,15 +3713,14 @@ vjs.RemainingTimeDisplay.prototype.createEl = function(){
3541
3713
 
3542
3714
  vjs.RemainingTimeDisplay.prototype.updateContent = function(){
3543
3715
  if (this.player_.duration()) {
3544
- if (this.player_.duration()) {
3545
- this.content.innerHTML = '<span class="vjs-control-text">Remaining Time </span>' + '-'+ vjs.formatTime(this.player_.remainingTime());
3546
- }
3716
+ this.content.innerHTML = '<span class="vjs-control-text">Remaining Time </span>' + '-'+ vjs.formatTime(this.player_.remainingTime());
3547
3717
  }
3548
3718
 
3549
3719
  // Allows for smooth scrubbing, when player can't keep up.
3550
3720
  // var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
3551
3721
  // this.content.innerHTML = vjs.formatTime(time, this.player_.duration());
3552
- };/**
3722
+ };
3723
+ /**
3553
3724
  * Toggle fullscreen video
3554
3725
  * @param {vjs.Player|Object} player
3555
3726
  * @param {Object=} options
@@ -3643,7 +3814,25 @@ vjs.SeekBar.prototype.updateARIAAttributes = function(){
3643
3814
  };
3644
3815
 
3645
3816
  vjs.SeekBar.prototype.getPercent = function(){
3646
- return this.player_.currentTime() / this.player_.duration();
3817
+ var currentTime;
3818
+ // Flash RTMP provider will not report the correct time
3819
+ // immediately after a seek. This isn't noticeable if you're
3820
+ // seeking while the video is playing, but it is if you seek
3821
+ // while the video is paused.
3822
+ if (this.player_.techName === 'Flash' && this.player_.seeking()) {
3823
+ var cache = this.player_.getCache();
3824
+ if (cache.lastSetCurrentTime) {
3825
+ currentTime = cache.lastSetCurrentTime;
3826
+ }
3827
+ else {
3828
+ currentTime = this.player_.currentTime();
3829
+ }
3830
+ }
3831
+ else {
3832
+ currentTime = this.player_.currentTime();
3833
+ }
3834
+
3835
+ return currentTime / this.player_.duration();
3647
3836
  };
3648
3837
 
3649
3838
  vjs.SeekBar.prototype.onMouseDown = function(event){
@@ -3758,11 +3947,11 @@ vjs.VolumeControl = vjs.Component.extend({
3758
3947
  vjs.Component.call(this, player, options);
3759
3948
 
3760
3949
  // hide volume controls when they're not supported by the current tech
3761
- if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
3950
+ if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {
3762
3951
  this.addClass('vjs-hidden');
3763
3952
  }
3764
3953
  player.on('loadstart', vjs.bind(this, function(){
3765
- if (player.tech.features && player.tech.features.volumeControl === false) {
3954
+ if (player.tech.features && player.tech.features['volumeControl'] === false) {
3766
3955
  this.addClass('vjs-hidden');
3767
3956
  } else {
3768
3957
  this.removeClass('vjs-hidden');
@@ -3879,7 +4068,8 @@ vjs.VolumeLevel.prototype.createEl = function(){
3879
4068
  return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
3880
4069
  className: 'vjs-volume-handle'
3881
4070
  });
3882
- };/**
4071
+ };
4072
+ /**
3883
4073
  * Mute the audio
3884
4074
  * @param {vjs.Player|Object} player
3885
4075
  * @param {Object=} options
@@ -3893,11 +4083,11 @@ vjs.MuteToggle = vjs.Button.extend({
3893
4083
  player.on('volumechange', vjs.bind(this, this.update));
3894
4084
 
3895
4085
  // hide mute toggle if the current tech doesn't support volume control
3896
- if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
4086
+ if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {
3897
4087
  this.addClass('vjs-hidden');
3898
4088
  }
3899
4089
  player.on('loadstart', vjs.bind(this, function(){
3900
- if (player.tech.features && player.tech.features.volumeControl === false) {
4090
+ if (player.tech.features && player.tech.features['volumeControl'] === false) {
3901
4091
  this.addClass('vjs-hidden');
3902
4092
  } else {
3903
4093
  this.removeClass('vjs-hidden');
@@ -3947,7 +4137,8 @@ vjs.MuteToggle.prototype.update = function(){
3947
4137
  vjs.removeClass(this.el_, 'vjs-vol-'+i);
3948
4138
  }
3949
4139
  vjs.addClass(this.el_, 'vjs-vol-'+level);
3950
- };/**
4140
+ };
4141
+ /**
3951
4142
  * Menu button with a popup for showing the volume slider.
3952
4143
  * @constructor
3953
4144
  */
@@ -4037,7 +4228,10 @@ vjs.PosterImage.prototype.createEl = function(){
4037
4228
  };
4038
4229
 
4039
4230
  vjs.PosterImage.prototype.onClick = function(){
4040
- this.player_.play();
4231
+ // Only accept clicks when controls are enabled
4232
+ if (this.player().controls()) {
4233
+ this.player_.play();
4234
+ }
4041
4235
  };
4042
4236
  /* Loading Spinner
4043
4237
  ================================================================================ */
@@ -4082,24 +4276,13 @@ vjs.LoadingSpinner.prototype.createEl = function(){
4082
4276
  /* Big Play Button
4083
4277
  ================================================================================ */
4084
4278
  /**
4085
- * Initial play button. Shows before the video has played.
4279
+ * Initial play button. Shows before the video has played. The hiding of the
4280
+ * big play button is done via CSS and player states.
4086
4281
  * @param {vjs.Player|Object} player
4087
4282
  * @param {Object=} options
4088
4283
  * @constructor
4089
4284
  */
4090
- vjs.BigPlayButton = vjs.Button.extend({
4091
- /** @constructor */
4092
- init: function(player, options){
4093
- vjs.Button.call(this, player, options);
4094
-
4095
- if (!player.controls()) {
4096
- this.hide();
4097
- }
4098
-
4099
- player.on('play', vjs.bind(this, this.hide));
4100
- // player.on('ended', vjs.bind(this, this.show));
4101
- }
4102
- });
4285
+ vjs.BigPlayButton = vjs.Button.extend();
4103
4286
 
4104
4287
  vjs.BigPlayButton.prototype.createEl = function(){
4105
4288
  return vjs.Button.prototype.createEl.call(this, 'div', {
@@ -4110,15 +4293,11 @@ vjs.BigPlayButton.prototype.createEl = function(){
4110
4293
  };
4111
4294
 
4112
4295
  vjs.BigPlayButton.prototype.onClick = function(){
4113
- // Go back to the beginning if big play button is showing at the end.
4114
- // Have to check for current time otherwise it might throw a 'not ready' error.
4115
- //if(this.player_.currentTime()) {
4116
- //this.player_.currentTime(0);
4117
- //}
4118
4296
  this.player_.play();
4119
4297
  };
4120
4298
  /**
4121
- * @fileoverview Media Technology Controller - Base class for media playback technology controllers like Flash and HTML5
4299
+ * @fileoverview Media Technology Controller - Base class for media playback
4300
+ * technology controllers like Flash and HTML5
4122
4301
  */
4123
4302
 
4124
4303
  /**
@@ -4132,48 +4311,155 @@ vjs.MediaTechController = vjs.Component.extend({
4132
4311
  init: function(player, options, ready){
4133
4312
  vjs.Component.call(this, player, options, ready);
4134
4313
 
4135
- // Make playback element clickable
4136
- // this.addEvent('click', this.proxy(this.onClick));
4137
-
4138
- // player.triggerEvent('techready');
4314
+ this.initControlsListeners();
4139
4315
  }
4140
4316
  });
4141
4317
 
4142
- // destroy: function(){},
4143
- // createElement: function(){},
4144
-
4145
4318
  /**
4146
- * Handle a click on the media element. By default will play the media.
4319
+ * Set up click and touch listeners for the playback element
4320
+ * On desktops, a click on the video itself will toggle playback,
4321
+ * on a mobile device a click on the video toggles controls.
4322
+ * (toggling controls is done by toggling the user state between active and
4323
+ * inactive)
4324
+ *
4325
+ * A tap can signal that a user has become active, or has become inactive
4326
+ * e.g. a quick tap on an iPhone movie should reveal the controls. Another
4327
+ * quick tap should hide them again (signaling the user is in an inactive
4328
+ * viewing state)
4147
4329
  *
4148
- * On android browsers, having this toggle play state interferes with being
4149
- * able to toggle the controls and toggling play state with the play button
4330
+ * In addition to this, we still want the user to be considered inactive after
4331
+ * a few seconds of inactivity.
4332
+ *
4333
+ * Note: the only part of iOS interaction we can't mimic with this setup
4334
+ * is a touch and hold on the video element counting as activity in order to
4335
+ * keep the controls showing, but that shouldn't be an issue. A touch and hold on
4336
+ * any controls will still keep the user active
4150
4337
  */
4151
- vjs.MediaTechController.prototype.onClick = (function(){
4152
- if (vjs.IS_ANDROID) {
4153
- return function () {};
4154
- } else {
4155
- return function () {
4156
- if (this.player_.controls()) {
4157
- if (this.player_.paused()) {
4158
- this.player_.play();
4159
- } else {
4160
- this.player_.pause();
4161
- }
4162
- }
4163
- };
4338
+ vjs.MediaTechController.prototype.initControlsListeners = function(){
4339
+ var player, tech, activateControls, deactivateControls;
4340
+
4341
+ tech = this;
4342
+ player = this.player();
4343
+
4344
+ var activateControls = function(){
4345
+ if (player.controls() && !player.usingNativeControls()) {
4346
+ tech.addControlsListeners();
4347
+ }
4348
+ };
4349
+
4350
+ deactivateControls = vjs.bind(tech, tech.removeControlsListeners);
4351
+
4352
+ // Set up event listeners once the tech is ready and has an element to apply
4353
+ // listeners to
4354
+ this.ready(activateControls);
4355
+ player.on('controlsenabled', activateControls);
4356
+ player.on('controlsdisabled', deactivateControls);
4357
+ };
4358
+
4359
+ vjs.MediaTechController.prototype.addControlsListeners = function(){
4360
+ var preventBubble, userWasActive;
4361
+
4362
+ // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
4363
+ // trigger mousedown/up.
4364
+ // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
4365
+ // Any touch events are set to block the mousedown event from happening
4366
+ this.on('mousedown', this.onClick);
4367
+
4368
+ // We need to block touch events on the video element from bubbling up,
4369
+ // otherwise they'll signal activity prematurely. The specific use case is
4370
+ // when the video is playing and the controls have faded out. In this case
4371
+ // only a tap (fast touch) should toggle the user active state and turn the
4372
+ // controls back on. A touch and move or touch and hold should not trigger
4373
+ // the controls (per iOS as an example at least)
4374
+ //
4375
+ // We always want to stop propagation on touchstart because touchstart
4376
+ // at the player level starts the touchInProgress interval. We can still
4377
+ // report activity on the other events, but won't let them bubble for
4378
+ // consistency. We don't want to bubble a touchend without a touchstart.
4379
+ this.on('touchstart', function(event) {
4380
+ // Stop the mouse events from also happening
4381
+ event.preventDefault();
4382
+ event.stopPropagation();
4383
+ // Record if the user was active now so we don't have to keep polling it
4384
+ userWasActive = this.player_.userActive();
4385
+ });
4386
+
4387
+ preventBubble = function(event){
4388
+ event.stopPropagation();
4389
+ if (userWasActive) {
4390
+ this.player_.reportUserActivity();
4391
+ }
4392
+ };
4393
+
4394
+ // Treat all touch events the same for consistency
4395
+ this.on('touchmove', preventBubble);
4396
+ this.on('touchleave', preventBubble);
4397
+ this.on('touchcancel', preventBubble);
4398
+ this.on('touchend', preventBubble);
4399
+
4400
+ // Turn on component tap events
4401
+ this.emitTapEvents();
4402
+
4403
+ // The tap listener needs to come after the touchend listener because the tap
4404
+ // listener cancels out any reportedUserActivity when setting userActive(false)
4405
+ this.on('tap', this.onTap);
4406
+ };
4407
+
4408
+ /**
4409
+ * Remove the listeners used for click and tap controls. This is needed for
4410
+ * toggling to controls disabled, where a tap/touch should do nothing.
4411
+ */
4412
+ vjs.MediaTechController.prototype.removeControlsListeners = function(){
4413
+ // We don't want to just use `this.off()` because there might be other needed
4414
+ // listeners added by techs that extend this.
4415
+ this.off('tap');
4416
+ this.off('touchstart');
4417
+ this.off('touchmove');
4418
+ this.off('touchleave');
4419
+ this.off('touchcancel');
4420
+ this.off('touchend');
4421
+ this.off('click');
4422
+ this.off('mousedown');
4423
+ };
4424
+
4425
+ /**
4426
+ * Handle a click on the media element. By default will play/pause the media.
4427
+ */
4428
+ vjs.MediaTechController.prototype.onClick = function(event){
4429
+ // We're using mousedown to detect clicks thanks to Flash, but mousedown
4430
+ // will also be triggered with right-clicks, so we need to prevent that
4431
+ if (event.button !== 0) return;
4432
+
4433
+ // When controls are disabled a click should not toggle playback because
4434
+ // the click is considered a control
4435
+ if (this.player().controls()) {
4436
+ if (this.player().paused()) {
4437
+ this.player().play();
4438
+ } else {
4439
+ this.player().pause();
4440
+ }
4164
4441
  }
4165
- })();
4442
+ };
4443
+
4444
+ /**
4445
+ * Handle a tap on the media element. By default it will toggle the user
4446
+ * activity state, which hides and shows the controls.
4447
+ */
4448
+
4449
+ vjs.MediaTechController.prototype.onTap = function(){
4450
+ this.player().userActive(!this.player().userActive());
4451
+ };
4166
4452
 
4167
4453
  vjs.MediaTechController.prototype.features = {
4168
- volumeControl: true,
4454
+ 'volumeControl': true,
4169
4455
 
4170
4456
  // Resizing plugins using request fullscreen reloads the plugin
4171
- fullscreenResize: false,
4457
+ 'fullscreenResize': false,
4172
4458
 
4173
4459
  // Optional events that we can manually mimic with timers
4174
4460
  // currently not triggered by video-js-swf
4175
- progressEvents: false,
4176
- timeupdateEvents: false
4461
+ 'progressEvents': false,
4462
+ 'timeupdateEvents': false
4177
4463
  };
4178
4464
 
4179
4465
  vjs.media = {};
@@ -4210,13 +4496,13 @@ vjs.Html5 = vjs.MediaTechController.extend({
4210
4496
  /** @constructor */
4211
4497
  init: function(player, options, ready){
4212
4498
  // volume cannot be changed from 1 on iOS
4213
- this.features.volumeControl = vjs.Html5.canControlVolume();
4499
+ this.features['volumeControl'] = vjs.Html5.canControlVolume();
4214
4500
 
4215
4501
  // In iOS, if you move a video element in the DOM, it breaks video playback.
4216
- this.features.movingMediaElementInDOM = !vjs.IS_IOS;
4502
+ this.features['movingMediaElementInDOM'] = !vjs.IS_IOS;
4217
4503
 
4218
4504
  // HTML video is able to automatically resize when going to fullscreen
4219
- this.features.fullscreenResize = true;
4505
+ this.features['fullscreenResize'] = true;
4220
4506
 
4221
4507
  vjs.MediaTechController.call(this, player, options, ready);
4222
4508
 
@@ -4232,6 +4518,14 @@ vjs.Html5 = vjs.MediaTechController.extend({
4232
4518
  this.el_.src = source.src;
4233
4519
  }
4234
4520
 
4521
+ // Determine if native controls should be used
4522
+ // Our goal should be to get the custom controls on mobile solid everywhere
4523
+ // so we can remove this all together. Right now this will block custom
4524
+ // controls on touch enabled laptops like the Chrome Pixel
4525
+ if (vjs.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] !== false) {
4526
+ this.useNativeControls();
4527
+ }
4528
+
4235
4529
  // Chrome and Safari both have issues with autoplay.
4236
4530
  // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
4237
4531
  // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
@@ -4243,10 +4537,7 @@ vjs.Html5 = vjs.MediaTechController.extend({
4243
4537
  }
4244
4538
  });
4245
4539
 
4246
- this.on('click', this.onClick);
4247
-
4248
4540
  this.setupTriggers();
4249
-
4250
4541
  this.triggerReady();
4251
4542
  }
4252
4543
  });
@@ -4264,7 +4555,7 @@ vjs.Html5.prototype.createEl = function(){
4264
4555
  // Check if this browser supports moving the element into the box.
4265
4556
  // On the iPhone video will break if you move the element,
4266
4557
  // So we have to create a brand new element.
4267
- if (!el || this.features.movingMediaElementInDOM === false) {
4558
+ if (!el || this.features['movingMediaElementInDOM'] === false) {
4268
4559
 
4269
4560
  // If the original tag is still there, remove it.
4270
4561
  if (el) {
@@ -4313,6 +4604,37 @@ vjs.Html5.prototype.eventHandler = function(e){
4313
4604
  e.stopPropagation();
4314
4605
  };
4315
4606
 
4607
+ vjs.Html5.prototype.useNativeControls = function(){
4608
+ var tech, player, controlsOn, controlsOff, cleanUp;
4609
+
4610
+ tech = this;
4611
+ player = this.player();
4612
+
4613
+ // If the player controls are enabled turn on the native controls
4614
+ tech.setControls(player.controls());
4615
+
4616
+ // Update the native controls when player controls state is updated
4617
+ controlsOn = function(){
4618
+ tech.setControls(true);
4619
+ };
4620
+ controlsOff = function(){
4621
+ tech.setControls(false);
4622
+ };
4623
+ player.on('controlsenabled', controlsOn);
4624
+ player.on('controlsdisabled', controlsOff);
4625
+
4626
+ // Clean up when not using native controls anymore
4627
+ cleanUp = function(){
4628
+ player.off('controlsenabled', controlsOn);
4629
+ player.off('controlsdisabled', controlsOff);
4630
+ };
4631
+ tech.on('dispose', cleanUp);
4632
+ player.on('usingcustomcontrols', cleanUp);
4633
+
4634
+ // Update the state of the player to using native controls
4635
+ player.usingNativeControls(true);
4636
+ };
4637
+
4316
4638
 
4317
4639
  vjs.Html5.prototype.play = function(){ this.el_.play(); };
4318
4640
  vjs.Html5.prototype.pause = function(){ this.el_.pause(); };
@@ -4376,29 +4698,19 @@ vjs.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; };
4376
4698
 
4377
4699
  vjs.Html5.prototype.preload = function(){ return this.el_.preload; };
4378
4700
  vjs.Html5.prototype.setPreload = function(val){ this.el_.preload = val; };
4701
+
4379
4702
  vjs.Html5.prototype.autoplay = function(){ return this.el_.autoplay; };
4380
4703
  vjs.Html5.prototype.setAutoplay = function(val){ this.el_.autoplay = val; };
4704
+
4705
+ vjs.Html5.prototype.controls = function(){ return this.el_.controls; }
4706
+ vjs.Html5.prototype.setControls = function(val){ this.el_.controls = !!val; }
4707
+
4381
4708
  vjs.Html5.prototype.loop = function(){ return this.el_.loop; };
4382
4709
  vjs.Html5.prototype.setLoop = function(val){ this.el_.loop = val; };
4383
4710
 
4384
4711
  vjs.Html5.prototype.error = function(){ return this.el_.error; };
4385
- // networkState: function(){ return this.el_.networkState; },
4386
- // readyState: function(){ return this.el_.readyState; },
4387
4712
  vjs.Html5.prototype.seeking = function(){ return this.el_.seeking; };
4388
- // initialTime: function(){ return this.el_.initialTime; },
4389
- // startOffsetTime: function(){ return this.el_.startOffsetTime; },
4390
- // played: function(){ return this.el_.played; },
4391
- // seekable: function(){ return this.el_.seekable; },
4392
4713
  vjs.Html5.prototype.ended = function(){ return this.el_.ended; };
4393
- // videoTracks: function(){ return this.el_.videoTracks; },
4394
- // audioTracks: function(){ return this.el_.audioTracks; },
4395
- // videoWidth: function(){ return this.el_.videoWidth; },
4396
- // videoHeight: function(){ return this.el_.videoHeight; },
4397
- // textTracks: function(){ return this.el_.textTracks; },
4398
- // defaultPlaybackRate: function(){ return this.el_.defaultPlaybackRate; },
4399
- // playbackRate: function(){ return this.el_.playbackRate; },
4400
- // mediaGroup: function(){ return this.el_.mediaGroup; },
4401
- // controller: function(){ return this.el_.controller; },
4402
4714
  vjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };
4403
4715
 
4404
4716
  /* HTML5 Support Testing ---------------------------------------------------- */
@@ -4504,7 +4816,14 @@ vjs.Flash = vjs.MediaTechController.extend({
4504
4816
 
4505
4817
  // If source was supplied pass as a flash var.
4506
4818
  if (source) {
4507
- flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
4819
+ if (source.type && vjs.Flash.isStreamingType(source.type)) {
4820
+ var parts = vjs.Flash.streamToParts(source.src);
4821
+ flashVars['rtmpConnection'] = encodeURIComponent(parts.connection);
4822
+ flashVars['rtmpStream'] = encodeURIComponent(parts.stream);
4823
+ }
4824
+ else {
4825
+ flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
4826
+ }
4508
4827
  }
4509
4828
 
4510
4829
  // Add placeholder to player div
@@ -4622,9 +4941,6 @@ vjs.Flash = vjs.MediaTechController.extend({
4622
4941
  // Update reference to playback technology element
4623
4942
  tech.el_ = el;
4624
4943
 
4625
- // Now that the element is ready, make a click on the swf play the video
4626
- vjs.on(el, 'click', tech.bind(tech.onClick));
4627
-
4628
4944
  // Make sure swf is actually ready. Sometimes the API isn't actually yet.
4629
4945
  vjs.Flash.checkReady(tech);
4630
4946
  });
@@ -4667,10 +4983,16 @@ vjs.Flash.prototype.pause = function(){
4667
4983
  };
4668
4984
 
4669
4985
  vjs.Flash.prototype.src = function(src){
4670
- // Make sure source URL is abosolute.
4671
- src = vjs.getAbsoluteURL(src);
4672
-
4673
- this.el_.vjs_src(src);
4986
+ if (vjs.Flash.isStreamingSrc(src)) {
4987
+ src = vjs.Flash.streamToParts(src);
4988
+ this.setRtmpConnection(src.connection);
4989
+ this.setRtmpStream(src.stream);
4990
+ }
4991
+ else {
4992
+ // Make sure source URL is abosolute.
4993
+ src = vjs.getAbsoluteURL(src);
4994
+ this.el_.vjs_src(src);
4995
+ }
4674
4996
 
4675
4997
  // Currently the SWF doesn't autoplay if you load a source later.
4676
4998
  // e.g. Load player w/ no source, wait 2s, set src.
@@ -4680,6 +5002,20 @@ vjs.Flash.prototype.src = function(src){
4680
5002
  }
4681
5003
  };
4682
5004
 
5005
+ vjs.Flash.prototype.currentSrc = function(){
5006
+ var src = this.el_.vjs_getProperty('currentSrc');
5007
+ // no src, check and see if RTMP
5008
+ if (src == null) {
5009
+ var connection = this.rtmpConnection(),
5010
+ stream = this.rtmpStream();
5011
+
5012
+ if (connection && stream) {
5013
+ src = vjs.Flash.streamFromParts(connection, stream);
5014
+ }
5015
+ }
5016
+ return src;
5017
+ };
5018
+
4683
5019
  vjs.Flash.prototype.load = function(){
4684
5020
  this.el_.vjs_load();
4685
5021
  };
@@ -4703,7 +5039,7 @@ vjs.Flash.prototype.enterFullScreen = function(){
4703
5039
 
4704
5040
  // Create setters and getters for attributes
4705
5041
  var api = vjs.Flash.prototype,
4706
- readWrite = 'preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
5042
+ readWrite = 'rtmpConnection,rtmpStream,preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
4707
5043
  readOnly = 'error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(',');
4708
5044
  // Overridden: buffered
4709
5045
 
@@ -4744,7 +5080,7 @@ vjs.Flash.isSupported = function(){
4744
5080
  };
4745
5081
 
4746
5082
  vjs.Flash.canPlaySource = function(srcObj){
4747
- if (srcObj.type in vjs.Flash.formats) { return 'maybe'; }
5083
+ if (srcObj.type in vjs.Flash.formats || srcObj.type in vjs.Flash.streamingFormats) { return 'maybe'; }
4748
5084
  };
4749
5085
 
4750
5086
  vjs.Flash.formats = {
@@ -4754,6 +5090,11 @@ vjs.Flash.formats = {
4754
5090
  'video/m4v': 'MP4'
4755
5091
  };
4756
5092
 
5093
+ vjs.Flash.streamingFormats = {
5094
+ 'rtmp/mp4': 'MP4',
5095
+ 'rtmp/flv': 'FLV'
5096
+ };
5097
+
4757
5098
  vjs.Flash['onReady'] = function(currSwf){
4758
5099
  var el = vjs.el(currSwf);
4759
5100
 
@@ -4768,9 +5109,6 @@ vjs.Flash['onReady'] = function(currSwf){
4768
5109
  // Update reference to playback technology element
4769
5110
  tech.el_ = el;
4770
5111
 
4771
- // Now that the element is ready, make a click on the swf play the video
4772
- tech.on('click', tech.onClick);
4773
-
4774
5112
  vjs.Flash.checkReady(tech);
4775
5113
  };
4776
5114
 
@@ -4893,6 +5231,54 @@ vjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){
4893
5231
 
4894
5232
  return objTag + attrsString + '>' + paramsString + '</object>';
4895
5233
  };
5234
+
5235
+ vjs.Flash.streamFromParts = function(connection, stream) {
5236
+ return connection + '&' + stream;
5237
+ };
5238
+
5239
+ vjs.Flash.streamToParts = function(src) {
5240
+ var parts = {
5241
+ connection: '',
5242
+ stream: ''
5243
+ };
5244
+
5245
+ if (! src) {
5246
+ return parts;
5247
+ }
5248
+
5249
+ // Look for the normal URL separator we expect, '&'.
5250
+ // If found, we split the URL into two pieces around the
5251
+ // first '&'.
5252
+ var connEnd = src.indexOf('&');
5253
+ var streamBegin;
5254
+ if (connEnd !== -1) {
5255
+ streamBegin = connEnd + 1;
5256
+ }
5257
+ else {
5258
+ // If there's not a '&', we use the last '/' as the delimiter.
5259
+ connEnd = streamBegin = src.lastIndexOf('/') + 1;
5260
+ if (connEnd === 0) {
5261
+ // really, there's not a '/'?
5262
+ connEnd = streamBegin = src.length;
5263
+ }
5264
+ }
5265
+ parts.connection = src.substring(0, connEnd);
5266
+ parts.stream = src.substring(streamBegin, src.length);
5267
+
5268
+ return parts;
5269
+ };
5270
+
5271
+ vjs.Flash.isStreamingType = function(srcType) {
5272
+ return srcType in vjs.Flash.streamingFormats;
5273
+ };
5274
+
5275
+ // RTMP has four variations, any string starting
5276
+ // with one of these protocols should be valid
5277
+ vjs.Flash.RTMP_RE = /^rtmp[set]?:\/\//i;
5278
+
5279
+ vjs.Flash.isStreamingSrc = function(src) {
5280
+ return vjs.Flash.RTMP_RE.test(src);
5281
+ };
4896
5282
  /**
4897
5283
  * @constructor
4898
5284
  */