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
二.功能介绍
手机端登录成功后,运行的主界面如下图所示:

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)当收到对方的视频聊天邀请时,将显示视频邀请窗口
(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效果如下图所示:

5. 实现屏幕分享
屏幕分享的请求/应答逻辑几乎与视频聊天请求/应答逻辑是一模一样的。这里就不再罗列响应的代码了。
下面的截图是以Windows与鸿蒙手机端互动为例:Windows端作为请求方,手机端作为应答方。(反过来也是一样的)
(1)PC端发起请求

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

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

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


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

导入插件并且打包完后,安卓苹果便可以通过uni.requireNativePlugin来获取插件实例,鸿蒙直接通过import就可以获取到插件。
五. 源码下载
-
uni-app 端:VideoChatMini-uniapp.rar (支持Android、iOS、HarmonyOS)
-
服务端 + PC 端:VideoChatMini.rar (支持Windows、Linux、银河麒麟、统信UOS)
在这里,我也给出了服务端和PC端的源码,服务端和PC端都是 C# 开发的(开发环境是 VS2022),跨平台使用的是.NET Core 。
如此一来,所有的PC之间、所有的手机之间、以及所有的手机和PC之间,都可以互通的,也就是可以相互视频通话,以及观看屏幕/桌面。
关于这个Demo,如果你有好的建议,请留言给我,谢谢。
