一个 Dart 应用看四大直播平台:六端部署、自研弹幕协议
【免费下载链接】dart_simple_live简简单单的看直播项目地址: https://gitcode.com/GitHub_Trending/da/dart_simple_live
你在 B 站、斗鱼、虎牙、抖音四个平台看直播,通常要装四个 App。Simple Live(dart_simple_live)把它们塞进了同一个 Dart 直播应用:房间列表、清晰度切换、弹幕接收共用一套核心库,一份代码跑 Android、iOS、Windows、macOS、Linux、Android TV 六端。
30 秒速览
- 是什么:一个 Flutter 跨平台直播客户端,口号是"简简单单的看直播"。仓库实际是四个项目组成的 monorepo,分工如下表。
- 解决什么问题:四个平台的房间接口、播放流、弹幕协议完全不同,核心库用
LiveSite和LiveDanmaku两个统一接口把它们全部包掉,上层代码不用关心具体平台。 - 适合谁:想白嫖多平台直播的观众(六端覆盖)、想学 WebSocket 弹幕协议对接的开发者、在做 Flutter 直播应用开发的人。
- ⚠️注意:README 明确不提供 Release 安装包,必须自行编译。
| 模块 | 定位 | 状态 |
|---|---|---|
simple_live_core | 核心库:站点数据 + 弹幕协议 | 主力,纯 Dart |
simple_live_app | 手机/PC 客户端 | Android/iOS 稳定,桌面三端 BETA |
simple_live_tv_app | Android TV 客户端 | BETA |
simple_live_console | 纯 Dart 控制台程序 | 验证核心库用 |
环境搭建 & 快速跑通
- Flutter SDK 要求
3.38(README 声明的环境版本)。 - 核心库是本地 path 依赖,app 的 pubspec 里这样写的:
simple_live_core: path: ../simple_live_core- 跑手机/PC 端:
git clone https://gitcode.com/GitHub_Trending/da/dart_simple_live cd dart_simple_live/simple_live_app flutter pub get flutter run- 只想验证核心库(搜房间、拉播放流、收弹幕),直接跑
simple_live_console,纯 Dart,不用平台打包。
核心能力逐层拆解
统一四平台数据:LiveSite 接口抽象
痛点:四个平台的分类、房间列表、清晰度、播放流接口返回结构互不相同,App 层如果按平台写 if-else 会迅速失控。
思路:LiveSite是基类,四个站点各写一个实现类,方法签名固定,上层拿roomId就能换出房间详情和播放流。
// simple_live_core/lib/src/interface/live_site.dart(精简) class LiveSite { String id = ""; String name = ""; Future<List<LiveCategory>> getCategores() => Future.value(<LiveCategory>[]); Future<LiveRoomDetail> getRoomDetail({required String roomId}) => ...; Future<List<LivePlayQuality>> getPlayQualites( {required LiveRoomDetail detail}) => ...; Future<LivePlayUrl> getPlayUrls( {required LiveRoomDetail detail, required LivePlayQuality quality}) => ...; LiveDanmaku getDanmaku() => LiveDanmaku(); }坑:各平台的"房间 ID"不是一回事。B 站外显 roomid 要先查一次getInfoByRoom换成内部 id 才能走后续接口;抖音要同时持有webRid和roomId两个值(参考simple_live_core/lib/src/douyin_site.dart)。基于这套接口做二次开发时,ID 映射是最先要处理的事。
拿到能播的流:三套签名算法
痛点:播放流链接不是简单 GET 能拿到的。B 站要 wbi 签名,抖音、斗鱼要 JS 级签名,这是整个 core 库里代码量最大的部分。
B 站 wbi 的套路:取img_key/sub_key,按固定乱序表拼出 32 位 mixin key,参数排序后拼串做 MD5:
// simple_live_core/lib/src/bilibili_site.dart(节选) String getMixinKey(String origin) { return mixinKeyEncTab.fold("", (s, i) => s + origin[i]).substring(0, 32); } Future<Map<String, String>> getWbiSign(String url) async { var (imgKey, subKey) = await getWbiKeys(); // 静态缓存,会话内只取一次 var mixinKey = getMixinKey(imgKey + subKey); var params = Map<String, String>.from(Uri.parse(url).queryParameters); params["wts"] = (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(); // 参数按 key 排序、value 过滤 "!'()*" 后拼接(此处省略几行) var query = /* 拼接好的参数字符串 */; params["w_rid"] = md5.convert(utf8.encode("$query$mixinKey")).toString(); return params; }抖音的 a_bogus 和斗鱼的ub98484234签名原本是网页 JS。项目的做法是在纯 Dart 里嵌入 QuickJS 引擎(git 依赖dart_quickjs),把网页签名脚本抠出来直接执行:
// simple_live_core/lib/src/scripts/douyu_sign.dart static String getSign(String html, String rid) { JsRuntime js = JsRuntime(memoryLimit: 4 * 1024 * 1024); js.eval(kCryptoJs); // CryptoJS 库 js.eval(html); // 页面里抠出的签名函数 var data = js.eval("ub98484234('$rid','$did','$time')"); js.dispose(); return data; }坑:UA 和 cookie 必须配套。B 站请求要先从 spi 接口取buvid3/buvid4塞进 cookie,这套逻辑在getHeader()里;只抄 wbi 签名不抄 buvid 部分,接口会直接返回 -412。
接通弹幕流:四种二进制协议
痛点:四家弹幕全是 WebSocket,但帧格式各不相同——B 站是 16 字节头二进制,虎牙是 Tars 二进制,斗鱼是自定义分帧,抖音是 protobuf。
思路:LiveDanmaku接口统一收口,每个平台一个实现类,内部各自解析,对外只吐统一模型LiveMessage(类型、用户名、内容、颜色)。心跳和重连封装在公共的WebScoketUtils里。以斗鱼的入房帧为例,逻辑很直白:
// simple_live_core/lib/src/danmaku/douyu_danmaku.dart void joinRoom(roomId) { webScoketUtils?.sendMessage( serializeDouyu("type@=loginreq/roomid@=$roomId/")); webScoketUtils?.sendMessage( serializeDouyu("type@=joingroup/rid@=$roomId/gid@=-9999/")); }🔌 四家协议对比:
| 平台 | 帧格式 | 心跳间隔 | 压缩 |
|---|---|---|---|
| B 站 | 16 字节头二进制 | 60s | brotli |
| 虎牙 | Tars 二进制(自研tars_dart编解码) | 60s | 无 |
| 斗鱼 | 自定义 STT 分帧 | 45s | 无 |
| 抖音 | protobuf | 10s | gzip |
虎牙用的 Tars 协议项目在simple_live_core/packages/tars_dart/里自带了一套 Dart 实现(输入/输出流、结构体编解码),不想查外部资料的话直接参考这个目录的源码。
坑:斗鱼弹幕要过滤"阴间弹幕"——消息里没有dms字段的就是刷出来的异常弹幕,代码里直接return丢弃,不过滤的话弹幕区会被垃圾内容刷屏。
把同一套核心部署到六个端:App 层组装
痛点:播放、弹幕、同步这套能力要同时跑在手机上、PC 上、电视遥控器上,各端资源与交互差异大。
思路:App 层完全不实现直播逻辑,只做 UI 组装——播放走media_kit,弹幕渲染走canvas_danmaku,数据同步给了三条路:局域网 UDP 直连、SignalR 远端同步、WebDAV:
# simple_live_app/pubspec.yaml(节选) media_kit: ^1.2.2 # 视频播放 canvas_danmaku: ^0.2.7 # 弹幕渲染 floating: ^6.0.0 # PIP 画中画 extended_image: ^10.0.1 # 图片缓存 signalr_netcore: ^1.4.4 # 远端同步坑:电视端和手机端是两个独立壳工程(simple_live_tv_app),共用 core 但 UI 体系完全不同(TV 版围绕焦点导航重写),别指望手机端的 PIP、画中画等功能在电视端原样存在。桌面三端目前是 BETA。
踩坑实录 / 实战 Q&A
- Q:
pub get会失败吗?会,如果你连不上 git 源。simple_live_core依赖 git 上的dart_quickjs(JS 引擎),pub 解析时需要能拉取该仓库;网络受限时先备好 git 代理或缓存。 - Q:为什么 B 站分类接口第一次调不通?首次请求要顺路取
buvid3/buvid4,wbi key 也是静态缓存的。这两步都藏在bilibili_site.dart的getHeader()和getWbiKeys()里——参考源码时请整段抄,别只抄签名函数。 - Q:弹幕连上几秒后断了,正常吗?正常,心跳不发平台就断你。间隔因平台而异(10~60s,见上表),
WebScoketUtils已统一处理自动重连,断开时通过onClose回调把"正在尝试重连"抛给 UI 层。 - Q:有没有现成的编译包能下?没有。README 第一行就写了"本项目不提供 Release 安装包,请自行编译后运行测试"。
- Q:不登录账号能用吗?能。公开房间可看流、可收弹幕;登录 B 站账号(扫码或网页登录)主要影响清晰度档位和 SC 等付费信息展示。
性能与体验红线
直播类应用就四件事不能松:连接保持、断线恢复、弹幕渲染开销、图片内存。
| 指标 | 参考值 | 达成手段 |
|---|---|---|
| 弹幕保活 | 心跳 10s~60s 分平台 | 各LiveDanmaku.heartbeatTime |
| 断线恢复 | 自动重连,UI 有提示 | WebScoketUtils重连回调 |
| 弹幕渲染 | Canvas 逐帧绘制 | canvas_danmaku,不碰 DOM |
| 图片内存 | 本地缓存 + 降采样 | extended_image |
路线图 & 社区
目前手机端与 iOS 是稳定主力,桌面三端和 TV 端仍在 BETA,核心库是唯一"非实验性"的部分。想深入协议细节,从两个目录入手:simple_live_core/lib/src/(四个站点实现与弹幕协议)和simple_live_core/packages/tars_dart/(自研 Tars 编解码)。测试用例可以看simple_live_core/test/和simple_live_console/test/。同作者的 C# 版 AllLive 也是同类实现,可交叉对照协议思路。
收束
一套核心代码,吃下四家直播平台的流和弹幕,再铺到六种设备上——这就是 Simple Live 的价值。clone 下来跑simple_live_console或 App,10 分钟能见到画面。
【免费下载链接】dart_simple_live简简单单的看直播项目地址: https://gitcode.com/GitHub_Trending/da/dart_simple_live
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考