Skip to content

Instantly share code, notes, and snippets.

@ThomasBurleson
Last active October 31, 2018 19:54
Show Gist options
  • Save ThomasBurleson/7576083 to your computer and use it in GitHub Desktop.
Save ThomasBurleson/7576083 to your computer and use it in GitHub Desktop.
Demonstration of refactor of DownloadRatioRules.js > transform deep nesting of promise chains to an easily-maintained, flattened, sequential chain.
if (downloadRatio < 1.0) {
self.debug.log("Download ratio is poor.");
if (current > 0) {
self.debug.log("We are not at the lowest bitrate, so switch down.");
self.manifestExt.getRepresentationFor(current - 1, data).then(
function (representation1) {
self.manifestExt.getBandwidth(representation1).then(
function (oneDownBandwidth) {
self.manifestExt.getRepresentationFor(current, data).then(
function (representation2) {
self.manifestExt.getBandwidth(representation2).then(
function (currentBandwidth) {
switchRatio = oneDownBandwidth / currentBandwidth;
self.debug.log("Switch ratio: " + switchRatio);
if (downloadRatio < switchRatio) {
self.debug.log("Things must be going pretty bad, switch all the way down.");
deferred.resolve(new MediaPlayer.rules.SwitchRequest(0));
} else {
self.debug.log("Things could be better, so just switch down one index.");
deferred.resolve(new MediaPlayer.rules.SwitchRequest(current - 1));
}
}
);
}
);
}
);
}
);
} else {
self.debug.log("We are at the lowest bitrate and cannot switch down, use current.");
deferred.resolve(new MediaPlayer.rules.SwitchRequest(current));
}
} else {
self.debug.log("Download ratio is good.");
self.manifestExt.getRepresentationCount(data).then(
function (max) {
max -= 1; // 0 based
if (current < max) {
self.debug.log("We are not at the highest bitrate, so switch up.");
self.manifestExt.getRepresentationFor(current + 1, data).then(
function (representation1) {
self.manifestExt.getBandwidth(representation1).then(
function (oneUpBandwidth) {
self.manifestExt.getRepresentationFor(current, data).then(
function (representation2) {
self.manifestExt.getBandwidth(representation2).then(
function (currentBandwidth) {
}
);
}
);
}
);
}
);
} else {
self.debug.log("We are at the highest bitrate and cannot switch up, use current.");
deferred.resolve(new MediaPlayer.rules.SwitchRequest(max));
}
}
);
}
@ThomasBurleson
Copy link
Author

Getting close:

