W3C home > Mailing lists > Public > public-webrtc@w3.org > September 2014

Re: Promises + WebRTC = <3

From: Harald Alvestrand <harald@alvestrand.no>
Date: Mon, 22 Sep 2014 01:21:25 -0700
Message-ID: <541FDC05.7050400@alvestrand.no>
To: Jan-Ivar Bruaroey <jib@mozilla.com>, "public-webrtc@w3.org" <public-webrtc@w3.org>
Jan-Ivar,

promises for getUserMedia (promiseUserMedia?) is a Media Capture TF matter.
Can you repost to that list?

(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)).....

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 :.
>
>


-- 
Surveillance is pervasive. Go Dark.
Received on Monday, 22 September 2014 08:21:57 UTC

This archive was generated by hypermail 2.3.1 : Monday, 23 October 2017 15:19:41 UTC