UNPKG

mediaelement

Version:
571 lines (497 loc) 15.8 kB
'use strict'; import window from 'global/window'; import document from 'global/document'; import mejs from '../core/mejs'; import i18n from '../core/i18n'; import {renderer} from '../core/renderer'; import {createEvent} from '../utils/general'; import {NAV, IS_IE, IS_EDGE} from '../utils/constants'; import {typeChecks, absolutizeUrl} from '../utils/media'; /** * Shim that falls back to Flash if a media type is not supported. * * Any format not supported natively, including, RTMP, FLV, HLS and M(PEG)-DASH (if browser does not support MSE), * will play using Flash. */ /** * Core detector, plugins are added below * */ export const PluginDetector = { /** * Cached version numbers * @type {Array} */ plugins: [], /** * Test a plugin version number * @param {String} plugin - In this scenario 'flash' will be tested * @param {Array} v - An array containing the version up to 3 numbers (major, minor, revision) * @return {Boolean} */ hasPluginVersion: (plugin, v) => { const pv = PluginDetector.plugins[plugin]; v[1] = v[1] || 0; v[2] = v[2] || 0; return (pv[0] > v[0] || (pv[0] === v[0] && pv[1] > v[1]) || (pv[0] === v[0] && pv[1] === v[1] && pv[2] >= v[2])); }, /** * Detect plugin and store its version number * * @see PluginDetector.detectPlugin * @param {String} p * @param {String} pluginName * @param {String} mimeType * @param {String} activeX * @param {Function} axDetect */ addPlugin: (p, pluginName, mimeType, activeX, axDetect) => { PluginDetector.plugins[p] = PluginDetector.detectPlugin(pluginName, mimeType, activeX, axDetect); }, /** * Obtain version number from the mime-type (all but IE) or ActiveX (IE) * * @param {String} pluginName * @param {String} mimeType * @param {String} activeX * @param {Function} axDetect * @return {int[]} */ detectPlugin: (pluginName, mimeType, activeX, axDetect) => { let version = [0, 0, 0], description, ax ; // Firefox, Webkit, Opera; avoid MS Edge since `plugins` cannot be accessed if (NAV.plugins !== null && NAV.plugins !== undefined && typeof NAV.plugins[pluginName] === 'object') { description = NAV.plugins[pluginName].description; if (description && !(typeof NAV.mimeTypes !== 'undefined' && NAV.mimeTypes[mimeType] && !NAV.mimeTypes[mimeType].enabledPlugin)) { version = description.replace(pluginName, '').replace(/^\s+/, '').replace(/\sr/gi, '.').split('.'); for (let i = 0, total = version.length; i < total; i++) { version[i] = parseInt(version[i].match(/\d+/), 10); } } // Internet Explorer / ActiveX } else if (window.ActiveXObject !== undefined) { try { ax = new ActiveXObject(activeX); if (ax) { version = axDetect(ax); } } catch (e) { console.log(e); } } return version; } }; /** * Add Flash detection * */ PluginDetector.addPlugin('flash', 'Shockwave Flash', 'application/x-shockwave-flash', 'ShockwaveFlash.ShockwaveFlash', (ax) => { // adapted from SWFObject let version = [], d = ax.GetVariable("$version") ; if (d) { d = d.split(" ")[1].split(","); version = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; } return version; }); const FlashMediaElementRenderer = { /** * Create the player instance and add all native events/methods/properties as possible * * @param {MediaElement} mediaElement Instance of mejs.MediaElement already created * @param {Object} options All the player configuration options passed through constructor * @param {Object[]} mediaFiles List of sources with format: {src: url, type: x/y-z} * @return {Object} */ create: (mediaElement, options, mediaFiles) => { const flash = {}; let isActive = false; flash.options = options; flash.id = mediaElement.id + '_' + flash.options.prefix; flash.mediaElement = mediaElement; flash.flashState = {}; flash.flashApi = null; flash.flashApiStack = []; const props = mejs.html5media.properties, assignGettersSetters = (propName) => { // add to flash state that we will store flash.flashState[propName] = null; const capName = `${propName.substring(0, 1).toUpperCase()}${propName.substring(1)}`; flash[`get${capName}`] = () => { if (flash.flashApi !== null) { if (typeof flash.flashApi['get_' + propName] === 'function') { const value = flash.flashApi['get_' + propName](); if (propName === 'buffered') { return { start: () => { return 0; }, end: () => { return value; }, length: 1 }; } return value; } else { return null; } } else { return null; } }; flash[`set${capName}`] = (value) => { if (propName === 'src') { value = absolutizeUrl(value); } // send value to Flash if (flash.flashApi !== null && flash.flashApi['set_' + propName] !== undefined) { try { flash.flashApi['set_' + propName](value); } catch (e) { console.log(e); } } else { // store for after "READY" event fires flash.flashApiStack.push({ type: 'set', propName: propName, value: value }); } }; } ; for (let i = 0, total = props.length; i < total; i++) { assignGettersSetters(props[i]); } const methods = mejs.html5media.methods, assignMethods = (methodName) => { flash[methodName] = () => { if (isActive) { if (flash.flashApi !== null) { // send call up to Flash ExternalInterface API if (flash.flashApi[`fire_${methodName}`]) { try { flash.flashApi[`fire_${methodName}`](); } catch (e) { console.log(e); } } else { console.log('flash', 'missing method', methodName); } } else { // store for after "READY" event fires flash.flashApiStack.push({ type: 'call', methodName: methodName }); } } }; } ; methods.push('stop'); for (let i = 0, total = methods.length; i < total; i++) { assignMethods(methods[i]); } // give initial events like in others renderers const initEvents = ['rendererready']; for (let i = 0, total = initEvents.length; i < total; i++) { const event = createEvent(initEvents[i], flash); mediaElement.dispatchEvent(event); } // add a ready method that Flash can call to window[`__ready__${flash.id}`] = () => { flash.flashReady = true; flash.flashApi = document.getElementById(`__${flash.id}`); // do call stack if (flash.flashApiStack.length) { for (let i = 0, total = flash.flashApiStack.length; i < total; i++) { const stackItem = flash.flashApiStack[i]; if (stackItem.type === 'set') { const propName = stackItem.propName, capName = `${propName.substring(0, 1).toUpperCase()}${propName.substring(1)}` ; flash[`set${capName}`](stackItem.value); } else if (stackItem.type === 'call') { flash[stackItem.methodName](); } } } }; window[`__event__${flash.id}`] = (eventName, message) => { const event = createEvent(eventName, flash); if (message) { try { event.data = JSON.parse(message); event.details.data = JSON.parse(message); } catch (e) { event.message = message; } } // send event from Flash up to the mediaElement flash.mediaElement.dispatchEvent(event); }; // insert Flash object flash.flashWrapper = document.createElement('div'); // If the access script flag does not have any of the valid values, set to `sameDomain` by default if (['always', 'sameDomain'].indexOf(flash.options.shimScriptAccess) === -1) { flash.options.shimScriptAccess = 'sameDomain'; } const autoplay = mediaElement.originalNode.autoplay, flashVars = [ `uid=${flash.id}`, `autoplay=${autoplay}`, `allowScriptAccess=${flash.options.shimScriptAccess}`, `preload=${mediaElement.originalNode.getAttribute('preload') || ''}` ], isVideo = mediaElement.originalNode !== null && mediaElement.originalNode.tagName.toLowerCase() === 'video', flashHeight = (isVideo) ? mediaElement.originalNode.height : 1, flashWidth = (isVideo) ? mediaElement.originalNode.width : 1; if (mediaElement.originalNode.getAttribute('src')) { flashVars.push(`src=${mediaElement.originalNode.getAttribute('src')}`); } if (flash.options.enablePseudoStreaming === true) { flashVars.push(`pseudostreamstart=${flash.options.pseudoStreamingStartQueryParam}`); flashVars.push(`pseudostreamtype=${flash.options.pseudoStreamingType}`); } if (flash.options.streamDelimiter) { flashVars.push(`streamdelimiter=${encodeURIComponent(flash.options.streamDelimiter)}`); } if (flash.options.proxyType) { flashVars.push(`proxytype=${flash.options.proxyType}`); } mediaElement.appendChild(flash.flashWrapper); mediaElement.originalNode.style.display = 'none'; let settings = []; if (IS_IE || IS_EDGE) { const specialIEContainer = document.createElement('div'); flash.flashWrapper.appendChild(specialIEContainer); if (IS_EDGE) { settings = [ 'type="application/x-shockwave-flash"', `data="${flash.options.pluginPath}${flash.options.filename}"`, `id="__${flash.id}"`, `width="${flashWidth}"`, `height="${flashHeight}'"` ]; } else { settings = [ 'classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"', 'codebase="//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab"', `id="__${flash.id}"`, `width="${flashWidth}"`, `height="${flashHeight}"` ]; } if (!isVideo) { settings.push('style="clip: rect(0 0 0 0); position: absolute;"'); } specialIEContainer.outerHTML = `<object ${settings.join(' ')}>` + `<param name="movie" value="${flash.options.pluginPath}${flash.options.filename}?x=${new Date()}" />` + `<param name="flashvars" value="${flashVars.join('&amp;')}" />` + `<param name="quality" value="high" />` + `<param name="bgcolor" value="#000000" />` + `<param name="wmode" value="transparent" />` + `<param name="allowScriptAccess" value="${flash.options.shimScriptAccess}" />` + `<param name="allowFullScreen" value="true" />` + `<div>${i18n.t('mejs.install-flash')}</div>` + `</object>`; } else { settings = [ `id="__${flash.id}"`, `name="__${flash.id}"`, 'play="true"', 'loop="false"', 'quality="high"', 'bgcolor="#000000"', 'wmode="transparent"', `allowScriptAccess="${flash.options.shimScriptAccess}"`, 'allowFullScreen="true"', 'type="application/x-shockwave-flash"', 'pluginspage="//www.macromedia.com/go/getflashplayer"', `src="${flash.options.pluginPath}${flash.options.filename}"`, `flashvars="${flashVars.join('&')}"` ]; // set width&height attributes for video only if (isVideo) { settings.push(`width="${flashWidth}"`); settings.push(`height="${flashHeight}"`); } else { settings.push('style="position: fixed; left: -9999em; top: -9999em;"'); } flash.flashWrapper.innerHTML = `<embed ${settings.join(' ')}>`; } flash.flashNode = flash.flashWrapper.lastChild; flash.hide = () => { isActive = false; if (isVideo) { flash.flashNode.style.display = 'none'; } }; flash.show = () => { isActive = true; if (isVideo) { flash.flashNode.style.display = ''; } }; flash.setSize = (width, height) => { flash.flashNode.style.width = `${width}px`; flash.flashNode.style.height = `${height}px`; if (flash.flashApi !== null && typeof flash.flashApi.fire_setSize === 'function') { flash.flashApi.fire_setSize(width, height); } }; flash.destroy = () => { flash.flashNode.remove(); }; if (mediaFiles && mediaFiles.length > 0) { for (let i = 0, total = mediaFiles.length; i < total; i++) { if (renderer.renderers[options.prefix].canPlayType(mediaFiles[i].type)) { flash.setSrc(mediaFiles[i].src); break; } } } return flash; } }; const hasFlash = PluginDetector.hasPluginVersion('flash', [10, 0, 0]); if (hasFlash) { /** * Register media type based on URL structure if Flash is detected * */ typeChecks.push((url) => { url = url.toLowerCase(); if (url.startsWith('rtmp')) { if (~url.indexOf('.mp3')) { return 'audio/rtmp'; } else { return 'video/rtmp'; } } else if (/\.og(a|g)/i.test(url)) { return 'audio/ogg'; } else if (~url.indexOf('.m3u8')) { return 'application/x-mpegURL'; } else if (~url.indexOf('.mpd')) { return 'application/dash+xml'; } else if (~url.indexOf('.flv')) { return 'video/flv'; } else { return null; } }); // VIDEO const FlashMediaElementVideoRenderer = { name: 'flash_video', options: { prefix: 'flash_video', filename: 'mediaelement-flash-video.swf', enablePseudoStreaming: false, // start query parameter sent to server for pseudo-streaming pseudoStreamingStartQueryParam: 'start', // pseudo streaming type: use `time` for time based seeking (MP4) or `byte` for file byte position (FLV) pseudoStreamingType: 'byte', // Possible values: `none`, `HTTP`, `CONNECTOnly`, `CONNECT`, and `best` proxyType: '', // Indicate how to delimit folder structure in RTMP (`/`, `:`, `>`, etc.) streamDelimiter: '', }, /** * Determine if a specific element type can be played with this render * * @param {String} type * @return {Boolean} */ canPlayType: (type) => ~['video/mp4', 'video/rtmp', 'audio/rtmp', 'rtmp/mp4', 'audio/mp4', 'video/flv', 'video/x-flv'].indexOf(type.toLowerCase()), create: FlashMediaElementRenderer.create }; renderer.add(FlashMediaElementVideoRenderer); // HLS const FlashMediaElementHlsVideoRenderer = { name: 'flash_hls', options: { prefix: 'flash_hls', filename: 'mediaelement-flash-video-hls.swf' }, /** * Determine if a specific element type can be played with this render * * @param {String} type * @return {Boolean} */ canPlayType: (type) => ~['application/x-mpegurl', 'application/vnd.apple.mpegurl', 'audio/mpegurl', 'audio/hls', 'video/hls'].indexOf(type.toLowerCase()), create: FlashMediaElementRenderer.create }; renderer.add(FlashMediaElementHlsVideoRenderer); // M(PEG)-DASH const FlashMediaElementMdashVideoRenderer = { name: 'flash_dash', options: { prefix: 'flash_dash', filename: 'mediaelement-flash-video-mdash.swf' }, /** * Determine if a specific element type can be played with this render * * @param {String} type * @return {Boolean} */ canPlayType: (type) => ~['application/dash+xml'].indexOf(type.toLowerCase()), create: FlashMediaElementRenderer.create }; renderer.add(FlashMediaElementMdashVideoRenderer); // AUDIO const FlashMediaElementAudioRenderer = { name: 'flash_audio', options: { prefix: 'flash_audio', filename: 'mediaelement-flash-audio.swf' }, /** * Determine if a specific element type can be played with this render * * @param {String} type * @return {Boolean} */ canPlayType: (type) => ~['audio/mp3'].indexOf(type.toLowerCase()), create: FlashMediaElementRenderer.create }; renderer.add(FlashMediaElementAudioRenderer); // AUDIO - ogg const FlashMediaElementAudioOggRenderer = { name: 'flash_audio_ogg', options: { prefix: 'flash_audio_ogg', filename: 'mediaelement-flash-audio-ogg.swf' }, /** * Determine if a specific element type can be played with this render * * @param {String} type * @return {Boolean} */ canPlayType: (type) => ~['audio/ogg', 'audio/oga', 'audio/ogv'].indexOf(type.toLowerCase()), create: FlashMediaElementRenderer.create }; renderer.add(FlashMediaElementAudioOggRenderer); }