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

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

本文将涉及以下内容:

  • 介绍如何使用Reactive ExtensionsRx),演示这一系列操作用起来,就像写Hello World一样简单;
  • 用我自制的“准游戏引擎”FlysEngine,只需少量代码,即可实现桌面弹幕的效果;
  • 最后提供一波“伸手党”福利,文中所有可运行、完整代码,将按原样奉上。

Rx.NET

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

C#.NET 2.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示例代码如下:

Observable.Create<int>(async (o, cancellationToken) =>
{
for (var i = 0; i < 5; ++i)
{
await Task.Delay(1000);
o.OnNext(i);
}
o.OnCompleted();
})

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

使用Rx

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

DouyuBarrage.ChatMessageFromUrl("https://www.douyu.com/scboy")

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

Rx的其它好处

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

同时抓多个直播间的弹幕

#load ".\barrage.linq"
DouyuBarrage.ChatMessageFromUrl("https://www.douyu.com/scboy")
.Select(x => new { Room = "scboy", Message = x.Message })
.Merge(DouyuBarrage.ChatMessageFromUrl("https://www.douyu.com/topic/lscs?rid=633019")
.Select(x => new { Room = "lalala", Message = x.Message}))

效果如下:

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

扩展简单

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

public IObservable<JToken> JObjectFromUrl(string url)
=> RawFromUrl(url).Select(MsgTool.DecodeStringToJObject); public IObservable<Barrage> ChatMessageFromUrl(string url) =>
JObjectFromUrl(url)
.Where(x => x["type"].Value<string>() == "chatmsg")
.Select(Barrage.FromJToken);

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

桌面弹幕

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

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

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

桌面弹幕的要点

  • 渲染文字 DirectWrite
  • 文字移动 将文字从屏幕右边移动到左边;
  • 检测是否离开屏幕 如果屏幕上不显示弹幕,即可将弹幕删除;
  • 初始位置确定 如果一行显示不下,则将弹幕放在下一行。

渲染文字

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

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

文字移动

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

public void MoveLeft(float dt, float speed)
{
Position.X -= dt * speed;
}

检测是否离开屏幕

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

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

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

var node = barrages.First;
while (node != null)
{
var next = node.Next;
// do work here
node = next;
}

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

初始位置确定

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

float GetNewY()
{
float y = 0;
while (barrages.Reverse().Where(x => x.Position.Y == y).Select(x => x.Rect.Right).FirstOrDefault() > form.Width)
{
y += FontSize;
}
return y;
}

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

彩色emoji表情

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

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

