perf(stream): 加速进入串流的用户感知体验#27
Conversation
合并 6 项优化让点击应用→看到画面更快: 1. ServerInfo 缓存复用 (TTL 30s) ComputerManager 新增 cacheServerInfo/getCachedServerInfo。 轮询路径 (updateComputerFromPoll) 与 AppListPage 刷新均写入缓存; StreamingSession.fetchServerInfo 命中即跳过一次 HTTPS round-trip (典型局域网省 50-300ms)。 2. AI LLM 探测延后到串流就绪后 StreamPage.aboutToAppear 不再立即调用 nvHttp.checkAiAvailable(), 避免与 serverinfo / launchApp 抢带宽和 TLS 资源。改成 launchStream 返回后通过 probeAiAvailableDeferred 异步触发,用户感知延迟 < 2s。 3. AppListPage 预热启动链路资源 aboutToAppear 调用 StreamingSession.getDecoderCapabilities() 触发 .so 加载 + NAPI 注册(首次冷启动省 100-500ms),同时预读三组 设置项让 startStreaming 时走内存缓存。 4. startStreaming 并行化 将 fetchServerInfo (网络) 与 initializeNative (本地) 改为 Promise.all 并行执行,省下两者中较短的耗时。 5. drStart 提前进入 CONNECTED StreamingSession 新增 setDecoderStartedCallback;StreamViewModel 在 drStart 触发时立即把 connectionState 切到 CONNECTED,让 loading 蒙层比原来等 session.start() resolve 更早消失。 6. Settings 预热(随 #3 一起完成)
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 Walkthrough工作流此 PR 优化了 Moonlight 流启动流程。通过在 ComputerManager 中添加 TTL 缓存机制、在 StreamingSession 中实现解码器启动回调和并行化异步操作,以及在 StreamViewModel 中提早更新 UI 连接状态,减少网络往返并加快用户可见的响应。同时推迟 StreamPage 的 AI 检查,避免阻塞流启动。 变更流启动性能和 UI 响应性优化
代码审查工作量估计🎯 3 (中等) | ⏱️ ~20 分钟 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
entry/src/main/ets/service/ComputerManager.ets (3)
587-591:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win删除电脑时应清理 ServerInfo 缓存
removeComputer清理了computers和offlineCount,但未清理serverInfoCache。虽然缓存 30 秒后会过期,但为了一致性和避免微小的内存泄漏,建议同步删除。🔧 建议的修复
async removeComputer(uuid: string): Promise<void> { this.computers.delete(uuid); this.offlineCount.delete(uuid); + this.serverInfoCache.delete(uuid); await this.saveComputers(); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@entry/src/main/ets/service/ComputerManager.ets` around lines 587 - 591, removeComputer currently deletes entries from computers and offlineCount and calls saveComputers but does not remove the corresponding entry from serverInfoCache; update the async removeComputer(uuid: string) method to also call serverInfoCache.delete(uuid) (or equivalent cache removal) when removing a computer so the serverInfoCache, computers, and offlineCount are kept consistent and avoid lingering cache entries.
1092-1104:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win解除配对后应使 ServerInfo 缓存失效
unpairComputer更改了配对状态,但未调用invalidateServerInfoCache(uuid)。缓存中的ServerInfo可能仍包含paired=true,导致后续"进入串流"时读取到过期状态。根据
invalidateServerInfoCache方法注释(第 418-422 行),配对/解配后应主动失效缓存。🔧 建议的修复
async unpairComputer(uuid: string): Promise<void> { const computer = this.computers.get(uuid); if (!computer) { throw new Error('电脑不存在'); } const nvHttp = NvHttp.fromComputer(computer, this.context || undefined); await nvHttp.unpair(); computer.pairState = PairState.NOT_PAIRED; computer.serverCert = ''; + this.invalidateServerInfoCache(uuid); await this.saveComputers(); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@entry/src/main/ets/service/ComputerManager.ets` around lines 1092 - 1104, unpairComputer currently updates computer.pairState, clears serverCert and calls saveComputers but does not invalidate the cached ServerInfo, so callers may still see paired=true; update unpairComputer (the method) to call invalidateServerInfoCache(uuid) after setting PairState.NOT_PAIRED (and before/after saveComputers as you prefer) to ensure the cached ServerInfo for that uuid is cleared; reference invalidateServerInfoCache and PairState.NOT_PAIRED when making the change.
1044-1087:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win配对成功后应使 ServerInfo 缓存失效
pairComputer成功配对后更新了pairState和serverCert,但未调用invalidateServerInfoCache(uuid)。缓存中的旧ServerInfo可能包含paired=false,导致状态不一致。建议在配对成功后(第 1066 行之后)失效缓存,确保后续请求获取最新状态。
🔧 建议的修复
computer.pairState = PairState.PAIRED; if (result.serverCert) { const buf = buffer.from(result.serverCert); computer.serverCert = buf.toString('base64'); } computer.pairName = result.pairName || ''; + this.invalidateServerInfoCache(uuid); await this.saveComputers(); return pin;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@entry/src/main/ets/service/ComputerManager.ets` around lines 1044 - 1087, After a successful pairing in pairComputer, the ServerInfo cache isn't invalidated so stale data (e.g., paired=false) may be returned; modify pairComputer so that after setting computer.pairState = PairState.PAIRED, updating computer.serverCert and computer.pairName and awaiting this.saveComputers(), you call invalidateServerInfoCache(uuid) to evict the cached ServerInfo for that computer (ensure you reference the same uuid parameter and the existing invalidateServerInfoCache function).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@entry/src/main/ets/pages/StreamPage.ets`:
- Around line 457-477: probeAiAvailableDeferred() starts a setTimeout but never
clears it, causing callbacks (and Toasts/requests) after the page is destroyed;
add a class-level timer id (e.g., aiProbeTimerId: number | null) and assign the
return of setTimeout in probeAiAvailableDeferred(), then clear it in
aboutToDisappear() via clearTimeout(this.aiProbeTimerId) and null it;
additionally, guard the async callback by checking the page-is-mounted flag or
that aiKeyNvHttp still exists before using it to avoid post-destroy
actions—apply the same pattern to the other deferred probes referenced around
the other occurrences (lines ~688-701 and ~1328-1330) such as any other
probeXDeferred functions.
In `@entry/src/main/ets/service/streaming/StreamingSession.ets`:
- Around line 1017-1020: The drStart handler currently calls the external
decoderStartedCallback directly which can throw and bubble into native callback
flow; wrap the invocation of decoderStartedCallback inside a try/catch within
drStart (the arrow function named drStart) so any thrown error is caught, log
the error via existing logger/console (include context like
"decoderStartedCallback error"), and do not rethrow so native callback chain
remains stable.
In `@entry/src/main/ets/viewmodel/StreamViewModel.ets`:
- Around line 360-366: The callback passed to
streamingSession.setDecoderStartedCallback is too permissive and can flip
ERROR/DISCONNECTED back to CONNECTED; change the callback to first capture the
session instance (e.g., const session = this.streamingSession) and then only set
connectionState = StreamConnectionState.CONNECTED when this.connectionState ===
StreamConnectionState.CONNECTING and this.streamingSession === session, updating
connectionProgress and connectionStageText only under those conditions; use
streamingSession.setDecoderStartedCallback(...) and the captured session
reference to ensure the callback belongs to the current session before mutating
connectionState.
---
Outside diff comments:
In `@entry/src/main/ets/service/ComputerManager.ets`:
- Around line 587-591: removeComputer currently deletes entries from computers
and offlineCount and calls saveComputers but does not remove the corresponding
entry from serverInfoCache; update the async removeComputer(uuid: string) method
to also call serverInfoCache.delete(uuid) (or equivalent cache removal) when
removing a computer so the serverInfoCache, computers, and offlineCount are kept
consistent and avoid lingering cache entries.
- Around line 1092-1104: unpairComputer currently updates computer.pairState,
clears serverCert and calls saveComputers but does not invalidate the cached
ServerInfo, so callers may still see paired=true; update unpairComputer (the
method) to call invalidateServerInfoCache(uuid) after setting
PairState.NOT_PAIRED (and before/after saveComputers as you prefer) to ensure
the cached ServerInfo for that uuid is cleared; reference
invalidateServerInfoCache and PairState.NOT_PAIRED when making the change.
- Around line 1044-1087: After a successful pairing in pairComputer, the
ServerInfo cache isn't invalidated so stale data (e.g., paired=false) may be
returned; modify pairComputer so that after setting computer.pairState =
PairState.PAIRED, updating computer.serverCert and computer.pairName and
awaiting this.saveComputers(), you call invalidateServerInfoCache(uuid) to evict
the cached ServerInfo for that computer (ensure you reference the same uuid
parameter and the existing invalidateServerInfoCache function).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d357090f-330e-44c7-abfb-a3e78010afc4
📒 Files selected for processing (5)
entry/src/main/ets/pages/AppListPageV2.etsentry/src/main/ets/pages/StreamPage.etsentry/src/main/ets/service/ComputerManager.etsentry/src/main/ets/service/streaming/StreamingSession.etsentry/src/main/ets/viewmodel/StreamViewModel.ets
1. launchApp 成功后立即 invalidateServerInfoCache(computerId) 缓存里的 currentGame 在进入串流后必然变化,30s TTL 内复用 会导致下次 fetchServerInfo 命中缓存时拿到陈旧值,可能误触发 resumeApp 路径或漏 quitApp。 2. UsbDriverService.listKnownUsbGamepads 显式判 undefined 模拟器/无 USB 权限场景下 usbManager.getDevices() 返回 undefined, 原代码进入 for 循环触发 TypeError,每 5 秒一次噪声。
- StreamPage: AI 探测 setTimeout 在 aboutToDisappear 中清理,避免页面销毁后弹 Toast - StreamingSession: drStart 回调用 try/catch 隔离,外部异常不再冒泡到 native 回调链 - StreamViewModel: drStart 提前置 CONNECTED 加 session 实例校验 + 仅 CONNECTING 时生效, 防止旧会话延迟回调把 ERROR/DISCONNECTED 改回 CONNECTED
概述
合并 6 项优化让点击应用→看到画面更快。冷启动预计省 0.5–1.5 秒,热启动省 0.3–0.8 秒。
改动清单
关键实现
cacheServerInfo/getCachedServerInfo/invalidateServerInfoCache,5s 轮询路径与列表刷新均写入缓存StreamingSession.fetchServerInfo命中即跳过 HTTPS round-tripstartStreaming把网络请求与本地 NAPI 初始化用Promise.all并行setDecoderStartedCallback→ ViewModel 在 drStart 触发即切 CONNECTEDprobeAiAvailableDeferred,launchStream 完成后 2s 异步触发验证
hvigorw assembleHap --mode module -p product=default)注意
未提交 submodule 指针变更(aubio / moonlight-common-c)。
Summary by CodeRabbit
发布说明
性能优化