app php01 790c53caa0 最后一版uniaiim 4 ヶ月 前
..
utssdk 790c53caa0 最后一版uniaiim 4 ヶ月 前
changelog.md 790c53caa0 最后一版uniaiim 4 ヶ月 前
encrypt 790c53caa0 最后一版uniaiim 4 ヶ月 前
package.json 790c53caa0 最后一版uniaiim 4 ヶ月 前
readme.md 790c53caa0 最后一版uniaiim 4 ヶ月 前

readme.md

WebRTC音视频通话

主要功能

  1. 2人/多人音视频通话
  2. 静音/闭麦
  3. 切换摄像头
  4. 暂停/继续视频流

集成步骤

  1. demo使用的是vue3,HBuilderX导入的时候选择vue3,vue2也是支持的
  2. 拷贝demo里的Info.plist和AndroidManifest.xml到项目根目录
  3. 集成插件,集成插件步骤请参考https://www.cnblogs.com/wenrisheng/p/18323027
  4. demo/static/NodeJS是websocket服务器,使用node app.js命令既可以运行
  5. 修改demo的socket服务器地址(webSocketUrl)改为电脑ip
  6. socket业务可以使用各自的服务器,支持用来发送接收信令,可以使用socket、webSocket、socket.IO都可以,demo里的socket服务只是配合演示流程

    
    socketTask = uni.connectSocket({
    	url: 'ws://172.16.11.37:8088',
    	complete: () => {}
    });
    
    
  7. 如果socket服务器是采用局域网IP连接,ios某些机型连接局域网时首次访问会弹出局域网授权信息,点击确定后再次点击界面上的"连接socket"按钮,socket连接状态可以查看控制台或代码

  8. demo演示流程: 点击界面上的加入房间即可进行视频通话 整体业务流程:

  9. 点击"加入房间",会向房间里的其他人发送新成员加入消息

    
    {
    	msgType: "join",
    	userId: "xx"
    }
    
    
  10. 其他成员收到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
    }
    
    
  11. 用户收到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
    }
    
    
    
  12. 用户收到answer消息时,设置setRemoteDescription,双方收到对方的iceCandidate消息时,都调用addIceCandidate接口设置

  13. 完成以上流程后,就可以进行视频通话了

接口


import {
	UTSWebRTC
} from "@/uni_modules/wrs-uts-webrtc"

