Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 73 additions & 68 deletions entry/src/main/ets/service/network/NetworkBoostService.ets
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@
* 本服务静默忽略。
*
* 后续步骤:
* Step 3: 订阅 'netSceneChange' 弱信号预测 → 主动降码率/提示用户
* Step 4: 引入 netHandover 多网切换(远程串流场景,需 LINKTURBO 受限权限)
*/

import { netBoost, netQuality } from '@kit.NetworkBoostKit';
import { Available, BusinessError, deviceInfo } from '@kit.BasicServicesKit';
import { StreamSocketFds } from '../streaming/MoonBridge';

const STREAMING_SERVICE_TYPE: netQuality.ServiceType = 'realtimeGame';

/** setDataFlowDesc 起始 API 版本 */
const DATA_FLOW_DESC_MIN_API = 26;

type StreamQoeState = 'good' | 'poor';

/**
Expand Down Expand Up @@ -73,43 +76,6 @@ export interface SystemNetScene {

export type SystemNetSceneListener = (scene: SystemNetScene) => void;

export type NetworkBoostTransportProtocol = 'udp' | 'tcp';
export type NetworkBoostFlowPriority = 'normal' | 'high';

export interface NetworkBoostEndpoint {
address: string;
port: number;
}

export interface NetworkBoostFlowExpectation {
/** 上行带宽,单位 Kbps */
uplinkBandwidthKbps?: number;
/** 下行带宽,单位 Kbps */
downlinkBandwidthKbps?: number;
/** 期望时延,单位毫秒 */
latencyMs?: number;
/** 请求/上传对象大小,单位 KB */
objectSizeKb?: number;
/** 流优先级 */
priority?: NetworkBoostFlowPriority;
/** 是否倾向低功耗传输 */
lowPowerMode?: boolean;
}

export interface NetworkBoostFiveTupleFlow {
flowType: 'fiveTuple';
protocol: NetworkBoostTransportProtocol;
local: NetworkBoostEndpoint;
remote: NetworkBoostEndpoint;
expectations?: NetworkBoostFlowExpectation;
}

export interface NetworkBoostSocketFlow {
flowType: 'socket';
socketFd: number;
expectations?: NetworkBoostFlowExpectation;
}

