Skip to content

Fix 120Hz keepalive in smart framerate mode#35

Open
qiin2333 wants to merge 2 commits into
masterfrom
codex/fix-smart-framerate-120hz
Open

Fix 120Hz keepalive in smart framerate mode#35
qiin2333 wants to merge 2 commits into
masterfrom
codex/fix-smart-framerate-120hz

Conversation

@qiin2333

@qiin2333 qiin2333 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add native DisplaySoloist frame pacing to keep high-refresh streaming active under HarmonyOS smart framerate
  • reapply NativeVSync, NativeWindow, DisplaySoloist, and XComponent frame-rate hints during streaming and surface/layout changes
  • register an XComponent onFrame callback and improve frame-rate hint logging

Closes #34

Verification

  • git diff --check
  • DEVECO_SDK_HOME=/Users/mac/ohos-sdk-cache/6.1-Release-mac/sdk-ci-shape node hvigorw.js assembleHar --mode module -p module=nativelib@default -p product=default
  • DEVECO_SDK_HOME=/Users/mac/ohos-sdk-cache/6.1-Release-mac/sdk-ci-shape node hvigorw.js assembleHap --mode module -p module=entry@default -p product=default (ArkTS compile passes; PackageHap fails locally because no Java Runtime is installed)

Summary by CodeRabbit

发布说明

  • 新功能

    • 在高于 60fps 场景下新增显示层帧率保活与提示机制,优化高刷新率屏幕的串流播放平滑性与同步性。
    • 增强帧率提示的刷新策略,以更稳定地应用各层帧率期望值。
  • bug修复

    • 在页面销毁和串流结束时确保帧率保活被可靠关闭,避免残留影响。

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b62abff9-56b9-41b6-8e97-8dafeadead1e

📥 Commits

Reviewing files that changed from the base of the PR and between 07d3dab and 57ba824.

📒 Files selected for processing (4)
  • entry/src/main/ets/pages/StreamPage.ets
  • nativelib/src/main/cpp/moonlight_bridge.cpp
  • nativelib/src/main/cpp/native_render.cpp
  • nativelib/src/main/cpp/native_render.h
🚧 Files skipped from review as they are similar to previous changes (4)
  • nativelib/src/main/cpp/native_render.h
  • entry/src/main/ets/pages/StreamPage.ets
  • nativelib/src/main/cpp/moonlight_bridge.cpp
  • nativelib/src/main/cpp/native_render.cpp

📝 Walkthrough

Walkthrough

该PR为Moonlight在HarmonyOS NEXT智能帧率模式下120fps流播放时可能降至60Hz的问题,引入DisplaySoloist显示层高刷保活与帧率提示持续刷新机制,从Native API加载、生命周期管理,到应用层控制,完整实现显示层高刷保持链路。

Changes

DisplaySoloist帧率保活与动态刷新

Layer / File(s) Summary
Native层能力声明与DisplaySoloist API加载
nativelib/src/main/cpp/native_render.h, nativelib/src/main/cpp/native_render.cpp
NativeRender新增SetDisplayFramePacerEnabledRefreshFrameRateHints公共接口,以及InitDisplaySoloistReleaseDisplaySoloistApplyDisplaySoloistFrameRate私有方法。通过dlsym动态加载OH_DisplaySoloist_* API(API 12+),并在可用时创建、启动、管理DisplaySoloist生命周期。新增displayFramePacerEnabled_displaySoloist_displaySoloistStarted_lastFrameRateHintTime_等成员状态。
帧率提示刷新与NativeWindow策略调整
nativelib/src/main/cpp/native_render.cpp
新增RefreshFrameRateHints(bool force)实现2秒限频刷新机制,按照优先级依次调用ApplyFrameRateRangeApplyNativeWindowFrameRateApplyDisplaySoloistFrameRate;调整NativeWindow帧率设置策略从默认改为strategy=EXACT固定请求configuredFps_;在SubmitFrame开始处集成周期性刷新调用,保证渲染过程中帧率提示持续有效。
XComponent帧率增强与NAPI导出
nativelib/src/main/cpp/moonlight_bridge.cpp, nativelib/src/main/cpp/moonlight_bridge.h, nativelib/src/main/cpp/napi_init.cpp
扩展CheckAndLoadXCFrameRateApis加载OH_NativeXComponent_RegisterOnFrameCallback,在MoonBridge_SetXComponentFrameRate中注册onFrame回调;新增MoonBridge_SetDisplayFramePacerEnabledMoonBridge_RefreshFrameRateHints两个NAPI导出函数,分别转发到Native层的DisplaySoloist控制和刷新接口。
JS桥接与应用层生命周期集成
entry/src/main/ets/service/streaming/MoonBridge.ets, entry/src/main/ets/pages/StreamPage.ets
MoonBridge新增setDisplayFramePacerEnabledrefreshFrameRateHints方法;StreamPage新增applyDisplayFrameRateHints私有方法统一管理帧率提示和pacer,在launchStreamstartStreamingXComponent.onAreaChangeprepareStreamEndUiImmediateaboutToDisappear中调用,确保流的启动、运行、区域变化和结束全生命周期的帧率提示同步。

