comparison johndyer-mediaelement-13fa20a/src/js/me-shim.js @ 0:032bc65ebafc

added core components
author George Fazekas <gyorgy.fazekas@eecs.qmul.ac.uk>
date Wed, 06 Mar 2013 15:45:48 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:032bc65ebafc
1
2 // Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties
3 mejs.MediaPluginBridge = {
4
5 pluginMediaElements:{},
6 htmlMediaElements:{},
7
8 registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) {
9 this.pluginMediaElements[id] = pluginMediaElement;
10 this.htmlMediaElements[id] = htmlMediaElement;
11 },
12
13 // when Flash/Silverlight is ready, it calls out to this method
14 initPlugin: function (id) {
15
16 var pluginMediaElement = this.pluginMediaElements[id],
17 htmlMediaElement = this.htmlMediaElements[id];
18
19 // find the javascript bridge
20 switch (pluginMediaElement.pluginType) {
21 case "flash":
22 pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id);
23 break;
24 case "silverlight":
25 pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id);
26 pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS;
27 break;
28 }
29
30 if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) {
31 pluginMediaElement.success(pluginMediaElement, htmlMediaElement);
32 }
33 },
34
35 // receives events from Flash/Silverlight and sends them out as HTML5 media events
36 // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html
37 fireEvent: function (id, eventName, values) {
38
39 var
40 e,
41 i,
42 bufferedTime,
43 pluginMediaElement = this.pluginMediaElements[id];
44
45 pluginMediaElement.ended = false;
46 pluginMediaElement.paused = true;
47
48 // fake event object to mimic real HTML media event.
49 e = {
50 type: eventName,
51 target: pluginMediaElement
52 };
53
54 // attach all values to element and event object
55 for (i in values) {
56 pluginMediaElement[i] = values[i];
57 e[i] = values[i];
58 }
59
60 // fake the newer W3C buffered TimeRange (loaded and total have been removed)
61 bufferedTime = values.bufferedTime || 0;
62
63 e.target.buffered = e.buffered = {
64 start: function(index) {
65 return 0;
66 },
67 end: function (index) {
68 return bufferedTime;
69 },
70 length: 1
71 };
72
73 pluginMediaElement.dispatchEvent(e.type, e);
74 }
75 };
76
77 /*
78 Default options
79 */
80 mejs.MediaElementDefaults = {
81 // allows testing on HTML5, flash, silverlight
82 // auto: attempts to detect what the browser can do
83 // native: forces HTML5 playback
84 // shim: disallows HTML5, will attempt either Flash or Silverlight
85 // none: forces fallback view
86 mode: 'auto',
87 // remove or reorder to change plugin priority and availability
88 plugins: ['flash','silverlight'],
89 // shows debug errors on screen
90 enablePluginDebug: false,
91 // overrides the type specified, useful for dynamic instantiation
92 type: '',
93 // path to Flash and Silverlight plugins
94 pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']),
95 // name of flash file
96 flashName: 'flashmediaelement.swf',
97 // turns on the smoothing filter in Flash
98 enablePluginSmoothing: false,
99 // name of silverlight file
100 silverlightName: 'silverlightmediaelement.xap',
101 // default if the <video width> is not specified
102 defaultVideoWidth: 480,
103 // default if the <video height> is not specified
104 defaultVideoHeight: 270,
105 // overrides <video width>
106 pluginWidth: -1,
107 // overrides <video height>
108 pluginHeight: -1,
109 // rate in milliseconds for Flash and Silverlight to fire the timeupdate event
110 // larger number is less accurate, but less strain on plugin->JavaScript bridge
111 timerRate: 250,
112 success: function () { },
113 error: function () { }
114 };
115
116 /*
117 Determines if a browser supports the <video> or <audio> element
118 and returns either the native element or a Flash/Silverlight version that
119 mimics HTML5 MediaElement
120 */
121 mejs.MediaElement = function (el, o) {
122 return mejs.HtmlMediaElementShim.create(el,o);
123 };
124
125 mejs.HtmlMediaElementShim = {
126
127 create: function(el, o) {
128 var
129 options = mejs.MediaElementDefaults,
130 htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el,
131 tagName = htmlMediaElement.tagName.toLowerCase(),
132 isMediaTag = (tagName == 'audio' || tagName == 'video'),
133 src = htmlMediaElement.getAttribute('src'),
134 poster = htmlMediaElement.getAttribute('poster'),
135 autoplay = htmlMediaElement.getAttribute('autoplay'),
136 preload = htmlMediaElement.getAttribute('preload'),
137 controls = htmlMediaElement.getAttribute('controls'),
138 playback,
139 prop;
140
141 // extend options
142 for (prop in o) {
143 options[prop] = o[prop];
144 }
145
146
147 // is this a true HTML5 media element
148 if (isMediaTag) {
149 isVideo = (htmlMediaElement.tagName.toLowerCase() == 'video');
150 } else {
151 // fake source from <a href=""></a>
152 src = htmlMediaElement.getAttribute('href');
153 }
154
155 // clean up attributes
156 src = (src == 'undefined' || src == '' || src === null) ? null : src;
157 poster = (typeof poster == 'undefined' || poster === null) ? '' : poster;
158 preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload;
159 autoplay = !(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false');
160 controls = !(typeof controls == 'undefined' || controls === null || controls === 'false');
161
162 // test for HTML5 and plugin capabilities
163 playback = this.determinePlayback(htmlMediaElement, options, mejs.MediaFeatures.supportsMediaTag, isMediaTag, src);
164
165 if (playback.method == 'native') {
166 // second fix for android
167 if (mejs.MediaFeatures.isBustedAndroid) {
168 htmlMediaElement.src = playback.url;
169 htmlMediaElement.addEventListener('click', function() {
170 htmlMediaElement.play();
171 }, true);
172 }
173
174 // add methods to native HTMLMediaElement
175 return this.updateNative( playback.htmlMediaElement, options, autoplay, preload, playback);
176 } else if (playback.method !== '') {
177 // create plugin to mimic HTMLMediaElement
178 return this.createPlugin( htmlMediaElement, options, playback.method, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster, autoplay, preload, controls);
179 } else {
180 // boo, no HTML5, no Flash, no Silverlight.
181 this.createErrorMessage( htmlMediaElement, options, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster );
182 }
183 },
184
185 videoRegExp: /(mp4|m4v|ogg|ogv|webm|flv|wmv|mpeg)/gi,
186
187 determinePlayback: function(htmlMediaElement, options, supportsMediaTag, isMediaTag, src) {
188 var
189 mediaFiles = [],
190 i,
191 j,
192 k,
193 l,
194 n,
195 type,
196 result = { method: '', url: '', htmlMediaElement: htmlMediaElement, isVideo: (htmlMediaElement.tagName.toLowerCase() != 'audio')},
197 pluginName,
198 pluginVersions,
199 pluginInfo,
200 dummy;
201
202 // STEP 1: Get URL and type from <video src> or <source src>
203
204 // supplied type overrides <video type> and <source type>
205 if (typeof options.type != 'undefined' && options.type !== '') {
206
207 // accept either string or array of types
208 if (typeof options.type == 'string') {
209 mediaFiles.push({type:options.type, url:src});
210 } else {
211
212 for (i=0; i<options.type.length; i++) {
213 mediaFiles.push({type:options.type[i], url:src});
214 }
215 }
216
217 // test for src attribute first
218 } else if (src !== null) {
219 type = this.formatType(src, htmlMediaElement.getAttribute('type'));
220 mediaFiles.push({type:type, url:src});
221
222 // then test for <source> elements
223 } else {
224 // test <source> types to see if they are usable
225 for (i = 0; i < htmlMediaElement.childNodes.length; i++) {
226 n = htmlMediaElement.childNodes[i];
227 if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') {
228 src = n.getAttribute('src');
229 type = this.formatType(src, n.getAttribute('type'));
230 mediaFiles.push({type:type, url:src});
231 }
232 }
233 }
234
235 // STEP 2: Test for playback method
236
237 // special case for Android which sadly doesn't implement the canPlayType function (always returns '')
238 if (mejs.MediaFeatures.isBustedAndroid) {
239 htmlMediaElement.canPlayType = function(type) {
240 return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : '';
241 };
242 }
243
244
245 // test for native playback first
246 if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'native')) {
247
248 if (!isMediaTag) {
249 var tagName = 'video';
250 if (mediaFiles.length > 0 && mediaFiles[0].url !== null && this.getTypeFromFile(mediaFiles[0].url).indexOf('audio') > -1) {
251 tagName = 'audio';
252 result.isVideo = false;
253 }
254
255 // create a real HTML5 Media Element
256 dummy = document.createElement(tagName);
257 htmlMediaElement.parentNode.insertBefore(dummy, htmlMediaElement);
258 htmlMediaElement.style.display = 'none';
259
260 // use this one from now on
261 result.htmlMediaElement = htmlMediaElement = dummy;
262 }
263
264 for (i=0; i<mediaFiles.length; i++) {
265 // normal check
266 if (htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== ''
267 // special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg')
268 || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== '') {
269 result.method = 'native';
270 result.url = mediaFiles[i].url;
271 break;
272 }
273 }
274
275 if (result.method === 'native') {
276 return result;
277 }
278 }
279
280 // if native playback didn't work, then test plugins
281 if (options.mode === 'auto' || options.mode === 'shim') {
282 for (i=0; i<mediaFiles.length; i++) {
283 type = mediaFiles[i].type;
284
285 // test all plugins in order of preference [silverlight, flash]
286 for (j=0; j<options.plugins.length; j++) {
287
288 pluginName = options.plugins[j];
289
290 // test version of plugin (for future features)
291 pluginVersions = mejs.plugins[pluginName];
292 for (k=0; k<pluginVersions.length; k++) {
293 pluginInfo = pluginVersions[k];
294
295 // test if user has the correct plugin version
296 if (mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) {
297
298 // test for plugin playback types
299 for (l=0; l<pluginInfo.types.length; l++) {
300 // find plugin that can play the type
301 if (type == pluginInfo.types[l]) {
302 result.method = pluginName;
303 result.url = mediaFiles[i].url;
304 return result;
305 }
306 }
307 }
308 }
309 }
310 }
311 }
312
313 // what if there's nothing to play? just grab the first available
314 if (result.method === '') {
315 result.url = mediaFiles[0].url;
316 }
317
318 return result;
319 },
320
321 formatType: function(url, type) {
322 var ext;
323
324 // if no type is supplied, fake it with the extension
325 if (url && !type) {
326 return this.getTypeFromFile(url);
327 } else {
328 // only return the mime part of the type in case the attribute contains the codec
329 // see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element
330 // `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4`
331
332 if (type && ~type.indexOf(';')) {
333 return type.substr(0, type.indexOf(';'));
334 } else {
335 return type;
336 }
337 }
338 },
339
340 getTypeFromFile: function(url) {
341 var ext = url.substring(url.lastIndexOf('.') + 1);
342 return (this.videoRegExp.test(ext) ? 'video' : 'audio') + '/' + ext;
343 },
344
345 createErrorMessage: function(htmlMediaElement, options, downloadUrl, poster) {
346 var errorContainer = document.createElement('div');
347 errorContainer.className = 'me-cannotplay';
348
349 try {
350 errorContainer.style.width = htmlMediaElement.width + 'px';
351 errorContainer.style.height = htmlMediaElement.height + 'px';
352 } catch (e) {}
353
354 errorContainer.innerHTML = (poster !== '') ?
355 '<a href="' + downloadUrl + '"><img src="' + poster + '" /></a>' :
356 '<a href="' + downloadUrl + '"><span>Download File</span></a>';
357
358 htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement);
359 htmlMediaElement.style.display = 'none';
360
361 options.error(htmlMediaElement);
362 },
363
364 createPlugin:function(htmlMediaElement, options, isVideo, pluginType, mediaUrl, poster, autoplay, preload, controls) {
365 var width = 1,
366 height = 1,
367 pluginid = 'me_' + pluginType + '_' + (mejs.meIndex++),
368 pluginMediaElement = new mejs.PluginMediaElement(pluginid, pluginType, mediaUrl),
369 container = document.createElement('div'),
370 specialIEContainer,
371 node,
372 initVars;
373
374 // check for placement inside a <p> tag (sometimes WYSIWYG editors do this)
375 node = htmlMediaElement.parentNode;
376 while (node !== null && node.tagName.toLowerCase() != 'body') {
377 if (node.parentNode.tagName.toLowerCase() == 'p') {
378 node.parentNode.parentNode.insertBefore(node, node.parentNode);
379 break;
380 }
381 node = node.parentNode;
382 }
383
384 if (isVideo) {
385 width = (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth;
386 height = (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight;
387 } else {
388 if (options.enablePluginDebug) {
389 width = 320;
390 height = 240;
391 }
392 }
393
394 // register plugin
395 pluginMediaElement.success = options.success;
396 mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement);
397
398 // add container (must be added to DOM before inserting HTML for IE)
399 container.className = 'me-plugin';
400 htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement);
401
402 // flash/silverlight vars
403 initVars = [
404 'id=' + pluginid,
405 'isvideo=' + ((isVideo) ? "true" : "false"),
406 'autoplay=' + ((autoplay) ? "true" : "false"),
407 'preload=' + preload,
408 'width=' + width,
409 'startvolume=' + options.startVolume,
410 'timerrate=' + options.timerRate,
411 'height=' + height];
412
413 if (mediaUrl !== null) {
414 if (pluginType == 'flash') {
415 initVars.push('file=' + mejs.Utility.encodeUrl(mediaUrl));
416 } else {
417 initVars.push('file=' + mediaUrl);
418 }
419 }
420 if (options.enablePluginDebug) {
421 initVars.push('debug=true');
422 }
423 if (options.enablePluginSmoothing) {
424 initVars.push('smoothing=true');
425 }
426 if (controls) {
427 initVars.push('controls=true'); // shows controls in the plugin if desired
428 }
429
430 switch (pluginType) {
431 case 'silverlight':
432 container.innerHTML =
433 '<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '">' +
434 '<param name="initParams" value="' + initVars.join(',') + '" />' +
435 '<param name="windowless" value="true" />' +
436 '<param name="background" value="black" />' +
437 '<param name="minRuntimeVersion" value="3.0.0.0" />' +
438 '<param name="autoUpgrade" value="true" />' +
439 '<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' +
440 '</object>';
441 break;
442
443 case 'flash':
444
445 if (mejs.MediaFeatures.isIE) {
446 specialIEContainer = document.createElement('div');
447 container.appendChild(specialIEContainer);
448 specialIEContainer.outerHTML =
449 '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' +
450 'id="' + pluginid + '" width="' + width + '" height="' + height + '">' +
451 '<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' +
452 '<param name="flashvars" value="' + initVars.join('&amp;') + '" />' +
453 '<param name="quality" value="high" />' +
454 '<param name="bgcolor" value="#000000" />' +
455 '<param name="wmode" value="transparent" />' +
456 '<param name="allowScriptAccess" value="always" />' +
457 '<param name="allowFullScreen" value="true" />' +
458 '</object>';
459
460 } else {
461
462 container.innerHTML =
463 '<embed id="' + pluginid + '" name="' + pluginid + '" ' +
464 'play="true" ' +
465 'loop="false" ' +
466 'quality="high" ' +
467 'bgcolor="#000000" ' +
468 'wmode="transparent" ' +
469 'allowScriptAccess="always" ' +
470 'allowFullScreen="true" ' +
471 'type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" ' +
472 'src="' + options.pluginPath + options.flashName + '" ' +
473 'flashvars="' + initVars.join('&') + '" ' +
474 'width="' + width + '" ' +
475 'height="' + height + '"></embed>';
476 }
477 break;
478 }
479 // hide original element
480 htmlMediaElement.style.display = 'none';
481
482 // FYI: options.success will be fired by the MediaPluginBridge
483
484 return pluginMediaElement;
485 },
486
487 updateNative: function(htmlMediaElement, options, autoplay, preload, playback) {
488 // add methods to video object to bring it into parity with Flash Object
489 for (var m in mejs.HtmlMediaElement) {
490 htmlMediaElement[m] = mejs.HtmlMediaElement[m];
491 }
492
493 /*
494 Chrome now supports preload="none"
495 if (mejs.MediaFeatures.isChrome) {
496
497 // special case to enforce preload attribute (Chrome doesn't respect this)
498 if (preload === 'none' && !autoplay) {
499
500 // forces the browser to stop loading (note: fails in IE9)
501 htmlMediaElement.src = '';
502 htmlMediaElement.load();
503 htmlMediaElement.canceledPreload = true;
504
505 htmlMediaElement.addEventListener('play',function() {
506 if (htmlMediaElement.canceledPreload) {
507 htmlMediaElement.src = playback.url;
508 htmlMediaElement.load();
509 htmlMediaElement.play();
510 htmlMediaElement.canceledPreload = false;
511 }
512 }, false);
513 // for some reason Chrome forgets how to autoplay sometimes.
514 } else if (autoplay) {
515 htmlMediaElement.load();
516 htmlMediaElement.play();
517 }
518 }
519 */
520
521 // fire success code
522 options.success(htmlMediaElement, htmlMediaElement);
523
524 return htmlMediaElement;
525 }
526 };
527
528 window.mejs = mejs;
529 window.MediaElement = mejs.MediaElement;