.NET 斗鱼直播弹幕客户端(下)

前言

在上篇文章中,我们提到了如何使用 .NET 连接斗鱼TV直播弹幕的基本操作。然而想要做得好,做得容易扩展,就需要做进一步的代码整理。

本文将涉及以下内容:

  • 介绍如何使用  ReactiveExtensions (  Rx ),演示这一系列操作用起来,就像写  HelloWorld 一样简单;

  • 用我自制的“准游戏引擎”  FlysEngine ,只需少量代码,即可实现桌面弹幕的效果;

  • 最后提供一波“伸手党”福利,文中所有可运行、完整代码,将按原样奉上。

Rx.NET

Rx ,是 ReactiveExtensions 的缩写,据说 Rx 发明于 .NET2.0 时代的微软。那时候还没有 async/await 。后来,也许由于 RX 对编程语言要求不高(如不要求内置 协程coroutine ), RX 反倒在 .NET 之外的其它编程语言中大行其道。如 rx.jsRxJava 等等。

C#.NET2.0 就提供了 yield 关键字,然后 3.0 提供了 LINQ5.0 提供了 async/await ,因此很多时候 RX 的意义不大。但在某些情况下(如这种情况),就有意义了,原因请见下图:

单数据 多数据
同步 T IEnumerable<T>
异步 Task<T> Observable<T>IAsyncEnumerable<T>

C#协程 支持同步多数据,异步单数据,但不支持同步多数据( C# 8.0 现在已经支持 IAsyncEnumerable<T> ),本文将使用 Rx 来包装上一篇文章的斗鱼TV直播弹幕客户端。

来先看一波老代码:

注意剪头所指的位置,那是基础代码“出口”,或者业务逻辑“入口”,基础代码不能简单地 return 打断,因为它要不停地输出数据,这时就需要像 协程 等编程语言功能,或者 Rx 的支持。

Rx -Hello World

首先引入 NuGetSystem.Reactive ,一个简单的“异步多值返回”的 Rx 示例代码如下:

麻雀虽小,五脏俱全,如代码所示,几乎只需在正常代码外包一层 Rx ,即可享受 Rx 的好处。

使用 Rx

使用起来就更简单了,上篇展示的长达 252 行代码的 demo ,现在只需一行代码,即可无侵入式地调用:

调用结果如下(和昨天效果完全一样):

Rx 的其它好处

除了调用简单之外, Rx 的扩展也非常非常简单,比如完成以下操作,以前可能非常麻烦,需要改多处代码,而使用 Rx ,只需像 LINQ 一样加几个指令即可:

同时抓多个直播间的弹幕

效果如下:

只需一个 Merge 指令即可合并两个直播间的弹幕( Observable<T>

扩展简单

比如只想提取特殊的弹幕,或者数据之前想做一些转换,可以使用 WhereSelect 等数据过滤和转换操作符,符合 LINQ 的习惯,非常好用。比如我正常弹幕的提取,其实是从 JObjectFromUrl 转换而来, JObjectFromUrl ,又是从 RawFromUrl 转换而来,这提高了扩展性,又无需修改老代码,正是所谓“对扩展开放,对修改封闭”的开放-封闭原则:

又比如可能我只想提取彩色弹幕,我只需 ChatMessageFromUrl().Where(x=>x.Color!=0xffffff) 即可,非常方便。

桌面弹幕

这可能是另一个主题——实时渲染,用到了我自己写的“准游戏引擎” FlysEngine ,因此需要安装 NuGet 包: FlysEngine.Desktop

桌面弹幕 不同于 网页弹幕 ,只能在网页中显示,而 桌面弹幕 可以直接显示在屏幕最上方。有些公司年会可能用到了 桌面弹幕 ,这无疑增加了主持人与观众们的互动,提高了群众参与的积极性。

注意:本文中所说 FlysEngine 的实质是 Direct2DWindowsAPIUpdateLayeredWindowIndirect 函数。如果不想使用 FlysEngine ,完全可以使用其它方式代替。最简单的方式是使用 WPF ,然后设置 AllowsTransparency=true ,但这样性能会差一些。本文介绍的方法, CPU 使用率将保持在 0% 左右!

桌面弹幕的要点

  • 渲染文字 DirectWrite

  • 文字移动将文字从屏幕右边移动到左边;

  • 检测是否离开屏幕如果屏幕上不显示弹幕,即可将弹幕删除;

  • 初始位置确定如果一行显示不下,则将弹幕放在下一行。

渲染文字

渲染文字一般是通过 DirectWrite ,它性能很好,功能也强大。 FlysEngineDirectWrite 封装了,因此直接用便是。

注意: DirectWrite 不仅渲染文字,还提供了 .Metrics 属性,可以计算文字 渲染之后 的大小,这会让事情变得容易很多。

文字移动

文字移动首先需要一个位置,随着时间变化,将该位置的 X 坐标不段减少即可。这可以通过 FlysEngine 中的 UpdateLogic 事件实现,它会定期调用,传入一个 floatdt ,代码离上一次调用 UpdateLogic 的时间间隔。因此可以利用这个 dt 变量,计算是弹幕的新位置:

检测是否离开屏幕

由于我们已知弹幕是矩形,(很显然屏幕也是矩形)因此这个检测比较简单,直接判断文字的 右边缘 是否 大于0 即可。

也由于需要经常/频繁地删除在屏幕上的弹幕对象,因此最好储存弹幕的数据结构别使用 O(n) 的集合,如最好别使用 List<T> ,它是线性表。我这里使用的是 链表.NET 的链表实现是 LinkedList<T> (很多人以为是 List<T> )。

多说一句,链接的遍历算法如下( while 循环):

之所以不使用 foreach 来遍历,因为这样遍历可以实现高性能的“边遍历、边删除”的实现。

初始位置确定

这一点思想需要多想想,需要从第一行开始,从后往前看,看最后那一边弹幕是否大于屏幕右边。只要想清楚了,代码很容易:

有了这些,就可以愉快地感受屏幕弹幕啦!

彩色 emoji 表情

Direct2D 支持——但默认不显示弹幕 emoji 表情:

要多加一个枚举让其支持:

支持彩色 emoji 表情后,效果如下:

最终效果昨天已经见过了,如下:

本文(包括上文)所用的代码如下:

id 链接
老式代码 https://github.com/sdcb/blog-data/blob/master/2019/20191013-douyu-barrage-with-dotnet-2/barrage_tranditional.linq
新式代码 https://github.com/sdcb/blog-data/blob/master/2019/20191013-douyu-barrage-with-dotnet-2/barrage.linq
合并弹幕 https://github.com/sdcb/blog-data/blob/master/2019/20191013-douyu-barrage-with-dotnet-2/barrage-combine.linq
桌面弹幕 https://github.com/sdcb/blog-data/blob/master/2019/20191013-douyu-barrage-with-dotnet-2/desktop-barrage.linq

喜欢的朋友请“刷一波666:rocket::rocket::rocket:”,并关注我的微信公众号:【DotNet骚操作】