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

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"