Mercurial > hg > env-test-daniele
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/johndyer-mediaelement-13fa20a/src/js/me-shim.js Wed Mar 06 15:45:48 2013 +0000 @@ -0,0 +1,529 @@ + +// Handles calls from Flash/Silverlight and reports them as native <video/audio> events and properties +mejs.MediaPluginBridge = { + + pluginMediaElements:{}, + htmlMediaElements:{}, + + registerPluginElement: function (id, pluginMediaElement, htmlMediaElement) { + this.pluginMediaElements[id] = pluginMediaElement; + this.htmlMediaElements[id] = htmlMediaElement; + }, + + // when Flash/Silverlight is ready, it calls out to this method + initPlugin: function (id) { + + var pluginMediaElement = this.pluginMediaElements[id], + htmlMediaElement = this.htmlMediaElements[id]; + + // find the javascript bridge + switch (pluginMediaElement.pluginType) { + case "flash": + pluginMediaElement.pluginElement = pluginMediaElement.pluginApi = document.getElementById(id); + break; + case "silverlight": + pluginMediaElement.pluginElement = document.getElementById(pluginMediaElement.id); + pluginMediaElement.pluginApi = pluginMediaElement.pluginElement.Content.MediaElementJS; + break; + } + + if (pluginMediaElement.pluginApi != null && pluginMediaElement.success) { + pluginMediaElement.success(pluginMediaElement, htmlMediaElement); + } + }, + + // receives events from Flash/Silverlight and sends them out as HTML5 media events + // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html + fireEvent: function (id, eventName, values) { + + var + e, + i, + bufferedTime, + pluginMediaElement = this.pluginMediaElements[id]; + + pluginMediaElement.ended = false; + pluginMediaElement.paused = true; + + // fake event object to mimic real HTML media event. + e = { + type: eventName, + target: pluginMediaElement + }; + + // attach all values to element and event object + for (i in values) { + pluginMediaElement[i] = values[i]; + e[i] = values[i]; + } + + // fake the newer W3C buffered TimeRange (loaded and total have been removed) + bufferedTime = values.bufferedTime || 0; + + e.target.buffered = e.buffered = { + start: function(index) { + return 0; + }, + end: function (index) { + return bufferedTime; + }, + length: 1 + }; + + pluginMediaElement.dispatchEvent(e.type, e); + } +}; + +/* +Default options +*/ +mejs.MediaElementDefaults = { + // allows testing on HTML5, flash, silverlight + // auto: attempts to detect what the browser can do + // native: forces HTML5 playback + // shim: disallows HTML5, will attempt either Flash or Silverlight + // none: forces fallback view + mode: 'auto', + // remove or reorder to change plugin priority and availability + plugins: ['flash','silverlight'], + // shows debug errors on screen + enablePluginDebug: false, + // overrides the type specified, useful for dynamic instantiation + type: '', + // path to Flash and Silverlight plugins + pluginPath: mejs.Utility.getScriptPath(['mediaelement.js','mediaelement.min.js','mediaelement-and-player.js','mediaelement-and-player.min.js']), + // name of flash file + flashName: 'flashmediaelement.swf', + // turns on the smoothing filter in Flash + enablePluginSmoothing: false, + // name of silverlight file + silverlightName: 'silverlightmediaelement.xap', + // default if the <video width> is not specified + defaultVideoWidth: 480, + // default if the <video height> is not specified + defaultVideoHeight: 270, + // overrides <video width> + pluginWidth: -1, + // overrides <video height> + pluginHeight: -1, + // rate in milliseconds for Flash and Silverlight to fire the timeupdate event + // larger number is less accurate, but less strain on plugin->JavaScript bridge + timerRate: 250, + success: function () { }, + error: function () { } +}; + +/* +Determines if a browser supports the <video> or <audio> element +and returns either the native element or a Flash/Silverlight version that +mimics HTML5 MediaElement +*/ +mejs.MediaElement = function (el, o) { + return mejs.HtmlMediaElementShim.create(el,o); +}; + +mejs.HtmlMediaElementShim = { + + create: function(el, o) { + var + options = mejs.MediaElementDefaults, + htmlMediaElement = (typeof(el) == 'string') ? document.getElementById(el) : el, + tagName = htmlMediaElement.tagName.toLowerCase(), + isMediaTag = (tagName == 'audio' || tagName == 'video'), + src = htmlMediaElement.getAttribute('src'), + poster = htmlMediaElement.getAttribute('poster'), + autoplay = htmlMediaElement.getAttribute('autoplay'), + preload = htmlMediaElement.getAttribute('preload'), + controls = htmlMediaElement.getAttribute('controls'), + playback, + prop; + + // extend options + for (prop in o) { + options[prop] = o[prop]; + } + + + // is this a true HTML5 media element + if (isMediaTag) { + isVideo = (htmlMediaElement.tagName.toLowerCase() == 'video'); + } else { + // fake source from <a href=""></a> + src = htmlMediaElement.getAttribute('href'); + } + + // clean up attributes + src = (src == 'undefined' || src == '' || src === null) ? null : src; + poster = (typeof poster == 'undefined' || poster === null) ? '' : poster; + preload = (typeof preload == 'undefined' || preload === null || preload === 'false') ? 'none' : preload; + autoplay = !(typeof autoplay == 'undefined' || autoplay === null || autoplay === 'false'); + controls = !(typeof controls == 'undefined' || controls === null || controls === 'false'); + + // test for HTML5 and plugin capabilities + playback = this.determinePlayback(htmlMediaElement, options, mejs.MediaFeatures.supportsMediaTag, isMediaTag, src); + + if (playback.method == 'native') { + // second fix for android + if (mejs.MediaFeatures.isBustedAndroid) { + htmlMediaElement.src = playback.url; + htmlMediaElement.addEventListener('click', function() { + htmlMediaElement.play(); + }, true); + } + + // add methods to native HTMLMediaElement + return this.updateNative( playback.htmlMediaElement, options, autoplay, preload, playback); + } else if (playback.method !== '') { + // create plugin to mimic HTMLMediaElement + return this.createPlugin( htmlMediaElement, options, playback.method, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster, autoplay, preload, controls); + } else { + // boo, no HTML5, no Flash, no Silverlight. + this.createErrorMessage( htmlMediaElement, options, (playback.url !== null) ? mejs.Utility.absolutizeUrl(playback.url) : '', poster ); + } + }, + + videoRegExp: /(mp4|m4v|ogg|ogv|webm|flv|wmv|mpeg)/gi, + + determinePlayback: function(htmlMediaElement, options, supportsMediaTag, isMediaTag, src) { + var + mediaFiles = [], + i, + j, + k, + l, + n, + type, + result = { method: '', url: '', htmlMediaElement: htmlMediaElement, isVideo: (htmlMediaElement.tagName.toLowerCase() != 'audio')}, + pluginName, + pluginVersions, + pluginInfo, + dummy; + + // STEP 1: Get URL and type from <video src> or <source src> + + // supplied type overrides <video type> and <source type> + if (typeof options.type != 'undefined' && options.type !== '') { + + // accept either string or array of types + if (typeof options.type == 'string') { + mediaFiles.push({type:options.type, url:src}); + } else { + + for (i=0; i<options.type.length; i++) { + mediaFiles.push({type:options.type[i], url:src}); + } + } + + // test for src attribute first + } else if (src !== null) { + type = this.formatType(src, htmlMediaElement.getAttribute('type')); + mediaFiles.push({type:type, url:src}); + + // then test for <source> elements + } else { + // test <source> types to see if they are usable + for (i = 0; i < htmlMediaElement.childNodes.length; i++) { + n = htmlMediaElement.childNodes[i]; + if (n.nodeType == 1 && n.tagName.toLowerCase() == 'source') { + src = n.getAttribute('src'); + type = this.formatType(src, n.getAttribute('type')); + mediaFiles.push({type:type, url:src}); + } + } + } + + // STEP 2: Test for playback method + + // special case for Android which sadly doesn't implement the canPlayType function (always returns '') + if (mejs.MediaFeatures.isBustedAndroid) { + htmlMediaElement.canPlayType = function(type) { + return (type.match(/video\/(mp4|m4v)/gi) !== null) ? 'maybe' : ''; + }; + } + + + // test for native playback first + if (supportsMediaTag && (options.mode === 'auto' || options.mode === 'native')) { + + if (!isMediaTag) { + var tagName = 'video'; + if (mediaFiles.length > 0 && mediaFiles[0].url !== null && this.getTypeFromFile(mediaFiles[0].url).indexOf('audio') > -1) { + tagName = 'audio'; + result.isVideo = false; + } + + // create a real HTML5 Media Element + dummy = document.createElement(tagName); + htmlMediaElement.parentNode.insertBefore(dummy, htmlMediaElement); + htmlMediaElement.style.display = 'none'; + + // use this one from now on + result.htmlMediaElement = htmlMediaElement = dummy; + } + + for (i=0; i<mediaFiles.length; i++) { + // normal check + if (htmlMediaElement.canPlayType(mediaFiles[i].type).replace(/no/, '') !== '' + // special case for Mac/Safari 5.0.3 which answers '' to canPlayType('audio/mp3') but 'maybe' to canPlayType('audio/mpeg') + || htmlMediaElement.canPlayType(mediaFiles[i].type.replace(/mp3/,'mpeg')).replace(/no/, '') !== '') { + result.method = 'native'; + result.url = mediaFiles[i].url; + break; + } + } + + if (result.method === 'native') { + return result; + } + } + + // if native playback didn't work, then test plugins + if (options.mode === 'auto' || options.mode === 'shim') { + for (i=0; i<mediaFiles.length; i++) { + type = mediaFiles[i].type; + + // test all plugins in order of preference [silverlight, flash] + for (j=0; j<options.plugins.length; j++) { + + pluginName = options.plugins[j]; + + // test version of plugin (for future features) + pluginVersions = mejs.plugins[pluginName]; + for (k=0; k<pluginVersions.length; k++) { + pluginInfo = pluginVersions[k]; + + // test if user has the correct plugin version + if (mejs.PluginDetector.hasPluginVersion(pluginName, pluginInfo.version)) { + + // test for plugin playback types + for (l=0; l<pluginInfo.types.length; l++) { + // find plugin that can play the type + if (type == pluginInfo.types[l]) { + result.method = pluginName; + result.url = mediaFiles[i].url; + return result; + } + } + } + } + } + } + } + + // what if there's nothing to play? just grab the first available + if (result.method === '') { + result.url = mediaFiles[0].url; + } + + return result; + }, + + formatType: function(url, type) { + var ext; + + // if no type is supplied, fake it with the extension + if (url && !type) { + return this.getTypeFromFile(url); + } else { + // only return the mime part of the type in case the attribute contains the codec + // see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#the-source-element + // `video/mp4; codecs="avc1.42E01E, mp4a.40.2"` becomes `video/mp4` + + if (type && ~type.indexOf(';')) { + return type.substr(0, type.indexOf(';')); + } else { + return type; + } + } + }, + + getTypeFromFile: function(url) { + var ext = url.substring(url.lastIndexOf('.') + 1); + return (this.videoRegExp.test(ext) ? 'video' : 'audio') + '/' + ext; + }, + + createErrorMessage: function(htmlMediaElement, options, downloadUrl, poster) { + var errorContainer = document.createElement('div'); + errorContainer.className = 'me-cannotplay'; + + try { + errorContainer.style.width = htmlMediaElement.width + 'px'; + errorContainer.style.height = htmlMediaElement.height + 'px'; + } catch (e) {} + + errorContainer.innerHTML = (poster !== '') ? + '<a href="' + downloadUrl + '"><img src="' + poster + '" /></a>' : + '<a href="' + downloadUrl + '"><span>Download File</span></a>'; + + htmlMediaElement.parentNode.insertBefore(errorContainer, htmlMediaElement); + htmlMediaElement.style.display = 'none'; + + options.error(htmlMediaElement); + }, + + createPlugin:function(htmlMediaElement, options, isVideo, pluginType, mediaUrl, poster, autoplay, preload, controls) { + var width = 1, + height = 1, + pluginid = 'me_' + pluginType + '_' + (mejs.meIndex++), + pluginMediaElement = new mejs.PluginMediaElement(pluginid, pluginType, mediaUrl), + container = document.createElement('div'), + specialIEContainer, + node, + initVars; + + // check for placement inside a <p> tag (sometimes WYSIWYG editors do this) + node = htmlMediaElement.parentNode; + while (node !== null && node.tagName.toLowerCase() != 'body') { + if (node.parentNode.tagName.toLowerCase() == 'p') { + node.parentNode.parentNode.insertBefore(node, node.parentNode); + break; + } + node = node.parentNode; + } + + if (isVideo) { + width = (options.videoWidth > 0) ? options.videoWidth : (htmlMediaElement.getAttribute('width') !== null) ? htmlMediaElement.getAttribute('width') : options.defaultVideoWidth; + height = (options.videoHeight > 0) ? options.videoHeight : (htmlMediaElement.getAttribute('height') !== null) ? htmlMediaElement.getAttribute('height') : options.defaultVideoHeight; + } else { + if (options.enablePluginDebug) { + width = 320; + height = 240; + } + } + + // register plugin + pluginMediaElement.success = options.success; + mejs.MediaPluginBridge.registerPluginElement(pluginid, pluginMediaElement, htmlMediaElement); + + // add container (must be added to DOM before inserting HTML for IE) + container.className = 'me-plugin'; + htmlMediaElement.parentNode.insertBefore(container, htmlMediaElement); + + // flash/silverlight vars + initVars = [ + 'id=' + pluginid, + 'isvideo=' + ((isVideo) ? "true" : "false"), + 'autoplay=' + ((autoplay) ? "true" : "false"), + 'preload=' + preload, + 'width=' + width, + 'startvolume=' + options.startVolume, + 'timerrate=' + options.timerRate, + 'height=' + height]; + + if (mediaUrl !== null) { + if (pluginType == 'flash') { + initVars.push('file=' + mejs.Utility.encodeUrl(mediaUrl)); + } else { + initVars.push('file=' + mediaUrl); + } + } + if (options.enablePluginDebug) { + initVars.push('debug=true'); + } + if (options.enablePluginSmoothing) { + initVars.push('smoothing=true'); + } + if (controls) { + initVars.push('controls=true'); // shows controls in the plugin if desired + } + + switch (pluginType) { + case 'silverlight': + container.innerHTML = +'<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" id="' + pluginid + '" name="' + pluginid + '" width="' + width + '" height="' + height + '">' + +'<param name="initParams" value="' + initVars.join(',') + '" />' + +'<param name="windowless" value="true" />' + +'<param name="background" value="black" />' + +'<param name="minRuntimeVersion" value="3.0.0.0" />' + +'<param name="autoUpgrade" value="true" />' + +'<param name="source" value="' + options.pluginPath + options.silverlightName + '" />' + +'</object>'; + break; + + case 'flash': + + if (mejs.MediaFeatures.isIE) { + specialIEContainer = document.createElement('div'); + container.appendChild(specialIEContainer); + specialIEContainer.outerHTML = +'<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" ' + +'id="' + pluginid + '" width="' + width + '" height="' + height + '">' + +'<param name="movie" value="' + options.pluginPath + options.flashName + '?x=' + (new Date()) + '" />' + +'<param name="flashvars" value="' + initVars.join('&') + '" />' + +'<param name="quality" value="high" />' + +'<param name="bgcolor" value="#000000" />' + +'<param name="wmode" value="transparent" />' + +'<param name="allowScriptAccess" value="always" />' + +'<param name="allowFullScreen" value="true" />' + +'</object>'; + + } else { + + container.innerHTML = +'<embed id="' + pluginid + '" name="' + pluginid + '" ' + +'play="true" ' + +'loop="false" ' + +'quality="high" ' + +'bgcolor="#000000" ' + +'wmode="transparent" ' + +'allowScriptAccess="always" ' + +'allowFullScreen="true" ' + +'type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" ' + +'src="' + options.pluginPath + options.flashName + '" ' + +'flashvars="' + initVars.join('&') + '" ' + +'width="' + width + '" ' + +'height="' + height + '"></embed>'; + } + break; + } + // hide original element + htmlMediaElement.style.display = 'none'; + + // FYI: options.success will be fired by the MediaPluginBridge + + return pluginMediaElement; + }, + + updateNative: function(htmlMediaElement, options, autoplay, preload, playback) { + // add methods to video object to bring it into parity with Flash Object + for (var m in mejs.HtmlMediaElement) { + htmlMediaElement[m] = mejs.HtmlMediaElement[m]; + } + + /* + Chrome now supports preload="none" + if (mejs.MediaFeatures.isChrome) { + + // special case to enforce preload attribute (Chrome doesn't respect this) + if (preload === 'none' && !autoplay) { + + // forces the browser to stop loading (note: fails in IE9) + htmlMediaElement.src = ''; + htmlMediaElement.load(); + htmlMediaElement.canceledPreload = true; + + htmlMediaElement.addEventListener('play',function() { + if (htmlMediaElement.canceledPreload) { + htmlMediaElement.src = playback.url; + htmlMediaElement.load(); + htmlMediaElement.play(); + htmlMediaElement.canceledPreload = false; + } + }, false); + // for some reason Chrome forgets how to autoplay sometimes. + } else if (autoplay) { + htmlMediaElement.load(); + htmlMediaElement.play(); + } + } + */ + + // fire success code + options.success(htmlMediaElement, htmlMediaElement); + + return htmlMediaElement; + } +}; + +window.mejs = mejs; +window.MediaElement = mejs.MediaElement; \ No newline at end of file