UNPKG

mediaelement

Version:
416 lines (360 loc) 10.8 kB
'use strict'; /** * Facebook renderer * * It creates an <iframe> from a <div> with specific configuration. * @see https://developers.facebook.com/docs/plugins/embedded-video-player */ const FacebookApi = { promise: null, /** * Create a queue to prepare the creation of <iframe> * * @param {Object} settings - an object with settings needed to create <iframe> */ load: (settings) => { if (typeof FB !== 'undefined') { FacebookApi._createPlayer(settings); } else { FacebookApi.promise = FacebookApi.promise || mejs.Utils.loadScript(`https://connect.facebook.net/${settings.options.lang}/sdk.js`); FacebookApi.promise.then(() => { FB.init(settings.options); setTimeout(() => { FacebookApi._createPlayer(settings); }, 50); }); } }, /** * Create a new instance of Facebook API player and trigger a custom event to initialize it * * @param {Object} settings - the instance ID */ _createPlayer: (settings) => { window[`__ready__${settings.id}`](); } }; const FacebookRenderer = { name: 'facebook', options: { prefix: 'facebook', facebook: { appId: '', xfbml: true, version: 'v2.10', lang: 'en_US' } }, /** * Determine if a specific element type can be played with this render * * @param {String} type * @return {Boolean} */ canPlayType: (type) => ~['video/facebook', 'video/x-facebook'].indexOf(type.toLowerCase()), /** * 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 apiStack = [], fb = {}, readyState = 4 ; let hasStartedPlaying = false, paused = true, ended = false, fbPlayer = null, src = '', poster = '', autoplay = mediaElement.originalNode.autoplay ; fb.options = options; fb.id = mediaElement.id + '_' + options.prefix; fb.mediaElement = mediaElement; if (mejs.Features.isiPhone && mediaElement.originalNode.getAttribute('poster')) { poster = mediaElement.originalNode.getAttribute('poster'); mediaElement.originalNode.removeAttribute('poster'); } // wrappers for get/set const props = mejs.html5media.properties, assignGettersSetters = (propName) => { const capName = `${propName.substring(0, 1).toUpperCase()}${propName.substring(1)}`; fb[`get${capName}`] = () => { if (fbPlayer !== null) { const value = null; // figure out how to get youtube dta here switch (propName) { case 'currentTime': return fbPlayer.getCurrentPosition(); case 'duration': return fbPlayer.getDuration(); case 'volume': return fbPlayer.getVolume(); case 'paused': return paused; case 'ended': return ended; case 'muted': return fbPlayer.isMuted(); case 'buffered': return { start: () => { return 0; }, end: () => { return 0; }, length: 1 }; case 'src': return src; case 'readyState': return readyState; } return value; } else { return null; } }; fb[`set${capName}`] = (value) => { if (fbPlayer !== null) { switch (propName) { case 'src': const url = typeof value === 'string' ? value : value[0].src; src = url; // Only way is to destroy instance and all the events fired, // and create new one fbContainer.remove(); fbContainer = document.createElement('div'); fbContainer.id = fb.id; fbContainer.className = 'fb-video'; fbContainer.setAttribute('data-href', url); fbContainer.setAttribute('data-allowfullscreen', 'true'); fbContainer.setAttribute('data-controls', 'false'); mediaElement.originalNode.parentNode.insertBefore(fbContainer, mediaElement.originalNode); mediaElement.originalNode.style.display = 'none'; FacebookApi.load({ lang: fb.options.lang, id: fb.id }); // This method reloads video on-demand FB.XFBML.parse(); if (autoplay) { fbPlayer.play(); } break; case 'currentTime': fbPlayer.seek(value); break; case 'muted': if (value) { fbPlayer.mute(); } else { fbPlayer.unmute(); } setTimeout(() => { const event = mejs.Utils.createEvent('volumechange', fb); mediaElement.dispatchEvent(event); }, 50); break; case 'volume': fbPlayer.setVolume(value); setTimeout(() => { const event = mejs.Utils.createEvent('volumechange', fb); mediaElement.dispatchEvent(event); }, 50); break; case 'readyState': const event = mejs.Utils.createEvent('canplay', fb); mediaElement.dispatchEvent(event); break; default: console.log('facebook ' + fb.id, propName, 'UNSUPPORTED property'); break; } } else { // store for after "READY" event fires apiStack.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) => { fb[methodName] = () => { if (fbPlayer !== null) { switch (methodName) { case 'play': return fbPlayer.play(); case 'pause': return fbPlayer.pause(); case 'load': return null; } } else { apiStack.push({type: 'call', methodName: methodName}); } }; } ; for (let i = 0, total = methods.length; i < total; i++) { assignMethods(methods[i]); } /** * Dispatch a list of events * * @private * @param {Array} events */ function assignEvents (events) { for (let i = 0, total = events.length; i < total; i++) { const event = mejs.Utils.createEvent(events[i], fb, true); mediaElement.dispatchEvent(event); } } window['__ready__' + fb.id] = () => { FB.Event.subscribe('xfbml.ready', (msg) => { if (msg.type === 'video' && fb.id === msg.id) { mediaElement.fbPlayer = fbPlayer = msg.instance; // Set proper size since player dimensions are unknown before this event const fbIframe = document.getElementById(fb.id), width = fbIframe.offsetWidth, height = fbIframe.offsetHeight, events = ['mouseover', 'mouseout'], assignIframeEvents = (e) => { const event = mejs.Utils.createEvent(e.type, fb); mediaElement.dispatchEvent(event); } ; fb.setSize(width, height); if (!mediaElement.originalNode.muted) { fbPlayer.unmute(); } if (autoplay) { fbPlayer.play(); } for (let i = 0, total = events.length; i < total; i++) { fbIframe.addEventListener(events[i], assignIframeEvents); } fb.eventHandler = {}; // remove previous listeners const fbEvents = ['startedPlaying', 'paused', 'finishedPlaying', 'startedBuffering', 'finishedBuffering']; for (let i = 0, total = fbEvents.length; i < total; i++) { const event = fbEvents[i], handler = fb.eventHandler[event] ; if (handler !== undefined && handler !== null && !mejs.Utils.isObjectEmpty(handler) && typeof handler.removeListener === 'function') { handler.removeListener(event); } } // do call stack if (apiStack.length) { for (let i = 0, total = apiStack.length; i < total; i++) { const stackItem = apiStack[i]; if (stackItem.type === 'set') { const propName = stackItem.propName, capName = `${propName.substring(0, 1).toUpperCase()}${propName.substring(1)}` ; fb[`set${capName}`](stackItem.value); } else if (stackItem.type === 'call') { fb[stackItem.methodName](); } } } assignEvents(['rendererready', 'loadeddata', 'canplay', 'progress', 'loadedmetadata', 'timeupdate']); let timer; // Custom Facebook events fb.eventHandler.startedPlaying = fbPlayer.subscribe('startedPlaying', () => { if (!hasStartedPlaying) { hasStartedPlaying = true; } paused = false; ended = false; assignEvents(['play', 'playing', 'timeupdate']); // Workaround to update progress bar timer = setInterval(() => { fbPlayer.getCurrentPosition(); assignEvents(['timeupdate']); }, 250); }); fb.eventHandler.paused = fbPlayer.subscribe('paused', () => { paused = true; ended = false; assignEvents(['pause']); }); fb.eventHandler.finishedPlaying = fbPlayer.subscribe('finishedPlaying', () => { paused = true; ended = true; assignEvents(['ended']); clearInterval(timer); timer = null; }); fb.eventHandler.startedBuffering = fbPlayer.subscribe('startedBuffering', () => { assignEvents(['progress', 'timeupdate']); }); fb.eventHandler.finishedBuffering = fbPlayer.subscribe('finishedBuffering', () => { assignEvents(['progress', 'timeupdate']); }); } }); }; // Create fb <iframe> markup src = mediaFiles[0].src; let fbContainer = document.createElement('div'); fbContainer.id = fb.id; fbContainer.className = 'fb-video'; fbContainer.setAttribute('data-href', src); fbContainer.setAttribute('data-allowfullscreen', 'true'); fbContainer.setAttribute('data-controls', !!mediaElement.originalNode.controls); mediaElement.originalNode.parentNode.insertBefore(fbContainer, mediaElement.originalNode); mediaElement.originalNode.style.display = 'none'; FacebookApi.load({ options: fb.options.facebook, id: fb.id }); fb.hide = () => { fb.pause(); if (fbPlayer) { fbContainer.style.display = 'none'; } }; fb.setSize = (width) => { if (fbPlayer !== null && !isNaN(width)) { fbContainer.style.width = width; } }; fb.show = () => { if (fbPlayer) { fbContainer.style.display = ''; } }; fb.destroy = () => { if (poster) { mediaElement.originalNode.setAttribute('poster', poster); } }; return fb; } }; /** * Register Facebook type based on URL structure * */ mejs.Utils.typeChecks.push((url) => ~(url.toLowerCase()).indexOf('//www.facebook') ? 'video/x-facebook' : null); mejs.Renderers.add(FacebookRenderer);