uniapp开发WebRTC语音直播间支持app(android+IOS)和H5,并记录了所有踩得坑

7 篇文章 0 订阅
订阅专栏
2 篇文章 0 订阅
订阅专栏

一、效果图

二、主要功能

1. 创建自己的语音直播间

2. 查询所有直播间列表

3.加入房间

4.申请上位

5.麦克风控制

6.声音控制

7.赠送礼物(特效 + 批量移动动画)

8.退出房间

三、原理

1.uniapp 实现客户端H5、安卓、苹果

2.webRTC实现语音直播间(具体原理网上有很多文章我就不讲了,贴个图)

3.使用node.js搭建信令服务器(我用的是socket)

4.礼物及特效使用svga

四、踩坑及解决方案

1. 客户端(这里重点在于app端)一定要在视图层创建webRTC!!!不要在逻辑层创建!!!因为会要求使用安全连接,也就是说要用到SSL证书,这个很多人都没有,有的话当我没说。如何在视图层创建RTC呢?在uniapp中使用renderjs!

<script module="webRTC" lang="renderjs">
new RTCPeerConnection(iceServers)
</script>

2. (这里重点也在于app)客户端创建和信令服务器进行通信的socket时app端在页面跳转后socket状态消失无法响应信令服务器消息。解决方案是:一定不要在客户端视图层创建socket!!!也就是说socket不要创建在renderjs里,要在逻辑层用uniapp提供的api进行创建,然后使用uniapp文档中说明的逻辑层和视图层的通信方式进行通信,这样虽然在开发中有些繁琐,但是能解决问题。

onShow(){
// socketTask是使用uniapp提供的uni.connectSocket创建出来的socket实例
// watchSocketMessage代理了socket实例的onMessage方法
socketTask.watchSocketMessage = (data) => {
				this.watchSocketMessage(data)
			}
    
}

methed:{
    watchSocketMessage(){
        // 这里是收到信令服务器socket后的逻辑
    }
}
// 这里是逻辑层和renderjs通信的方式,通过监听状态的改变从而触发renderjs的对应的方法
// 注意在页面刚加载完成后这些方法会被默认触发一边,所以要在这些放方法做好判断return出去

<view :rid="rid" :change:rid="webRTC.initRid" :userId="userId" :change:userId="webRTC.initUserId"
			:giftnum="giftnum" :change:giftnum="webRTC.initgiftnum" :micPosition="micPosition"
			:change:micPosition="webRTC.initMicPositions" :giftPosition="giftPosition"
			:change:giftPosition="webRTC.initGiftPosition" :RTCJoin="RTCJoin" :change:RTCJoin="webRTC.changeRTCjoin"
			:RTCOffier="RTCOffier" :change:RTCOffier="webRTC.changeRTCoffier" :RTCAnswer="RTCAnswer" :isAudio="isAudio"
			:change:isAudio="webRTC.changeIsAudio" :change:RTCAnswer="webRTC.changeRTCAnswer"
			:RTCCandidate="RTCCandidate" :change:RTCCandidate="webRTC.changeRTCCandidate" :isTrue="isTrue"
			:change:isTrue="webRTC.changeIsTrue" :newMess="newMess" :change:newMess="webRTC.changeNewMessage"
			:isMedia="isMedia" :name="name" :change:name="webRTC.changeName" :change:isMedia="webRTC.changeIsMedia"
			:animos="animos" :change:animos="changeAnimos" class="chat">
</view>

3.连接顺序的问题,一定是:新进入的用户通过信令服务器给房间已有用户发送Offer,用户接收到Offer回应Answer,记住这个逻辑!

4.因为webRTC是运行在视图层的(也就是浏览器),而苹果默认浏览器是Safari,Safari浏览器默认机制是在用户主动和页面进行交互后,自动播放声音才会生效(也就是才有声音),所以在IOS端所有用户进入直播房间后默认都是静音的,用户主动开启音频才会受到直播间的声音(这是目前我发现的最好的解决办法)

五、核心代码(只有关键步骤)

