使用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算法
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356