Skip to content

Asynchronous Decisions

nweber edited this page Mar 6, 2013 · 1 revision

Most of the decisions made in the player are asynchronous. We can’t know how any particular, even the smallest, will be made. For example…​ When deciding whether or not more data should be buffered, the decision could be quick (is the amount currently buffered less than Manifest.minBufferTime?) or it could be a complex operation that requires information from a server. Perhaps more data will be buffered if the player detects that the user’s latency is higher. Or perhaps during a particularly high-traffic portion of the video the player will buffer more data than usual.

To make the asynchronous logic as easy as possible the player is using a Promises framework. https://github.com/kriskowal/q

The general idea is that every decision returns a Promise object which will be fulfilled at a later point in time. When the promise is created a callback is added to be called when the promise is fulfilled.

This allows the decision to be asynchronous while letting the code be organized in a way that has some semblance of order.

To create and return a promise, a deferred object must first be created. This object contains the promise that will be returned. Later, when a value has been produced, the deferred object can be resolved, which will fulfill the promise callback. It’s even OK to resolve a deferred instance before returning the promise. As soon as a callback is added to the promise it will be fulfilled if the deferred instance was already resolved. A deferred instance can also be rejected should the promise cannot be fulfilled. This will trigger the fail callback (if any) registered with the promise.

Here’s a very basic example:

function getPromise () {
     var deferred = Q.defer();
     deferred.resolve(value);
     return deferred.promise;
}

function doSomething () {
     getPromise().then(
          function (returnValue) {
               console.log("Got the promise value!");
          }
     );
}

If a decision does not need to be asynchronous, the framework has a method that returns a synchronous promise. This method is Q.when(value).

Here’s a slightly more real example:

function shouldLoadNextFragment () {
     var result = (this.bufferLength < manifest.minBufferTime);
     Q.when(result);
}

function loadNextFragment () {
     var deferred = Q.defer(),
         req;

     fragmentHandler.getUrlForTime(this.currentTime).then(
          function (url) {
               req = new XMLHttpRequest();
               req.responseType = "arraybuffer";
               req.open("GET", url, true);
               req.onload = function () {
                    deferred.resolve(req.response);
               };
               req.onerror = function () {
                    deferred.reject("Error loading fragment");
               }
          }
     );

     return deferred.promise;
}

function doLoadIfRequired () {
     shouldLoadNextFragment().then(
          function (result) {
               if (result) {
                    return loadNextFragment();
               } else {
                    return Q.when(null);
               }
          }
     ).then(
          function (data) {
               if (data !== null) {
                    console.log("Loaded a fragment!");
               } else {
                    console.log("Did not load a fragment.");
               }
          }
     );
}
Clone this wiki locally