uniapp+vue3+node实现视频通话(移动端)

概要

前端:借助uniapp组件(live-pusher,video)进行推流、拉流

后端:借助node-media-server插件:一个 Node.js 实现的RTMP/HTTP/WebSocket/HLS/DASH流媒体服务器

整体架构流程

一、前端:

视频页面布局

<view>

		<!-- 拉流 -->
		<video id="myVideo" class="live-video" :src="src" @error="videoErrorCallback" autoplay objectFit="cover"
			:style="{width:windowW+'px',height:windowH+'px'}"></video>
		<!-- 推流 -->
		<live-pusher id='livePusher' ref="livePusher" class="live-pusher"
			:style="{width:livePusherStyle[0]+'px',height:livePusherStyle[1]+'px',top:livePusherStyle[2]+'px',right:livePusherStyle[3]+'px'}"
			:url="url" mode="SD" :muted="false" :enable-camera="true" :auto-focus="true" :beauty="0" whiteness="0"
			aspect="9:16" @statechange="statechange" @netstatus="netstatus" @error="error"></live-pusher>

		<!-- 背景虚拟图片 -->
		<view class="img-bg" v-if="liveStyle != 0 && liveStyle != 2">
			<!-- <view class="img-bg" v-if="liveStyle != 0 && liveStyle != 2"> -->
			<image :src="fimg" mode="aspectFill" class="img-bg-user"
				:style="{width:windowW+'px',height:windowH+'px',opacity:(liveStyle == 2 || liveStyle == 1 ||liveStyle == 0 ? '0':'1')}">
			</image>
			<view class="blur"
				:style="{width:windowW+'px',height:windowH+'px',opacity:(liveStyle == 2 || liveStyle == 0 ? '0':'1')}">
			</view>
		</view>
		<!-- 背景 -->
		<view class="bg">
			<image src="../../../static/live/topbg.png" class="bg-top" :style="{width:windowW+'px'}"></image>
			<image src="../../../static/live/btbg.png" class="bg-bottom"
				:style="{width:windowW+'px',top:windowH-168+'px'}"></image>
		</view>
		<!-- 头部信息 -->
		<view class="top-title" :style="{width:windowW+'px',top:statusBarH+'px'}">
			<!-- <image src="../../../static/live/colses.png" class="closs-img"></image> -->
			<text class="title" v-if="liveStyle != 2 && liveStyle != 5">{{title}}</text>
			<text class="timer" v-if="liveStyle == 2 || liveStyle == 5">{{timer}}</text>
		</view>
		<!-- 用户信息和按钮 -->
		<view v-if="liveStyle == 0">
			<view class="user" :style="{width:windowW+'px',top:statusBarH+84+'px'}">
				<image :src="fimg" class="user-img"></image>
				<text class="user-name">{{fname}}</text>
			</view>
			<view class="btns" :style="{width:windowW+'px',bottom:bottomH+46+'px'}">
				<view class="btn" @click="back(1)">
					<view class="btn-r cancel">
						<image src="../../../static/live/phone.png" class="cancel-img"></image>
					</view>
					<text class="btn-name">取消</text>
				</view>
				<view class="btn" @click="changeState(3)">
					<view class="btn-r voice">
						<image src="../../../static/live/yuyin.png" class="voice-img"></image>
					</view>
					<text class="btn-name">切到语音通话</text>
				</view>
			</view>
		</view>
		<view v-if="liveStyle == 1">
			<view class="user" :style="{width:windowW+'px',top:windowH*0.28+'px'}">
				<image :src="fimg" class="user-img-bag"></image>
				<text class="user-name">{{fname}}</text>
			</view>
			<view class="btns-top" :style="{width:windowW+'px',bottom:bottomH+195+'px'}">
				<view class="btn">

				</view>
				<view class="btn" @click="changeState(5)">
					<view class="btn-r voice">
						<image src="../../../static/live/yuyin.png" class="voice-img"></image>
					</view>
					<text class="btn-name">切到语音通话</text>
				</view>
			</view>
			<view class="btns" :style="{width:windowW+'px',bottom:bottomH+46+'px'}">
				<view class="btn" @click="cancelLive()">
					<view class="btn-r cancel">
						<image src="../../../static/live/phone.png" class="cancel-img"></image>
					</view>
					<text class="btn-name">挂断</text>
				</view>
				<view class="btn" @click="ingLive()">
					<view class="btn-r live">
						<image src="../../../static/live/lives.png" class="live-img"></image>
					</view>
					<text class="btn-name">接听</text>
				</view>
			</view>
		</view>
		<view v-if="liveStyle == 2">
			<view class="btns" :style="{width:windowW+'px',bottom:bottomH+46+'px'}">
				<view class="btn" @click="changeState(5)">
					<view class="btn-r voice">
						<image src="../../../static/live/yuyin.png" class="voice-img"></image>
					</view>
					<text class="btn-name">切到语音通话</text>
				</view>
				<view class="btn" @click="back(min)">
					<view class="btn-r cancel">
						<image src="../../../static/live/phone.png" class="cancel-img"></image>
					</view>
					<text class="btn-name">挂断</text>
				</view>
				<view class="btn" @click="tapVioce">
					<view class="btn-r voice">
						<image src="../../../static/live/xiangji.png" class="live-img">
						</image>
					</view>
					<text class="btn-name">切换摄像头</text>
				</view>
			</view>
		</view>
		<view v-if="liveStyle == 3">
			<view class="user" :style="{width:windowW+'px',top:windowH*0.28+'px'}">
				<image :src="fimg" class="user-img-bag"></image>
				<text class="user-name">{{fname}}</text>
			</view>
			<view class="btns" :style="{width:windowW+'px',bottom:bottomH+46+'px'}">
				<view class="btn" @click="tapMkf">
					<view class="btn-r" :style="{backgroundColor:(mkf?'#fff':'rgba(0,0,0,0.3)')}">
						<image :src="'../../../static/live/'+(mkf?'opmkf.png':'comkf.png') " class="live-img"></image>
					</view>
					<text class="btn-name">{{mkfTxt}}</text>
				</view>
				<view class="btn" @click="back(1)">
					<view class="btn-r cancel">
						<image src="../../../static/live/phone.png" class="cancel-img"></image>
					</view>
					<text class="btn-name">挂断</text>
				</view>
				<view class="btn" @click="tapVioce">
					<view class="btn-r" :style="{backgroundColor:(vioce?'#fff':'rgba(0,0,0,0.3)')}">
						<image :src="'../../../static/live/'+(vioce?'opvoice.png':'covoice.png')" class="live-img">
						</image>
					</view>
					<text class="btn-name">{{vioceTxt}}</text>
				</view>
			</view>
		</view>
		<view v-if="liveStyle == 4">
			<view class="user" :style="{width:windowW+'px',top:statusBarH+84+'px'}">
				<image :src="fimg" class="user-img"></image>
				<text class="user-name">{{fname}}</text>
			</view>
			<view class="btns" :style="{width:windowW+'px',bottom:bottomH+46+'px'}">
				<view class="btn" @click="back(2)">
					<view class="btn-r cancel">
						<image src="../../../static/live/phone.png" class="cancel-img"></image>
					</view>
					<text class="btn-name">挂断</text>
				</view>
				<view class="btn" @click="changeState(5)">
					<view class="btn-r live">
						<image src="../../../static/live/phone1.png" class="live-img"></image>
					</view>
					<text class="btn-name">接听</text>
				</view>
			</view>
		</view>
		<view v-if="liveStyle == 5">
			<view class="user" :style="{width:windowW+'px',top:windowH*0.28+'px'}">
				<image :src="fimg" class="user-img-bag"></image>
				<text class="user-name">{{fname}}</text>
			</view>
			<view class="btns" :style="{width:windowW+'px',bottom:bottomH+46+'px'}">
				<view class="btn" @click="tapMkf">
					<view class="btn-r" :style="{backgroundColor:(mkf?'#fff':'rgba(0,0,0,0.3)')}">
						<image :src="'../../../static/live/'+(mkf?'opmkf.png':'comkf.png') " class="live-img"></image>
					</view>
					<text class="btn-name">{{mkfTxt}}</text>
				</view>
				<view class="btn" @click="back(min)">
					<view class="btn-r cancel">
						<image src="../../../static/live/phone.png" class="cancel-img"></image>
					</view>
					<text class="btn-name">挂断</text>
				</view>
				<view class="btn" @click="tapVioce">
					<view class="btn-r" :style="{backgroundColor:(vioce?'#fff':'rgba(0,0,0,0.3)')}">
						<image :src="'../../../static/live/'+(vioce?'opvoice.png':'covoice.png')" class="live-img">
						</image>
					</view>
					<text class="btn-name">{{vioceTxt}}</text>
				</view>
			</view>
		</view>
	</view>