target.DrawText("												

.NET斗鱼直播弹幕客户端(下)的更多相关文章

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

    .NET斗鱼直播弹幕客户端(2021) 离之前更新的两篇<.NET斗鱼直播弹幕客户端>已经有一段时间,近期有许多客户向我反馈刚好有这方面的需求,但之前的代码不能用了--但网上许多流传的No ...

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

    现在直播平台由于弹幕的存在,主播与观众可以更轻松地进行互动,非常受年轻群众的欢迎.斗鱼TV就是一款非常流行的直播平台,弹幕更是非常火爆.看到有不少主播接入弹幕语音播报器.弹幕点歌等模块,这都需要首先连 ...

  3. ubuntu下使用OBS开斗鱼直播

    系统环境:ubuntu 15.10,OBS Studio 0.13.1 OBS是可以在linux,windows,mac下直播的开源软件,官方地址:https://obsproject.com/ 斗鱼 ...

  4. android文件管理器源码、斗鱼直播源码、企业级erp源码等

    Android精选源码 文件清理管理器 自定义水平带数字的进度条以及自定义圆形带数字的进度条 利用sectionedRecyclerViewAdapter实现分组列表的recyclerView源码 流 ...

  5. Android Studio 直播弹幕

    我只是搬运:https://blog.csdn.net/HighForehead/article/details/55520199 写的很好很详细,挺有参考价值的 demo直通车:https://do ...

  6. Python爬虫实例(二)使用selenium抓取斗鱼直播平台数据

    程序说明:抓取斗鱼直播平台的直播房间号及其观众人数,最后统计出某一时刻的总直播人数和总观众人数. 过程分析: 一.进入斗鱼首页http://www.douyu.com/directory/all 进入 ...

  7. Scrapy项目 - 实现斗鱼直播网站信息爬取的爬虫设计

    要求编写的程序可爬取斗鱼直播网站上的直播信息,如:房间数,直播类别和人气等.熟悉掌握基本的网页和url分析,同时能灵活使用Xmind工具对Python爬虫程序(网络爬虫)流程图进行分析.   一.项目 ...

  8. iOS仿QQ侧滑菜单、登录按钮动画、仿斗鱼直播APP、城市选择器、自动布局等源码

    iOS精选源码 QQ侧滑菜单,右滑菜单,QQ展开菜单,QQ好友分组 登录按钮 3分钟快捷创建高性能轮播图 ScrollView嵌套ScrolloView(UITableView .UICollecti ...

  9. 使用Selenium模拟浏览器抓取斗鱼直播间信息

    获取斗鱼直播间每个房间的名称.观看人数.tag.主播名字 代码: import time from multiprocessing import Pool from selenium import w ...

随机推荐

  1. codeforces 811 E. Vladik and Entertaining Flags(线段树+并查集)

    题目链接:http://codeforces.com/contest/811/problem/E 题意:给定一个行数为10 列数10w的矩阵,每个方块是一个整数, 给定l和r 求范围内的联通块数量 所 ...

  2. 【Offer】[58-2] 【左旋转字符串】

    题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部.请定义一个函数实现字符串左旋转操作的功能.比如,输入字符串"a ...

  3. Codeforces1093E_Intersection of Permutations

    题意 给定两个排列a和b,两种操作,交换b_i和b_j,询问a[l_a...r_a]和b[l_b...r_b]有多少个数相同. 分析 由于给的是排列,保证b的每个数都有a的对应,构造数组c,c[i]表 ...

  4. Go从入门到放弃

    Go语言介绍 为什么你应该学习Go语言? 开发环境准备 从零开始搭建Go语言开发环境 VS Code配置Go语言开发环境 Go语言基础 Go语言基础之变量和常量 Go语言基础之基本数据类型 Go语言基 ...

  5. pandas可视化:各种图的简单使用

    一.Matplotlib中几种图的名字 折线图:plot 柱形图:bar 直方图:hist 箱线图:box 密度图:kde 面积图:area 散点图:scatter 散点图矩阵:scatter_mat ...

  6. NLP(十七)利用tensorflow-serving部署kashgari模型

      在文章NLP(十五)让模型来告诉你文本中的时间中,我们已经学会了如何利用kashgari模块来完成序列标注模型的训练与预测,在本文中,我们将会了解如何tensorflow-serving来部署模型 ...

  7. Unity3D_07_日志、文本打印

    1.Debug.Log(“hello”); 2.打开控制台查看日志:ctrl+shift+c 3.输出一个位置的坐标(需要转换成字符串.ToString()) Vector3 worldPositio ...

  8. STL中bitset的用法

    终于又来写博客了 == bitset存储的是二进数位,就和一个bool性数组差不多.用法上和数组的操作方式也差不多. 每位只占一个字节,大大优化了空间,可以通过数组形式访问. bitset定义 可以用 ...

  9. TestNG(六) 忽略测试

    package com.course.testng.suite; import org.testng.annotations.Test; public class IgnoreTest { @Test ...

  10. .Net轻量状态机Stateless

    很多业务系统开发中,不可避免的会出现状态变化,通常采用的情形可能是使用工作流去完成,但是对于简单场景下,用工作流有点大财小用感觉,比如订单业务中,订单状态的变更,涉及到的状态量不是很多,即使通过简单的 ...