惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
T
ThreatConnect
SecWiki News
SecWiki News
F
Future of Privacy Forum
AWS News Blog
AWS News Blog
C
Cisco Blogs
A
Arctic Wolf
Vercel News
Vercel News
The GitHub Blog
The GitHub Blog
Scott Helme
Scott Helme
V
V2EX
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
K
Kaspersky official blog
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
P
Privacy International News Feed
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News | PayPal Newsroom
Schneier on Security
Schneier on Security
NISL@THU
NISL@THU
Microsoft Azure Blog
Microsoft Azure Blog
量子位
The Hacker News
The Hacker News
Stack Overflow Blog
Stack Overflow Blog
Security Latest
Security Latest
M
Microsoft Research Blog - Microsoft Research
Google Online Security Blog
Google Online Security Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
I
InfoQ
Google DeepMind News
Google DeepMind News
Y
Y Combinator Blog
The Cloudflare Blog
Microsoft Security Blog
Microsoft Security Blog
Martin Fowler
Martin Fowler
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Troy Hunt's Blog
F
Fox-IT International blog
S
Security @ Cisco Blogs
博客园 - 司徒正美
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
Comments on: Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
LINUX DO - 最新话题
GbyAI
GbyAI
Project Zero
Project Zero
腾讯CDC
T
Tailwind CSS Blog

博客园_首页

