使用dash.js中的默认ABR算法及自定义ABR算法


一、默认ABR算法寻径

1. 寻找默认ABRRule文件

VS Code打开dash.js可以找到默认ABR算法源代码位置:/src/streaming/rules/abr

主要规则有:ThroughputRule.js,BolaRule.js(根据isDynamic()选择是否进行主要规则的动态使用);

次要规则有:AbandonRequestRule.js,DroppedFramesRule.js,InsufficientBufferRule.js,SwitchHistoryRule.js(默认状态下播放器将对比所在主要规则和所有次要规则的使用效果)。

2. 默认ABRRule文件调用过程回溯

以ThroughputRule为例,检索所有源码中ThroughputRule的引用情况,可以找到ABRRulesCollection.js中有import该文件。实际上,在ABRRulesCollection.js的import list中,可以看到所有默认ABRRule都被引用。

import ThroughputRule from './ThroughputRule';
import InsufficientBufferRule from './InsufficientBufferRule';
import AbandonRequestsRule from './AbandonRequestsRule';
import DroppedFramesRule from './DroppedFramesRule';
import SwitchHistoryRule from './SwitchHistoryRule';
import BolaRule from './BolaRule';
import FactoryMaker from '../../../core/FactoryMaker';
import SwitchRequest from '../SwitchRequest';

其中,ABRRulesCollection.js把所有ABR算法都归类为qualitySwitchRules。

const QUALITY_SWITCH_RULES = 'qualitySwitchRules';

在ABRRulesCollection.js中不难找到initialize()方法,可以看到该方法定义了qualitySwitchRules数组,并将所有import的ABRRule都push进去了,当然前提是Settings.js中的属性useDefaultABRRules为true。这里先忽略后面加入自定义ABRRule的步骤,后文会讲。

    function initialize() {
        qualitySwitchRules = [];
        abandonFragmentRules = [];

        if (settings.get().streaming.abr.useDefaultABRRules) {
            // Only one of BolaRule and ThroughputRule will give a switchRequest.quality !== SwitchRequest.NO_CHANGE.
            // This is controlled by useBufferOccupancyABR mechanism in AbrController.
            qualitySwitchRules.push(
                BolaRule(context).create({
                    dashMetrics: dashMetrics,
                    mediaPlayerModel: mediaPlayerModel,
                    settings: settings
                })
            );
            qualitySwitchRules.push(
                ThroughputRule(context).create({
                    dashMetrics: dashMetrics
                })
            );
            qualitySwitchRules.push(
                InsufficientBufferRule(context).create({
                    dashMetrics: dashMetrics
                })
            );
            qualitySwitchRules.push(
                SwitchHistoryRule(context).create()
            );
            qualitySwitchRules.push(
                DroppedFramesRule(context).create()
            );
            abandonFragmentRules.push(
                AbandonRequestsRule(context).create({
                    dashMetrics: dashMetrics,
                    mediaPlayerModel: mediaPlayerModel,
                    settings: settings
                })
            );
        }

        // add custom ABR rules if any
        const customRules = mediaPlayerModel.getABRCustomRules();
        customRules.forEach(function (rule) {
            if (rule.type === QUALITY_SWITCH_RULES) {
                qualitySwitchRules.push(rule.rule(context).create());
            }

            if (rule.type === ABANDON_FRAGMENT_RULES) {
                abandonFragmentRules.push(rule.rule(context).create());
            }
        });
    }

既然所有ABRRule都被放入ABRRulesCollection的qualitySwitchRules中,不妨检索ABRRulesCollection的引用情况,可以看到AbrController.js中有ABRRulesCollection初始化的步骤。

    function createAbrRulesCollection() {
        abrRulesCollection = ABRRulesCollection(context).create({
            dashMetrics: dashMetrics,
            mediaPlayerModel: mediaPlayerModel,
            settings: settings
        });

        abrRulesCollection.initialize();
    }

另外通过进一步检索可以找到MediaPlayer.js里面有AbrController的设置步骤。

    function createPlaybackControllers() {
        // ...
        abrController.setConfig({
            streamController: streamController,
            domStorage: domStorage,
            mediaPlayerModel: mediaPlayerModel,
            dashMetrics: dashMetrics,
            adapter: adapter,
            videoModel: videoModel,
            settings: settings
        });
        abrController.createAbrRulesCollection();
        // ...
    }

到这一步ABRRule的调用过程回溯就差不多了,因为MediaPlayer.js就是集成整个客户端媒体播放器大部分实现方法的地方,包括一些常用的get方法也能在这里找到。对于默认ABRRule的调用过程我们只需要知道个大概,主要是在进行自定义修改前最好可以知道整个框架是怎么走下来的,也对之后自定义时各种方法定义和接口调用有一个大概的了解。

3. 自定义ABRRule文件的使用

因为篇幅限制本文暂不阐述自定义ABRRule的代码实现,先说明一下在已经有了代码实现的情况下怎么把它放入dash.js中使用。本文以dash.js自带的自定义ABRRule文件LowestBitrateRule.js作为示例,这个文件是我在dash.js samples文档中学习Advanced section的Custom ABR Rules时找到的,通过网址栏找到dash.js中该网页HTML文件的位置,用Chrome进入示例播放器页面按F12查看HTML源码可以找到LowestBitrateRule.js和该index.html在同一个目录下:/samples/advanced/abr/LowestBitrateRule.js。