let webRTC = new UTSWebRTC()

  • 设置webRTC的回调

    
    // 设置webRTC的回调
    webRTC.onCallback((resp) => {
    	let opt = resp.opt
    	this.showMsg("webRTC.onCallback opt:" + opt)
    	switch (opt) {
    		// 信令状态改变
    		case "onSignalingChange": {
    			this.showMsg("onSignalingChange:" + JSON.stringify(resp))
    			let state = resp.state
    			if (state) {
    				switch (state) {
    					case 0: {
    						this.showMsg("RTCSignalingStateStable")
    					}
    					break;
    					case 1: {
    						this.showMsg("RTCSignalingStateHaveLocalOffer")
    					}
    					break;
    					case 2: {
    						this.showMsg("RTCSignalingStateHaveLocalPrAnswer")
    					}
    					break;
    					case 3: {
    						// 
    						this.showMsg("RTCSignalingStateHaveRemoteOffer")
    					}
    					break;
    					case 4: {
    						this.showMsg("RTCSignalingStateHaveRemotePrAnswer")
    					}
    					break;
    					case 5: {
    						this.showMsg("RTCSignalingStateClosed")
    						let userId = resp.userId
    						if (userId) {
    							this.userLeave(userId)
    						}
    					}
    
    					break;
    					default:
    						break;
    				}
    			}
    		}
    		break;
    		case "onIceGatheringChange": {
    			this.showMsg("onIceGatheringChange:" + JSON.stringify(resp))
    			let state = resp.state
    			if (state) {
    				switch (state) {
    					case 0: {
    						this.showMsg("RTCIceGatheringStateNew")
    					}
    					break;
    					case 1: {
    						this.showMsg("RTCIceGatheringStateGathering")
    					}
    					break;
    					case 2: {
    						this.showMsg("RTCIceGatheringStateComplete")
    					}
    					break;
    					default:
    						break;
    				}
    			}
    		}
    		break;
    		// 生成IceCandidate
    		case "onIceCandidate": {
    			this.showMsg("onIceCandidate")
    			let userId = resp.userId
    			let candidate = resp.iceCandidate
    			this.sendSocketData({
    				msgType: "iceCandidate",
    				fromUserId: this.userId,
    				toUserId: userId,
    				id: candidate.sdpMid,
    				label: candidate.sdpMLineIndex,
    				candidate: candidate.sdp
    			})
    		}
    		break;
    		case "onIceConnectionChange": {
    			this.showMsg("onIceConnectionChange:" + JSON.stringify(resp))
    			let state = resp.state
    			switch (state) {
    				case 0: {
    					this.showMsg("RTCIceConnectionStateNew")
    				}
    				break;
    				case 1: {
    					this.showMsg("RTCIceConnectionStateChecking")
    				}
    				break;
    				case 2: {
    					// 这步没有
    					this.showMsg("RTCIceConnectionStateConnected")
    				}
    				break;
    				case 3: {
    					this.showMsg("RTCIceConnectionStateCompleted")
    				}
    				break;
    				case 4: {
    					this.showMsg("RTCIceConnectionStateFailed")
    				}
    				break;
    				case 5: {
    					// 通讯被断开,一般是对方掉线或者STUN/TURN 服务器问题:如果 ICE 服务器配置不当,或者 STUN/TURN 服务器不可用,可能会导致连接失败。确保你的 STUN/TURN 服务器正常工作并且可达。
    					this.showMsg("RTCIceConnectionStateDisconnected")
    					let userId = resp.userId
    					if (userId) {
    						this.userLeave(userId)
    					}
    				}
    				break;
    				case 6: {
    					this.showMsg(" RTCIceConnectionStateClosed")
    					let userId = resp.userId
    					if (userId) {
    						this.userLeave(userId)
    					}
    				}
    				break;
    				case 7: {
    					this.showMsg(" RTCIceConnectionStateCount")
    				}
    				break;
    				default:
    					break;
    			}
    
    		}
    
    		break;
    		// 收到其他用户的音频或视频流
    		case "onAddStream": {
    			let userId = resp.userId
    			this.showMsg("onAddStream:" + JSON.stringify(resp))
    			if (userId) {
    				let stream = resp.stream
    				if (stream) {
    					// 如果有视频流,则显示其他用户的视频
    					let videoTracks = stream.videoTracks
    					if (videoTracks) {
    						if (videoTracks.length > 0) {
    							let exist = this.existUser(userId)
    							if (!exist) {
    								console.log("显示远程视频流:" + userId)
    								this.otherPersons.push({
    									userId: userId
    								})
    							}
    						}
    					}
    				}
    			}
    
    		}
    		break;
    		case "onRemoveStream": {
    
    		}
    		break;
    
    		default:
    			break;
    	}
    })
    
    
  • 初始化本地视频

    
    // 初始化视频
    webRTC.initVideoTrack({
    	trackId: "video0",
    	isScreencast: false // 仅对Android生效
    })
    
    
  • 初始化本地音频

    
    // 初始化音频
    webRTC.initAudioTrack({
    	trackId: "audio0"
    })
    
    
  • 配置音频,仅支持iOS

    
    webRTC.configureAudioSession({
    	category: "playAndRecord",
    	mode: "voiceChat"
    })
    
    
  • 开启相机抓流/切换摄像头

    
    // 开始本地抓流
    webRTC.startVideoCapture({
    	isFront: this.isFront,
    	width: 1280, // width仅支持Android
    	height: 720, // height仅支持Android
    	fps: 30
    })
    
    
  • 暂停抓流

    
    webRTC.stopVideoCapture()
    
    
    
  • 创建连接

iceServers支持类型:

  1. 第一种

    
    {
    urls: ["xxx"]
    }
    
    
  2. 第二种

    
    {
    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"
    	]
    }]
    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
    if (!audioFlag) {
    	this.showMsg("添加本地音频出错:" + JSON.stringify(videoFlag))
    }
    
    
  • 创建offer

    
    webRTC.createOffer({
    	userId: userId,
    	setLocalDescription: false
    }, (resp) => {
    	let flag = resp.flag
    	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
    	if (localFlag) {
    	}
    	}
    )
    
    
  • 设置远程RemoteDescription

    
    webRTC.setRemoteDescription({
    	userId: userId,
    	type: type, // 支持offer、pranswer、answer
    	sdp: sdp
    }, (resp) => {
    	let flag = resp.flag
    	if (flag) {
    		}
    	}
    )
    
    
  • 创建answer

    
    webRTC.createAnswer({
    	userId: userId,
    	setLocalDescription: false
    }, (answerResp) => {
    	let flag = answerResp.flag
    	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-webrtc-view ref='localView' :style="'width:'+width+'px;height:'+height+'px;'"
	@onLoadView="onLoadLocalView" :userId="userId"></wrs-uts-webrtc-view>
	
  • 渲染本地视频

    
    // 渲染本地视频界面
    this.$refs.localView.renderLocalVideo()
    
    
  • 渲染其他用户视频

目前有2种方式:

  1. 调用接口,常用于2人通话

    
    this.$refs.remoteView.renderRemoteVideo(userId)
    
    
  2. 绑定userId属性,常用于多人通话

    
    :userId="userId"