给 VRChat 世界塞个真浏览器
vrc-browser-bridge 把一个真浏览器塞进 VRChat 世界里当屏幕用。VRChat 的 Udon 沙盒没 WebView 没 CEF 嵌不了 Chromium 连实时 socket 都开不了, 标准意义上的内嵌浏览器在这套环境里根本不存在。这项目的做法是绕开限制, 把浏览器留在外面: 本地跑一个真 Chromium, 画面走 RTSP 推成低延迟流送进世界当一块屏幕, 玩家在世界里的操作再转发回来驱动它。Chromium 始终是页面状态的唯一权威, 世界里看到的只是它的投影加一层遥控。
拆开是两条流加一条回路。控制方向是 mock 或 Udon 事件经 WebSocket 进来, 我用 Playwright/CDP 接住落到 Chromium; 媒体方向是 Chromium 画面经 FFmpeg 到 MediaMTX 推成 RTSP, 由世界里的 AVPro 播放。还有条日志回路: VRChat 把内容写进 output_log, 一个 Companion 去 tail 它, 把带 VRCBRIDGE 前缀的行捞出来送回控制服务器。走 log 是因为 Udon 没正经出网通道, log 反而是它唯一稳定能对外说话的地方。
事件协议单抽成了 @vrcbb/protocol。每条都是 JSON, 带固定 v: 1、sessionId、单调递增的 seq, 坐标统一归一化到 0..1; 进来先过校验, 再按 sessionId + playerId + source + seq 去重排序, 多人环境事件乱序重发是常态, 不做这层后面全是玄学 bug。控制分 open 和 locked: open 认所有合法事件, locked 得先抢控制锁(lock_request / lock_release 带 30 秒 TTL), 公共屏幕不能几个人同时点。操作覆盖指针移动/按下/抬起、滚轮、文字提交、Enter/Escape/Tab/Ctrl+L、URL 提交、前进后退刷新, 画质 360p/720p/1080p/auto 运行时可切。控制服务器另开了 /health、/media-state、/snapshot, snapshot 直接吐一张 JPEG 方便调试。
有两个点值得记。一是先做了个 mock 输入页, 不连 VRChat 也能把本地闭环跑通, 省得每改一行都戴头显进世界验证。二是 Windows 下 PowerShell 执行策略会拦 .ps1 shim, 直接用 npm.cmd / npx.cmd 绕过; FFmpeg 和 MediaMTX 不打包, 当外部二进制处理, 还留了 --dry-run 只打印命令不执行。
仓库是 npm workspaces 的 monorepo, 全程 TS strict, control-server / companion / media-pipeline / mock-input 各自成 app, 协议单独一个包。目前还在 WIP, 本地闭环通了, 媒体流和日志通路在逐步接, 版本还都是 0.1.0。