做共享目录实时同步,踩过这些坑 华为公司发布半导体演进新范式 - “韬(τ)定律”(Tau Law) Linux时区修改为CST Go 语言入门学习笔记基础版 给热水器装上“电量显示”:用 Shelly Gen4 脚本实现零改装水量预测 踩坑实录:接口正常Feign调用字段值为空 耿同学学术打假,就是学术版《狂人日记》;学术打假,就是清扫垃圾 浙江事业编笔试上周出分!面试进入倒计时,该如何高效冲刺 - 里奥不吃奥利奥 FastApiAdmin 后端接口开发好了,前端管理界面怎么调用与显示? 我写了 50 个 Claude Code Skill 才发现,前 30 个都白写了 告别 "cd /var/log" !用 journalctl 统一掌控 Linux 日志 我用自己的微信聊天记录,微调了一个“数字分身” AI运动APP开发的常见问题集锦一 复盘梳理-如何深入并抽象 告别手动复制!公众号文章批量导出工具,极致提升内容运营效率 【学习笔记】《Python编程 从入门到实践》第2章:变量命名规则、字符串操作与数值类型详解 Docker--Docker简介及系统架构 别再瞎搞 AI 了!大厂AI业务落地的五个关键环节!(建议新手直接照搬) [MAF的Agent管道详解-01]塑智能体边界,从AIAgent抽象类开始 平台智能化到了分水岭:为什么配置代码化才是 AI Coding 的下一代接口 P.4文本统计工具 高光谱拼接算法(二)Harris 角点探测 - 哥布林学者 Claude Code “悄悄”装了 Python 包?别再让它“投错胎”了 - only赟 影刀 vs 八爪鱼 RPA:到底选哪个?一篇讲透 AI Coding开始进入第四个时代,我还没上车呢! 完整学习LLM(四):Token是什么 【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (1)---基础 LitCTF2026web部分wp CAD子系统,是自研还是外包? 什么是教程地狱?5个信号说明你已经陷入(附3步摆脱方法) polygon出题教程 Manim物理模拟:别自己写欧拉了! AI 学习笔记:Agent 的应用演示 - 凌杰 分享一个CAN报文编辑器软件 洛谷P13016 [GESP202506 六级] 最大因数 MiniCPM-V 4.6 部署实战:基于 GPUStack 与 SGLang 的端侧多模态模型部署 用 FRP 打通云服务器与本地 Ubuntu,让 Codex 远程调试本地硬件 软考 - 架构设计师 知识点总结 给 FastApiAdmin 加个“会议纪要”模块,我把后端二次开发的坑踩了个遍 聊一聊 MES系统如何实现多种标签打印并支持不同打印机 2026第四届LitCTF网络安全挑战赛Pwn的wp 断尺问题:戴德金分割现实悖论 给句子做个“语义审计”:从词向量到句子向量的方法论 当AI“卡壳”在生产环境:MCP Server 如何帮我们破局 ofdkit-harmony 0.2.0 发布:鸿蒙原生 OFD 阅读库,已上架 ohpm 有了AI测试工具,还需要掌握Playwright、Pytest、Selenium这些框架吗? 组织转型实录——我把传统研发团队改成AI驱动,踩了无数坑 为什么 AI Coding 难进生产环境?深入了解 Everything-Claude-Code ! 到底 TMD 用哪个: npm, pnpm, Yarn, Bun, Deno? 傻瓜, 当然用 npm 啦 上周热点回顾(5.18-5.24) [对比学习LangChain和MAF-04]针对消息的设计 TrueAsync Server 为 PHP 带来了原生的高性能 HTTP 服务器 规则漂移 帆软市场部为什么能成为高人效增长系统? 22. LangChain LCEL,用 | 串联AI的魔法语言 - 老陈说编程 完整学习LLM(二):大模型到底是什么 洛谷-P11942 [KTSC 2025] 重塑矩阵 题解 哈哈哈哈哈打不过我吧,没有办法我(vllm)就是这么强大! Hermes Edu Skills 从 170 到 188:一次中文教育 Agent Skill Pack 的工程化升级 一个外行,半年搞定机械臂:我的从0到1踩坑实录 新写了个直播录制工具,可录制抖音快手斗鱼直播 15天学会AI应用开发(一)搭建AI大模型应用开发环境 Childhood,23款童年卡牌游戏复刻 Github Copilot配置GPT5.5报错:'temperature' does not support 0.1 with this model. Only the default (1) value is supported. - Eric zhou 单曲循环 ClassIn 在 Linux 下无法播放音频 把 TeXstudio / LaTeX 工程交给 AI:texstudio-mcp 功能详解 .NET 8 Web开发入门(六):Blazor 全栈开发——告别 JavaScript 焦虑 别让 LLM 写文件:一套 Agent 进度跟踪的工程化范式 - BurningFish Qt Bridges for C# 深度技术解析 Multus 多网卡方案:IPVLAN 模式 被流量逼出来的架构:从一台服务器到云原生的 17 次蜕变 —— 集群、缓存、MQ、微服务、Docker、K8S 的前世今生 Claude Code安装全流程 Windows保姆级教程 awk 命令练习(从入门到进阶) Java + Spring实现Hermes Agent之龙虾、Skills、Mcp和沙箱代码执行环境思路 轨迹的蓝图:方程求解与交点计算 Agent新技术分享-Forge论文已被ACM接受 PowerMem 记忆系统的遗忘设计,从神经元到代码工程 我用了FastApiAdmin后,连夜把踩过的坑都整理出来了 一个程序员眼中的 AI 核心概念,讲透 LLM 、Agent 、MCP 、Skill 、RAG... 网络安全在线就能打的内网靶场推荐 & Dawn Breaker 单域靶场 WP CTF 中如何用提示词发挥大模型的最大实力:从聊天助手到大手子 PyTorch KernelAgent 源码解读 ---(6)--- Composer 高光谱拼接算法(一)扫推式成像和航带拼接算法 一文看懂fofa常用语法,告别混淆,精准打击! 从零搭建量化投资系统:用 Qlib 一行代码搞定均线分析 企业 AI 落地,第一件事不是买模型,而是建好企业知识库 如何在Oracle Agent Factory中配置国内厂商的LLM? Codex 换模型太麻烦?这个开源桌面工具帮你一键切换 Avalonia中的动画 2026软考|十大管理超全通俗笔记,备考闭眼记! rv1126b内置phy接hub交换机芯片 React 可拖拽列宽 + 点击行选中 ProTable 封装笔记 五大实锤证据:AI不会终结低代码,只会倒逼技术进化 【硬核脑洞】16位实模式最后的疯狂:我们能否在 640KB 常规内存里手搓一个 MD 模拟器? 基于.Net的NetCoreKevin框架中AgentFramework实现AI智能体Skill和工具动态管理和加载 PostgreSQL 高可用集群 patroni 自动故障转移测试 自己使用C++开发的仿OpenClaw、Hermes智能体工具 记一次 .NET 某集群管理软件 内存暴涨分析 StarBlog番外(5) 从1.6到1.10,基于Avalonia AOT 开发的 Publisher 半年进化之路
uni-app 实现视频聊天、屏幕分享,支持Android、HarmonyOS、iOS
zhuweisky · 2026-05-26 · via 博客园_首页

  uni-app 作为国内主流的手机APP开发框架,一套代码,可以同时支持安卓、苹果和纯血鸿蒙(HarmonyOS),真是太方便了。当要开发在多种手机设备上运行的APP时,这会为我们节省大量的时间成本。

  之前我们基于.NET Core实现过一个PC端 视频聊天和远程桌面的Demo (支持Windows、信创Linux),现在基于uni-app强大的跨平台能力,我们来为这个Demo增加手机APP端(并且使得APP端与PC端可以互通)。虽然,不同的手机系统上调用的底层的音视频API不一样,但我们将其封装成统一的uni-app模块,这样在uni-app层,就可以统一调用了。现在,我们来看看具体的实现过程,文末有Demo源码可以下载。