注意:

视频通话状态liveStyle:  0:发起视频 1:接收视频 2:视频中 3:发起语音  4:接收语音  5语音中

功能:

<script>
	export default {
		data() {
			return {
				uuid: '',
				ffid: '',
				uid: '',
				fid: '',
				fimg: '',
				fname: '',
				url: '', // 推流地址
				src: 'rtmp://192.168.137.1:1935/live/id', // 拉流地址
				windowW: '', //设备宽度
				windowH: '', //设备高度
				statusBarH: '', //状态栏高度
				bottomH: '', //底部安全区域
				timer: '00:00', // 通话时间
				liveStyle: 0, //视频通话状态 0:发起视频 1:接收视频 2:视频中 3:发起语音  4:接收语音  5语音中
				vioce: false, //是否开启扬声器
				mkf: false, //是否开启麦克风
				mkfTxt: '麦克风已关',
				vioceTxt: '扬声器已关',
				min: 2, //发起方   接收方判断
			}
		},
		onLoad(e) {
			this.url = 'rtmp://192.168.137.1:1935/live/' + e.ffid + e.uuid
			this.src = 'rtmp://192.168.137.1:1935/live/' + e.uuid + e.ffid
			// 接收跳转的数据
			this.gainInformation(e)
			this.getPhoneStyle();
			this.liveCancel();
			this.liveing();
			this.changeVoice();
		},
		onReady() {
			// 注意:需要在onReady中 或 onLoad 延时
			this.context = uni.createLivePusherContext("livePusher", this);
			setTimeout(() => {
				this.loadState(this.liveStyle)
			}, 10)
			// this.testsx();
		},
		computed: {
			// 视频窗口大小
			livePusherStyle() {
				if (this.liveStyle == 2) {
					return [100, 168, this.statusBarH + 4, 4]
				} else {
					return [this.windowW, this.windowH, 0, 0]
				}
			},
			// 头部提示信息
			title() {
				if (this.liveStyle == 0) {
					return '等待对方接受邀请...';
				} else if (this.liveStyle == 1) {
					return '邀请你视频通话...';
				} else if (this.liveStyle == 3) {
					return '等待对方接受邀请...';
				} else if (this.liveStyle == 4) {
					return '邀请你语音通话...';
				}
			}
		},
		methods: {
			// 获取基本信息
			gainInformation(e) {
				this.liveStyle = e.style
				// 判断跳回层
				if (e.style == 0 || e.style == 3) {
					this.min = 1;
				}
				// 时间开始
				if (e.style == 2 || e.style == 5) {
					this.timess();
				}
				this.uid = e.uid
				this.fid = e.fid
				this.ffid = e.ffid
				this.uuid = e.uuid
				
				this.fimg = e.fimg
				this.fname = e.fname
				console.log('推流',this.url);
				console.log('拉流',this.src);
			},
			//测试顺序
			testxx() {
				new Promise(() => {
					this.context = uni.createLivePusherContext("livePusher", this);
					console.log('a')
				})
			},
			testsx() {
				async () => {
					await this.testxx();
					console.log('b')
					this.loadState(this.liveStyle);
				}
			},
			// 进入该页面到一些预先需要处理到的判断
			loadState(e) {
				// 判断发起方还是接收方
				// if (e == 0 || e == 3) {
				// 	this.changeRinging(1)
				// 	this.start();
				// } else if (e == 2 || e == 5) {
				// 	this.changeRinging(0)
				// }
				if (e == 2 || e == 3) {
					this.changeRinging(1)
					this.start();
				} else if (e == 1 || e == 5) {
					this.changeRinging(0)
				}
			},
			// 取消或者拒绝
			back(e) {
				// 返回上层
				uni.navigateBack({
					delta: e
				})
				// 关闭音乐
				this.changeRinging(0);
				// 关闭推流
				// this.stop()
			},
			changeRinging(e) {
				// 控制音频播放与关闭
				// e==0为关闭;e==1为开启
				uni.$emit('ringing', {
					msg: e
				})
			},
			// 切换语音或者视频
			changeState(e) {
				// 切换各种状态
				this.liveStyle = e
				if (e == 2 || e == 5) {
					// 关闭音乐
					this.changeRinging(0);
					// 时间开始
					this.timess();
				}
				// 通知对方切换语音
				if (e == 5 || e == 3) {
					let data = {
						uid: this.fid,
						fid: this.uid,
						e: e,
						tip: 6 //已经进入语音
					}
					uni.$emit('voiceing', data)
				}
			},
			// 获取设备信息
			getPhoneStyle() {
				uni.getSystemInfo({
					success: (res) => {
						// console.log(res.windowWidth)
						// console.log(res.windowHeight)
						this.windowW = res.screenWidth;
						this.windowH = res.screenHeight;
						this.statusBarH = res.statusBarHeight;
						this.bottomH = res.screenHeight - res.safeArea.bottom
					}
				});
			},
			// 麦克风切换
			tapMkf() {
				this.mkf = !this.mkf
				if (this.mkf) {
					this.mkfTxt = '麦克风已关'
				} else {
					this.mkfTxt = '麦克风已开'
				}
			},
			// 扬声器切换
			tapVioce() {
				this.vioce = !this.vioce
				if (this.vioce) {
					this.vioceTxt = '扬声器已关'
				} else {
					this.vioceTxt = '扬声器已开'
				}
			},

			// 处理对方反馈
			// 对方拒绝
			liveCancel() {
				uni.$on('liveCancels', (data) => {
					this.back(1)
				})
			},
			// 处理接收对方反馈
			// 对方接收视频
			liveing() {
				uni.$on('liveings', (data) => {
					this.liveStyle = data.e
					// 关闭音乐
					this.changeRinging(0)
					this.timess();
				})
			},
			// 处理接收对方反馈
			// 对方接收语音
			changeVoice() {
				uni.$on('voiceings', (data) => {
					if (data.e == 5) {
						this.liveStyle = 5;
						// 关闭音乐
						this.changeRinging(0);
						this.timess();
					} else if (data.e == 3) {
						this.liveStyle = 3;
					}

				})
			},
			// 拒绝对方
			cancelLive() {
				console.log('拒绝对方11111111');
				// 通知对方我已经拒绝了
				let data = {
					uid: this.fid,
					fid: this.uid,
					tip: 4 //拒绝
				}
				uni.$emit('liveback', data)
				this.back(2)
			},
			// 通知对方我已经进入视频
			ingLive() {
				let data = {
					uid: this.fid,
					fid: this.uid,
					e: 2,
					tip: 5 //已经进入视频
				}
				uni.$emit('liveing', data)
				this.liveStyle = 2
				// 关闭音乐
				this.changeRinging(0)
				this.timess();
			},
			// 计时器
			timess() {
				let n_s = 1;
				let n_m = 0;
				setInterval(() => {
					let setn_s = n_s;
					let setn_m = n_m;
					if (n_s < 10) {
						setn_s = '0' + n_s;
					}
					if (n_m < 10) {
						setn_m = '0' + n_m;
					}
					this.timer = setn_m + ':' + setn_s;
					n_s++;
					if (n_s > 59) {
						n_m++;
						n_s = 0;
					}
				}, 1000);
			},
			statechange(e) {
				console.log("statechange:" + JSON.stringify(e));
			},
			netstatus(e) {
				console.log("netstatus:" + JSON.stringify(e));
			},
			error(e) {
				console.log("error:" + JSON.stringify(e));
			},
			start: function() {
				this.context.start({
					success: (a) => {
						console.log("livePusher.start:" + JSON.stringify(a));
					}
				});
			},
			close: function() {
				this.context.close({
					success: (a) => {
						console.log("livePusher.close:" + JSON.stringify(a));
					}
				});
			},
			snapshot: function() {
				this.context.snapshot({
					success: (e) => {
						console.log(JSON.stringify(e));
					}
				});
			},
			resume: function() {
				this.context.resume({
					success: (a) => {
						console.log("livePusher.resume:" + JSON.stringify(a));
					}
				});
			},
			pause: function() {
				this.context.pause({
					success: (a) => {
						console.log("livePusher.pause:" + JSON.stringify(a));
					}
				});
			},
			stop: function() {
				this.context.stop({
					success: (a) => {
						console.log(JSON.stringify(a));
					}
				});
			},
			switchCamera: function() {
				this.context.switchCamera({
					success: (a) => {
						console.log("livePusher.switchCamera:" + JSON.stringify(a));
					}
				});
			},
			startPreview: function() {
				this.context.startPreview({
					success: (a) => {
						console.log("livePusher.startPreview:" + JSON.stringify(a));
					}
				});
			},
			stopPreview: function() {
				this.context.stopPreview({
					success: (a) => {
						console.log("livePusher.stopPreview:" + JSON.stringify(a));
					}
				});
			}
		}
	}
