-
Notifications
You must be signed in to change notification settings - Fork 7.5k
/
player.js
1385 lines (1191 loc) · 42.5 KB
/
player.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* An instance of the `vjs.Player` class is created when any of the Video.js setup methods are used to initialize a video.
*
* ```js
* var myPlayer = videojs('example_video_1');
* ```
*
* In the follwing example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready.
*
* ```html
* <video id="example_video_1" data-setup='{}' controls>
* <source src="my-source.mp4" type="video/mp4">
* </video>
* ```
*
* After an instance has been created it can be accessed globally using `Video('example_video_1')`.
*
* @class
* @extends vjs.Component
*/
vjs.Player = vjs.Component.extend({
/**
* player's constructor function
*
* @constructs
* @method init
* @param {Element} tag The original video tag used for configuring options
* @param {Object=} options Player options
* @param {Function=} ready Ready callback function
*/
init: function(tag, options, ready){
this.tag = tag; // Store the original tag used to set options
// Set Options
// The options argument overrides options set in the video tag
// which overrides globally set options.
// This latter part coincides with the load order
// (tag must exist before Player)
options = vjs.obj.merge(this.getTagSettings(tag), options);
// Cache for video property values.
this.cache_ = {};
// Set poster
this.poster_ = options['poster'];
// Set controls
this.controls_ = options['controls'];
// Original tag settings stored in options
// now remove immediately so native controls don't flash.
// May be turned back on by HTML5 tech if nativeControlsForTouch is true
tag.controls = false;
// Run base component initializing with new options.
// Builds the element through createEl()
// Inits and embeds any child components in opts
vjs.Component.call(this, this, options, ready);
// Update controls className. Can't do this when the controls are initially
// set because the element doesn't exist yet.
if (this.controls()) {
this.addClass('vjs-controls-enabled');
} else {
this.addClass('vjs-controls-disabled');
}
// TODO: Make this smarter. Toggle user state between touching/mousing
// using events, since devices can have both touch and mouse events.
// if (vjs.TOUCH_ENABLED) {
// this.addClass('vjs-touch-enabled');
// }
// Firstplay event implimentation. Not sold on the event yet.
// Could probably just check currentTime==0?
this.one('play', function(e){
var fpEvent = { type: 'firstplay', target: this.el_ };
// Using vjs.trigger so we can check if default was prevented
var keepGoing = vjs.trigger(this.el_, fpEvent);
if (!keepGoing) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
});
this.on('ended', this.onEnded);
this.on('play', this.onPlay);
this.on('firstplay', this.onFirstPlay);
this.on('pause', this.onPause);
this.on('progress', this.onProgress);
this.on('durationchange', this.onDurationChange);
this.on('error', this.onError);
this.on('fullscreenchange', this.onFullscreenChange);
// Make player easily findable by ID
vjs.players[this.id_] = this;
if (options['plugins']) {
vjs.obj.each(options['plugins'], function(key, val){
this[key](val);
}, this);
}
this.listenForUserActivity();
}
});
/**
* Player instance options, surfaced using vjs.options
* vjs.options = vjs.Player.prototype.options_
* Make changes in vjs.options, not here.
* All options should use string keys so they avoid
* renaming by closure compiler
* @type {Object}
* @private
*/
vjs.Player.prototype.options_ = vjs.options;
/**
* Destroys the video player and does any necessary cleanup
*
* myPlayer.dispose();
*
* This is especially helpful if you are dynamically adding and removing videos
* to/from the DOM.
*/
vjs.Player.prototype.dispose = function(){
this.trigger('dispose');
// prevent dispose from being called twice
this.off('dispose');
// Kill reference to this player
vjs.players[this.id_] = null;
if (this.tag && this.tag['player']) { this.tag['player'] = null; }
if (this.el_ && this.el_['player']) { this.el_['player'] = null; }
// Ensure that tracking progress and time progress will stop and plater deleted
this.stopTrackingProgress();
this.stopTrackingCurrentTime();
if (this.tech) { this.tech.dispose(); }
// Component dispose
vjs.Component.prototype.dispose.call(this);
};
vjs.Player.prototype.getTagSettings = function(tag){
var options = {
'sources': [],
'tracks': []
};
vjs.obj.merge(options, vjs.getAttributeValues(tag));
// Get tag children settings
if (tag.hasChildNodes()) {
var children, child, childName, i, j;
children = tag.childNodes;
for (i=0,j=children.length; i<j; i++) {
child = children[i];
// Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
childName = child.nodeName.toLowerCase();
if (childName === 'source') {
options['sources'].push(vjs.getAttributeValues(child));
} else if (childName === 'track') {
options['tracks'].push(vjs.getAttributeValues(child));
}
}
}
return options;
};
vjs.Player.prototype.createEl = function(){
var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');
var tag = this.tag;
// Remove width/height attrs from tag so CSS can make it 100% width/height
tag.removeAttribute('width');
tag.removeAttribute('height');
// Empty video tag tracks so the built-in player doesn't use them also.
// This may not be fast enough to stop HTML5 browsers from reading the tags
// so we'll need to turn off any default tracks if we're manually doing
// captions and subtitles. videoElement.textTracks
if (tag.hasChildNodes()) {
var nodes, nodesLength, i, node, nodeName, removeNodes;
nodes = tag.childNodes;
nodesLength = nodes.length;
removeNodes = [];
while (nodesLength--) {
node = nodes[nodesLength];
nodeName = node.nodeName.toLowerCase();
if (nodeName === 'track') {
removeNodes.push(node);
}
}
for (i=0; i<removeNodes.length; i++) {
tag.removeChild(removeNodes[i]);
}
}
// Make sure tag ID exists
tag.id = tag.id || 'vjs_video_' + vjs.guid++;
// Give video tag ID and class to player div
// ID will now reference player box, not the video tag
el.id = tag.id;
el.className = tag.className;
// Update tag id/class for use as HTML5 playback tech
// Might think we should do this after embedding in container so .vjs-tech class
// doesn't flash 100% width/height, but class only applies with .video-js parent
tag.id += '_html5_api';
tag.className = 'vjs-tech';
// Make player findable on elements
tag['player'] = el['player'] = this;
// Default state of video is paused
this.addClass('vjs-paused');
// Make box use width/height of tag, or rely on default implementation
// Enforce with CSS since width/height attrs don't work on divs
this.width(this.options_['width'], true); // (true) Skip resize listener on load
this.height(this.options_['height'], true);
// Wrap video tag in div (el/box) container
if (tag.parentNode) {
tag.parentNode.insertBefore(el, tag);
}
vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
return el;
};
// /* Media Technology (tech)
// ================================================================================ */
// Load/Create an instance of playback technlogy including element and API methods
// And append playback element in player div.
vjs.Player.prototype.loadTech = function(techName, source){
// Pause and remove current playback technology
if (this.tech) {
this.unloadTech();
// if this is the first time loading, HTML5 tag will exist but won't be initialized
// so we need to remove it if we're not loading HTML5
} else if (techName !== 'Html5' && this.tag) {
vjs.Html5.disposeMediaElement(this.tag);
this.tag = null;
}
this.techName = techName;
// Turn off API access because we're loading a new tech that might load asynchronously
this.isReady_ = false;
var techReady = function(){
this.player_.triggerReady();
// Manually track progress in cases where the browser/flash player doesn't report it.
if (!this.features['progressEvents']) {
this.player_.manualProgressOn();
}
// Manually track timeudpates in cases where the browser/flash player doesn't report it.
if (!this.features['timeupdateEvents']) {
this.player_.manualTimeUpdatesOn();
}
};
// Grab tech-specific options from player options and add source and parent element to use.
var techOptions = vjs.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]);
if (source) {
if (source.src == this.cache_.src && this.cache_.currentTime > 0) {
techOptions['startTime'] = this.cache_.currentTime;
}
this.cache_.src = source.src;
}
// Initialize tech instance
this.tech = new window['videojs'][techName](this, techOptions);
this.tech.ready(techReady);
};
vjs.Player.prototype.unloadTech = function(){
this.isReady_ = false;
this.tech.dispose();
// Turn off any manual progress or timeupdate tracking
if (this.manualProgress) { this.manualProgressOff(); }
if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
this.tech = false;
};
// There's many issues around changing the size of a Flash (or other plugin) object.
// First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
// Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.
// To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.
// reloadTech: function(betweenFn){
// vjs.log('unloadingTech')
// this.unloadTech();
// vjs.log('unloadedTech')
// if (betweenFn) { betweenFn.call(); }
// vjs.log('LoadingTech')
// this.loadTech(this.techName, { src: this.cache_.src })
// vjs.log('loadedTech')
// },
/* Fallbacks for unsupported event types
================================================================================ */
// Manually trigger progress events based on changes to the buffered amount
// Many flash players and older HTML5 browsers don't send progress or progress-like events
vjs.Player.prototype.manualProgressOn = function(){
this.manualProgress = true;
// Trigger progress watching when a source begins loading
this.trackProgress();
// Watch for a native progress event call on the tech element
// In HTML5, some older versions don't support the progress event
// So we're assuming they don't, and turning off manual progress if they do.
// As opposed to doing user agent detection
this.tech.one('progress', function(){
// Update known progress support for this playback technology
this.features['progressEvents'] = true;
// Turn off manual progress tracking
this.player_.manualProgressOff();
});
};
vjs.Player.prototype.manualProgressOff = function(){
this.manualProgress = false;
this.stopTrackingProgress();
};
vjs.Player.prototype.trackProgress = function(){
this.progressInterval = setInterval(vjs.bind(this, function(){
// Don't trigger unless buffered amount is greater than last time
// log(this.cache_.bufferEnd, this.buffered().end(0), this.duration())
/* TODO: update for multiple buffered regions */
if (this.cache_.bufferEnd < this.buffered().end(0)) {
this.trigger('progress');
} else if (this.bufferedPercent() == 1) {
this.stopTrackingProgress();
this.trigger('progress'); // Last update
}
}), 500);
};
vjs.Player.prototype.stopTrackingProgress = function(){ clearInterval(this.progressInterval); };
/*! Time Tracking -------------------------------------------------------------- */
vjs.Player.prototype.manualTimeUpdatesOn = function(){
this.manualTimeUpdates = true;
this.on('play', this.trackCurrentTime);
this.on('pause', this.stopTrackingCurrentTime);
// timeupdate is also called by .currentTime whenever current time is set
// Watch for native timeupdate event
this.tech.one('timeupdate', function(){
// Update known progress support for this playback technology
this.features['timeupdateEvents'] = true;
// Turn off manual progress tracking
this.player_.manualTimeUpdatesOff();
});
};
vjs.Player.prototype.manualTimeUpdatesOff = function(){
this.manualTimeUpdates = false;
this.stopTrackingCurrentTime();
this.off('play', this.trackCurrentTime);
this.off('pause', this.stopTrackingCurrentTime);
};
vjs.Player.prototype.trackCurrentTime = function(){
if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
this.currentTimeInterval = setInterval(vjs.bind(this, function(){
this.trigger('timeupdate');
}), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
};
// Turn off play progress tracking (when paused or dragging)
vjs.Player.prototype.stopTrackingCurrentTime = function(){ clearInterval(this.currentTimeInterval); };
// /* Player event handlers (how the player reacts to certain events)
// ================================================================================ */
/**
* Fired when the user agent begins looking for media data
* @event loadstart
*/
vjs.Player.prototype.onLoadStart;
/**
* Fired when the player has initial duration and dimension information
* @event loadedmetadata
*/
vjs.Player.prototype.onLoadedMetaData;
/**
* Fired when the player has downloaded data at the current playback position
* @event loadeddata
*/
vjs.Player.prototype.onLoadedData;
/**
* Fired when the player has finished downloading the source data
* @event loadedalldata
*/
vjs.Player.prototype.onLoadedAllData;
/**
* Fired whenever the media begins or resumes playback
* @event play
*/
vjs.Player.prototype.onPlay = function(){
vjs.removeClass(this.el_, 'vjs-paused');
vjs.addClass(this.el_, 'vjs-playing');
};
/**
* Fired the first time a video is played
*
* Not part of the HLS spec, and we're not sure if this is the best
* implementation yet, so use sparingly. If you don't have a reason to
* prevent playback, use `myPlayer.one('play');` instead.
*
* @event firstplay
*/
vjs.Player.prototype.onFirstPlay = function(){
//If the first starttime attribute is specified
//then we will start at the given offset in seconds
if(this.options_['starttime']){
this.currentTime(this.options_['starttime']);
}
this.addClass('vjs-has-started');
};
/**
* Fired whenever the media has been paused
* @event pause
*/
vjs.Player.prototype.onPause = function(){
vjs.removeClass(this.el_, 'vjs-playing');
vjs.addClass(this.el_, 'vjs-paused');
};
/**
* Fired when the current playback position has changed
*
* During playback this is fired every 15-250 milliseconds, depnding on the
* playback technology in use.
* @event timeupdate
*/
vjs.Player.prototype.onTimeUpdate;
/**
* Fired while the user agent is downloading media data
* @event progress
*/
vjs.Player.prototype.onProgress = function(){
// Add custom event for when source is finished downloading.
if (this.bufferedPercent() == 1) {
this.trigger('loadedalldata');
}
};
/**
* Fired when the end of the media resource is reached (currentTime == duration)
* @event ended
*/
vjs.Player.prototype.onEnded = function(){
if (this.options_['loop']) {
this.currentTime(0);
this.play();
}
};
/**
* Fired when the duration of the media resource is first known or changed
* @event durationchange
*/
vjs.Player.prototype.onDurationChange = function(){
// Allows for cacheing value instead of asking player each time.
this.duration(this.techGet('duration'));
};
/**
* Fired when the volume changes
* @event volumechange
*/
vjs.Player.prototype.onVolumeChange;
/**
* Fired when the player switches in or out of fullscreen mode
* @event fullscreenchange
*/
vjs.Player.prototype.onFullscreenChange = function() {
if (this.isFullScreen) {
this.addClass('vjs-fullscreen');
} else {
this.removeClass('vjs-fullscreen');
}
};
/**
* Fired when there is an error in playback
* @event error
*/
vjs.Player.prototype.onError = function(e) {
vjs.log('Video Error', e);
};
// /* Player API
// ================================================================================ */
/**
* Object for cached values.
* @private
*/
vjs.Player.prototype.cache_;
vjs.Player.prototype.getCache = function(){
return this.cache_;
};
// Pass values to the playback tech
vjs.Player.prototype.techCall = function(method, arg){
// If it's not ready yet, call method when it is
if (this.tech && !this.tech.isReady_) {
this.tech.ready(function(){
this[method](arg);
});
// Otherwise call method now
} else {
try {
this.tech[method](arg);
} catch(e) {
vjs.log(e);
throw e;
}
}
};
// Get calls can't wait for the tech, and sometimes don't need to.
vjs.Player.prototype.techGet = function(method){
if (this.tech && this.tech.isReady_) {
// Flash likes to die and reload when you hide or reposition it.
// In these cases the object methods go away and we get errors.
// When that happens we'll catch the errors and inform tech that it's not ready any more.
try {
return this.tech[method]();
} catch(e) {
// When building additional tech libs, an expected method may not be defined yet
if (this.tech[method] === undefined) {
vjs.log('Video.js: ' + method + ' method not defined for '+this.techName+' playback technology.', e);
} else {
// When a method isn't available on the object it throws a TypeError
if (e.name == 'TypeError') {
vjs.log('Video.js: ' + method + ' unavailable on '+this.techName+' playback technology element.', e);
this.tech.isReady_ = false;
} else {
vjs.log(e);
}
}
throw e;
}
}
return;
};
/**
* start media playback
*
* myPlayer.play();
*
* @return {vjs.Player} self
*/
vjs.Player.prototype.play = function(){
this.techCall('play');
return this;
};
/**
* Pause the video playback
*
* myPlayer.pause();
*
* @return {vjs.Player} self
*/
vjs.Player.prototype.pause = function(){
this.techCall('pause');
return this;
};
/**
* Check if the player is paused
*
* var isPaused = myPlayer.paused();
* var isPlaying = !myPlayer.paused();
*
* @return {Boolean} false if the media is currently playing, or true otherwise
*/
vjs.Player.prototype.paused = function(){
// The initial state of paused should be true (in Safari it's actually false)
return (this.techGet('paused') === false) ? false : true;
};
/**
* Get or set the current time (in seconds)
*
* // get
* var whereYouAt = myPlayer.currentTime();
*
* // set
* myPlayer.currentTime(120); // 2 minutes into the video
*
* @param {Number|String=} seconds The time to seek to
* @return {Number} The time in seconds, when not setting
* @return {vjs.Player} self, when the current time is set
*/
vjs.Player.prototype.currentTime = function(seconds){
if (seconds !== undefined) {
// cache the last set value for smoother scrubbing
this.cache_.lastSetCurrentTime = seconds;
this.techCall('setCurrentTime', seconds);
// improve the accuracy of manual timeupdates
if (this.manualTimeUpdates) { this.trigger('timeupdate'); }
return this;
}
// cache last currentTime and return
// default to 0 seconds
return this.cache_.currentTime = (this.techGet('currentTime') || 0);
};
/**
* Get the length in time of the video in seconds
*
* var lengthOfVideo = myPlayer.duration();
*
* **NOTE**: The video must have started loading before the duration can be
* known, and in the case of Flash, may not be known until the video starts
* playing.
*
* @return {Number} The duration of the video in seconds
*/
vjs.Player.prototype.duration = function(seconds){
if (seconds !== undefined) {
// cache the last set value for optimiized scrubbing (esp. Flash)
this.cache_.duration = parseFloat(seconds);
return this;
}
if (this.cache_.duration === undefined) {
this.onDurationChange();
}
return this.cache_.duration;
};
// Calculates how much time is left. Not in spec, but useful.
vjs.Player.prototype.remainingTime = function(){
return this.duration() - this.currentTime();
};
// http://dev.w3.org/html5/spec/video.html#dom-media-buffered
// Buffered returns a timerange object.
// Kind of like an array of portions of the video that have been downloaded.
// So far no browsers return more than one range (portion)
/**
* Get a TimeRange object with the times of the video that have been downloaded
*
* If you just want the percent of the video that's been downloaded,
* use bufferedPercent.
*
* // Number of different ranges of time have been buffered. Usually 1.
* numberOfRanges = bufferedTimeRange.length,
*
* // Time in seconds when the first range starts. Usually 0.
* firstRangeStart = bufferedTimeRange.start(0),
*
* // Time in seconds when the first range ends
* firstRangeEnd = bufferedTimeRange.end(0),
*
* // Length in seconds of the first time range
* firstRangeLength = firstRangeEnd - firstRangeStart;
*
* @return {Object} A mock TimeRange object (following HTML spec)
*/
vjs.Player.prototype.buffered = function(){
var buffered = this.techGet('buffered'),
start = 0,
buflast = buffered.length - 1,
// Default end to 0 and store in values
end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;
if (buffered && buflast >= 0 && buffered.end(buflast) !== end) {
end = buffered.end(buflast);
// Storing values allows them be overridden by setBufferedFromProgress
this.cache_.bufferEnd = end;
}
return vjs.createTimeRange(start, end);
};
/**
* Get the percent (as a decimal) of the video that's been downloaded
*
* var howMuchIsDownloaded = myPlayer.bufferedPercent();
*
* 0 means none, 1 means all.
* (This method isn't in the HTML5 spec, but it's very convenient)
*
* @return {Number} A decimal between 0 and 1 representing the percent
*/
vjs.Player.prototype.bufferedPercent = function(){
return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
};
/**
* Get or set the current volume of the media
*
* // get
* var howLoudIsIt = myPlayer.volume();
*
* // set
* myPlayer.volume(0.5); // Set volume to half
*
* 0 is off (muted), 1.0 is all the way up, 0.5 is half way.
*
* @param {Number} percentAsDecimal The new volume as a decimal percent
* @return {Number} The current volume, when getting
* @return {vjs.Player} self, when setting
*/
vjs.Player.prototype.volume = function(percentAsDecimal){
var vol;
if (percentAsDecimal !== undefined) {
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
this.cache_.volume = vol;
this.techCall('setVolume', vol);
vjs.setLocalStorage('volume', vol);
return this;
}
// Default to 1 when returning current volume.
vol = parseFloat(this.techGet('volume'));
return (isNaN(vol)) ? 1 : vol;
};
/**
* Get the current muted state, or turn mute on or off
*
* // get
* var isVolumeMuted = myPlayer.muted();
*
* // set
* myPlayer.muted(true); // mute the volume
*
* @param {Boolean=} muted True to mute, false to unmute
* @return {Boolean} True if mute is on, false if not, when getting
* @return {vjs.Player} self, when setting mute
*/
vjs.Player.prototype.muted = function(muted){
if (muted !== undefined) {
this.techCall('setMuted', muted);
return this;
}
return this.techGet('muted') || false; // Default to false
};
// Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf)
vjs.Player.prototype.supportsFullScreen = function(){ return this.techGet('supportsFullScreen') || false; };
/**
* Increase the size of the video to full screen
*
* myPlayer.requestFullScreen();
*
* In some browsers, full screen is not supported natively, so it enters
* "full window mode", where the video fills the browser window.
* In browsers and devices that support native full screen, sometimes the
* browser's default controls will be shown, and not the Video.js custom skin.
* This includes most mobile devices (iOS, Android) and older versions of
* Safari.
*
* @return {vjs.Player} self
*/
vjs.Player.prototype.requestFullScreen = function(){
var requestFullScreen = vjs.support.requestFullScreen;
this.isFullScreen = true;
if (requestFullScreen) {
// the browser supports going fullscreen at the element level so we can
// take the controls fullscreen as well as the video
// Trigger fullscreenchange event after change
// We have to specifically add this each time, and remove
// when cancelling fullscreen. Otherwise if there's multiple
// players on a page, they would all be reacting to the same fullscreen
// events
vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(e){
this.isFullScreen = document[requestFullScreen.isFullScreen];
// If cancelling fullscreen, remove event listener.
if (this.isFullScreen === false) {
vjs.off(document, requestFullScreen.eventName, arguments.callee);
}
this.trigger('fullscreenchange');
}));
this.el_[requestFullScreen.requestFn]();
} else if (this.tech.supportsFullScreen()) {
// we can't take the video.js controls fullscreen but we can go fullscreen
// with native controls
this.techCall('enterFullScreen');
} else {
// fullscreen isn't supported so we'll just stretch the video element to
// fill the viewport
this.enterFullWindow();
this.trigger('fullscreenchange');
}
return this;
};
/**
* Return the video to its normal size after having been in full screen mode
*
* myPlayer.cancelFullScreen();
*
* @return {vjs.Player} self
*/
vjs.Player.prototype.cancelFullScreen = function(){
var requestFullScreen = vjs.support.requestFullScreen;
this.isFullScreen = false;
// Check for browser element fullscreen support
if (requestFullScreen) {
document[requestFullScreen.cancelFn]();
} else if (this.tech.supportsFullScreen()) {
this.techCall('exitFullScreen');
} else {
this.exitFullWindow();
this.trigger('fullscreenchange');
}
return this;
};
// When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
vjs.Player.prototype.enterFullWindow = function(){
this.isFullWindow = true;
// Storing original doc overflow value to return to when fullscreen is off
this.docOrigOverflow = document.documentElement.style.overflow;
// Add listener for esc key to exit fullscreen
vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey));
// Hide any scroll bars
document.documentElement.style.overflow = 'hidden';
// Apply fullscreen styles
vjs.addClass(document.body, 'vjs-full-window');
this.trigger('enterFullWindow');
};
vjs.Player.prototype.fullWindowOnEscKey = function(event){
if (event.keyCode === 27) {
if (this.isFullScreen === true) {
this.cancelFullScreen();
} else {
this.exitFullWindow();
}
}
};
vjs.Player.prototype.exitFullWindow = function(){
this.isFullWindow = false;
vjs.off(document, 'keydown', this.fullWindowOnEscKey);
// Unhide scroll bars.
document.documentElement.style.overflow = this.docOrigOverflow;
// Remove fullscreen styles
vjs.removeClass(document.body, 'vjs-full-window');
// Resize the box, controller, and poster to original sizes
// this.positionAll();
this.trigger('exitFullWindow');
};
vjs.Player.prototype.selectSource = function(sources){
// Loop through each playback technology in the options order
for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {
var techName = vjs.capitalize(j[i]),
tech = window['videojs'][techName];
// Check if the browser supports this technology
if (tech.isSupported()) {
// Loop through each source object
for (var a=0,b=sources;a<b.length;a++) {
var source = b[a];
// Check if source can be played with this technology
if (tech['canPlaySource'](source)) {
return { source: source, tech: techName };
}
}
}
}
return false;
};
/**
* The source function updates the video source
*
* There are three types of variables you can pass as the argument.
*
* **URL String**: A URL to the the video file. Use this method if you are sure
* the current playback technology (HTML5/Flash) can support the source you
* provide. Currently only MP4 files can be used in both HTML5 and Flash.
*
* myPlayer.src("http://www.example.com/path/to/video.mp4");
*
* **Source Object (or element):** A javascript object containing information
* about the source file. Use this method if you want the player to determine if
* it can support the file using the type information.
*
* myPlayer.src({ type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" });
*
* **Array of Source Objects:** To provide multiple versions of the source so
* that it can be played using HTML5 across browsers you can use an array of
* source objects. Video.js will detect which version is supported and load that
* file.
*
* myPlayer.src([
* { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" },
* { type: "video/webm", src: "http://www.example.com/path/to/video.webm" },
* { type: "video/ogg", src: "http://www.example.com/path/to/video.ogv" }
* ]);
*
* @param {String|Object|Array=} source The source URL, object, or array of sources
* @return {vjs.Player} self
*/
vjs.Player.prototype.src = function(source){
// Case: Array of source objects to choose from and pick the best to play
if (source instanceof Array) {
var sourceTech = this.selectSource(source),
techName;
if (sourceTech) {
source = sourceTech.source;
techName = sourceTech.tech;
// If this technology is already loaded, set source
if (techName == this.techName) {
this.src(source); // Passing the source object
// Otherwise load this technology with chosen source
} else {
this.loadTech(techName, source);
}
} else {
this.el_.appendChild(vjs.createEl('p', {
innerHTML: this.options()['notSupportedMessage']
}));