🎯 3 (Moderate) | ⏱️ ~25 minutes

可能相关的 PR:

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题「Fix 120Hz keepalive in smart framerate mode」准确反映了主要变更:解决HarmonyOS智能帧率模式下120Hz保活问题。
Linked Issues check ✅ Passed 代码变更完整实现了Issue #34的所有核心需求:添加DisplaySoloist保活、重新应用XComponent/NativeVSync/NativeWindow帧率提示、注册onFrame回调、支持前后台和Surface恢复场景。
Out of Scope Changes check ✅ Passed 所有变更均在Issue #34的范围内,包括DisplaySoloist集成、帧率提示管理、错误恢复和日志增强,未发现超范围改动。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-smart-framerate-120hz

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
nativelib/src/main/cpp/native_render.cpp (1)

332-350: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

缺少“降级/清除”路径,120Hz 提示会在降到 60fps 或停流后残留。

当前逻辑只在 configuredFps_ > 60 时重新应用提示;一旦从 120→60,或者 ArkTS 仅调用 SetDisplayFramePacerEnabled(false),这里只会停掉 DisplaySoloist,不会把已经写到 NativeWindow/NativeVSync 的高刷提示恢复为默认值。页面保留 surface 显示战报或流内切到 60fps 时,设备仍可能继续维持高刷新率。

Also applies to: 419-439, 478-500

🤖 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 `@nativelib/src/main/cpp/native_render.cpp` around lines 332 - 350,
SetConfiguredFps currently only re-applies high-FPS hints when fps>60 and simply
releases DisplaySoloist when fps<=60, leaving previously-applied
NativeVSync/NativeWindow high-refresh hints in place; modify SetConfiguredFps to
explicitly clear or reset frame-rate hints when downgrading (e.g., when
configuredFps_ <= 60 or when displayFramePacerEnabled_ becomes false) by calling
the same mechanisms that apply defaults (or explicit clear functions) in
ApplyFrameRateRange and ApplyNativeWindowFrameRate (or add
ClearFrameRateRange/ClearNativeWindowFrameRate helpers) so that any high-refresh
hints written to NativeVSync/NativeWindow are reverted; ensure
ReleaseDisplaySoloist and the branch where displayFramePacerEnabled_.load() is
false also trigger these clear/reset calls to remove lingering 120Hz hints.
nativelib/src/main/cpp/moonlight_bridge.cpp (1)

1998-2018: ⚠️ Potential issue | 🟠 Major

XComponent 帧率设置:API20 调用失败时应继续回退到旧路径
MoonBridge_SetXComponentFrameRate() 在 19982005 检测到 g_pfnXCSetFrameRateNew 存在后,无论 xcRet 成功与否都会立刻 return,导致下面 20082018 的 g_pfnGetNativeXC/g_pfnXCSetFrameRateOld 旧路径永远不会执行。建议仅在 API20 调用成功时返回;失败时移除无条件 return,让代码继续尝试旧路径(并以 HarmonyOS 对该返回码的约定作为成功判定)。

