Mercurial > hg > env-test-daniele
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('&') + '" />' + | |
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; |