HarmonyOS 6.0跨端远程控制

各位掘友们好,前面发表过自己在学习鸿蒙时总结的一些小笔记,而这篇则是我学习的实现!

我就卖关子了,直接开门开门见山!我利用AI的帮助实现了跨端远程控制。可以在win/mac系统电脑上控制鸿蒙的2in1/pc设备。具体如何实现的呢?请听我一一道来。

背景

那天老大语重心长的对我说,小林啊,客户那边需求有一个远程控制的功能,我一听心想这很简单市面上那么多,多看几款把功能仿出来不就行了。后面老大一说具体需求给我听的一愣一愣的,什么!在win/mac系统通过浏览器去控制鸿蒙设备。说完老大给我讲了一下大概思路然后眼神坚定的看着我,没办法还是硬着头皮接下这个任务,(也可能是年轻觉得别能做自己也能做!)。

相关技术栈

  1. win(控制侧):JavaScript、Html、Css3、JMUXER视频渲染库
  2. 服务:Node.js
  3. HarmonyOS(被控制侧):C++、ArkTS

功能

  1. 基本键鼠操作
  2. 双向复制粘贴

整体逻辑

sequenceDiagram participant WIN as Win 浏览器<br/>(controller) participant SVR as Node.js 中继<br/>(Express + ws) participant HAV as 鸿蒙设备<br/>(device) Note over WIN,HAV: ═══ 连接建立阶段 ═══ WIN->>SVR: 1. HTTP GET /?room=8888 SVR-->>WIN: index.html + controller.js + JMuxer WIN->>SVR: 2. WSS /ws?room=8888&role=controller SVR-->>WIN: WebSocket 连接成功 WIN->>SVR: 3. {type:'join', role:'controller', room:'8888'} HAV->>SVR: 4. WSS /ws?room=8888&role=device SVR-->>HAV: WebSocket 连接成功 HAV->>SVR: 5. {type:'join', role:'device'} HAV->>SVR: {type:'screen_started', width, height} SVR->>WIN: 6. {type:'device_connected', width, height} Note over WIN,HAV: ═══ 主流程:视频 + 控制 ═══ loop 持续录屏 (30fps) HAV->>SVR: 7. [Binary] H.264 NAL 裸流 SVR->>WIN: 转发 [Binary] H.264 WIN->>WIN: JMuxer 封装 fMP4 → MSE → <video> 播放 end WIN->>SVR: 8. {type:'pointer', action, x, y} SVR->>HAV: 转发 pointer HAV->>HAV: injectMouseEvent --> 系统鼠标注入 WIN->>SVR: 9. {type:'wheel', deltaX, deltaY} SVR->>HAV: 转发 wheel HAV->>HAV: injectMouseWheelEvent --> 系统滚轮注入 WIN->>SVR: 10. {type:'key', action, key, code} SVR->>HAV: 转发 key HAV->>HAV: injectKeyEvent --> 系统键盘注入 Note over WIN,HAV: ═══ 剪贴板同步 ═══ WIN->>WIN: 11. Ctrl+V → PlanC 读取 Win 剪贴板 WIN->>SVR: {type:'clipboard', data: text} SVR->>HAV: 转发 clipboard HAV->>HAV: pasteboard.setDataSync(text) WIN->>SVR: 注入 Ctrl+V 按键序列 SVR->>HAV: 转发 Ctrl+V 按键 HAV->>HAV: 系统级 Ctrl+V 粘贴 HAV->>HAV: 12. 用户复制 → pasteboard.on('update') HAV->>SVR: {type:'clipboard', data: text} SVR->>WIN: 转发 clipboard WIN->>WIN: navigator.clipboard.writeText() Note over WIN,HAV: ═══ 保活 ═══ par 心跳保活 WIN->>SVR: {type:'ping'} SVR-->>WIN: pong and HAV->>SVR: {type:'ping'} SVR-->>HAV: pong end