1. 客户端socket

const socketTask = {
	socket: null,
	connect: () => {

		getApp().globalData.socket = uni.connectSocket({
			url:'ws://180.76.158.110:9000/socket/websocketv',
			// url: 'ws://192.168.3.254:9000/socket/websocketv',
			complete: (e) => {
				console.log(e);
			},
		});

		getApp().globalData.socket.onOpen((data) => {
			console.log("111111111");
			getApp().globalData.socket.send({
				data: JSON.stringify({
					type: "newConnect",
					userId: uni.getStorageSync('user').id,
				})
			})
		})

		getApp().globalData.socket.onClose((res) => {
			console.log("连接关闭", res);
			getApp().globalData.socket = null;
			setTimeout(() => {
				socketTask.connect()
			}, 3000)
		})

		getApp().globalData.socket.onError((err) => {
			console.log("连接异常", err);
			getApp().globalData.socket = null;
			setTimeout(() => {
				socketTask.connect()
			}, 1)
		})

		getApp().globalData.socket.onMessage((data) => {
			socketTask.watchSocketMessage(data)
		})

	},
	start: function() {
		this.connect()
	},
	watchSocketMessage: function() {
		// 这里实现自己的业务逻辑
	}
}

export default socketTask

2.客户端房间列表页

async onShow() {
			if (!getApp().globalData.socket) {
				await socketTask.start();
			}
			socketTask.watchSocketMessage = (data) => {
				console.log("===========收到新消息==========",data);
				this.watchSocketMessages(data)
			}
		},
methed:{
// 监听socket消息
			watchSocketMessages(res) {
				try {
					const socket_msg = JSON.parse(res.data);
					console.log("收到新消息", socket_msg);
					switch (socket_msg.type) {
						case "homeList":
							if (socket_msg.data.length == 0) {
								this.homeList = [];
								uni.showToast({
									title: "暂无房间,快去创建一个吧",
									icon: "none"
								})
							} else {
								this.homeList = socket_msg.data;
							}
							break
						case "leave":
							getApp().globalData.socket.send({
								data: JSON.stringify({
									type: "homeList",
									userId: this.userInfo.userId,
								})
							})
							break
						case "createSuccess":
							uni.redirectTo({
								url: `broadRoom?rid=${socket_msg.data.groupId}&&userId=${this.userInfo.id}&&groupInfo=${JSON.stringify(socket_msg.data)}`
							})
							break
					}
				} catch (e) {

				}
			},
}

3.客户端直播间

逻辑层:

async onShow() {
			const that = this;
			if (!getApp().globalData.socket) {
				console.log("socket不存在,重新连接");
				await socketTask.start();
			}
			socketTask.watchSocketMessage = (data) => {
				this.watchSocketMessage(data)
			}
			// 编译平台信息
			uni.getSystemInfo({
				success(res) {
					console.log("当前平台是", res);
					if (res.osName == 'ios') {
						console.log("我是ios", res)
						that.isMedia = 'ios';
					} else {
						console.log("我是安卓", res)
						that.isMedia = 'android';
					}

				}
			})

		}

methed:{
async watchSocketMessage(date) {
				const data = JSON.parse(date.data);
				switch (data.type) {
					case "join":
						console.log("join成功", data);
						this.newMessaGes(data);
						this.setUserList(data.admin);
						this.updataNewMic(data)
						// 找出自己以外的其他用户
						const arr = this.userList.filter((item, index) => {
							return item.userId !== this.userId
						})
						console.log("找出自己以外的其他用户", arr)
						// 通知renderjs层创建RTC
						this.RTCJoin = arr;
						this.updataIsShow()
						break

					case "newjoin":
						this.newMessaGes(data);
						this.setUserList(data.admin);
						break

					case "offer":
						//通知renderjs层有新人进入创建answer
						console.log("收到offer", data)
						this.RTCOffier = data;
						break
					case "answer":
						// 找到对应peer,设置answer
						console.log("收到offer", data)
						this.RTCAnswer = data;
						break
					case "candidate":
						// 找到对应的peer,将candidate添加进去
						this.RTCCandidate = data;
						break
					case "leave":
						if (data.data == "房主已解散房间") {
							this.closesAdmin()
						} else {
							const datas = {
								data,
							}
							this.newMessaGes(datas)
							this.setUserList(data.admin);
							this.updataNewMic(data);
						}
						break
					case "apply-admin":
						this.updataIsApply(data.data)
						break
					case "newMic":
						this.updataNewMic(data)
						break
					case "uplMicro":
						this.updataNewMic(data)
						break
					case "newMessage":
						this.newMess = data;
						break
				}
			},
}