🤖 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 `@nativelib/src/main/cpp/moonlight_bridge.cpp` around lines 1998 - 2018,
MoonBridge_SetXComponentFrameRate(): currently when g_pfnXCSetFrameRateNew is
present the code calls it and unconditionally returns, preventing the fallback
path; change the flow so you only return (GetUndefined(env)) when the new call
succeeds (check xcRet against the HarmonyOS success convention), and if xcRet
indicates failure log the error and do NOT return so execution falls through to
the g_pfnGetNativeXC / g_pfnXCSetFrameRateOld fallback; keep existing logging
for both paths and retain the GetUndefined(env) return after the fallback.
🤖 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 1277-1314: launchStream enables DisplaySoloist/XComponent hints
via applyDisplayFrameRateHints(true) before awaiting viewModel.startStreaming,
but failures leave the pacer/hints enabled; change launchStream to wrap the
await this.viewModel.startStreaming(...) in a try/catch/finally (or at minimum
catch) and on error disable the pacer/hints by calling
applyDisplayFrameRateHints(false) (or directly call
MoonBridge.setDisplayFramePacerEnabled(false) and reset XComponent/frame hints)
so any startup exception cleans up state; keep the original logging/throw
behavior but ensure applyDisplayFrameRateHints(false) is invoked on the error
path (also apply same fix to the other launch path referenced around lines
1381-1431).

In `@nativelib/src/main/cpp/native_render.cpp`:
- Around line 242-297: Add a mutex to serialize access to
DisplaySoloist/window/frame-rate state and update the lifecycle functions to use
it: create a member std::mutex (e.g., displayMutex_) and protect reads/writes of
displaySoloist_, displaySoloistStarted_, window_, lastFrameRateHintTime_, and
configuredFps_ with std::lock_guard in InitDisplaySoloist,
ReleaseDisplaySoloist, SetDisplayFramePacerEnabled,
ApplyDisplaySoloistFrameRate, ApplyNativeWindowFrameRate, RefreshFrameRateHints,
SubmitFrame, SetNativeWindow, and SetConfiguredFps; for calls into external APIs
(g_pfnDisplaySoloistStart/Stop/Destroy) copy the pointer/flags under the lock to
local variables, release the lock, then invoke the external function to avoid
deadlocks while still preventing UAF/race windows. Ensure all state transitions
use the same mutex so no concurrent destroy/start or updates occur.

---

Outside diff comments:
In `@nativelib/src/main/cpp/moonlight_bridge.cpp`:
- Around line 1998-2018: MoonBridge_SetXComponentFrameRate(): currently when
g_pfnXCSetFrameRateNew is present the code calls it and unconditionally returns,
preventing the fallback path; change the flow so you only return
(GetUndefined(env)) when the new call succeeds (check xcRet against the
HarmonyOS success convention), and if xcRet indicates failure log the error and
do NOT return so execution falls through to the g_pfnGetNativeXC /
g_pfnXCSetFrameRateOld fallback; keep existing logging for both paths and retain
the GetUndefined(env) return after the fallback.

In `@nativelib/src/main/cpp/native_render.cpp`:
- Around line 332-350: SetConfiguredFps currently only re-applies high-FPS hints
when fps>60 and simply releases DisplaySoloist when fps<=60, leaving
previously-applied NativeVSync/NativeWindow high-refresh hints in place; modify
SetConfiguredFps to explicitly clear or reset frame-rate hints when downgrading
(e.g., when configuredFps_ <= 60 or when displayFramePacerEnabled_ becomes
false) by calling the same mechanisms that apply defaults (or explicit clear
functions) in ApplyFrameRateRange and ApplyNativeWindowFrameRate (or add
ClearFrameRateRange/ClearNativeWindowFrameRate helpers) so that any high-refresh
hints written to NativeVSync/NativeWindow are reverted; ensure
ReleaseDisplaySoloist and the branch where displayFramePacerEnabled_.load() is
false also trigger these clear/reset calls to remove lingering 120Hz hints.
🪄 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: 2e6c796f-e176-4c0f-9899-2e50c69b7a76

📥 Commits

Reviewing files that changed from the base of the PR and between 3d6cfb6 and 07d3dab.

📒 Files selected for processing (7)
  • entry/src/main/ets/pages/StreamPage.ets
  • entry/src/main/ets/service/streaming/MoonBridge.ets
  • nativelib/src/main/cpp/moonlight_bridge.cpp
  • nativelib/src/main/cpp/moonlight_bridge.h
  • nativelib/src/main/cpp/napi_init.cpp
  • nativelib/src/main/cpp/native_render.cpp
  • nativelib/src/main/cpp/native_render.h