一. 开发环境

开发工具:HBuilderX 5.04

开发语言:VUE3+JS

测试手机:华为 Mate 60 Pro (纯血鸿蒙) 、小米16 、 iPhone 12

HarmonyOS:6.0.0

Andriod:16

IOS:17.0.2 

二.功能介绍

  手机端登录成功后,运行的主界面如下图所示:  

微信图片_20260522103347_58_74 

1. 视频聊天

(1)每个登录的用户都可向其他任意在线用户发送视频聊天请求。

(2)当收到来自其他在线用户的视频聊天邀请时,可接受或拒绝对方的请求。

(3)当接受其他在线用户的视频聊天邀请时,即可开启视频聊天。 

2. 屏幕分享

(1)每个登录的用户都可向其他任意在线用户发送屏幕分享请求;当对方未响应时,可主动取消屏幕分享请求。

(2)当收到来自其他在线用户请求屏幕分享时,可接受或拒绝对方的请求。

(3)当发送方收到其他在线用户同意屏幕分享时,即可观看对方的屏幕。

三.具体实现

 下面我们讲一下Demo中核心的代码实现

1.自定义消息类型 InformationTypes

export const informationType = {
/**
* 视频请求
* */

VideoRequest: 0,
/**
* 回复视频请求的结果
* */
VideoResult: 1,
/**
* 通知对方 挂断 视频连接
* */
CloseVideo: 2,

/**
* 通知好友 网络原因,导致 视频中断
* */
NetReasonCloseVideo: 3,

/**
* 通知对方(忙线中) 挂断 视频连接
* */
BusyLine: 4,

/**
* 远程桌面请求
* */
DesktopRequest: 5,

/**
* 回复远程桌面请求的结果
* */
DesktopResult: 6,

/**
* 主动取消远程桌面请求
* */
CancelDesktop: 7,

/**
* 对方(主人端)主动断开远程桌面
* */
OwnerCloseDesktop: 8,

/**
* 客人端断开远程桌面连接
* */
GuestCloseDesktop: 9
}

2. 发送视频请求

(1)当发起视频聊天时,将显示视频聊天窗口,并发送视频连接请求

//通过import导入OMCS插件包里的多媒体管理器类以及其他连接器类    
import {
        MultimediaManagerFactory,
        DesktopConnector,
        CameraConnector,
        MicrophoneConnector
    } from "../OMCS/OMCS-Uni";


//获取到多媒体管理器
const multimediamanager = MultimediaManagerFactory.GetSingleton()