视图层:

<script module="webRTC" lang="renderjs">

// 以下方法都在methed:{}中



// 监听changeRTCCandidate
			async changeRTCCandidate(data) {
				if (!data) {
					return
				}
				console.log("this.otherPeerConnections", this.otherPeerConnections);
				let arrs = this.otherPeerConnections.concat(this.myPeerConnections);


				if (arrs.length == 0) {
					return
				}

				let peerr = arrs.filter(item => {
					return item.otherId == data.userId
				})
				
				if (peerr[0].peer == {}) {
					return
				} else {
					console.log("candidatecandidate", data.candidate)
					await peerr[0].peer.addIceCandidate(new RTCIceCandidate(data.candidate))
				}
			},
			// 监听answer,找到对应peer设置answer
			async changeRTCAnswer(data) {
				if (!data) {
					return
				}
				let peers = this.myPeerConnections.filter(item => {
					return item.otherId == data.userId
				})
				console.log("peers[0]", peers[0])
				await peers[0].peer.setRemoteDescription(new RTCSessionDescription(data.answer))
			},
			// 监听offier,RTCAnswer的创建
			async changeRTCoffier(data) {
				if (!data) {
					return
				}
				let pear = null;
				try {
					pear = new RTCPeerConnection(iceServers);
				} catch (e) {
					console.log("实例化RTC-pear失败", e);
				}

				// 将音频流加入到Peer中
				this.localStream.getAudioTracks()[0].enabled = this.isTrue;
				this.localStream.getTracks().forEach(
					(track) => pear.addTrack(track, this.localStream)
				);
				this.otherPeerConnections.push({
					peer: pear,
					otherId: data.userId
				})
				//当远程用户向对等连接添加流时,我们将显示它
				pear.ontrack = (event) => {
					// 为该用户创建audio
					const track = event.track || event.streams[0]?.getTracks()[0];
					if (track && track.kind === 'audio') {
						console.log("存在音轨", event.streams[0]);
						this.renderAudio(data.userId, event.streams[0]);
					} else {
						console.warn("No audio track found in the received stream.");
					}
				};

				// 通过监听onicecandidate事件获取candidate信息
				pear.onicecandidate = async (event) => {
					if (event.candidate) {
						// 通过信令服务器发送candidate信息给用户B
						await this.$ownerInstance.callMethod("sendCandidate", {
							type: "candidate",
							userId: this.userId,
							rid: this.rid,
							msg: event.candidate,
							formUserId: data.userId,
						})
					}
				}

				pear.setRemoteDescription(new RTCSessionDescription(data.offer))

				// 接收端创建answer并发送给发起端
				pear.createAnswer().then(answer => {
					pear.setLocalDescription(answer);
					// 通知serve层给房间用户发送answer
					this.$ownerInstance.callMethod("sendAnswer", {
						type: "answer",
						userId: this.userId,
						rid: this.rid,
						msg: answer,
						formUserId: data.userId,
					})
				})
			},
			// 发起连接申请,offier的创建
			changeRTCjoin(RTCjoin) {
				if (!RTCjoin) {
					return
				}
				RTCjoin.forEach((item, index) => {
					let peer = null;
					try {
						peer = new RTCPeerConnection(iceServers);
					} catch (e) {
						console.log("实例化RTC失败", e);
					}

					this.localStream.getAudioTracks()[0].enabled = this.isTrue;
					this.localStream.getTracks().forEach(
						(track) => peer.addTrack(track, this.localStream)
					);

					peer.ontrack = (event) => {
						console.log("发起连接申请,offier的创建:peer.ontrack");
						const track = event.track || event.streams[0]?.getTracks()[0];
						if (track && track.kind === 'audio') {
							console.log("存在音轨2", event.streams[0]);
							this.renderAudio(item.userId, event.streams[0]);
						} else {
							console.warn("No audio track found in the received stream.");
						}
					};

					// 通过监听onicecandidate事件获取candidate信息
					peer.onicecandidate = (event) => {
						if (event.candidate) {
							// 通过信令服务器发送candidate信息给用户B
							this.$ownerInstance.callMethod("sendCandidate", {
								type: "candidate",
								userId: this.userId,
								rid: this.rid,
								msg: event.candidate,
								formUserId: item.userId,
							})
						}
					}
					this.myPeerConnections.push({
						peer: peer,
						otherId: item.userId
					})

					peer.createOffer(this.offerOptions).then(offer => {
						peer.setLocalDescription(offer);
						// 通知serve层给房间用户发送offier
						this.$ownerInstance.callMethod("sendOffier", {
							type: "offer",
							userId: this.userId,
							rid: this.rid,
							msg: offer,
							formUserId: item.userId,
						})
					})
				})
			},

			renderAudio(uid, stream) {
				let audio2 = document.getElementById(`audio_${uid}`);
				console.log("audio_name", `audio_${uid}`);
				if (!audio2) {
					audio2 = document.createElement('audio');
					audio2.id = `audio_${uid}`;
					audio2.setAttribute("webkit-playsinline", "");
					audio2.setAttribute("autoplay", true);
					audio2.setAttribute("playsinline", "");
					audio2.onloadedmetadata = () => {
						if (this.isAudio == 1) {
							console.log("不自动播放");
							audio2.pause();
						} else {
							audio2.play();
						}
					};

					this.audioList.push(audio2)
				}
				if ("srcObject" in audio2) {
					console.log("使用了srcObject赋值");
					audio2.srcObject = stream;
				} else {
					console.log("找不到srcObject赋值");
					audio2.src = window.URL.createObjectURL(stream);
				}
			},