export class NetworkBoostService {
private static instance: NetworkBoostService | null = null;

Expand All @@ -124,7 +90,8 @@ export class NetworkBoostService {
private sceneCallback: ((list: Array<netQuality.NetworkScene>) => void) | null = null;
private latestScene: SystemNetScene | null = null;
private sceneListeners: Set<SystemNetSceneListener> = new Set();
private activeDataFlow: NetworkBoostFiveTupleFlow | NetworkBoostSocketFlow | null = null;
private registeredFlowFds: number[] = [];
private sceneEntered: boolean = false;

static getInstance(): NetworkBoostService {
if (!NetworkBoostService.instance) {
Expand Down Expand Up @@ -214,29 +181,84 @@ export class NetworkBoostService {
}

/**
* HarmonyOS 26.0.0: 为关键串流 socket 标记实时游戏流
* HarmonyOS 26.0.0: 为关键串流 socket 注册系统级流描述,获得 per-flow 调度优先级
*
* 只有在调用方能提供 native socket fd 或完整五元组时才调用;
* 当前 ArkTS 层没有可靠本地端口/fd,因此先作为可选入口保留
* fd 来自 native 层 getStreamSocketFds()。注册失败不影响串流;
* 会话结束或 socket 关闭前由 clearDataFlowDesc() 注销
*/
setDataFlowDesc(flow: NetworkBoostFiveTupleFlow | NetworkBoostSocketFlow): boolean {
registerStreamingFlows(fds: StreamSocketFds): void {
if (!this.supported || !this.active) {
return false;
return;
}
if (deviceInfo.sdkApiVersion < DATA_FLOW_DESC_MIN_API) {
console.info(`[NetworkBoost] API ${deviceInfo.sdkApiVersion} < ${DATA_FLOW_DESC_MIN_API},跳过流描述注册`);
return;
}

try {
netBoost.setSceneDesc({
scene: STREAMING_SERVICE_TYPE,
sceneEvent: netBoost.SceneEvent.SCENE_EVENT_ENTER
});
if (flow.expectations?.lowPowerMode !== undefined && deviceInfo.sdkApiVersion >= 24) {
netBoost.setLowPowerMode(flow.expectations.lowPowerMode);
this.sceneEntered = true;
} catch (err) {
this.handleErr('registerStreamingFlows/setSceneDesc', err);
return;
}

this.registeredFlowFds = [];
this.registerOneFlow('videoRtp', fds.videoRtp.fd);
this.registerOneFlow('audioRtp', fds.audioRtp.fd);
this.registerOneFlow('control', fds.control.fd);
}

private registerOneFlow(name: string, fd: number): void {
if (fd < 0) {
console.info(`[NetworkBoost] ${name} fd 不可用,跳过注册`);
return;
}
try {
netBoost.setDataFlowDesc({
dataFlowInfo: fd,
scene: STREAMING_SERVICE_TYPE,
sceneEvent: netBoost.SceneEvent.SCENE_EVENT_ENTER,
expectations: { priority: netBoost.PriorityLevel.PRIO_HIGH }
});
this.registeredFlowFds.push(fd);
console.info(`[NetworkBoost] setDataFlowDesc ${name} fd=${fd} priority=HIGH`);
} catch (err) {
this.handleErr(`setDataFlowDesc(${name})`, err);
}
}

private clearDataFlowDesc(): void {
const fds = this.registeredFlowFds;
this.registeredFlowFds = [];
fds.forEach((fd) => {
try {
netBoost.setDataFlowDesc({
dataFlowInfo: fd,
scene: STREAMING_SERVICE_TYPE,
sceneEvent: netBoost.SceneEvent.SCENE_EVENT_LEAVE
});
console.info(`[NetworkBoost] setDataFlowDesc fd=${fd} event=leave`);
} catch (err) {
console.warn(`[NetworkBoost] setDataFlowDesc(fd=${fd}) leave 失败: ${(err as Error).message}`);
}
this.activeDataFlow = flow;
console.info('[NetworkBoost] setSceneDesc scene=realtimeGame event=enter');
return true;
});
if (!this.sceneEntered) {
return;
}
this.sceneEntered = false;

try {
netBoost.setSceneDesc({
scene: STREAMING_SERVICE_TYPE,
sceneEvent: netBoost.SceneEvent.SCENE_EVENT_LEAVE
});
console.info('[NetworkBoost] setSceneDesc scene=realtimeGame event=leave');
} catch (err) {
this.handleErr('setDataFlowDesc', err);
return false;
console.warn(`[NetworkBoost] clearDataFlowDesc 失败: ${(err as Error).message}`);
}
}

Expand Down Expand Up @@ -380,23 +402,6 @@ export class NetworkBoostService {
this.sceneCallback = null;
}

private clearDataFlowDesc(): void {
if (!this.activeDataFlow) {
this.activeDataFlow = null;
return;
}
try {
netBoost.setSceneDesc({
scene: STREAMING_SERVICE_TYPE,
sceneEvent: netBoost.SceneEvent.SCENE_EVENT_LEAVE
});
console.info('[NetworkBoost] setSceneDesc scene=realtimeGame event=leave');
} catch (err) {
console.warn(`[NetworkBoost] clearDataFlowDesc 澶辫触: ${(err as Error).message}`);
}
this.activeDataFlow = null;
}

private handleErr(api: string, err: object): void {
const error = err as BusinessError;
if (error.code === 801) {
Expand Down
41 changes: 41 additions & 0 deletions entry/src/main/ets/service/streaming/MoonBridge.ets
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,35 @@ export interface RttInfo {
variance: number;
}

/**
* native getStreamSocketFds 返回的扁平结构
*/
interface NativeStreamSocketFds {
videoRtpFd: number;
videoRtpPort: number;
audioRtpFd: number;
audioRtpPort: number;
controlFd: number;
controlPort: number;
}

/**
* 串流 socket 信息(fd 为 -1 表示该流不可用)
*/
export interface StreamSocketInfo {
fd: number;
localPort: number;
}

/**
* 串流会话的活跃 socket 集合(仅在连接建立后有效)
*/
export interface StreamSocketFds {
videoRtp: StreamSocketInfo;
audioRtp: StreamSocketInfo;
control: StreamSocketInfo;
}

/**
* Native 麦克风统计信息
*/
Expand Down Expand Up @@ -727,6 +756,18 @@ class MoonBridgeClass {
return nativeLib.getHostFeatureFlags();
}

getStreamSocketFds(): StreamSocketFds | null {
const raw = nativeLib.getStreamSocketFds() as NativeStreamSocketFds | null;
if (!raw) {
return null;
}
const video: StreamSocketInfo = { fd: raw.videoRtpFd, localPort: raw.videoRtpPort };
const audio: StreamSocketInfo = { fd: raw.audioRtpFd, localPort: raw.audioRtpPort };
const control: StreamSocketInfo = { fd: raw.controlFd, localPort: raw.controlPort };
const result: StreamSocketFds = { videoRtp: video, audioRtp: audio, control: control };
return result;
}

sendClientSdrWhiteNits(nits: number): number {
return nativeLib.sendClientSdrWhiteNits(nits);
}
Expand Down
13 changes: 13 additions & 0 deletions entry/src/main/ets/service/streaming/StreamingSession.ets
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,19 @@ export class StreamingSession implements Ds5TouchpadInputSink {
this.isRunning = true;
// Network Boost Kit: 声明实时游戏类业务,让系统优先调度
NetworkBoostService.getInstance().start();
// Network Boost Kit: 注册串流 socket 流描述,获得 per-flow 调度优先级。
// startConnectionAsync 已返回,video/audio/control socket 均已建立。
try {
const sockets = MoonBridge.getStreamSocketFds();
if (sockets) {
console.info(`[StreamingSession] 串流 socket: video fd=${sockets.videoRtp.fd} port=${sockets.videoRtp.localPort}, ` +
`audio fd=${sockets.audioRtp.fd} port=${sockets.audioRtp.localPort}, ` +
`control fd=${sockets.control.fd} port=${sockets.control.localPort}`);
NetworkBoostService.getInstance().registerStreamingFlows(sockets);
}
} catch (err) {
console.warn(`[StreamingSession] 串流流描述注册失败(不影响串流): ${(err as Error).message}`);
}
} catch (err) {
await this.cleanup();
throw new Error(`串流会话启动失败: ${(err as Error).message}`);
Expand Down
25 changes: 25 additions & 0 deletions nativelib/src/main/cpp/moonlight_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1494,6 +1494,31 @@ napi_value MoonBridge_GetHostFeatureFlags(napi_env env, napi_callback_info info)
return result;
}

napi_value MoonBridge_GetStreamSocketFds(napi_env env, napi_callback_info info) {
(void)info;

LI_STREAM_SOCKETS sockets;
LiGetStreamSockets(&sockets);

napi_value result;
napi_create_object(env, &result);

const char* fdKeys[] = { "videoRtpFd", "audioRtpFd", "controlFd" };
const int fdValues[] = { sockets.videoRtp.fd, sockets.audioRtp.fd, sockets.control.fd };
const char* portKeys[] = { "videoRtpPort", "audioRtpPort", "controlPort" };
const int portValues[] = { sockets.videoRtp.localPort, sockets.audioRtp.localPort, sockets.control.localPort };

for (int i = 0; i < 3; i++) {
napi_value value;
napi_create_int32(env, fdValues[i], &value);
napi_set_named_property(env, result, fdKeys[i], value);
napi_create_int32(env, portValues[i], &value);
napi_set_named_property(env, result, portKeys[i], value);
}

return result;
}

napi_value MoonBridge_SendClientSdrWhiteNits(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
Expand Down
1 change: 1 addition & 0 deletions nativelib/src/main/cpp/moonlight_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ napi_value MoonBridge_GetPendingAudioDuration(napi_env env, napi_callback_info i
napi_value MoonBridge_GetPendingVideoFrames(napi_env env, napi_callback_info info);
napi_value MoonBridge_GetEstimatedRttInfo(napi_env env, napi_callback_info info);
napi_value MoonBridge_GetHostFeatureFlags(napi_env env, napi_callback_info info);
napi_value MoonBridge_GetStreamSocketFds(napi_env env, napi_callback_info info);
napi_value MoonBridge_SendClientSdrWhiteNits(napi_env env, napi_callback_info info);
napi_value MoonBridge_GetLaunchUrlQueryParameters(napi_env env, napi_callback_info info);

Expand Down
1 change: 1 addition & 0 deletions nativelib/src/main/cpp/napi_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ static napi_value Init(napi_env env, napi_value exports) {
{ "getPendingVideoFrames", nullptr, MoonBridge_GetPendingVideoFrames, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "getEstimatedRttInfo", nullptr, MoonBridge_GetEstimatedRttInfo, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "getHostFeatureFlags", nullptr, MoonBridge_GetHostFeatureFlags, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "getStreamSocketFds", nullptr, MoonBridge_GetStreamSocketFds, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "sendClientSdrWhiteNits", nullptr, MoonBridge_SendClientSdrWhiteNits, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "getLaunchUrlQueryParameters", nullptr, MoonBridge_GetLaunchUrlQueryParameters, nullptr, nullptr, nullptr, napi_default, nullptr },

Expand Down
Loading