回看ABRRulesCollection.js中加入自定义ABRRule的步骤:

    function initialize() {
        qualitySwitchRules = [];
        abandonFragmentRules = [];
        // ...
        // add custom ABR rules if any
        const customRules = mediaPlayerModel.getABRCustomRules();
        customRules.forEach(function (rule) {
            if (rule.type === QUALITY_SWITCH_RULES) {
                qualitySwitchRules.push(rule.rule(context).create());
            }

            if (rule.type === ABANDON_FRAGMENT_RULES) {
                abandonFragmentRules.push(rule.rule(context).create());
            }
        });
    }

这里可以看到所有加入的自定义ABRRule都在rule里,而function(rule)是customRules.forEach()的参数,customRules由mediaPlayerModel.getABRCustomRules()返回值确定。再次通过检索可以找到mediaPlayerModel.js中关于ABRCustomRule的方法:

function MediaPlayerModel() {

    let instance,
        UTCTimingSources,
        xhrWithCredentials,
        customABRRule;

    const DEFAULT_UTC_TIMING_SOURCE = {
        scheme: 'urn:mpeg:dash:utc:http-xsdate:2014',
        value: 'http://time.akamai.com/?iso&ms'
    };
    const context = this.context;
    const settings = Settings(context).getInstance();

    function setup() {
        UTCTimingSources = [];
        xhrWithCredentials = {
            default: DEFAULT_XHR_WITH_CREDENTIALS
        };
        customABRRule = [];
    }

    //TODO Should we use Object.define to have setters/getters? makes more readable code on other side.
    function findABRCustomRuleIndex(rulename) {
        let i;
        for (i = 0; i < customABRRule.length; i++) {
            if (customABRRule[i].rulename === rulename) {
                return i;
            }
        }
        return -1;
    }

    function getABRCustomRules() {
        return customABRRule;
    }

    function addABRCustomRule(type, rulename, rule) {
        if (typeof type !== 'string' || (type !== ABRRulesCollection.ABANDON_FRAGMENT_RULES && type !== ABRRulesCollection.QUALITY_SWITCH_RULES) ||
            typeof rulename !== 'string') {
            throw Constants.BAD_ARGUMENT_ERROR;
        }
        let index = findABRCustomRuleIndex(rulename);
        if (index === -1) {
            // add rule
            customABRRule.push({
                type: type,
                rulename: rulename,
                rule: rule
            });
        } else {
            // update rule
            customABRRule[index].type = type;
            customABRRule[index].rule = rule;
        }
    }

    function removeABRCustomRule(rulename) {
        if (rulename) {
            let index = findABRCustomRuleIndex(rulename);
            //if no rulename custom rule has been found, do nothing
            if (index !== -1) {
                // remove rule
                customABRRule.splice(index, 1);
            }
        } else {
            //if no rulename is defined, remove all ABR custome rules
            customABRRule = [];
        }
    }
    // ...
}

其中,findABRCustomRuleIndex(rulename)用于检索现有customABRRule里有没有和CustomRule一样的rulename,如果没有则返回-1,如果有则返回相应index。addABRCustomRule(type, rulename, rule)用于将CustomRule加入到customABRRule集合中,如果index为-1则push一个新Rule,否则更新现有同名的Rule。getABRCustomRules()用于返回customABRRule集合。

那么问题来了,我们如何调用addABRCustomRule(type, rulename, rule)。之后通过查阅dash.js的API documentation可以发现这个方法可以直接再HTML代码中调用。以下给出示例完整的HTML代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <title>Custom ABR Rules</title>

    <script src="../../../dist/dash.all.debug.js"></script>
    <!--dash.all.min.js should be used in production over dash.all.debug.js
        Debug files are not compressed or obfuscated making the file size much larger compared with dash.all.min.js-->
    <!--<script src="../../dist/dash.all.min.js"></script>-->

    <script src="LowestBitrateRule.js" class="code"></script>

    <script class="code">
        function init() {
            var video,
                player,
                url = "https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd";

            video = document.querySelector("video");
            player = dashjs.MediaPlayer().create();

            /* don't use dash.js default rules */
            player.updateSettings({
                'streaming': {
                    'abr': {
                        'useDefaultABRRules': false
                    }
                }
            });

            /* add my custom quality switch rule. Look at LowestBitrateRule.js to know more */
            /* about the structure of a custom rule */
            player.addABRCustomRule('qualitySwitchRules', 'LowestBitrateRule', LowestBitrateRule);

            player.initialize(video, url, true);
        }
    </script>

    <style>
        video {
            width: 640px;
            height: 360px;
        }
    </style>
</head>
<body>
<div>
    <video></video>
</div>
<script>
    document.addEventListener("DOMContentLoaded", function () {
        init();
    });
</script>
<script src="../../highlighter.js"></script>
</body>
</html>

可以发现示例网页中直接把LowestBitrateRule.js作为src脚本导入,然后通过player.addABRCustomRule()调用API进行自定义ABRRule嵌入。值得注意的是在此之前代码中调用player.updateSettings()把默认Settings中的useDefaultABRRules设为false,即屏蔽所有默认ABRRule,这样在拉流的时候播放器只会按照LowestBitrateRule拉取最低码率的视频,通过运行HTML网页可以测试调用成功。

使用dashjs中的默认ABR算法及自定义ABR算法
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。