async initMedia() {
				const that = this;

				console.log("##########", this.isMedia);
				// #ifdef APP-PLUS
				if (this.isMedia == 'android') {
					console.log("androidandroidandroidandroid");
					await plus.android.requestPermissions(
						['android.permission.RECORD_AUDIO'],
						async (resultObj) => {
								var result = 0;
								for (var i = 0; i < resultObj.granted.length; i++) {
									var grantedPermission = resultObj.granted[i];
									result = 1
								}
								for (var i = 0; i < resultObj.deniedPresent.length; i++) {
									var deniedPresentPermission = resultObj.deniedPresent[i];
									result = 0
								}
								for (var i = 0; i < resultObj.deniedAlways.length; i++) {
									var deniedAlwaysPermission = resultObj.deniedAlways[i];
									result = -1
								}
								that.localStream = await that.getUserMedia();
								that.$ownerInstance.callMethod("sendJoin", {
									type: "join",
									userId: that.userId,
									rid: that.rid,
									name: that.name
								})
							},
							function(error) {
								console.log("导入android出现错误", error);
							}
					);
				} else {
					console.log("iosiosiosiosiosios");
					that.localStream = await that.getUserMedia().catch(err => {
						console.log("出错了", err);
					})
					that.$ownerInstance.callMethod("sendJoin", {
						type: "join",
						userId: that.userId,
						rid: that.rid,
						name: that.name
					})
				}

				// #endif
				// #ifdef H5
				that.localStream = await that.getUserMedia();
				// 通知serve层加入成功
				this.$ownerInstance.callMethod("sendJoin", {
					type: "join",
					userId: this.userId,
					rid: this.rid,
					name: this.name
				})
				// #endif
			},
			getUserMedia(then) {
				return new Promise((resolve, reject) => {
					navigator.mediaDevices.getUserMedia(this.mediaConstraints).then((stream) => {
						return resolve(stream);
					}).catch(err => {
						if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
							// 用户拒绝了授权
							reject(new Error('用户拒绝了访问摄像头和麦克风的请求'));
						} else if (err.name === 'NotFoundError' || err.name === 'DevicesNotFoundError') {
							// 没有找到摄像头或麦克风
							reject(new Error('没有找到摄像头或麦克风'));
						} else if (err.name === 'NotReadableError' || err.name === 'TrackStartError') {
							// 摄像头或麦克风不可读
							reject(new Error('摄像头或麦克风不可读'));
						} else if (err.name === 'OverconstrainedError' || err.name ===
							'ConstraintNotSatisfiedError') {
							// 由于媒体流的约束条件无法满足,请求被拒绝
							reject(new Error('请求被拒绝,由于媒体流的约束条件无法满足'));
						} else if (err.name === 'TypeError' || err.name === 'TypeError') {
							// 发生了类型错误
							reject(new Error('发生了类型错误'));
						} else {
							// 其他未知错误
							reject(new Error('发生了未知错误'));
						}
					})
				});
			},
