更新记录
1.0.1(2026-04-07)
- 增加切换扬声器/听筒接口
- 增加切换前后摄像头接口
- 增加开启/关闭视频流接口
1.0.0(2026-04-06)
- 视频通话
平台兼容性
uni-app x(3.7.3)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | 5.0 | √ | - | - |
其他
| 多语言 | 暗黑模式 | 宽屏模式 |
|---|---|---|
| √ | √ | √ |
WebRTC音视频通话
主要功能
- 2人/多人音视频通话
- 静音/闭麦
- 切换摄像头
- 暂停/继续视频流
- 切换扬声器/听筒
uniapp版本https://ext.dcloud.net.cn/plugin?name=wrs-uts-webrtc
集成步骤
- 拷贝demo里的Info.plist和AndroidManifest.xml到项目根目录
- 咨询或定制请点击上面"进入交流群"按钮私聊作者
- demo/static/NodeJS是websocket服务器,使用node app.js命令既可以运行,用户传送信令
- 修改demo的socket服务器地址(webSocketUrl)改为电脑ip
- socket业务可以使用各自的服务器,支持用来发送接收信令,可以使用socket、webSocket、socket.IO都可以,demo里的socket服务只是配合演示流程
socketTask = uni.connectSocket({
url: 'ws://172.16.11.37:8088',
complete: () => {}
});
- 如果socket服务器是采用局域网IP连接,ios某些机型连接局域网时首次访问会弹出局域网授权信息,点击确定后再次点击界面上的"连接socket"按钮,socket连接状态可以查看控制台或代码
- demo演示流程: 点击界面上的加入房间即可进行视频通话 整体业务流程:
- 点击"加入房间",会向房间里的其他人发送新成员加入消息
{
msgType: "join",
userId: "xx"
}
- 其他成员收到join消息时,会与该用户创建一个PeerConnection(同时把本地音视频加入PeerConnection),并生成offer,设置setLocalDescription,然后将offer数据发送给对方,同时会生成onIceCandidate,IceCandidate数据也要发送给对方
offer数据:
{
msgType: "sdp",
fromUserId: "xx",
toUserId: "xx",
type: "offer",
sdp: "xx"
}
IceCandidate数据:
{
msgType: "iceCandidate",
fromUserId: “xx”,
toUserId: "xx",
id: candidate.sdpMid,
label: candidate.sdpMLineIndex,
candidate: candidate.sdp
}
- 用户收到offer消息时,也创建一个PeerConnection(同时把本地音视频加入PeerConnection),并setRemoteDescription,然后生成answer,设置setLocalDescription,然后将answer数据发送给对方,同时会生成onIceCandidate,IceCandidate数据也要发送给对方
answer数据:
{
msgType: "sdp",
fromUserId: "xx",
toUserId: "",
type: "answer",
sdp: ""
}
IceCandidate数据:
{
msgType: "iceCandidate",
fromUserId: “xx”,
toUserId: "xx",
id: candidate.sdpMid,
label: candidate.sdpMLineIndex,
candidate: candidate.sdp
}
- 用户收到answer消息时,设置setRemoteDescription,双方收到对方的iceCandidate消息时,都调用addIceCandidate接口设置
- 完成以上流程后,就可以进行视频通话了
接口
import { UTSWebRTC } from "@/uni_modules/wrs-uts-webrtcx"
let webRTC = new UTSWebRTC()
- 设置webRTC的回调
// 设置webRTC的回调
webRTC.onCallback((resp) => {
let opt = resp.opt
showMsg("webRTC.onCallback opt:" + opt)
switch (opt) {
// 信令状态改变
case "onSignalingChange": {
showMsg("onSignalingChange:" + JSON.stringify(resp))
let state = resp.state
if (state != null) {
const stateValue = state! as number
switch (stateValue) {
case 0: {
showMsg("RTCSignalingStateStable")
}
break;
case 1: {
showMsg("RTCSignalingStateHaveLocalOffer")
}
break;
case 2: {
showMsg("RTCSignalingStateHaveLocalPrAnswer")
}
break;
case 3: {
//
showMsg("RTCSignalingStateHaveRemoteOffer")
}
break;
case 4: {
showMsg("RTCSignalingStateHaveRemotePrAnswer")
}
break;
case 5: {
showMsg("RTCSignalingStateClosed")
let userId = resp["userId"]
if (userId != null) {
userLeave(`${userId!}`)
}
}
break;
default:
break;
}
}
}
break;
case "onIceGatheringChange": {
showMsg("onIceGatheringChange:" + JSON.stringify(resp))
let state = resp["state"]
if (state != null) {
let stateValue = state as number
switch (stateValue) {
case 0: {
showMsg("RTCIceGatheringStateNew")
}
break;
case 1: {
showMsg("RTCIceGatheringStateGathering")
}
break;
case 2: {
showMsg("RTCIceGatheringStateComplete")
}
break;
default:
break;
}
}
}
break;
// 生成IceCandidate
case "onIceCandidate": {
console.error(`onIceCandidate resp:${JSON.stringify(resp)}`)
let toUserId = resp["userId"]
let candidate = resp["iceCandidate"]
if(candidate != null) {
let candidateObj = candidate! as UTSJSONObject
let sdpMid = candidateObj["sdpMid"]
let sdpMLineIndex = candidateObj["sdpMLineIndex"]
let sdp = candidateObj["sdp"]
if (toUserId != null && sdpMid != null && sdpMLineIndex != null && sdp != null) {
let params = {
msgType: "iceCandidate",
fromUserId: userId,
toUserId: toUserId!,
id: sdpMid!,
label: sdpMLineIndex!,
candidate: sdp!
}
showMsg(`发送 ice Candidate onIceCandidate:${JSON.stringify(params)}`)
sendSocketData(params)
}
}
}
break;
case "onIceConnectionChange": {
showMsg("onIceConnectionChange:" + JSON.stringify(resp))
let state = resp.state
switch (state) {
case 0: {
showMsg("RTCIceConnectionStateNew")
}
break;
case 1: {
showMsg("RTCIceConnectionStateChecking")
}
break;
case 2: {
// 这步没有
showMsg("RTCIceConnectionStateConnected")
}
break;
case 3: {
showMsg("RTCIceConnectionStateCompleted")
}
break;
case 4: {
console.error("RTCIceConnectionStateFailed")
showMsg("RTCIceConnectionStateFailed")
}
break;
case 5: {
// 通讯被断开,一般是对方掉线或者STUN/TURN 服务器问题:如果 ICE 服务器配置不当,或者 STUN/TURN 服务器不可用,可能会导致连接失败。确保你的 STUN/TURN 服务器正常工作并且可达。
showMsg("RTCIceConnectionStateDisconnected")
let userId = resp.userId
if (userId != null) {
userLeave(`${userId!}`)
}
}
break;
case 6: {
showMsg(" RTCIceConnectionStateClosed")
let userId = resp.userId
if (userId != null) {
userLeave(`${userId!}`)
}
}
break;
case 7: {
showMsg(" RTCIceConnectionStateCount")
}
break;
default:
break;
}
}
break;
// 收到其他用户的音频或视频流
case "onAddStream": {
let tempUserId = resp["userId"]
showMsg("onAddStream:" + JSON.stringify(resp))
if (tempUserId != null) {
let remoteUserId = `${tempUserId!}`
let tempStream = resp["stream"]
if (tempStream != null) {
// 如果有视频流,则显示其他用户的视频
let stream = tempStream! as UTSJSONObject
let tempVideoTracks = stream["videoTracks"]
if (tempVideoTracks != null) {
const videoTracks = tempVideoTracks! as Array<UTSJSONObject>
if (videoTracks.length > 0) {
let exist = existUser(remoteUserId)
if (!exist) {
let paramsModel = {}
paramsModel["userId"] = remoteUserId
let businessModel = {}
businessModel["business"] = "renderRemoteVideo"
businessModel["params"] = paramsModel
let businessArray = new Array<UTSJSONObject>()
businessArray.push(businessModel)
let renderParams = {}
renderParams["businessArray"] = businessArray
let renderParamsStr = JSON.stringify(renderParams)
let userModel = new RemoteUser()
userModel.userId = remoteUserId
userModel.renderParams = renderParamsStr
console.log(`收到远程视频流:${JSON.stringify(userModel)} renderParams:${renderParamsStr}`)
otherPersons.value.push(userModel)
}
}
}
}
}
}
break;
case "onRemoveStream": {
}
break;
default:
break;
}
})
- 初始化本地视频
// 初始化视频
webRTC.initVideoTrack({
trackId: "video0",
isScreencast: false // 仅对Android生效
})
- 初始化本地音频
// 初始化音频
webRTC.initAudioTrack({
trackId: "audio0"
})
- 配置音频,仅支持iOS
webRTC.configureAudioSession({
category: "playAndRecord",
mode: "voiceChat"
})
- 开启相机抓流/切换摄像头
// 开始本地抓流
let params = {
isFront: this.isFront,
width: 1280, // width仅支持Android
height: 720, // height仅支持Android
fps: 30
}
// params.cameraName = "xxx" //摄像头名称,传了cameraName的话isFront无效,cameraName从webRTC.getAllCameras()接口获取
webRTC.startVideoCapture(params)
- 获取所有摄像头名称, 仅支持Android
let result = webRTC.getAllCameras()
let cameraArray = result.cameras
- 暂停抓流
webRTC.stopVideoCapture()
- 开启/关闭本地视频
let enable = true
webRTC.setLocalVideoTrackEnable(enable)
- 开启/关闭本地音频
let enable = true
webRTC.setLocalAudioTrackEnable(enable)
- 创建连接
iceServers支持类型: 打洞服务器,可以自己搭建https://github.com/coturn/coturn
- 第一种
{
urls: ["xxx"]
}
- 第二种
{
urls: ["xxx"],
username: "xx", // 账号
credential: "xx" // 密码
}
let iceServers = [{
urls: ["stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302",
"stun:stun3.l.google.com:19302",
"stun:stun4.l.google.com:19302"
],
username: "xxxx",
credential: "xxxx"
}]
let params = {}
params["userId"] = userId
// params.iceServers = iceServers
// params.sdpSemantics = 1 // 0: RTCSdpSemanticsPlanB 1:RTCSdpSemanticsUnifiedPlan
// params.continualGatheringPolicy =
// 1 // 0: RTCContinualGatheringPolicyGatherOnce 1: RTCContinualGatheringPolicyGatherContinually
// params.constraints = {
// mandatory: {},
// optional: {
// DtlsSrtpKeyAgreement: "true"
// }
// }
userId = webRTC.createPeerConnection(params)
- 将本地视频加入连接
let videoResp = webRTC.addVideoTrack({
userId: userId,
streamIds: ["video0"]
})
let videoFlag = videoResp.flag
if (!videoFlag) {
this.showMsg("添加本地视频出错:" + JSON.stringify(videoFlag))
}
- 将本地音频加入连接
let audioResp = webRTC.addAudioTrack({
userId: userId,
streamIds: ["audio0"]
})
let audioFlag = audioResp["flag"]! as boolean
if (!audioFlag) {
this.showMsg("添加本地音频出错:" + JSON.stringify(videoFlag))
}
- 创建offer
webRTC.createOffer({
userId: userId,
setLocalDescription: false
}, (resp) => {
let flag = resp["flag"]! as boolean
if (flag) {
let sessionDescription = resp["sessionDescription"]
let type = sessionDescription["type"]
let sdp = sessionDescription["sdp"]
}
}
)
- 设置本地LocalDescription
webRTC.setLocalDescription({
userId: userId,
type: type, // 支持offer、pranswer、answer
sdp: sdp
}, (localDescResp) => {
let localFlag = localDescResp["flag"]! as boolean
if (localFlag) {
}
}
)
- 设置远程RemoteDescription
webRTC.setRemoteDescription({
userId: userId,
type: type, // 支持offer、pranswer、answer
sdp: sdp
}, (resp) => {
let flag = resp["flag"]! as boolean
if (flag) {
}
}
)
- 创建answer
webRTC.createAnswer({
userId: userId,
setLocalDescription: false
}, (answerResp) => {
let flag = answerResp["flag"]! as boolean
if (flag) {
// console.log("createAnswer result:" + JSON.stringify())
let sessionDescription = answerResp["sessionDescription"]
let type = sessionDescription["type"]
let sdp = sessionDescription["sdp"]
}
}
)
- 添加候选人IceCandidate,一般调用offer或answer时会生成多次IceCandidate,可以都发送给对方,对方设置多次
webRTC.addIceCandidate({
userId: userId,
sdpMid: sdpMid,
sdpMLineIndex: sdpMLineIndex,
sdp: sdp
}, (resp) => {
let flag = resp.flag
if (!flag) {
this.showMsg("addIceCandidate error:" + JSON.stringify(resp))
}
})
- 销毁某个用户的连接
webRTC.destroyPeerConnection({
userId: userId
})
- 销毁所有的连接
webRTC.destroyAllPeerConnection()
UI组件
使用wrs-uts-webrtc-view组件的页面要用nvue
<wrs-uts-webrtcx :style="'width:'+width+'px;height:'+height+'px;'" :params="localViewParams"
@onEvent="onLoadLocalView"></wrs-uts-webrtcx>
- 渲染本地视频 通过修改localViewParams参数来实现业务
// 渲染本地视频界面
let businessArray = new Array<UTSJSONObject>()
businessArray.push({
business: "renderLocalVideo" // 业务名称,渲染本地画面
})
let params = {}
params["businessArray"] = businessArray
localViewParams.value = JSON.stringify(params)
- 渲染其他用户视频
let paramsModel = {}
paramsModel["userId"] = remoteUserId // 用户ID
let businessModel = {}
businessModel["business"] = "renderRemoteVideo" // 渲染远程用户画面
businessModel["params"] = paramsModel
let businessArray = new Array<UTSJSONObject>()
businessArray.push(businessModel)
let renderParams = {}
renderParams["businessArray"] = businessArray
let renderParamsStr = JSON.stringify(renderParams)
- 切换前后摄像头
webRTC.startVideoCapture({
isFront: true, // 是否使用前摄像头
width: 1280, // 仅对Android生效
height: 720 // 仅对Android生效
})
- 开启/关闭视频流
webRTC.setLocalVideoTrackEnable(true)
- 切换扬声器/听筒
webRTC.setSpeakerEnable(true)

收藏人数:
购买源码授权版(
试用
使用 HBuilderX 导入示例项目
赞赏(0)
下载 19
赞赏 0
下载 11931035
赞赏 1914
赞赏
京公网安备:11010802035340号