UNPKG

mediaelement

Version:
338 lines (301 loc) 8.88 kB
'use strict'; /** * SoundCloud renderer * * Uses <iframe> approach and uses SoundCloud Widget API to manipulate it. * @see https://developers.soundcloud.com/docs/api/html5-widget */ const SoundCloudApi = { 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 SC !== 'undefined') { SoundCloudApi._createPlayer(settings); } else { SoundCloudApi.promise = SoundCloudApi.promise || mejs.Utils.loadScript('https://w.soundcloud.com/player/api.js'); SoundCloudApi.promise.then(() => { SoundCloudApi._createPlayer(settings); }); } }, /** * Create a new instance of SoundCloud Widget player and trigger a custom event to initialize it * * @param {Object} settings - an object with settings needed to create <iframe> */ _createPlayer: (settings) => { const player = SC.Widget(settings.iframe); window[`__ready__${settings.id}`](player); } }; const SoundCloudIframeRenderer = { name: 'soundcloud_iframe', options: { prefix: 'soundcloud_iframe' }, /** * Determine if a specific element type can be played with this render * * @param {String} type * @return {Boolean} */ canPlayType: (type) => ~['video/soundcloud', 'video/x-soundcloud'].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) => { // create our fake element that allows events and such to work const sc = {}, apiStack = [], readyState = 4, autoplay = mediaElement.originalNode.autoplay, isVideo = mediaElement.originalNode !== null && mediaElement.originalNode.tagName.toLowerCase() === 'video' ; let duration = 0, currentTime = 0, bufferedTime = 0, volume = 1, muted = false, paused = true, ended = false, scPlayer = null, scIframe = null ; sc.options = options; sc.id = mediaElement.id + '_' + options.prefix; sc.mediaElement = mediaElement; const props = mejs.html5media.properties, assignGettersSetters = (propName) => { const capName = `${propName.substring(0, 1).toUpperCase()}${propName.substring(1)}`; sc[`get${capName}`] = () => { if (scPlayer !== null) { const value = null; // figure out how to get dm dta here switch (propName) { case 'currentTime': return currentTime; case 'duration': return duration; case 'volume': return volume; case 'paused': return paused; case 'ended': return ended; case 'muted': return muted; case 'buffered': return { start: () => { return 0; }, end: () => { return bufferedTime * duration; }, length: 1 }; case 'src': return (scIframe) ? scIframe.src : ''; case 'readyState': return readyState; } return value; } else { return null; } }; sc[`set${capName}`] = (value) => { if (scPlayer !== null) { switch (propName) { case 'src': const url = typeof value === 'string' ? value : value[0].src; scPlayer.load(url); if (autoplay) { scPlayer.play(); } break; case 'currentTime': scPlayer.seekTo(value * 1000); break; case 'muted': if (value) { scPlayer.setVolume(0); // ? } else { scPlayer.setVolume(1); // ? } setTimeout(() => { const event = mejs.Utils.createEvent('volumechange', sc); mediaElement.dispatchEvent(event); }, 50); break; case 'volume': scPlayer.setVolume(value); setTimeout(() => { const event = mejs.Utils.createEvent('volumechange', sc); mediaElement.dispatchEvent(event); }, 50); break; case 'readyState': const event = mejs.Utils.createEvent('canplay', sc); mediaElement.dispatchEvent(event); break; default: console.log('sc ' + sc.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) => { sc[methodName] = () => { if (scPlayer !== null) { switch (methodName) { case 'play': return scPlayer.play(); case 'pause': return scPlayer.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]); } window['__ready__' + sc.id] = (_scPlayer) => { mediaElement.scPlayer = scPlayer = _scPlayer; if (autoplay) { scPlayer.play(); } 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)}`; sc[`set${capName}`](stackItem.value); } else if (stackItem.type === 'call') { sc[stackItem.methodName](); } } } // SoundCloud properties are async, so we don't fire the event until the property callback fires scPlayer.bind(SC.Widget.Events.PLAY_PROGRESS, () => { paused = false; ended = false; scPlayer.getPosition((_currentTime) => { currentTime = _currentTime / 1000; const event = mejs.Utils.createEvent('timeupdate', sc); mediaElement.dispatchEvent(event); }); }); scPlayer.bind(SC.Widget.Events.PAUSE, () => { paused = true; const event = mejs.Utils.createEvent('pause', sc); mediaElement.dispatchEvent(event); }); scPlayer.bind(SC.Widget.Events.PLAY, () => { paused = false; ended = false; const event = mejs.Utils.createEvent('play', sc); mediaElement.dispatchEvent(event); }); scPlayer.bind(SC.Widget.Events.FINISHED, () => { paused = false; ended = true; const event = mejs.Utils.createEvent('ended', sc); mediaElement.dispatchEvent(event); }); scPlayer.bind(SC.Widget.Events.READY, () => { scPlayer.getDuration((_duration) => { duration = _duration / 1000; const event = mejs.Utils.createEvent('loadedmetadata', sc); mediaElement.dispatchEvent(event); }); }); scPlayer.bind(SC.Widget.Events.LOAD_PROGRESS, () => { scPlayer.getDuration((loadProgress) => { if (duration > 0) { bufferedTime = duration * loadProgress; const event = mejs.Utils.createEvent('progress', sc); mediaElement.dispatchEvent(event); } }); scPlayer.getDuration((_duration) => { duration = _duration; const event = mejs.Utils.createEvent('loadedmetadata', sc); mediaElement.dispatchEvent(event); }); }); // give initial events const initEvents = ['rendererready', 'loadeddata', 'loadedmetadata', 'canplay']; for (let i = 0, total = initEvents.length; i < total; i++) { const event = mejs.Utils.createEvent(initEvents[i], sc); mediaElement.dispatchEvent(event); } }; // container for API API scIframe = document.createElement('iframe'); scIframe.id = sc.id; scIframe.width = isVideo ? '100%' : 1; scIframe.height = isVideo ? '100%' : 1; scIframe.frameBorder = 0; scIframe.style.visibility = isVideo ? 'visible' : 'hidden'; scIframe.src = mediaFiles[0].src; scIframe.scrolling = 'no'; mediaElement.appendChild(scIframe); mediaElement.originalNode.style.display = 'none'; const scSettings = { iframe: scIframe, id: sc.id }; SoundCloudApi.load(scSettings); sc.setSize = () => {}; sc.hide = () => { sc.pause(); if (scIframe) { scIframe.style.display = 'none'; } }; sc.show = () => { if (scIframe) { scIframe.style.display = ''; } }; sc.destroy = () => { scPlayer.destroy(); }; return sc; } }; /** * Register SoundCloud type based on URL structure * */ mejs.Utils.typeChecks.push((url) => /\/\/(w\.)?soundcloud.com/i.test(url) ? 'video/x-soundcloud' : null); mejs.Renderers.add(SoundCloudIframeRenderer);