</script>

4.信令服务器

略(就是socket,里面写swich,不会私信,小额收费)

anyRTC Flutter SDK :全面实现跨平台音视频互动
anyRTC的博客
11-10 3354
anyRTC SDK新增支持Flutter跨平台移动框架的方式接入,开发者基于anyRTC Flutter SDK可以简单高效的实现跨平台音视频和实时消息功能。下面先给大家介绍一下什么是Flutter。 什么是Flutter Flutter是谷歌的移动UI框架,可以快速在iOSAndroid上构建高质量的原生用户界面。Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。 简单来说Flutter是Google一个新的用于构
webrtc-app-demo
11-14
webrtc app demo
uni-app webrtc 实现H5音视频通讯
@必意玲
03-31 1万+
1. 写在前面 之前本人一直没有做过webrtc相关的开发(进行实时语音对话或视频对话的),我上家公司的老板突然找到我,让我帮他做一个webrtc的模块功能。通过uni-app开发,然后打包到H5网页上进行音视频沟通。我主要是没有接触过,也不知道怎么去做,只是会uni-app,但是去对接webrtc 拿到手一脸雾水。不知道从何开始。后面各种百度,各种查资料,算是把这个功能搞出来了,现在想起来还是挺心酸的。 其实开发webrtc并不难,但是如果是uni-app 开发打包到h5好像真没有几个可以参考的文档
uniapp使用websock实现实时聊天功能
最新发布
weixin_41030554的博客
08-08 350
随着移动互联网的发展,即时通讯应用变得越来越普遍。本文将介绍如何使用uni-app框架结合WebSocket实现一个简单的实时聊天功能。准备工作确保已经安装了uni-app开发环境。了解基本的Vue.js知识。WebSocket服务器已经搭建好并运行正常。创建项目使用HBuilderX创建一个新的uni-app项目。在项目...
WebRTC音频视频通话-支持androidiosH5
u011007569的博客
09-20 5828
uniapp基于原始WebRTC音视频功能,不依赖任何三方音视频服务及原生插件
anyRTC uni-app 跨平台SDK 发布!总有一款适合你!
anyRTC的博客
11-24 4166
简述 近期,收到很多的开发者伙伴的留言和建议,让我们适配uni-app 平台,来满足他们的跨平台开发的需求。anyRTC秉承着为广大开发者提供便利的开发环境,和对开发者们的支持,经过半月的努力,终于完成了uni-app 跨平台SDK的开发和测试。 截止到今天为止,anyRTC跨平台SDK已经包括了Electron、Flutter、uni-app 框架,为众多的开发者们提供了更多的选择,和更加良好的开发环境。 什么是uni-app uni-app 是一个使用 Vue.js 开发跨平台应用的前端框架,开发者编写
uniapp+websocket聊天功能实现(超详细!!附代码,可直接复用)
qq_47044909的博客
08-21 8862
最近项目上用到了聊天的功能,下面来分享一下关于socket,键盘弹出等问题,避免别的朋友踩。附整体框架的代码,可直接使用
uni-app利用renderjs实现安卓App上jssip+freeswitch+webrtc音视频通话功能
qq_44910894的博客
06-13 832
本文讲解了如何在uni-app项目开发安卓app时使用renderjs配合jssip库来实现注册到信令服务器并进行音频、视频通话的功能,感谢大家参考
第28篇WebRTC -IOS之在app中使用WebRTC
二刀流的博客
12-12 6224
关键词:WebRTC -IOS之在app中使用WebRTC 一、在app中使用WebRTC 为了构建WebRTC在一个原生的IOSapp中使用,它很容易构建WebRTC.framework.这个工作能被做借助ninja,具体如下,在下面命令中,你需要取代ios用你实际生成构建文件的位置路径。命令如下: ninja -C out/ios rtc_sdk_framework_objc 运行
uni-app、egg实战直播app和小程序全栈开发.zip
01-11
uni-app是一个基于Vue.js的多端开发框架,允许开发者用一套代码实现跨平台应用的开发支持iOSAndroidH5、小程序等多个平台。它提供了一套统一的API,简化了多端适配的复杂性,大大提高了开发效率。在直播APP的...
使用webview 封使用了webrtc 打开摄像头 的页面demo
12-22
http://blog.csdn.net/ren65432/article/details/53815832
WebRTC视频会议H5(React+Golang+ION-SFU)
亢少军的博客
07-13 2112
WebRTC技术经过多年的发展,已经非常成熟,它提供了HTML5流媒体技术的一整套解决方案及API,可用来实现一对一视频通话,视频会议,远程教育以及远程会诊等应用。尤其现在5G时代已经到来,WebRTC技术为必备技能。 本课程为WebRTC的实战案例课程,使用React+Golang+WebRTC实现视频会议系统。使用React实现浏览器PC Web,Golang实现房间管理及转发消息。流媒体采用RTC领域流行开源流媒体ion-sfu. 讲师介绍 讲师:亢少军 资深开发者,创业者。专注于视频通讯技术领域。国
WebRTCWebRTC与直播相关原理
irainsa的博客
04-06 694
即时通信(IM)和实时通信(声网Agora.io)都是一套网络通信系统,其本质都是对信息进行转发,其最大的特点就是对信息传递的时间规定时通信(IM:Instant Messaging):是一个实时通信系统,终端服务,允许两人或多人使用网络实时传输的文字消息、文件、语音与视频交流;本质是客户端与客户端进行消息的实时传递,技术基础就是基于Socket连接的实时数据读写按使用用途分为:企业即时通信和网站即时通信根据装载对象又可以分为手机即时通信和PC即时通信。
视频直播类App SDK盘点
PPCTC的博客
03-05 1万+
1、推流:即构、阿里云直播、七牛云等 即构科技由腾讯QQ团队创业,是市面暂时较好的推流SDK,但是费用太高,可以先做个对比。但美颜效果,连麦功能,狼人杀模式等确实相较其他SDK有很大的优势。 阿里云直播是由阿里集团推出的SDK,免费使用,但美颜效果一直都有问题,如果不满意的话,解决策略是接入另一个三方的美颜SDK。 七牛云效果还行,能满足市面上的大部分功能,就连麦方面的话,七牛云主体是客户端合流,...
多种多样的语音连麦方式
anyRTC的博客
02-26 2396
前言 语音连麦,视频通话这种基础功能大家都已经非常熟悉了,应用场景也十分广泛,例如连麦直播、游戏开黑、在线合唱、视频相亲等。 anyRTC为了让开发者们可以最找到适合自己的开发系统,目前我们已经适配了iOS、Androd、Web、小程序、Windows、macOS、Linux。还有跨平台系列,Flutter、uni-app、APICloud。保证各位开发者可以根据自己公司的实际情况来选择。 语音连麦APP的分类及玩法 语音直播类 游戏开黑类 聊天房类 语音游戏类 目前市场上的开黑类app大致可以分成以上
一文教你uni-app开发小程序直播功能,轻松打造专属直播间
专注java二开部署
11-29 1825
点击设置中的第三方设置 —> 添加插件 --> 点击小程序直播组件(获取AppID)支持在主包或分包内引入【直播组件】 live-player-plugin 代码包。2、使用 navigateTo 方法跳转进入直播间。1、使用 navigator 组件跳转进入直播间uni-app开发中在pages.json引入。直接在直播控制台创建好直播间,拿到房间id;(1) 主包引入和"pages"同级。3、服务端获取数据,提供给前端获取!这里就是我们创建的直播功能区域。点击进入直播后台系统。
uni-app:第四章 网络接口和其他
黄孝果的博客
06-10 337
<template> <view> <view v-show="false">v-show</view> <view v-if="true">v-if</view> <view>xxx</view> <button type="primary" @click="toPath">路由跳转</button> <view> <view>Vuex
uniapp使用uni自带websocket进行即时通讯
热门推荐
weixin_67299751的博客
03-09 1万+
最近再办一个uniapp做的即时通讯,把其中思路记载一下。技术栈采用uniapp+uview+vue2进行开发
uniapp使用webrtc视频通话
09-13
UniApp 是一个跨平台的应用开发框架,可以用于开发同时在多个平台上运行的应用程序,包括 Web、iOSAndroid 等。要在 UniApp 中使用 WebRTC 进行视频通话,可以按照以下步骤进行: 1. 在 UniApp 项目中引入 WebRTC 相关的库文件。可以使用第三方的开源库,例如 libwebrtc 或 SimpleWebRTC。 2. 在 UniApp 的页面中创建一个视频通话的界面。可以使用 `<canvas>` 元素或者 `<video>` 元素来显示视频内容。 3. 在 JavaScript 中调用 WebRTC 的 API,进行媒体流的获取和传输。 - 使用 `getUserMedia()` 方法获取本地音视频流。 - 使用 `RTCPeerConnection` 创建一个点对点连接,以建立视频通话。 - 使用 `addTrack()` 方法将本地视频流添加到连接中。 - 使用 `createOffer()` 方法创建一个 offer,发送给对方。 - 使用 `setRemoteDescription()` 方法设置对方的描述信息。 - 使用 `createAnswer()` 方法创建一个 answer,发送给对方。 - 使用 `setLocalDescription()` 方法设置本地的描述信息。 - 使用 `addIceCandidate()` 方法添加 ICE 候选项,用于 NAT 穿透。 4. 将接收到的远程媒体流显示在页面上,例如通过 `<canvas>` 或 `<video>` 元素。 以上是基本的步骤,具体使用的 API 可以根据具体的需求进行调整。需要注意的是,在不同平台上,WebRTC 的兼容性可能会有差异,需要进行相应的适配和测试。
写文章

