- From: Jan-Ivar Bruaroey <jib@mozilla.com>
- Date: Mon, 22 Sep 2014 11:49:27 -0400
- To: Harald Alvestrand <harald@alvestrand.no>, "public-webrtc@w3.org" <public-webrtc@w3.org>
On 9/22/14 4:21 AM, Harald Alvestrand wrote: > promises for getUserMedia (promiseUserMedia?) is a Media Capture TF matter. > Can you repost to that list? This is a promise-wrapper for peerConnection, not just gUM, so this seems like the better place. With Promises being the way forward, and because they work better the more API-points use them, their benefit in mediaDevices alone may not be obvious to everyone. So I thought it might be helpful for people to see how things might look in the future. > (I'm also interested in seeing your polyfill, if it's just a polyfill, > and figure out why it doesn't work in Chrome (if it doesn't))..... Then click the link [1] and view page source. It's all there! :-) I shouldn't have said "polyfill". I meant: This proof-of-concept test-script for Firefox has a promise-wrapper embedded in it. It is not meant to be a production polyfill, and doesn't work on Chrome because I didn't make that a priority (it calls mozPeerConnection for one). > On 09/19/2014 10:54 PM, Jan-Ivar Bruaroey wrote: >> If you don't care for promises, their minimal impact is: >> >> - navigator.getUserMedia(constraints, function(stream) { >> + mediaDevices.getUserMedia(constraints).then(function(stream) { >> video.srcObject = stream; >> video.play(); >> - }, function (err) { >> + }).catch(function (err) { >> console.log(err.message); >> }); >> >> But you'd be missing out by stopping there, as I believe they have a >> lot more to offer WebRTC. >> >> To illustrate, I've rewritten our local-loop replaceTrack demo-script >> to use a promises polyfill that works in Firefox Nightly [1]. Here's a >> cleaned-up excerpt: >> >> function call(pc, signal) { >> return mediaDevices.getUserMedia(myrequest) >> .then(video => { >> localvideo1.srcObject = video; >> localvideo1.play(); >> video1.getTracks().forEach(track => pc.addTrack(track, video)); >> return pc.createOffer(offer_options); >> }) >> .then(offer => pc.setLocalDescription(offer)) >> .then(() => signal.then(answer => >> pc.setRemoteDescription(answer))); >> } >> >> function pickup(pc, signal) { >> return (oneway.checked? new Promise(resolve => resolve()) : >> mediaDevices.getUserMedia(myrequest_reverse) >> .then(video => { >> localvideo2.srcObject = video; >> localvideo2.play(); >> video.getTracks().forEach(track => pc.addTrack(track, >> video)); >> })) >> .then(() => signal.then(offer => pc.setRemoteDescription(offer))) >> .then(() => pc.createAnswer(answer_options)) >> .then(answer => pc.setLocalDescription(answer)); >> } >> >> Promise.all([call(pc1, pc2.stable), pickup(pc2, pc1.haveLocalOffer)]) >> .then(() => log("HIP HIP HOORAY")) >> .catch(failed); // 1-line error handling >> >> This is a complete local-loop call. The two functions, call() and >> pickup(), may be a bit contrived, but illustrate that unlike most >> local-loop tests you've probably seen, each peer is set up >> asynchronously and in parallel, with only a signal sent across, much >> like in a remote call. >> >> Several things are going on here - which I will explain - and promises >> are used in three interesting ways: >> >> First, there's the promise-chains: then().then().then(). We start two >> chains in parallel and wait for both to complete with Promise.all. >> This is pretty straightforward promise-stuff, but powerful. >> >> Then, there's three new promises that I've added as >> read-only-attributes (yes!) to the peerConnection polyfill that >> trigger on signalingstate: >> >> pc.hasLocalOffer >> pc.hasRemoteOffer >> pc.stable >> >> Turns out promises can be used to track simple states. E.g. to make >> something happen when signalingstate changes to "has-local-offer", you >> just do: pc.hasLocalOffer.then(function(offer) { ... }); To make it >> more useful, I've made the fulfillment value be the offer (or answer >> depending), which means I can use them as the carrier 'signal' >> argument that gives the call() and pickup() functions the other peer's >> sdp when it becomes available. >> >> >> Lastly, my final use of promises was to fix an unfortunate race in >> local-loop calls. With the two peers less tightly coupled, I started >> seeing ICE failures. Turns out candidates were arriving before the >> other peer could receive them, and addIceCandidate was failing, >> complaining that it could not be called beforesetRemoteDescription. Is >> that by spec? >> >> To solve this, I relied again on the signalingstate promises: >> >> pc1.onicecandidate = obj => { >> pc2.haveRemoteOffer.then(() => { >> if (obj.candidate) { >> pc2.addIceCandidate(obj.candidate).catch(failed); >> } >> }); >> }; >> >> pc2.onicecandidate = obj => { >> pc1.stable.then(() => { >> if (obj.candidate) { >> pc1.addIceCandidate(obj.candidate).catch(failed); >> } >> }); >> }; >> >> Turns out promises can be used to queue function-calls. Calling then() >> multiple times on the same promise queues functions up to be executed >> in order later (or right away if the promise has already been >> resolved). This made it easy to subjugate addIceCandidate's timing-needs. >> >> So there you have it. Three uses of promises in the same API. I also >> enjoy that there is one line of error-handling in the whole thing. >> >> Feel free to have a look at the full test or take it for a spin: >> >> Comments welcome. >> >> [1] https://bug1033885.bugzilla.mozilla.org/attachment.cgi?id=8492552 >> >> .: Jan-Ivar :. .: Jan-Ivar :.
Received on Monday, 22 September 2014 15:49:56 UTC