diff --git a/src/howler.core.js b/src/howler.core.js index 5198bdfc..591e4d6c 100644 --- a/src/howler.core.js +++ b/src/howler.core.js @@ -588,7 +588,7 @@ self._preload = (typeof o.preload === 'boolean' || o.preload === 'metadata') ? o.preload : true; self._rate = o.rate || 1; self._sprite = o.sprite || {}; - self._src = (typeof o.src !== 'string') ? o.src : [o.src]; + self._src = (typeof o.src !== 'string' && !isMediaStream(o.src)) ? o.src : [o.src]; self._volume = o.volume !== undefined ? o.volume : 1; self._xhr = { method: o.xhr && o.xhr.method ? o.xhr.method : 'GET', @@ -603,6 +603,7 @@ self._endTimers = {}; self._queue = []; self._playLock = false; + self._isMediaStream = false; // Setup event listeners. self._onend = o.onend ? [{fn: o.onend}] : []; @@ -664,13 +665,28 @@ } // Make sure our source is in an array. - if (typeof self._src === 'string') { + if (typeof self._src === 'string' || isMediaStream(self._src)) { self._src = [self._src]; } // Loop through the sources and pick the first one that is compatible. for (var i=0; i= 0) { + if (Howler._howls[i]._src === self._src || (!self._isMediaStream && self._src.indexOf(Howler._howls[i]._src) >= 0)) { remCache = false; break; } @@ -2136,9 +2160,38 @@ _refreshBuffer: function(sound) { var self = this; - // Setup the buffer source for playback. - sound._node.bufferSource = Howler.ctx.createBufferSource(); - sound._node.bufferSource.buffer = cache[self._src]; + if (self._isMediaStream) { + // Special case for streams. Make a MediaStreamSource and set it as the + // bufferSource. + + // XXX There is a Chromium bug + // (https://bugs.chromium.org/p/chromium/issues/detail?id=933677) where + // remote MediaStreams don't play unless they are assigned to a media + // element. Workaround: + var ua = Howler._navigator ? Howler._navigator.userAgent : ''; + if (ua.indexOf('Chrome') !== -1) { + var tmpAudio = new Audio(); + + var tmpAudioCallback = function() { + if (tmpAudio) { + tmpAudio.removeEventListener('error', tmpAudioCallback); + tmpAudio.removeEventListener('canplaythrough', tmpAudioCallback); + tmpAudio = null; + } + }; + + tmpAudio.muted = true; + tmpAudio.addEventListener('error', tmpAudioCallback); + tmpAudio.addEventListener('canplaythrough', tmpAudioCallback); + tmpAudio.srcObject = self._src; + } + + sound._node.bufferSource = Howler.ctx.createMediaStreamSource(self._src); + } else { + // Setup the buffer source for playback. + sound._node.bufferSource = Howler.ctx.createBufferSource(); + sound._node.bufferSource.buffer = cache[self._src]; + } // Connect to the correct node. if (sound._panner) { @@ -2147,13 +2200,17 @@ sound._node.bufferSource.connect(sound._node); } - // Setup looping and playback rate. - sound._node.bufferSource.loop = sound._loop; - if (sound._loop) { - sound._node.bufferSource.loopStart = sound._start || 0; - sound._node.bufferSource.loopEnd = sound._stop || 0; + // MediaStreams can't have custom playback rates or loop, so don't set + // that up + if (!self._isMediaStream) { + // Setup looping and playback rate. + sound._node.bufferSource.loop = sound._loop; + if (sound._loop) { + sound._node.bufferSource.loopStart = sound._start || 0; + sound._node.bufferSource.loopEnd = sound._stop || 0; + } + sound._node.bufferSource.playbackRate.setValueAtTime(sound._rate, Howler.ctx.currentTime); } - sound._node.bufferSource.playbackRate.setValueAtTime(sound._rate, Howler.ctx.currentTime); return self; }, @@ -2270,7 +2327,12 @@ self._node.addEventListener('ended', self._endFn, false); // Setup the new audio node. - self._node.src = parent._src; + if (parent._isMediaStream) { + self._node.srcObject = parent._src; + } else { + self._node.src = parent._src; + } + self._node.preload = parent._preload === true ? 'auto' : parent._preload; self._node.volume = volume * Howler.volume(); @@ -2375,6 +2437,7 @@ /***************************************************************************/ var cache = {}; + var mediaStreamsSupported = 'MediaStream' in globalThis; /** * Buffer a sound from URL, Data URI or cache and decode to audio source (Web Audio API). @@ -2556,6 +2619,16 @@ Howler._setup(); }; + /** + * Helper function for checking whether a source is a MediaStream. Doesn't + * throw an error when MediaStreams aren't supported. + * @param {String | MediaStream | Array} src The source to check + * @return {Boolean} Returns true if the source is a MediaStream instance + */ + var isMediaStream = function(src) { + return mediaStreamsSupported && src instanceof MediaStream; + } + // Add support for AMD (Asynchronous Module Definition) libraries such as require.js. if (typeof define === 'function' && define.amd) { define([], function() {