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.
- checksums.yaml +7 -0
- data/lib/videojs_rails/version.rb +1 -1
- data/readme.md +1 -1
- data/vendor/assets/flash/video-js.swf +0 -0
- data/vendor/assets/javascripts/video.js.erb +637 -251
- data/vendor/assets/stylesheets/video-js.css.erb +429 -396
- metadata +9 -10
@@ -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.
|
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.
|
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
|
461
|
-
|
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
|
-
|
513
|
-
vjs.off(elem, type,
|
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
|
535
|
+
var el, propName;
|
536
|
+
|
537
|
+
el = document.createElement(tagName || 'div');
|
527
538
|
|
528
|
-
for (
|
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
|
623
|
-
objDef = '[object Object]';
|
633
|
+
var key, val1, val2;
|
624
634
|
|
625
|
-
//
|
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
|
-
|
794
|
-
|
795
|
-
|
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
|
-
|
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
|
-
|
875
|
-
|
876
|
-
//
|
877
|
-
//
|
878
|
-
|
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
|
-
|
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
|
-
//
|
889
|
-
//
|
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
|
-
//
|
892
|
-
// which would equal false if we just check for a false value.
|
893
|
-
//
|
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
|
-
|
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(
|
999
|
-
return
|
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
|
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
|
-
|
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
|
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
|
-
//
|
2390
|
-
//
|
2391
|
-
|
2392
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
-
|
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 &&
|
2913
|
-
end = buffered.end(
|
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:
|
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(
|
3211
|
-
if (
|
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_ !==
|
3214
|
-
this.controls_ =
|
3215
|
-
|
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
|
-
|
3488
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
*
|
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
|
-
*
|
4149
|
-
*
|
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.
|
4152
|
-
|
4153
|
-
|
4154
|
-
|
4155
|
-
|
4156
|
-
|
4157
|
-
|
4158
|
-
|
4159
|
-
|
4160
|
-
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
4671
|
-
|
4672
|
-
|
4673
|
-
|
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
|
*/
|