热门文章

  • uniapp开发WebRTC语音直播间支持app(android+IOS)和H5,并记录了所有踩得坑 5288
  • Vue3中Options-API 学习总结一 4418
  • 移动端开发的屏幕、图像、字体与布局的兼容适配 3913
  • 用TS封装了一个axios,支持全局拦截、实例拦截、单个请求拦截、loading等待功能 1939
  • 如何实现登录持久化 1915

分类专栏

  • vue 7篇
  • uniapp 2篇
  • 缓存 4篇
  • 微信小程序 1篇
  • ts 7篇
  • 布局适配 1篇
  • 可视化
  • js 5篇
  • nodejs 1篇
  • css 1篇
  • 前端模块化 2篇
  • DOM 1篇

最新评论

  • uniapp开发WebRTC语音直播间支持app(android+IOS)和H5,并记录了所有踩得坑

    叫我刘某人: 啊???你是?表情包

  • uniapp开发WebRTC语音直播间支持app(android+IOS)和H5,并记录了所有踩得坑

    折月煮清酒: 大佬 可以看看源码吗

  • uniapp开发WebRTC语音直播间支持app(android+IOS)和H5,并记录了所有踩得坑

    前端Baner: 能给我看看吗

  • uniapp开发WebRTC语音直播间支持app(android+IOS)和H5,并记录了所有踩得坑

    袁鹤桥: 哥,源码方便看一下吗

  • uniapp开发WebRTC语音直播间支持app(android+IOS)和H5,并记录了所有踩得坑

    香蕉is啵娜娜: 可以用,写两段script,一个scirpt lang="renderjs" ,这个里面可以用navigator