</script>

注意:

推流拉流地址一定要是一样的,需要加上你自己当前的ip地址(手机端无法检测到localhost)

视频接听页面功能+布局

<template>
	<view class="call" :style="{paddingTop:statusBarH+'px'}">
		<view class="call-live" :style="{width:windowW-16+'px'}">
			<image :src="fimg" mode="aspectFill" class="bg-img" :style="{width:windowW-16+'px'}">
			</image>
			<view class="blur" :style="{width:windowW-16+'px'}"></view>
			<view class="call-live-inner" :style="{width:windowW-16+'px'}">
				<!-- 用户信息 -->
				<view class="flexs" @click="pathPage(callNom)">
					<image :src="fimg" mode="aspectFill" class="call-img"></image>
					<view class="call-txt">
						<text class="call-name">{{fname}}</text>
						<text class="call-msg">{{msg}}</text>
					</view>
				</view>
				<!-- 按钮 -->
				<view class="flexs">
					<!-- 拒绝 -->
					<view class="call-refuse call-button" @click="back(1)">
						<image src="../../../static/live/phone.png" class="refuse-img"></image>
					</view>
					<!-- 视频 -->
					<view class="call-agree call-button" v-if="callNom==1" @click="pathPage(2)">
						<image src="../../../static/live/lives.png" class="live-img"></image>
					</view>
					<!-- 语音 -->
					<view class="call-agree call-button" v-if="callNom==4" @click="pathPage(5)">
						<image src="../../../static/live/phone1.png" class="voice-img"></image>
					</view>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				uid: '',
				fid: '',
				fimg: '',
				fname: '',
				windowW: '', //设备宽度
				windowH: '', //设备高度
				statusBarH: '', //状态栏高度
				bottomH: '', //底部安全区域
				msg: '邀请你视频通话', // 提示信息
				callNom: 1, //判断视频还是语音;1视频  4语音
				socketOpen: false,
				socketMsgQueue: [],
				uuid :'',
				ffid:''
			}
		},
		onLoad(e) {

			// 接收跳转的数据
			this.gainInformation(e);
			this.getPhoneStyle();
			this.changeRinging(1);
			this.changeVoice();
		},
		onReady() {

		},
		computed: {

		},
		methods: {
			// 获取基本信息
			gainInformation(e) {
				this.callNom = e.style
				this.uid = e.uid
				this.fid = e.fid
				this.fimg = e.fimg
				this.fname = e.fname
				this.uuid = e.uuid
				this.ffid = e.ffid
				if (e.style == 1) {
					this.msg = '邀请你视频通话';
				} else {
					this.msg = '邀请你语音通话';
				}
			},
			changeRinging(e) {
				// 控制音频播放与关闭
				// e==0为关闭;e==1为开启
				uni.$emit('ringing', {
					msg: e
				});
			},
			// 取消或者拒绝
			back(e) {
				// 返回上层
				uni.navigateBack({
					delta: e
				});
				// 关闭音乐
				this.changeRinging(0);
				// 通知对方我已经拒绝了
				let data = {
					uid: this.fid,
					fid: this.uid,
					tip: 4 //拒绝
				};
				uni.$emit('liveback', data);
			},
			// 接收视频/页面跳转
			pathPage(e) {
				if (e == 2) {
					// 通知对方我已经进入视频
					let data = {
						uid: this.fid,
						fid: this.uid,
						e:2,
						tip: 5 //已经进入视频
					}
					uni.$emit('liveing', data)
				}else if(e == 5){
					// 通知对方我已经进入语音
					let data = {
						uid: this.fid,
						fid: this.uid,
						e:5,
						tip: 5 //已经进入语音
					}
					uni.$emit('liveing', data)
				}


				uni.navigateTo({
					url: "/pages/buyCar/live/live?style=" + e + "&fid=" + this.fid + '&uid=' + this.uid +
						'&fname=' + this.fname + '&fimg=' + this.fimg + '&ffid=' + this.ffid + '&uuid=' + this.uuid
				});
			},
			// 对方接收视频或语音
			changeVoice() {
				uni.$on('voiceings', (data) => {
					if (data.e == 3) {
						this.callNom = 4;
					}
			
				});
			},
			// 获取设备信息
			getPhoneStyle() {
				uni.getSystemInfo({
					success: (res) => {
						this.windowW = res.windowWidth;
						this.windowH = res.windowHeight;
						this.statusBarH = res.statusBarHeight;
						this.bottomH = res.windowHeight - res.safeArea.bottom
					}
				});
			},
		}
	}
