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状态,收获也是满满!

相关推荐
卷福同学1 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师1 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc881 小时前
微软EDGE浏览器功能学习
前端·学习·edge
Csvn2 小时前
📊 SQL 入门 Day 11:CASE 表达式:SQL 里的 if-else 魔法
后端·sql
Csvn2 小时前
🎯 原生 `<dialog>` 元素:终于可以扔掉一半的自定义弹窗组件了?
前端
QQ_21696290962 小时前
Spring Boot 养老院管理系统:从入住、护理到费用结算的全流程实现(源码可领)
java·spring boot·后端
Csvn2 小时前
🎨 CSS @layer:用「层叠优先级」终结样式打架的世纪难题
前端
濮水大叔3 小时前
不必把 Vue3 写成“麻花”:从状态碎片到对象协作,重新理解 Zova 的前端心智模型
前端·typescript·vue3·ioc·tsx·zova
Vuji3 小时前
MCP: 一条 tool 调用链路的旅程
前端·人工智能
万少4 小时前
DeepSeek-V4-Flash 正式版上线了,但这 3 个坑我帮你提前踩了
前端·javascript·后端