V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
蒲公英 - 🚀上传App→生成二维码→扫码安装
支持网页上传 / 手机上传 / API 上传 / CI-CD 集成 / 全球 CDN 加速 / 密码访问 / 安装统计 / 二维码分享 / 不限安装次数 / 永久免费
Promoted by mzshxz
tiajinsha
V2EX  ›  分享创造

B 站前端开发工程师被裁员,开源了 B 站 app 源码。请大家支持一下方便俺后续找工作🥹

  •  3
     
  •   tiajinsha · 3 小时 22 分钟前 · 1270 次点击

    我用 React Native 做了一个功能完整的 B 站客户端,为了规避法律问题,所以具体代码我重新写了一遍。

    从 DASH 解码、弹幕系统到 WBI 签名,一个人把该踩的坑全踩了一遍


    先看效果

    截图预览


    <sub>首页热门 · 内联视频 · 穿插直播</sub>

    <sub>视频详情 · 简介 · 推荐视频</sub>

    <sub>竖屏播放 · 4K HDR · 多清晰度</sub>

    <sub>下载管理 · 局域网分享二维码</sub>

    <sub>直播 Tab · 关注主播在线 · 分区筛选</sub>

    <sub>直播详情 · 实时弹幕 · 舰长标记</sub>

    全宽内联视频、飘屏弹幕、实时直播、扫码登录、离线下载……大部分你在官方客户端能用到的核心功能,这个项目基本都有。

    跨平台运行,Android / iOS / Web 三端一套代码,Expo Go 扫码 5 分钟就能跑起来,不需要任何编译环境。


    为什么做这个

    B 站的开放接口虽然没有官方文档,但社区里已经有大量逆向分析的积累。我想验证一件事:用 React Native 能不能做出一个体验接近原生的视频应用,并且把视频播放、弹幕、直播这些最硬核的部分都做到位。

    结论是:可以,但需要踩很多坑。


    技术架构

    React Native 0.83 + Expo SDK 55
    ├── 路由        expo-router v4 (文件系统路由)
    ├── 状态管理    Zustand
    ├── 网络请求    Axios (自动注入 Cookie / WBI 签名)
    ├── 视频播放    react-native-video ( DASH MPD / HLS / MP4 )
    ├── 降级播放    react-native-webview ( HTML5 video 注入)
    ├── 页面滑动    react-native-pager-view
    └── 本地存储    @react-native-async-storage/async-storage
    

    整体是标准的文件系统路由结构,app/ 下每个文件对应一个页面,Stack 导航管理跳转层级。状态全部走 Zustand ,没有用 Redux 那一套,轻量很多。


    最硬的部分:DASH 视频播放

    B 站的高清视频走的是 DASH 协议,服务端返回的是分离的 video stream 和 audio stream ,需要客户端自己做 mux 。

    浏览器的 <video> 标签支持 MSE ( Media Source Extensions ),可以直接喂 DASH 数据。但 React Native 里的 react-native-video 底层走的是 Android ExoPlayer ,它需要一个标准的 .mpd 文件才能工作。

    B 站接口返回的是 JSON ,不是 MPD ,所以我手写了一个转换器:

    // utils/dash.ts
    export function buildDashMpdUri(dashData: DashData): string {
      const videoRep = dashData.video
        .map(v => `
          <Representation id="${v.id}" bandwidth="${v.bandwidth}"
            codecs="${v.codecs}" width="${v.width}" height="${v.height}" frameRate="${v.frameRate}">
            <BaseURL>${escapeXml(v.baseUrl)}</BaseURL>
            <SegmentBase indexRange="${v.segmentBase.indexRange}">
              <Initialization range="${v.segmentBase.initialization}"/>
            </SegmentBase>
          </Representation>`)
        .join('');
    
      const audioRep = dashData.audio
        .map(a => `
          <Representation id="${a.id}" bandwidth="${a.bandwidth}" codecs="${a.codecs}">
            <BaseURL>${escapeXml(a.baseUrl)}</BaseURL>
            <SegmentBase indexRange="${a.segmentBase.indexRange}">
              <Initialization range="${a.segmentBase.initialization}"/>
            </SegmentBase>
          </Representation>`)
        .join('');
    
      const mpd = `<?xml version="1.0" encoding="UTF-8"?>
    <MPD xmlns="urn:mpeg:dash:schema:mpd:2011" ...>
      ...${videoRep}${audioRep}...
    </MPD>`;
    
      // 转成 data URI ,直接喂给 react-native-video
      return `data:application/dash+xml;base64,${btoa(unescape(encodeURIComponent(mpd)))}`;
    }
    

    生成的 MPD data URI 直接传给 <Video source={{ uri: mpdUri }} />,ExoPlayer 拿到合法的 MPD 就能正确解码 1080P+ / 4K HDR 内容。


    WBI 签名:纯 TypeScript 手写 MD5

    B 站在 2023 年给核心 API 加了 WBI 签名校验。每次请求需要:

    1. /x/web-interface/nav 拉取 img_urlsub_url,从路径中提取密钥字符
    2. 按固定顺序重排密钥,取前 32 位拼出 mixin_key
    3. 在请求参数里加入 wts(当前时间戳),用 mixin_key 对参数做 MD5 签名,附上 w_rid

    关键在于 MD5——React Native 环境里没有 Node.js 的 crypto 模块,npm 上的 MD5 库又大多依赖 Buffer ,在 Hermes 引擎上会有兼容问题。

    最终选择自己实现了一个精简版 MD5 ,纯字符串操作,零外部依赖,在所有平台上跑得很稳。nav 接口结果缓存 12 小时,避免频繁请求。


    弹幕系统:两套机制,一套界面

    视频弹幕

    通过 /x/v1/dm/list.so 拉取 XML 格式的全量弹幕,解析后得到带时间戳的弹幕列表。播放时按当前进度 drip (逐帧滴入)到飘屏层。

    飘屏层(DanmakuOverlay)维护 5 条轨道,每条弹幕根据文字长度计算飞行时间,自动选择未被占用的轨道,避免重叠。颜色、字号、舰长标记全部还原。

    直播弹幕

    完全不同的机制——WebSocket 长连接,B 站直播弹幕走的是私有二进制协议(头部 16 字节,含包长度、协议版本、操作码、序列号)。

    useLiveDanmaku Hook 处理连接建立、心跳保活(每 30 秒发一次 [object Object] 心跳包)、Zlib 解压和数据包解析。支持普通弹幕、礼物事件、进场通知,舰长弹幕有独立标记和颜色。直播弹幕实时追加,保留最近 500 条。


    首页内联视频:BigVideoCard

    列表里的精选视频直接内联播放,静音自动开始,可见时播放、离屏时暂停。

    额外实现了水平手势快进:用 PanResponder 监听水平滑动,每 dx 像素对应一定秒数的快进/快退,滑动时显示浮层时间标签,松手跳转。进度条和缓冲条实时更新,和视频状态完全同步。


    全局迷你播放器

    这是体验细节里比较难做的一个功能。要求是:从视频详情页退出后,底部出现迷你播放器,续播当前视频,不会因为页面卸载而中断。

    实现方式:videoStore( Zustand )保存当前播放视频的元数据和播放状态,MiniPlayer 组件挂载在根布局 _layout.tsx 里,与路由层级完全解耦。视频详情页卸载时不销毁播放器,只是把控制权交给 MiniPlayer ,播放状态无缝衔接。


    扫码登录

    完整实现了 B 站的 OAuth 扫码流程:

    1. 调用 generateQRCode() 获取 qrcode_key 和二维码内容
    2. 用第三方二维码渲染服务生成图片展示给用户
    3. 每 2 秒轮询 pollQRCode,监听 code 字段变化
    4. 扫码确认后(code === 0),从响应头的 set-cookie 字段提取 SESSDATA
    5. 写入 AsyncStorage ,后续所有请求自动携带

    登录状态通过 authStore 持久化,App 启动时自动恢复。


    离线下载 + 局域网分享

    下载功能支持多清晰度选择( 720P / 1080P / 1080P+),后台队列下载,进度实时显示在导航栏角标。

    有一个比较有意思的功能:下载完成后,可以一键启动内置 HTTP 服务器,生成局域网访问二维码。同一 Wi-Fi 下的任意设备扫码,直接在浏览器播放已下载的视频,不需要任何额外 App 。


    技术栈总结

    技术选型 理由
    框架 React Native 0.83 + Expo SDK 55 跨平台,生态成熟
    路由 expo-router v4 文件系统路由,类 Next.js 体验
    状态 Zustand 轻量,无 boilerplate
    视频 react-native-video ExoPlayer 底层,支持 DASH/HLS
    弹幕 自研 两套协议,一套 UI
    WBI 签名 自研纯 TS MD5 零依赖,全平台兼容
    下载 自研队列 + HTTP server 支持局域网分享

    运行方式

    Expo Go ( 5 分钟,无需编译)

    git clone https://github.com/tiajinsha/JKVideo.git
    cd JKVideo
    npm install
    npx expo start
    

    用 Expo Go 扫码即可,视频降级为 WebView 方案。

    Dev Build (完整功能,推荐)

    npm install
    npx expo run:android
    

    解锁 DASH 原生播放、完整弹幕系统。

    直接安装( Android ) 前往 Releases 下载 APK ,安装即用。


    最后

    这个项目从零开始大概花了三周,期间踩的最深的坑是 DASH MPD 拼装( ExoPlayer 对 MPD 格式极为严格,差一个属性就报 SOURCE_ERROR)和直播弹幕的二进制协议解析(官方没有任何文档,全靠抓包逆向)。

    整个项目代码完全开源,欢迎 Star 、Fork 、提 Issue 和 PR 。

    GitHub:https://github.com/tiajinsha/JKVideo

    如果觉得有帮助,点个 Star 是对我最大的鼓励 ⭐

    22 条回复    2026-03-23 23:43:13 +08:00
    noway5566
        1
    noway5566  
       3 小时 5 分钟前
    图裂了。。。
    不是哥们 你么你真的被开了 60% 么?
    tiajinsha
        2
    tiajinsha  
    OP
       3 小时 3 分钟前
    嗯嗯
    stucom
        3
    stucom  
       3 小时 0 分钟前
    已 star ,学习一下
    tiajinsha
        4
    tiajinsha  
    OP
       2 小时 52 分钟前
    感谢
    jchencode
        5
    jchencode  
       2 小时 50 分钟前
    666 ,支持下,哈哈哈,卧槽
    yusf
        6
    yusf  
       2 小时 22 分钟前
    哥们为啥被裁员啊?
    loshine1992
        7
    loshine1992  
       2 小时 20 分钟前
    哥们小心哈,这个行为有一定风险的
    tiajinsha
        8
    tiajinsha  
    OP
       2 小时 10 分钟前
    @loshine1992 ,知道了
    tiajinsha
        9
    tiajinsha  
    OP
       2 小时 9 分钟前
    @yusf ai 太强大了呗,原来 2 个人的活儿,现在只需要一个人了😭
    Ricciardo
        10
    Ricciardo  
       1 小时 38 分钟前
    吓我一跳,看标题以为被裁后报复 B 站给阿 B 开源了
    hepin1989
        11
    hepin1989  
       37 分钟前
    做的非常对!
    JoeDH
        12
    JoeDH  
       36 分钟前
    凡人修仙传那天最高冲到了多少万
    rykka
        13
    rykka  
       33 分钟前
    支持,先 star ,再下载
    Ghrhrrv146
        14
    Ghrhrrv146  
       22 分钟前 via iPhone
    老哥注意法律风险
    bigdogbigpig
        15
    bigdogbigpig  
    PRO
       16 分钟前
    牛的,好手,祝福
    darlinghsu
        16
    darlinghsu  
       11 分钟前 via iPhone
    卧槽 前排 mark
    duanlian
        17
    duanlian  
       11 分钟前
    star 了,牛逼啊~
    pprocket
        18
    pprocket  
       9 分钟前
    看了下你说的 web 端防盗链和 4k 画质的限制,都是可以绕过的
    a1105288116
        19
    a1105288116  
       8 分钟前
    太强了
    tiajinsha
        20
    tiajinsha  
    OP
       5 分钟前
    @pprocket web 可以本地代理解决盗链,app 端可以伪造响应头
    EastLord
        21
    EastLord  
       1 分钟前 via iPhone
    是真的吗
    tiajinsha
        22
    tiajinsha  
    OP
       1 分钟前
    但是部署到线上,视频带宽解决不了
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   2789 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 15:44 · PVG 23:44 · LAX 08:44 · JFK 11:44
    ♥ Do have faith in what you're doing.