</script>

<style>
	.call {
		position: fixed;
		left: 0;
		padding: 16rpx;
	}

	.call-live {
		height: 200rpx;
	}

	.call-live-inner {
		position: absolute;
		display: flex;
		flex-direction: row;
		justify-content: space-between;
		height: 200rpx;
		align-items: center;
	}

	.bg-img {
		border-radius: 20rpx;
		height: 200rpx;
	}

	.blur {
		position: absolute;
		border-radius: 20rpx;
		height: 200rpx;
		top: 0;
		left: 0;
		background: rgba(0, 0, 0, .6);
		backdrop-filter: blur(25rpx);
	}

	.call-img {
		width: 84rpx;
		height: 84rpx;
		border-radius: 42rpx;
		margin-left: 32rpx;
	}

	.call-txt {
		padding-left: 24rpx;
	}

	.call-name {
		font-size: 32rpx;
		line-height: 44rpx;
		color: #fff;
	}

	.call-msg {
		font-size: 28rpx;
		line-height: 40rpx;
		color: rgba(255, 255, 255, 0.6);
	}

	.call-button {
		width: 84rpx;
		height: 84rpx;
		border-radius: 42rpx;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.call-refuse {
		background-color: #ff5d5b;
		margin-right: 64rpx;
	}

	.call-agree {
		background-color: #30c74d;
		margin-right: 32rpx;
	}

	.refuse-img {
		width: 56rpx;
		height: 56rpx;
	}

	.live-img {
		width: 42rpx;
		height: 42rpx;
	}

	.voice-img {
		width: 42rpx;
		height: 42rpx;
	}

	.flexs {
		display: flex;
		flex-direction: row;
	}
</style>

注意:

uniapp路由需要加样式方法(可以让你收到视频通话消息的时候弹出消息页面)

{
			"path": "pages/buyCar/live/call",
			"style": {
				"navigationBarTitleText": "",
				"navigationStyle": "custom", //默认头部去掉
				"backgroundColor": "transparent", //页面内容除外的变透明 (微信小程序不支持)
				"app-plus": {
						"animationType": "slide-in-top", // 从顶部进入
						"animationDuration": 300
					}
			}
},

总结:这些功能包括(发起视频、接收视频、视频中、发起语音、接收语音、语音中)都是需要websocket进行实时通信,这里就不细讲websocket了,需要的话可以看我之前博客

// 视频(跳转推流)
	uni.$on('live', (e) => {
		Live()
	})
	const Live = () => {
		let data = {
			name: user.uname,
			img: user.uimgurl,
			uid: user.uid,
			fid: friend.fid,
			tip: 3 //视频
		}
		uni.sendSocketMessage({
			data: JSON.stringify(data)
		});
		uni.navigateTo({
			url: "/pages/buyCar/live/live?style=2&fid=" + friend.fid + '&uid=' + user.uid + '&fname=' + friend
				.fname + '&fimg=' + friend.fimgurl + '&ffid=' + friend.fid + '&uuid=' + user.uid
		})
	}
	// 视频接收(socket)
	const liveSocket = () => {
		uni.onSocketMessage(function(res) {
			const msggs = JSON.parse(res.data)
			if (msggs.tip == 3) {
				// uni.navigateTo({
				// 	url: "/pages/buyCar/live/call?style=1&fid=" + msggs.fid + '&uid=' + msggs.uid +
				// 		'&fname=' + msggs.name + '&fimg=' + msggs.img+ '&ffid=' + friend.fid + '&uuid=' + user.uid
				// })
				uni.navigateTo({
					url: "/pages/buyCar/live/live?style=2&fid=" + friend.fid + '&uid=' + user.uid + '&fname=' + friend
						.fname + '&fimg=' + friend.fimgurl + '&ffid=' + friend.fid + '&uuid=' + user.uid
				})
			}

		});
	}

	// 拒绝视频
	// // 对方拒绝发送服务器
	uni.$on('liveback', (data) => {
		uni.sendSocketMessage({
			data: JSON.stringify(data)
		});
	})

	// // 对方拒绝接收 
	const liveCancel = () => {
		uni.onSocketMessage((res) => {
			const msggs = JSON.parse(res.data)
			if (msggs.tip == 4) {
				console.log('拒绝接收222222', msggs);
				uni.$emit('liveCancels', msggs)
			}
		});
	}

	// 接收视频
	// // 对方接收视频发送服务器
	uni.$on('liveing', (data) => {
		uni.sendSocketMessage({
			data: JSON.stringify(data)
		});
	})

	// // 对方接收视频接收 
	const liveIng = () => {
		uni.onSocketMessage((res) => {
			const msggs = JSON.parse(res.data)
			// console.log('接收视频接收', msggs);
			if (msggs.tip == 5) {
				uni.$emit('liveings', msggs)
			}
		});
	}
    // 接收语音
	// // 对方接收语音发送服务器
	uni.$on('voiceing', (data) => {
		uni.sendSocketMessage({
			data: JSON.stringify(data)
		});
	})
	
	// // 对方接收语音接收 
	const voiceIng = () => {
		uni.onSocketMessage((res) => {
			const msggs = JSON.parse(res.data)
			// console.log('接收语音接收', msggs);
			if (msggs.tip == 6) {
				uni.$emit('voiceings', msggs)
			}
		});
	}

二、后端:

下载插件:npm i node-media-server 

借助node-media-server实现推流拉流效果,具体操作可以查看官方文档https://github.com/illuspas/Node-Media-server/blob/master/README_CN.md

nodeJs代码

app.js编写

// 引入live
const NodeMediaServer = require('node-media-server');

const config = {
  rtmp: {
    port: 1935,
    chunk_size: 60000,
    gop_cache: true,
    ping: 30,
    ping_timeout: 60
  },
  http: {
    port: 8000,
    allow_origin: '*'
  }
};

var nms = new NodeMediaServer(config)
nms.run();

我用的是一个RTMP因为uniapp组件的live-pusher推流支持RTMP:RTMP 的最大优点是可以在服务器和客户端服务器之间保持稳定的连接,无论用户的互联网连接质量如何,它都可以无缝低延迟进行流媒体传输。这个技术主要通过将数据流分成相等的小部分(音频数据默认为 64 字节,视频数据默认为 128 字节)并将它们顺序传输到接收设备,然后将它们重新组合成视频流来实现的。

效果:

技术细节

需要通过websocket来实现视频通话,推流拉流地址一定要相同,推流拉流需要借助后端node-media-server 来实现

小结

可以关注本人,会持续更新,感谢观看

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
uniapp是一个基于Vue.js的跨平台开发框架,而Vue3是Vue.js的最新版本。它们的结合可以带来更好的开发体验和性能优化。uniapp提供了导航栏双页切换的功能,而Vue3的Pinia框架则提供了参数输出和方法调用的功能,还支持模块化和持久化存储。如果您有关于uniappVue3的使用问题,您可以查看uniapp官网获取更详细的信息和示例代码。uniapp在早期版本中开始支持Vue3的使用,并且从HBuilderX 3.2版本开始支持创建基于Vue3的uniapp项目。使用uniappVue3开发项目的流程与常规的Vue3开发类似。您可以按照官方提供的升级指南和使用文档来进行开发。希望以上信息对您有所帮助!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [uniapp+vue3+pinia框架(模块化+持久化存储)](https://download.csdn.net/download/qq_35079107/87910679)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [uniapp + vue3微信小程序开发(1)框架搭建](https://blog.csdn.net/qq_39404437/article/details/124345386)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [uniapp开发:uniapp之切换vue3,一直使用一直爽](https://blog.csdn.net/qq_42961150/article/details/121375073)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值