-
Notifications
You must be signed in to change notification settings - Fork 7.5k
/
Copy pathhtml5.js
498 lines (411 loc) · 15.7 KB
/
html5.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
/**
* @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API
*/
/**
* HTML5 Media Controller - Wrapper for HTML5 Media API
* @param {vjs.Player|Object} player
* @param {Object=} options
* @param {Function=} ready
* @constructor
*/
vjs.Html5 = vjs.MediaTechController.extend({
/** @constructor */
init: function(player, options, ready){
vjs.MediaTechController.call(this, player, options, ready);
this.setupTriggers();
var source = options['source'];
// Set the source if one is provided
// 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
// 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
// anyway so the error gets fired.
if (source && (this.el_.currentSrc !== source.src || (player.tag && player.tag.initNetworkState_ === 3))) {
this.setSource(source);
}
// Determine if native controls should be used
// Our goal should be to get the custom controls on mobile solid everywhere
// so we can remove this all together. Right now this will block custom
// controls on touch enabled laptops like the Chrome Pixel
if (vjs.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] === true) {
this.useNativeControls();
}
// Chrome and Safari both have issues with autoplay.
// In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
// In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
// This fixes both issues. Need to wait for API, so it updates displays correctly
player.ready(function(){
if (this.tag && this.options_['autoplay'] && this.paused()) {
delete this.tag['poster']; // Chrome Fix. Fixed in Chrome v16.
this.play();
}
});
this.triggerReady();
}
});
vjs.Html5.prototype.dispose = function(){
vjs.Html5.disposeMediaElement(this.el_);
vjs.MediaTechController.prototype.dispose.call(this);
};
vjs.Html5.prototype.createEl = function(){
var player = this.player_,
// If possible, reuse original tag for HTML5 playback technology element
el = player.tag,
attributes,
newEl,
clone;
// Check if this browser supports moving the element into the box.
// On the iPhone video will break if you move the element,
// So we have to create a brand new element.
if (!el || this['movingMediaElementInDOM'] === false) {
// If the original tag is still there, clone and remove it.
if (el) {
clone = el.cloneNode(false);
vjs.Html5.disposeMediaElement(el);
el = clone;
player.tag = null;
} else {
el = vjs.createEl('video');
// determine if native controls should be used
attributes = videojs.util.mergeOptions({}, player.tagAttributes);
if (!vjs.TOUCH_ENABLED || player.options()['nativeControlsForTouch'] !== true) {
delete attributes.controls;
}
vjs.setElementAttributes(el,
vjs.obj.merge(attributes, {
id:player.id() + '_html5_api',
'class':'vjs-tech'
})
);
}
// associate the player with the new tag
el['player'] = player;
vjs.insertFirst(el, player.el());
}
// Update specific tag settings, in case they were overridden
var settingsAttrs = ['autoplay','preload','loop','muted'];
for (var i = settingsAttrs.length - 1; i >= 0; i--) {
var attr = settingsAttrs[i];
var overwriteAttrs = {};
if (typeof player.options_[attr] !== 'undefined') {
overwriteAttrs[attr] = player.options_[attr];
}
vjs.setElementAttributes(el, overwriteAttrs);
}
return el;
// jenniisawesome = true;
};
// Make video events trigger player events
// May seem verbose here, but makes other APIs possible.
// Triggers removed using this.off when disposed
vjs.Html5.prototype.setupTriggers = function(){
for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {
this.on(vjs.Html5.Events[i], this.eventHandler);
}
};
vjs.Html5.prototype.eventHandler = function(evt){
// In the case of an error on the video element, set the error prop
// on the player and let the player handle triggering the event. On
// some platforms, error events fire that do not cause the error
// property on the video element to be set. See #1465 for an example.
if (evt.type == 'error' && this.error()) {
this.player().error(this.error().code);
// in some cases we pass the event directly to the player
} else {
// No need for media events to bubble up.
evt.bubbles = false;
this.player().trigger(evt);
}
};
vjs.Html5.prototype.useNativeControls = function(){
var tech, player, controlsOn, controlsOff, cleanUp;
tech = this;
player = this.player();
// If the player controls are enabled turn on the native controls
tech.setControls(player.controls());
// Update the native controls when player controls state is updated
controlsOn = function(){
tech.setControls(true);
};
controlsOff = function(){
tech.setControls(false);
};
player.on('controlsenabled', controlsOn);
player.on('controlsdisabled', controlsOff);
// Clean up when not using native controls anymore
cleanUp = function(){
player.off('controlsenabled', controlsOn);
player.off('controlsdisabled', controlsOff);
};
tech.on('dispose', cleanUp);
player.on('usingcustomcontrols', cleanUp);
// Update the state of the player to using native controls
player.usingNativeControls(true);
};
vjs.Html5.prototype.play = function(){ this.el_.play(); };
vjs.Html5.prototype.pause = function(){ this.el_.pause(); };
vjs.Html5.prototype.paused = function(){ return this.el_.paused; };
vjs.Html5.prototype.currentTime = function(){ return this.el_.currentTime; };
vjs.Html5.prototype.setCurrentTime = function(seconds){
try {
this.el_.currentTime = seconds;
} catch(e) {
vjs.log(e, 'Video is not ready. (Video.js)');
// this.warning(VideoJS.warnings.videoNotReady);
}
};
vjs.Html5.prototype.duration = function(){ return this.el_.duration || 0; };
vjs.Html5.prototype.buffered = function(){ return this.el_.buffered; };
vjs.Html5.prototype.volume = function(){ return this.el_.volume; };
vjs.Html5.prototype.setVolume = function(percentAsDecimal){ this.el_.volume = percentAsDecimal; };
vjs.Html5.prototype.muted = function(){ return this.el_.muted; };
vjs.Html5.prototype.setMuted = function(muted){ this.el_.muted = muted; };
vjs.Html5.prototype.width = function(){ return this.el_.offsetWidth; };
vjs.Html5.prototype.height = function(){ return this.el_.offsetHeight; };
vjs.Html5.prototype.supportsFullScreen = function(){
if (typeof this.el_.webkitEnterFullScreen == 'function') {
// Seems to be broken in Chromium/Chrome && Safari in Leopard
if (/Android/.test(vjs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(vjs.USER_AGENT)) {
return true;
}
}
return false;
};
vjs.Html5.prototype.enterFullScreen = function(){
var video = this.el_;
if ('webkitDisplayingFullscreen' in video) {
this.one('webkitbeginfullscreen', function() {
this.player_.isFullscreen(true);
this.one('webkitendfullscreen', function() {
this.player_.isFullscreen(false);
this.player_.trigger('fullscreenchange');
});
this.player_.trigger('fullscreenchange');
});
}
if (video.paused && video.networkState <= video.HAVE_METADATA) {
// attempt to prime the video element for programmatic access
// this isn't necessary on the desktop but shouldn't hurt
this.el_.play();
// playing and pausing synchronously during the transition to fullscreen
// can get iOS ~6.1 devices into a play/pause loop
this.setTimeout(function(){
video.pause();
video.webkitEnterFullScreen();
}, 0);
} else {
video.webkitEnterFullScreen();
}
};
vjs.Html5.prototype.exitFullScreen = function(){
this.el_.webkitExitFullScreen();
};
vjs.Html5.prototype.src = function(src) {
if (src === undefined) {
return this.el_.src;
} else {
// Setting src through `src` instead of `setSrc` will be deprecated
this.setSrc(src);
}
};
vjs.Html5.prototype.setSrc = function(src) {
this.el_.src = src;
};
vjs.Html5.prototype.load = function(){ this.el_.load(); };
vjs.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; };
vjs.Html5.prototype.poster = function(){ return this.el_.poster; };
vjs.Html5.prototype.setPoster = function(val){ this.el_.poster = val; };
vjs.Html5.prototype.preload = function(){ return this.el_.preload; };
vjs.Html5.prototype.setPreload = function(val){ this.el_.preload = val; };
vjs.Html5.prototype.autoplay = function(){ return this.el_.autoplay; };
vjs.Html5.prototype.setAutoplay = function(val){ this.el_.autoplay = val; };
vjs.Html5.prototype.controls = function(){ return this.el_.controls; };
vjs.Html5.prototype.setControls = function(val){ this.el_.controls = !!val; };
vjs.Html5.prototype.loop = function(){ return this.el_.loop; };
vjs.Html5.prototype.setLoop = function(val){ this.el_.loop = val; };
vjs.Html5.prototype.error = function(){ return this.el_.error; };
vjs.Html5.prototype.seeking = function(){ return this.el_.seeking; };
vjs.Html5.prototype.ended = function(){ return this.el_.ended; };
vjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };
vjs.Html5.prototype.playbackRate = function(){ return this.el_.playbackRate; };
vjs.Html5.prototype.setPlaybackRate = function(val){ this.el_.playbackRate = val; };
vjs.Html5.prototype.networkState = function(){ return this.el_.networkState; };
/**
* Check if HTML5 video is supported by this browser/device
* @return {Boolean}
*/
vjs.Html5.isSupported = function(){
// IE9 with no Media Player is a LIAR! (#984)
try {
vjs.TEST_VID['volume'] = 0.5;
} catch (e) {
return false;
}
return !!vjs.TEST_VID.canPlayType;
};
// Add Source Handler pattern functions to this tech
vjs.MediaTechController.withSourceHandlers(vjs.Html5);
/**
* The default native source handler.
* This simply passes the source to the video element. Nothing fancy.
* @param {Object} source The source object
* @param {vjs.Html5} tech The instance of the HTML5 tech
*/
vjs.Html5.nativeSourceHandler = {};
/**
* Check if the video element can handle the source natively
* @param {Object} source The source object
* @return {String} 'probably', 'maybe', or '' (empty string)
*/
vjs.Html5.nativeSourceHandler.canHandleSource = function(source){
var match, ext;
function canPlayType(type){
// IE9 on Windows 7 without MediaPlayer throws an error here
// https://github.com/videojs/video.js/issues/519
try {
return vjs.TEST_VID.canPlayType(type);
} catch(e) {
return '';
}
}
// If a type was provided we should rely on that
if (source.type) {
return canPlayType(source.type);
} else if (source.src) {
// If no type, fall back to checking 'video/[EXTENSION]'
match = source.src.match(/\.([^.\/\?]+)(\?[^\/]+)?$/i);
ext = match && match[1];
return canPlayType('video/'+ext);
}
return '';
};
/**
* Pass the source to the video element
* Adaptive source handlers will have more complicated workflows before passing
* video data to the video element
* @param {Object} source The source object
* @param {vjs.Html5} tech The instance of the Html5 tech
*/
vjs.Html5.nativeSourceHandler.handleSource = function(source, tech){
tech.setSrc(source.src);
};
/**
* Clean up the source handler when disposing the player or switching sources..
* (no cleanup is needed when supporting the format natively)
*/
vjs.Html5.nativeSourceHandler.dispose = function(){};
// Register the native source handler
vjs.Html5.registerSourceHandler(vjs.Html5.nativeSourceHandler);
/**
* Check if the volume can be changed in this browser/device.
* Volume cannot be changed in a lot of mobile devices.
* Specifically, it can't be changed from 1 on iOS.
* @return {Boolean}
*/
vjs.Html5.canControlVolume = function(){
var volume = vjs.TEST_VID.volume;
vjs.TEST_VID.volume = (volume / 2) + 0.1;
return volume !== vjs.TEST_VID.volume;
};
/**
* Check if playbackRate is supported in this browser/device.
* @return {[type]} [description]
*/
vjs.Html5.canControlPlaybackRate = function(){
var playbackRate = vjs.TEST_VID.playbackRate;
vjs.TEST_VID.playbackRate = (playbackRate / 2) + 0.1;
return playbackRate !== vjs.TEST_VID.playbackRate;
};
/**
* Set the tech's volume control support status
* @type {Boolean}
*/
vjs.Html5.prototype['featuresVolumeControl'] = vjs.Html5.canControlVolume();
/**
* Set the tech's playbackRate support status
* @type {Boolean}
*/
vjs.Html5.prototype['featuresPlaybackRate'] = vjs.Html5.canControlPlaybackRate();
/**
* Set the tech's status on moving the video element.
* In iOS, if you move a video element in the DOM, it breaks video playback.
* @type {Boolean}
*/
vjs.Html5.prototype['movingMediaElementInDOM'] = !vjs.IS_IOS;
/**
* Set the the tech's fullscreen resize support status.
* HTML video is able to automatically resize when going to fullscreen.
* (No longer appears to be used. Can probably be removed.)
*/
vjs.Html5.prototype['featuresFullscreenResize'] = true;
/**
* Set the tech's progress event support status
* (this disables the manual progress events of the MediaTechController)
*/
vjs.Html5.prototype['featuresProgressEvents'] = true;
// HTML5 Feature detection and Device Fixes --------------------------------- //
(function() {
var canPlayType,
mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i,
mp4RE = /^video\/mp4/i;
vjs.Html5.patchCanPlayType = function() {
// Android 4.0 and above can play HLS to some extent but it reports being unable to do so
if (vjs.ANDROID_VERSION >= 4.0) {
if (!canPlayType) {
canPlayType = vjs.TEST_VID.constructor.prototype.canPlayType;
}
vjs.TEST_VID.constructor.prototype.canPlayType = function(type) {
if (type && mpegurlRE.test(type)) {
return 'maybe';
}
return canPlayType.call(this, type);
};
}
// Override Android 2.2 and less canPlayType method which is broken
if (vjs.IS_OLD_ANDROID) {
if (!canPlayType) {
canPlayType = vjs.TEST_VID.constructor.prototype.canPlayType;
}
vjs.TEST_VID.constructor.prototype.canPlayType = function(type){
if (type && mp4RE.test(type)) {
return 'maybe';
}
return canPlayType.call(this, type);
};
}
};
vjs.Html5.unpatchCanPlayType = function() {
var r = vjs.TEST_VID.constructor.prototype.canPlayType;
vjs.TEST_VID.constructor.prototype.canPlayType = canPlayType;
canPlayType = null;
return r;
};
// by default, patch the video element
vjs.Html5.patchCanPlayType();
})();
// List of all HTML5 events (various uses).
vjs.Html5.Events = 'loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange'.split(',');
vjs.Html5.disposeMediaElement = function(el){
if (!el) { return; }
el['player'] = null;
if (el.parentNode) {
el.parentNode.removeChild(el);
}
// remove any child track or source nodes to prevent their loading
while(el.hasChildNodes()) {
el.removeChild(el.firstChild);
}
// remove any src reference. not setting `src=''` because that causes a warning
// in firefox
el.removeAttribute('src');
// force the media element to update its loading state by calling load()
// however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
if (typeof el.load === 'function') {
// wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
(function() {
try {
el.load();
} catch (e) {
// not supported
}
})();
}
};