//发起视频聊天请求函数
const goVideo = () => {
//发送视频连接请求   
multimediamanager.SendCustomizedMessage(ownerID.value,informationType.VideoRequest,[0],'')
getConnector(ownerID.value) //跳转到视频请求页面 uni.navigateTo({ url: `/pages/videoPage?userID=${userID.value}&ownerID=${ownerID.value}&isMyRequest=${1}` }) }

 (2)连接自己的摄像头,预览自己的视频:

//通过uniapp的条件编译,区分鸿蒙与ios和android            
<!-- #ifdef APP-HARMONY -->
            <view class="toolBox" @touchmove="drag" @touchstart="drag" @touchend="drag"
                :style="`z-index: 9999;position: fixed; left: ${cameraLeft}px; top: ${cameraTop}px;`">
                <HmosCameraSurfaceViewVue ref="camera_self_panel_view" class="selfVideoView">
                </HmosCameraSurfaceViewVue>
            </view>
            <!-- #endif -->

            <!-- #ifndef APP-HARMONY -->
            <view class="toolBox" @touchmove="drag" @touchstart="drag" @touchend="drag"
                :style="`z-index: 9999;position: fixed; left: ${cameraLeft}px; top: ${cameraTop}px;`">
                <CameraSurfaceView class="selfVideoView" ref="selfCameraView">
                </CameraSurfaceView>
            </view>
            <!-- #endif -->
//ios与android,端需要获取控件实例,然后调用控件里的方法设置控件,鸿蒙端直接使用我们封装好的HmosCameraSurfaceViewVue,就可以预览自己的视频
const selfCameraView = ref(null)
//在onReady里设置控件
onReady(() => {
console.log(selfCameraView.value);
selfCameraView.value.setVideo()
})        

(3)当发送聊天邀请时,将显示视频邀请窗口             

3. 回复对方视频请求

(1)当收到对方的视频聊天邀请时,将显示视频邀请窗口   

     微信图片_20260522110659_60_74

  (2)发送回复视频聊天请求消息

//挂断    
const handsup = () => {
//发送挂断消息
MultimediaManagerFactory.GetSingleton().SendCustomizedMessage(ownerID.value, informationType.CloseVideo, [],'')
        if (isRequestVideo.value) {
            uni.disConnector()
        }
        isRequestVideo.value = false
        isMyRequest.value = false
        uni.navigateBack();
    }
//接听
    const answer = () => {
//发送接听消息
MultimediaManagerFactory.GetSingleton().SendCustomizedMessage(ownerID.value, informationType.VideoResult, [1],
            '')
        isRequestVideo.value = true
        timer()
        requestCamera()
    }

4. 收到对方发送过来的消息回调

        multimediamanager.setCustomMessageReceivedCallback((res) => {
            try {
         //res为服务端返回过来的JSON数据
                 const message = JSON.parse(res)
                requestID.value = message.SourceUserID
                switch (message.InformationType) {
                    case informationType.VideoRequest:
                        //有人发出视频邀请
                        if (cameraConnector.value == null && microphoneConnector.value == null) {
                            getConnector(message.SourceUserID)
                        } else {
                            console.log("正在通话中");
                            return
                        }
                        uni.navigateTo({
                            url: `/pages/videoPage?userID=${userID.value}&ownerID=${requestID.value}&isMyRequest=${0}`
                        });
                        break;

                    case informationType.VideoResult:
                       //对方回复视频邀请请求
                        const res = new Stream(message.Content)
                        const videoBool = res.readBoolean()
                        if (videoBool) {
                            isRequestVideo1.value = true;
                        } else {
                            uni.showToast({
                                position: "bottom",
                                title: "对方挂断"
                            })
                            if (isRequestVideo1.value) {
                                disConnector()
                            }
                            isRequestVideo1.value = false;
                        }
    uni.$emit(
'updateVideoResult', { isRequestVideo: isRequestVideo1.value }) break case informationType.CloseVideo: if (isRequestVideo1.value) { disConnector() } uni.showToast({ position: "bottom", title: "对方挂断了音视频邀请" }) uni.navigateBack() break case informationType.DesktopRequest: console.log("有人请求远程桌面"); ownerID.value = message.SourceUserID popup.value.open() break case informationType.DesktopResult:                //请求远程桌面的回复 const stream = new Stream(message.Content) const bool = stream.readBoolean() console.log(bool); if (bool) { console.log(1); // #ifndef APP-HARMONY desktopConnector.value = new DesktopConnector(requestID.value) // #endif console.log(2); // #ifndef APP-HARMONY uni.__desktopConnector = desktopConnector.value // #endif console.log(3); uni.navigateTo({ url: `/pages/desktop?destUserID=${ownerID.value}` }) isScreenShareRequest.value = false } else { uni.showToast({ position: "bottom", title: "对方拒绝了屏幕分享请求" }) isScreenShareRequest.value = false } break case informationType.CancelDesktop:              uni.showToast({ position: "bottom", title: "对方主动断开了桌面连接" }) popup.value.close() break case informationType.OwnerCloseDesktop: uni.reLaunch({ url: `/pages/home?userID=${userID.value}` }) uni.showToast({ position: "bottom", title: "对方主动断开了桌面连接" }) // #ifndef APP-HARMONY desktopConnector.value.disconnect() uni.__desktopConnector = null // #endif break case informationType.GuestCloseDesktop: multimediamanager.stopScreenBroadcast() isSomeoneWatchScreen.value = false break } } catch (err) { console.log(err); } })

    当对方回复同意时,将连接到对方的麦克风和摄像头,开始视频聊天会话

onLoad((e) => {
console.log(e);
//监听对方是否同意视频连接
uni.$on('updateVideoResult', (data) => {
isRequestVideo.value = data.isRequestVideo
if (isRequestVideo.value) {
requestCamera()
timer()
}
})
//切换摄像头
MultimediaManagerFactory.GetSingleton().switchCameraDeviceIndex(cameraIndex.value);
//打开扬声器
MultimediaManagerFactory.GetSingleton().openSpeaker();
//设置是否打开麦克风
MultimediaManagerFactory.GetSingleton().setOutputAudio(isOpenMic.value);
ownerID.value = e.ownerID
console.log(ownerID.value);
isMyRequest.value = Boolean(Number(e.isMyRequest))

})

const requestCamera = () => {
        uni.cameraConnector.beginConnect()
        uni.microphoneConnector.beginConnect()
        // #ifndef APP-HARMONY
        console.log(selfCameraView.value);
        nextTick(() => {
            console.log(selfCameraView.value);
            selfCameraView.value.setVideo()
        })
        // #endif


        uni.cameraConnector.videoRequestIFrame()
    }

   视频会话的UI效果如下图所示:  

      微信图片_20260522114138_61_74

5. 实现屏幕分享

         屏幕分享的请求/应答逻辑几乎与视频聊天请求/应答逻辑是一模一样的。这里就不再罗列响应的代码了。

    下面的截图是以Windows与鸿蒙手机端互动为例:Windows端作为请求方,手机端作为应答方。(反过来也是一样的)

(1)PC端发起请求 

      image

 (2)手机端收到对方的屏幕分享请求时,将显示请求窗口。  

      90f0ad11025667b66a9120f596ed2e8e

  (3)当手机端同意请求时,PC端就可以观看手机的屏幕了。

      QQ_1779421573219 

 四.不同端的配置

 (1)由于苹果和安卓使用的是uniapp的原生语言插件,鸿蒙使用的是uts原生混编,所以插件的配置会有些许不同

QQ_1779431124597QQ_1779431181279

   如图所示,安卓苹果插件需要放在nativeplugins目录下,而鸿蒙插件需要放在uni_modules目录下。并且,安卓苹果的插件,需要通过uniapp云打包后,才能使用,鸿蒙直接运行即可。

QQ_1779432114469

   导入插件并且打包完后,安卓苹果便可以通过uni.requireNativePlugin来获取插件实例,鸿蒙直接通过import就可以获取到插件。 

五. 源码下载

 1. uni-app 端:VideoChatMini-uniapp.rar (支持Android、iOS、HarmonyOS)

 2. 服务端 + PC 端:VideoChatMini.rar  (支持Windows、Linux、银河麒麟、统信UOS)

  在这里,我也给出了服务端和PC端的源码,服务端和PC端都是 C# 开发的(开发环境是 VS2022),跨平台使用的是.NET Core 。

  如此一来,所有的PC之间、所有的手机之间、以及所有的手机和PC之间,都可以互通的,也就是可以相互视频通话,以及观看屏幕/桌面。

  关于这个Demo,如果你有好的建议,请留言给我,谢谢。