相关设计

  • c++层使用 OH_AVScreenCaptureStartScreenCaptureWithSurface 模式,录屏输出直接绑定到编码器的 Input Surface, 无任何拷贝避免了 YUV 数据在内存中来回拷贝。
  • 编码器回调 OnCodecOutput 通过 napi_threadsafe_function 将 NAL 数据从编码线程安全投递到 ArkTS 主线程中。
  • 浏览器端使用 JMuxer 将裸 H.264 NAL 单元封装为 fMP4 片段,交由 MSE(Media Source Extendions) 喂给 <video> 标签解码播放。

双向剪贴板同步

Plan C Paste 穿透 + @ohos.pasteboard + navigator.clipboard

  1. 在 DOM 中创建一个隐藏到屏幕外的<textarea>
  2. 用户按下 Ctrl+V 时不调用 preventDefault(),让浏览器执行原生粘贴
  3. 利用 e.clipboardData.getData('text/plain') 在 paste 事件回调中读取剪贴板
  4. 向鸿蒙发送 clipboard 消息 + 注入 Ctrl+V 按键序列化完成远程粘贴

防回环设计 :HarmonyOS ClipboardSyncHelper 使用 syncLock 标记,当自身写入粘贴板时加锁, pasteboard.on('update') 回调检测到锁则路过发送,避免无限回环。

服务

服务端是一个无业务逻辑的纯中继层,不做任何编解码,只根据 role 做房间内广播转发。

项目架构

bash 复制代码
RemoteControl/
├── entry/src/main/
│   ├── cpp/                          # C++ Native 层
│   │   ├── napi_init.cpp            # NAPI 入口:录屏/输入注入 JS 绑定
│   │   ├── screen_capture/
│   │   │   ├── screen_capture_manager.h/cpp  # 录屏 + H.264硬件编码
│   │   └── types/libentry/
│   │       └── index.d.ts           # NAPI TypeScript 类型声明
│   └── ets/
│       ├── pages/
│       │   └── WebSocketTestPage.ets # 主页面:连接配置 + 日志面板
│       └── util/
│           ├── service/
│           │   ├── WebRemoteHandler.ets    # 核心调度:连接/录屏/消息路由
│           │   ├── InputInjector.ets       # 输入注入封装(坐标转换)
│           │   ├── ClipboardService.ets    # 系统剪贴板读写
│           │   ├── ClipboardSyncHelper.ets # 剪贴板同步 + 防回环
│           │   └── WebSocketData.ets       # 消息类型接口定义
│           └── backgroundTask/
│               └── BackgroundTaskHelper.ets# 长时任务申请
├── server/                          # Node.js 中继服务器
│   ├── server.js                   # 入口:HTTP + WebSocket Server
│   ├── message-handler.js          # 消息路由:按 type 分发
│   ├── room-manager.js            # 房间管理:角色分组 + 广播
│   ├── client-manager.js          # 客户端生命周期管理
│   ├── config.js                  # 配置管理 (端口/Token)
│   └── web/
│       ├── index.html             # 控制端页面
│       └── controller.js          # 核心控制逻辑
|__

连接成功的效果

总结

在这个功能实现的过程中我收获到了以下果实。

  1. H.264 NAL 单元、SPS/PPS/I帧/P帧之间的关系。
  2. 对浏览器 PointerEvent API 的掌握上升了一个档次:button vs buttonspointerdown vs mousedown 的区别、setPointerCapture 的作用时机。
  3. 对鸿蒙 OH_Input_Manager 注入 API 有了实操理解:ActionTime 必须用 GetBootTimeUs() 微秒级时间戳,否则事件时序错乱。
  4. 浏览器安全策略不是"限制"是"设计"------navigator.clipboard 要求安全上下文是合理的。Plan C 是在这个前提下找一个合法的旁路,而不是打破它。
  5. 双向同步系统必须考虑回环问题,这是一个经典设计模式------任何 "写 --> 通知 --> 读 --> 写" 的循环链路都需要标记来源。

虽然实现的是局域网状态下的demo状态,收获也是满满!

相关推荐
To_OC1 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic2 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy2 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志2 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble3 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯4 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
戮漠summer5 小时前
Missing Semester 计算机教育中缺失的一课 Lecture 01 Shell
开发语言·后端·scala
名字还没想好☜5 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司5 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
江华森6 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http