大家在看

  • 【Cerberus库】一个确保数据的正确性和完整性的python库 502
  • 如何系统学习销售?
  • 【IEEE独立出版 | 快速稳定EI检索】2024年资源勘探与地下工程技术国际论坛 (REUET 2024)
  • 如何在浏览器中让视频倍速(最高16倍)
  • Threejs 实现3D 地图(05)3d 地图进场动画和地图边缘动画 662

最新文章

  • uniapp开发APP通用方法
  • axios进阶——取消已经发出去的请求
  • Web本地缓存的正确使用
2024年4篇
2023年2篇
2021年26篇

目录

目录

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

深圳坪山网站建设公司顺平县网站优化公司肇庆公司网站关键词优化方法武清网站关键词优化哪家值得信赖网站怎样优化网页电子商务网站优化网站优化价格贵不贵沧州河间网站推广优化石岛网站优化代理商德州网站seo优化公司做万词霸屏会对网站优化有影响吗徐汇区管理网站服务优化价格许昌网站优化哪里不错自动优化网站建设靠谱的传统行业网站优化团队成都企业网站优化云南营销型网站优化平台西藏网站关键词优化排名重庆武隆网站优化延吉市网站seo优化排名深圳整站优化网站制作软文推广网站排名优化哪里好如何优化网站图片大小网站的seo优化淘宝seo网站排名优化江苏省网站seo人工优化毕节网站优化seo培训孟州英文网站优化网站优化有什么表现安龙县网站优化营销网站外链优化过度香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声卫健委通报少年有偿捐血浆16次猝死汪小菲曝离婚始末何赛飞追着代拍打雅江山火三名扑火人员牺牲系谣言男子被猫抓伤后确诊“猫抓病”周杰伦一审败诉网易中国拥有亿元资产的家庭达13.3万户315晚会后胖东来又人满为患了高校汽车撞人致3死16伤 司机系学生张家界的山上“长”满了韩国人?张立群任西安交通大学校长手机成瘾是影响睡眠质量重要因素网友洛杉矶偶遇贾玲“重生之我在北大当嫡校长”单亲妈妈陷入热恋 14岁儿子报警倪萍分享减重40斤方法杨倩无缘巴黎奥运考生莫言也上北大硕士复试名单了许家印被限制高消费奥巴马现身唐宁街 黑色着装引猜测专访95后高颜值猪保姆男孩8年未见母亲被告知被遗忘七年后宇文玥被薅头发捞上岸郑州一火锅店爆改成麻辣烫店西双版纳热带植物园回应蜉蝣大爆发沉迷短剧的人就像掉进了杀猪盘当地回应沈阳致3死车祸车主疑毒驾开除党籍5年后 原水城县长再被查凯特王妃现身!外出购物视频曝光初中生遭15人围殴自卫刺伤3人判无罪事业单位女子向同事水杯投不明物质男子被流浪猫绊倒 投喂者赔24万外国人感慨凌晨的中国很安全路边卖淀粉肠阿姨主动出示声明书胖东来员工每周单休无小长假王树国卸任西安交大校长 师生送别小米汽车超级工厂正式揭幕黑马情侣提车了妈妈回应孩子在校撞护栏坠楼校方回应护栏损坏小学生课间坠楼房客欠租失踪 房东直发愁专家建议不必谈骨泥色变老人退休金被冒领16年 金额超20万西藏招商引资投资者子女可当地高考特朗普无法缴纳4.54亿美元罚金浙江一高校内汽车冲撞行人 多人受伤

深圳坪山网站建设公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化