Comment on lines 1277 to 1314
/**
* 设置 XComponent 帧率 + 获取 surfaceId + 启动串流
* 应用显示链路帧率提示。
* 120fps 串流时启动 native DisplaySoloist,并重申 XComponent / native 层帧率范围。
*/
private async launchStream(): Promise<void> {
const surfaceId = this.xComponentController.getXComponentSurfaceId();

// 设置 XComponent 期望帧率(通过 FrameNode → ArkUI_NodeHandle)
// 解决 MatePad 等设备渲染帧率被锁定在 60fps 的问题
private applyDisplayFrameRateHints(enablePacer: boolean = true): void {
const streamConfig = this.viewModel.streamConfig;
if (streamConfig && streamConfig.fps > 60) {
try {
if (enablePacer) {
MoonBridge.setDisplayFramePacerEnabled(true);
}
const frameNode = this.getUIContext().getFrameNodeById('streamSurface');
if (frameNode) {
MoonBridge.setXComponentFrameRate(frameNode, streamConfig.fps);
console.info(`XComponent 帧率已设置为 ${streamConfig.fps} fps`);
}
MoonBridge.refreshFrameRateHints();
} catch (e) {
console.warn(`设置 XComponent 帧率失败: ${e}`);
console.warn(`设置显示帧率提示失败: ${e}`);
}
} else if (enablePacer) {
try {
MoonBridge.setDisplayFramePacerEnabled(false);
} catch (_) {}
}
}

/**
* 设置 XComponent 帧率 + 获取 surfaceId + 启动串流
*/
private async launchStream(): Promise<void> {
const surfaceId = this.xComponentController.getXComponentSurfaceId();

// 设置 XComponent / DisplaySoloist 期望帧率,解决智能帧率下 120fps 回落到 60Hz 的问题
this.applyDisplayFrameRateHints(true);

await this.viewModel.startStreaming(this.computerId, this.appId, surfaceId, this.context, this.displayGuid, this.useVdd);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

启动前先打开 pacer,但失败路径没有回滚。

launchStream() 在真正 startStreaming() 之前就启用了 DisplaySoloist/XComponent/native hints,而 startStreaming()catch 里只打印日志。这样只要连接或启动阶段抛错,页面停在错误态时高刷保持仍可能继续生效,直到用户离开页面才被动清理。

建议改法
   async startStreaming(): Promise<void> {
     try {
       ...
       await this.launchStream();
       this.streamStartedAtMs = Date.now();
       this.applyDisplayFrameRateHints(true);
       ...
     } catch (err) {
+      try {
+        MoonBridge.setDisplayFramePacerEnabled(false);
+      } catch (_) {}
       console.error('开始串流失败:', err);
     }
   }

Also applies to: 1381-1431

🤖 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/pages/StreamPage.ets` around lines 1277 - 1314,
launchStream enables DisplaySoloist/XComponent hints via
applyDisplayFrameRateHints(true) before awaiting viewModel.startStreaming, but
failures leave the pacer/hints enabled; change launchStream to wrap the await
this.viewModel.startStreaming(...) in a try/catch/finally (or at minimum catch)
and on error disable the pacer/hints by calling
applyDisplayFrameRateHints(false) (or directly call
MoonBridge.setDisplayFramePacerEnabled(false) and reset XComponent/frame hints)
so any startup exception cleans up state; keep the original logging/throw
behavior but ensure applyDisplayFrameRateHints(false) is invoked on the error
path (also apply same fix to the other launch path referenced around lines
1381-1431).

Comment thread nativelib/src/main/cpp/native_render.cpp
@qiin2333

Copy link
Copy Markdown
Contributor Author

Addressed the actionable review feedback:

  • disabled DisplaySoloist if stream startup fails before the session is established
  • added fallback from the API 20 XComponent frame-rate path to the NativeXComponent path when the API 20 call returns an error
  • reset NativeVSync / NativeWindow frame-rate hints back to 60/default when disabling the display pacer or downgrading from high FPS
  • serialized NativeRender frame-rate/window/DisplaySoloist state with a mutex to avoid concurrent render/lifecycle races

Verification:

  • git diff --check
  • DEVECO_SDK_HOME=/Users/mac/ohos-sdk-cache/6.1-Release-mac/sdk-ci-shape node hvigorw.js assembleHar --mode module -p module=nativelib@default -p product=default
  • entry assembleHap reaches PackageHap; ArkTS compile passes and the remaining failure is local missing Java Runtime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HarmonyOS NEXT 智能帧率下 120fps 串流可能被降到 60Hz

1 participant