MediaPlayer.rules.DownloadRatioRule = function () {
"use strict";

    var self = null,
        noop = function() { },
        $log = null,                // updated during self.checkIndex()
        getRepresentation = null,   // updated during self.checkIndex()
        getBandwidth      = null,   // updated during self.checkIndex()

        /**
         *
         */
        getIndexedBandwidth  = function( index, data )
        {
            $log("getIndexedBandwidth( "+ index + " )");
            return getRepresentation( index, data ).then( getBandwidth ); 
        },
        /**
         *
         */
        getMaxBandwidth = function( )
        {
            $log("getMaxBandwidth()");

            return getRepresentation( )
                        .then( function(last)
                        {
                            return max - 1; // zero-index
                        };
        },
        /**
         *
         */
        checkRatio = function (newIdx, currentBandwidth, data) 
        {  
            $log("checkRatio()");

            return getIndexedBandwidth( newIdx, data)
                        .then( newBandwidth )
                        {
                            return (newBandwidth / currentBandwidth);
                        });
        },
        /**
         *
         */
        calculateTimesAndRatios = function( metrics )
        {
            var DOWNLOAD_RATIO_SAFETY_FACTOR = 0.75,
                httpRequests = metrics.HttpList,
                numRequests  = httpRequests ? httpRequests.length : 0,
                lastRequest  = numRequests  ? httpRequests[numRequests - 1] : null,
                info = {
                    totalTime     : lastRequest ? (lastRequest.tfinish.getTime() - lastRequest.trequest.getTime()) / 1000   : 0,
                    downloadTime  : lastRequest ? (lastRequest.tfinish.getTime() - lastRequest.tresponse.getTime()) / 1000  : 0,
                    totalRatio    : lastRequest ? lastRequest.mediaduration / totalTime                                     : NaN,
                    downloadRatio : lastRequest ? (lastRequest.mediaduration / downloadTime) * DOWNLOAD_RATIO_SAFETY_FACTOR : NaN
                };

            $log("calculateTimesAndRatios() - Checking download ratio rule...");

            if (!metrics) {

                $log("No metrics, bailing.");
                info = null;

            } else if ( numRequests === 0) {

                $log("No requests made for this stream yet, bailing.");
                info = null;

            } else if (info.totalTime <= 0) {

                $log("Don't know how long the download of the last fragment took, bailing.");
                info = null;

            } else if ( !lastRequest.mediaduration || lastRequest.mediaduration <= 0) {

                $log("Don't know the duration of the last media fragment, bailing.");
                info = null;

            } else if ( isNaN(downloadRatio) || isNaN(totalRatio) ) {

                $log("Total time: " + totalTime + "s");
                $log("Download time: " + downloadTime + "s");
                $log("The ratios are NaN, bailing.");

                info = null;
            }

            $log("Total ratio: "    + totalRatio);
            log("Download ratio: " + downloadRatio);

            return info;
        },
        /**
         *
         */
        calculateUpgradeSwitch = function( downloadRatio, current, max, data ) 
        {
            $log("calculateUpgrade()");

            var calculateUpgradeIndex = function (ratios) 
                {
                    var i, len;
                    for ( i = 0, len = ratios.length; i < len; i += 1) 
                    {
                        if (downloadRatio < ratios[i]) {
                            break;
                        }
                    }
                    return i;
                }),
                ratios = [ ];

            while ((i += 1) < max) {
                ratios.push( checkRatio( i, current, data ));
            }

            return  Q.all( ratios )
                     .then( calculateUpgradeIndex )
                     .then( function( index )
                     {
                        $log("Calculated ideal upgrade quality index == " + index);
                        return new MediaPlayer.rules.SwitchRequest( index );
                     });
        },
        /**
         *
         */
        requestDowngradeSwitch = function (downloadRatio, currentIndex, data) 
        {
            $log("requestDowngrade()");

            return Q.all([
                        getIndexedBandwidth( currentIndex - 1, data ),
                        getIndexedBandwidth( currentIndex, data ) 
                    ])
                    .then( Q.spread(function (previous, current) 
                    {
                        var switchRatio = previous / current,
                            message     = (downloadRatio < switchRatio)                               ? 
                                          "Things must be going pretty bad, switch all the way down." :
                                          "Things could be better, so just switch down one index.",
                            newIndex    = (downloadRatio < switchRatio) ? 0 : currentIndex - 1;


                        $log( "Switch ratio: ", switchRatio );
                        $log( message );

                        return new MediaPlayer.rules.SwitchRequest( newIndex );
                    }));
        },
        /**
         *
         */
        requestUpgradeSwitch = function(downloadRatio, currentIndex, data )
        {
            $log("requestUpgrade()");

            return Q.all([
                        getIndexedBandwidth( currentIndex, data ),
                        getIndexedBandwidth( currentIndex + 1, data ),
                        getMaxBandwidth()
                    ])
                    .then( Q.spread(function (current, next, max ) 
                    {
                        var switchRatio = next / current,
                            newIndex    = downloadRatio < switchRatio ? current     :
                                          downloadRatio > 1000.0      ? max         :
                                          downloadRatio > 100.0       ? next        : -1,

                            message     = downloadRatio < switchRatio ? "Not enough bandwidth to switch up."              :
                                          downloadRatio > 1000.0      ? "Tons of bandwidth available, go all the way up." :
                                          downloadRatio > 100.0       ? "Just enough bandwidth available, switch up one." : 
                                                                        "Not exactly sure where to go, so do some math.";
                        $log( "Switch ratio: ", switchRatio );
                        $log( message );

                        return  (newIndex < 0) ? 
                                calculateUpgradeSwitch( downloadRatio, current, max, data ) :
                                new MediaPlayer.rules.SwitchRequest( newIndex );
                    }));
        };

        // ******************************************
        // Publis DownloadRatioRule instance and API 
        // ******************************************

        return self = {
            /**
             * Post-construction injection to logger reference
             */ 
            debug       : undefined,
            /**
             * Post-construction injection to Manifest model
             */
            manifestExt : undefined,
            /**
             * Check QoS to determine if upgrade or downgrade is warranted...  
             */
            checkIndex  : function (current, metrics, data)
            {
                var deferrred = Q.defer(),
                    info      = null;

                // Dynamic update specific closure variables
                $log              = ( self.debug && self.debug.log ) ? self.debug.log : noop;
                getRepresentation = _.bind( self.manifestExt.getRepresentationFor, self.manifestExt ),
                getBandwidth      = _.bind( self.manifestExt.getBandwidth,         self.manifestExt ),


                $log( "DownloadRatioRule::checkIndex()" );

                info = calculateTimesAndRatios( metrics );

                if ( !info )
                {
                    deferred.resolve ( 
                        // @TODO - is this correct ?
                        new MediaPlayer.rules.SwitchRequest( );
                    );

                } else {

                    if (info.downloadRatio < 1.0) 
                    {
                        $log("Download ratio is poor.");
                        if (current > 0) 
                        {
                            $log("We are not at the lowest bitrate, so switch down.");
                            deferred.resolve( 
                                requestDowngradeSwitch( info.downloadRatio, current, data ) 
                            );

                        } else {

                            $log("We are at the lowest bitrate and cannot switch down, use current.");
                            deferred.resolve( 
                                new MediaPlayer.rules.SwitchRequest( current ) 
                            );
                        }

                    } else {

                        $log("Upgrade possible since download ratio is good.");
                        deferred.resolve( 
                            requestUpgradeSwitch( info.downloadRatio, current, data ) 
                        );
                    }
                }

                return deferred.promise;
            }
        };
};

MediaPlayer.rules.DownloadRatioRule.prototype = {
    constructor: MediaPlayer.rules.DownloadRatioRule
};

@sebastienbarre
Copy link

Thanks for this example, Thomas. Keep up the good work, I don't think any of this was disrespectful to the people behind Dash.js or could be misconstrued as such -- I'm not sure John was a neutral party in this discussion, but his input certainly got the ball rolling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment