videojs_rails 4.1.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  */