uni-app利用renderjs+webrtc 实现App音视频通话功能

<template>
	<view class="moveArea">
		<movable-area class="movablearea" style="right: 0rpx;">
			<movable-view :x="x" :y="y" :direction="direction" class="movableview" :animation="false" :disabled="!telMinShow"
				@touchend="handleTouchEnd" @change="handleChange" style="width:100vw;" :style="{'padding-left':telMinShow?'200rpx':'0rpx'}">
				<view class="frame" :style="{'--background-image':'url' +'(' + avatar + ')'}" @click="telMax" :class="telMinShow? 'frame-min':''">
					<image :src="udata.Avatar"
						style="position: fixed;filter: blur(10px);top: 0rpx;left: 0rpx;width: 100%;height: 100vh;">
					</image>
					
					<u-navbar :is-back="false" :title="`${(teltype>0?'视频':'语音')}通话`"
						:background="{'background' :'rgba(255, 255, 255, 0)'}" :title-color="'#ffffff'">
						<view class="slot-wrap" style="margin-left: 30rpx;" @click.stop="telMin">
								<image src="/static/message/68997b56a4789c4230ebedc8aa7ff95.png" style="width: 40rpx;height: 40rpx;"></image>
						</view>
					</u-navbar>
					<!-- <image :src="avatar" class="img"></image> -->

					<!-- 群聊视频/语音通话 -->
					<view class="frame-content flexcumal" v-if="RoomType == 1">
						<view class="avatar">
							<view :class="[computedAvatar(telusers)]" :id="`viewVideo_${items.ID}`"
								v-for="(items,indexs) in telusers" :key="indexs" v-if="items.state<2">
								<!-- <image :src="items.Avatar" :class="[computedAvatar(list)]" /> -->
							</view>
						</view>
						<!-- items.ID!=udata.ID&& -->
						<!-- <view v-else>
					<view style="border: 2rpx solid black;" :id="`viewVideo_${udata.ID}`" ></view>
					<view style="border: 2rpx solid black;position: fixed;top:10%;right: 10%;" :id="`viewVideo_${userId}`" >
					</view>
			</view> -->
						<!-- <view style="position: fixed;top: 60%;"> -->
						<view class="name">{{udata.UserName}}</view>
						<view class="text" v-if="answer">{{formatTime}}</view>
						<view class="text" v-else>等待对方接受邀请...</view>
						<!-- </view> -->
					</view>

					<!-- 单聊视频/语音通话 -->
					<view class="frame-content flexcumal" style="margin-left: 0rpx;" v-else>
						<view v-if="teltype">
							<!-- <view>1122</view> -->
							<view class="myVideoClass" :id="`viewVideo_${udata.ID}`" v-if="myOtherIs == 0"></view>
							<view class="myVideoClass" :id="`viewVideo_${userId}`" v-if="myOtherIs == 1"></view>
							<view class="otherVideoClass" :id="`viewVideo_${userId}`" v-if="myOtherIs == 0"
								@click.stop="Toggle(1)">
							</view>
							<view class="otherVideoClass" :id="`viewVideo_${udata.ID}`" v-if="myOtherIs == 1"
								@click.stop="Toggle(0)"></view>
						</view>
						<view v-else>
							<view style="margin-top: 25%;width: 100vw;" class="flexcumal">
								<image :src="userAvatar" style="height: 300rpx;width: 300rpx;"></image>
							</view>
							<view class="myVideoClass" :id="`viewVideo_${udata.ID}`" v-show="false"></view>
							<view class="myVideoClass" :id="`viewVideo_${userId}`" v-show="false"></view>
							<!-- <view class="otherVideoClass" :id="`viewVideo_${userId}`"></view> -->
						</view>

						<view style="position: fixed;top: 60%;width: 100vw;" class="flexcumal">
							<view class="name">{{udata.UserName}}</view>
							<view class="text" v-if="answer">{{formatTime}}</view>
							<view class="text" v-else>等待对方接受邀请...</view>
						</view>
					</view>

					<!-- 接通 -->
					<view class="frame-buttom flexal" style="padding-left: 23rpx;justify-content: center;"
						v-if="answer">

						<!-- 麦克风 -->
						<view>
							<view class="flexcumal" style="margin: 0rpx 47rpx;" v-if="isMicrophone"
								@click="isMicrophone = !isMicrophone">
								<image src="/static/video/f88d404003adc4cef0bd294e108a8b0.png"
									style="width: 134rpx;height: 134rpx;">
								</image>
								<view style="font-weight: 400;font-size: 30rpx;color: #FFFFFF;margin-top: 20rpx;">麦克风
								</view>
							</view>
							<view class="flexcumal" style="margin: 0rpx 47rpx;" v-else
								@click="isMicrophone = !isMicrophone">
								<view class="frame-icon flexcen">
									<image src="/static/video/96807520ac0c78fbe419e3ac0a95443.png"
										style="width: 40rpx;height: 48rpx;">
									</image>
								</view>
								<view style="font-weight: 400;font-size: 30rpx;color: #FFFFFF;margin-top: 20rpx;">麦克风已关
								</view>
							</view>
						</view>



						<!-- 取消 -->
						<view class="flexcumal" style="margin: 0rpx 47rpx;" @click="overTelFun()">
							<image src="/static/video/a90daeaeaaf3ef48bc0685042f66e00.png"
								style="width: 134rpx;height: 134rpx;">
							</image>
							<view style="font-weight: 400;font-size: 30rpx;color: #FFFFFF;margin-top: 20rpx;">取消</view>
						</view>

						<!-- 摄像头 -->
						<view v-if="teltype == 1">
							<view class="flexcumal" style="margin: 0rpx 47rpx;" v-if="isCamera"
								@click="isCamera = !isCamera">
								<image src="/static/video/b69f4bca24df9641bd3b5d13b4b7887.png"
									style="width: 134rpx;height: 134rpx;">
								</image>
								<view style="font-weight: 400;font-size: 30rpx;color: #FFFFFF;margin-top: 20rpx;">摄像头
								</view>
							</view>
							<view class="flexcumal" style="margin: 0rpx 47rpx;" v-else @click="isCamera = !isCamera">
								<view class="frame-icon flexcen">
									<image src="/static/video/5be3dcd235de1bacace1a3db17b8cfb.png"
										style="width: 48rpx;height: 48rpx;">
									</image>
								</view>
								<view style="font-weight: 400;font-size: 30rpx;color: #FFFFFF;margin-top: 20rpx;">摄像头已关
								</view>
							</view>
						</view>

						<!-- @click="isCamera = !isCamera" -->
						<view v-if="teltype == 0">
							<view class="flexcumal" style="margin: 0rpx 47rpx;" v-if="loudspeaker"
								@click="loudspeaker = !loudspeaker">
								<!-- <view class="frame-icon flexcen"> -->
								<image src="/static/video/5658eccd08dde2179d0b849ea983f35.png"
									style="width: 134rpx;height: 134rpx;">
								</image>
								<!-- </view> -->
								<view style="font-weight: 400;font-size: 30rpx;color: #FFFFFF;margin-top: 20rpx;">扬声器
								</view>
							</view>
							<!-- @click="isCamera = !isCamera" -->
							<view class="flexcumal" style="margin: 0rpx 47rpx;" v-else
								@click="loudspeaker = !loudspeaker">
								<view class="frame-icon flexcen">
									<image src="/static/video/558d096636ee4c3271fa4761c6a24b5.png"
										style="width: 49rpx;height: 48rpx;">
									</image>
								</view>
								<view style="font-weight: 400;font-size: 30rpx;color: #FFFFFF;margin-top: 20rpx;">扬声器已关
								</view>
							</view>
						</view>
					</view>

					<!-- 未接通 -->
					<view class="frame-buttom flexal" style="width: 100%;" v-else>
						<view class="flexcumal" style="width: 100%;" @click="overTelFun()">
							<image src="/static/video/a90daeaeaaf3ef48bc0685042f66e00.png"
								style="width: 134rpx;height: 134rpx;">
							</image>
							<view style="font-weight: 400;font-size: 30rpx;color: #FFFFFF;margin-top: 20rpx;">取消</view>
						</view>
						<!-- <view class="flexcumal" style="width: 50%;" @click="answer = !answer">
				<image src="/static/video/5492104548940f25e7b5ab255929f1d.png" style="width: 134rpx;height: 134rpx;">
				</image>
				<view style="font-weight: 400;font-size: 30rpx;color: #FFFFFF;margin-top: 20rpx;">接通</view>
			</view> -->
					</view>
					<!-- renderjs 操作 -->
					<view :msgid="msgid" :change:msgid="mettingRtc.msgidChange" :myuserid="myuserid"
						:roomType="RoomType" :change:roomType="mettingRtc.roomTypeChange"
						:change:myuserid="mettingRtc.myuseridInfo" :ulist="telusers"
						:change:ulist="mettingRtc.receiveUsersFun" :initRtc="initRtc"
						:change:initRtc="mettingRtc.initRtcInfo" :reciveRtc_Candidate="reciveRtc_Candidate"
						:change:reciveRtc_Candidate="mettingRtc.reciveRtcFun_Candidate"
						:reciveRtc_Offer="reciveRtc_Offer" :change:reciveRtc_Offer="mettingRtc.reciveRtcFun_Offer"
						:reciveRtc_Answer="reciveRtc_Answer" :change:reciveRtc_Answer="mettingRtc.reciveRtcFun_Answer"
						:isMicrophone="isMicrophone" :change:isMicrophone="mettingRtc.reciveAudioFun"
						:isCamera="isCamera" :change:isCamera="mettingRtc.reciveVideoFun"></view>
				</view>
			</movable-view>
		</movable-area>
	</view>

</template>

<script>
	export default {
		props: {
			// clientHeights:{
			// 	type: Number,
			// 	default: 0
			// }
		},
		data() {
			return {
				answer: false, //是否接听
				avatar: '', // 'https://cdn.uviewui.com/uview/album/9.jpg',
				microphone: true, //麦克风是否开启
				loudspeaker: true, //扬声器是否开启
				msgid: 0,
				msg: {},
				telusers: [], //通话人员
				teltype: 0, //0-语音 1-视频

				userId: 9,
				userAvatar: '', //其他用户头像
				myuserid: 0, //我的用户ID
				initRtc: false,
				reciveRtc_Candidate: [], //rtc-Candidate信息
				reciveRtc_Answer: null, //rtc_Answer信息
				reciveRtc_Offer: null, //rtc_Answer信息

				isMicrophone: true, //语音开关
				isCamera: true, //摄像头开关
				rtcOver: false, //rtc退出
				// 计时器
				isTiming: false,
				time: 0,
				timer: null,
				RoomType: 0, //单聊/群聊
				myOtherIs: 0, //切换大屏
				towTimeFun: null, //2分钟的计时任务
				// return {
				x: 0, //最小化通话x轴
				y: 0, //最小化通话y轴
				// x: 0, //最小化通话x轴
				// y: 0, //最小化通话y轴
				direction: 'all', //可移动方向  all:全部  vertical:垂直  horizontal:水平  none:禁止移动
				telMinShow: false, //是否最小化,当通话页面返回时,在页面打开全局悬浮通话组件
				controlTel:false,	//通话组件显示
			}
		},
		mounted(opt) {
			// this.msgid = opt.msgid
			this.getClineHeight()
			const msg = uni.getStorageSync('MsgId')
			console.log('msg',msg)
			const telMinSwitch = uni.getStorageSync('telMinSwitch')
			// console.log('是否最小化',telMinSwitch)
			this.msgid = msg.MsgId
			console.log("通话处理", this.msgid, msg);
			uni.setStorageSync('telshow', true)
			let controlTel = uni.getStorageSync('controlTel');
			let answer = uni.getStorageSync('answer');
			let time = uni.getStorageSync('telTime');
			let x = uni.getStorageSync('x');
			let y = uni.getStorageSync('y');
			console.log('x',x,'y',y)
			// console.log('',time)
			if (answer) {
				this.answer = true
				this.startTimer()
			}
			if(telMinSwitch){
				this.telMinShow = true
				// this.x = 900
				// this.y = 150
			}
			if (x) {
				this.x = x
			}else{
				if(telMinSwitch){
					this.x = 900
				}
			}
			if (y) {
				this.y = y
			}else{
				if(telMinSwitch){
					this.y = 150
				}
			}
			console.log('x1',this.x,'y1',this.y)
			if (time) {
				this.time = time
			}
			this.getmsginfo()

		},
		created() {
			this.udata = uni.getStorageSync('udata')
			console.log('通话组件创建时', this.udata)
			if (this.$eventBus) {
				this.$eventBus.$on("rtc_message", (msg) => {
					console.log('通话rtc信息接收', msg)
					if (msg.type == 999 && msg.msgid == this.msgid) {
						this.handelRtcFun(msg)
					}
				});
			}

		},
		computed: {
			formatTime() {
				const minutes = Math.floor(this.time / 60)
				const seconds = this.time % 60
				return `${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
			}
		},
		beforeDestroy() {
			// 页面返回销毁事件
			console.log('组件销毁了')
			this.beforeDestroyClick()
		},
		onReady() {

		},
		methods: {
			getClineHeight() {
				const res = uni.getSystemInfo({
					success: (res => {
						this.y = (res.windowHeight - uni.upx2px(80)) / 2;
						// console.log('getBarHeight',this.getBarHeight())
					})
				});
			},
			// 滑动结束
			handleTouchEnd(e) {
				console.log('滑动结束', e)
				uni.setStorageSync('x', parseInt(e.changedTouches[0].pageX));
				uni.setStorageSync('y', parseInt(e.changedTouches[0].pageY));
				// if (e.changedTouches[0].pageY < 130) {}
			},
			// 滑动
			handleChange(e) {
				// console.log('滑动',e)
			},
			// 放大通话窗口
			telMax() {
				console.log('打开通话')
				uni.setStorageSync('telMinSwitch',false)
				this.telMinShow = false
				this.x = 0
				this.getClineHeight()
				console.log('this.y',this.y,'this.x',this.x)
			},
			// 缩小通话窗口
			telMin(){
				let controlTel = uni.getStorageSync('controlTel')
				let telMinSwitch = uni.getStorageSync('telMinSwitch')
				let x = uni.getStorageSync('x');
				let y = uni.getStorageSync('y');
				console.log('telMinSwitch',!telMinSwitch)
				console.log('x',x,'y',y)
				// if(controlTel && !telMinSwitch){
					  uni.setStorageSync('telMinSwitch',true)
					  this.telMinShow = true
					  this.x = 900
					  this.y = 150
					  uni.setStorageSync('x', 0);
					  uni.setStorageSync('y', 0);
					  // if(x){
						 //  this.x = x
					  // }else{
						 //  console.log('我进来了X')
						 //  this.x = 900
					  // }
					  // if(y){
						 //  this.y = y
					  // }else{
						 //  console.log('我进来了Y')
						 //  this.y = 150
					  // }
						 
						  // return true
				// }
			},

			// 销毁事件
			beforeDestroyClick() {
				//销毁事件
				if (this.$eventBus) {
					// this.$eventBus.$off("rtc_message")
				}
				console.log('我被触发了,我是销毁事件',this.x,this.y)
				this.msgid = 0
				this.rtcOver = true
				this.answer = false
				//页面退出,停止推流
				this.isMicrophone = false
				this.isCamera = false
				uni.setStorageSync('telshow', false)
				uni.setStorageSync('movableareaShow', false);
				uni.setStorageSync('iconShow', true);
				uni.setStorageSync('telTime', this.time);
				
				// this.stopTimer()

			},
			// 开始计时
			startTimer() {
				this.isTiming = true
				this.timer = setInterval(() => {
					this.time++
				}, 1000)
			},
			// 结束计时
			// stopTimer() {
			// 	this.isTiming = false
			// 	clearInterval(this.timer)
			// 	if (this.towTimeFun) clearTimeout(this.towTimeFun)
			// },

			async getmsginfo() {
				var rs = await this.$DB.pullSQL(this.$DB.recordsTable, 'MsgId',
					`WHERE  MsgId=${this.msgid}`, 0, 1)
				console.log('通话信息', rs)
				if (rs && rs.length > 0) {
					this.msg = rs[0]
					if (typeof this.msg.MsgContent == 'string') this.msg.MsgContent = JSON.parse(this.msg.MsgContent)
					this.telusers = this.msg.MsgContent.ulist
					console.log('通话用户', this.telusers)
					this.teltype = (this.msg.MsgType == 7 ? 0 : 1)
					this.myuserid = this.udata.ID
					this.RoomType = this.msg.RoomType
					console.log('单聊/群聊', this.RoomType)
					setTimeout(() => {
						this.initRtc = true
						this.isMicrophone = true
						console.log('是否打开视频', this.teltype > 0)
						this.isCamera = (this.teltype > 0)
					}, 500)
					//用户发送 接通信息
					var myindex = this.telusers.findIndex(a => a.ID == this.myuserid)
					var userindex = this.telusers.findIndex(a => a.ID != this.myuserid)
					// console.log('除自己外,其他人',this.telusers[userindex].ID)
					this.userId = this.telusers[userindex].ID
					this.userAvatar = this.telusers[userindex].Avatar
					if (myindex >= 0 && this.telusers[myindex].state == 0) {
						this.sendTelMsgFun('PutThrough', this.telusers, this.msgid, this.myuserid, '接通')
						this.telusers[myindex].state = 1
						// this.startTimer()
					}

					//2分钟后执行 ,无通话用户执行退出
					// this.towTimeFun = setTimeout(() => {
					// 	if (this.telusers.filter(a => a.state == 1).length <= 1) {
					// 		console.log('2分钟后执行-进来了', this.telusers)
					// 		this.overTelFun()
					// 	}
					// }, 2 * 60 * 1000)

				}
			},
			//挂断通话
			overTelFun() {
				var that = this
				console.log('我进来了112233')
				this.sendTelMsgFun('Over', this.telusers, this.msgid, this.myuserid, '结束')
				that.$emit('closeMovableSubimt')
				uni.setStorageSync('movableareaShow', false);
				uni.setStorageSync('iconShow', true);
				// uni.setStorageSync('controlTel', false);
				this.$emit('closeMoveAreaVideo')
				// this.stopTimer()
				// setTimeout(() => {
				// uni.navigateBack({
				// 	delta: 1,
				// })
				// }, 500)
			},


			//收到离开请求* 
			handOverTel(uid) {

				var uindex = this.telusers.findIndex(a => a.ID == uid)
				if (uindex >= 0) {
					//清除下线用户 
					this.telusers[uindex].state = 2
					// this.telusers.splice(uindex, 1)
				}
				console.log('我是挂断', this.telusers)
				//参与用户全部挂断,结束
				if (this.telusers.filter(a => a.state == 1).length <= 1) {
					console.log('全部挂断返回')
					// uni.setStorageSync('controlTel', false);
					this.$emit('closeMoveAreaVideo')
					// setTimeout(() => {
					// uni.navigateBack({
					// 	delta: 1,
					// })
					// }, 500)
				}

			},

			//收到接通请求
			handPutThroughTel(uid) {
				var uindex = this.telusers.findIndex(a => a.ID == uid)
				console.log('uindex',uindex,uid,this.telusers)
				if (uindex >= 0) {
					//用户
					this.answer = true
					uni.setStorageSync('answer', true);
					this.telusers[uindex].state = 1
					this.startTimer()
				}

			},

			//收到rtc信息处理
			handelRtcFun(msg) {
				// console.log('recive-rtc', msg)
				if (msg.code == 'Offer') {
					this.reciveRtc_Candidate = []
					this.reciveRtc_Offer = msg
				} else if (msg.code == 'Answer') {
					this.reciveRtc_Candidate = []
					this.reciveRtc_Answer = msg
				} else if (msg.code == 'Candidate') {
					this.reciveRtc_Candidate.push(msg)
					// console.log('reciveRtc_Candidate,',this.reciveRtc_Candidate)
				} else if (msg.code == 'Over') {
					this.handOverTel(msg.sendid)
				} else if (msg.code == 'PutThrough') {
					this.handPutThroughTel(msg.sendid)
				} else if (msg.code == 'JoinUser') {
					this.getmsginfo()
				}
			},
			/**
			 * 接收 renderjs 传过来的数据
			 */
			async reciveRtcMessage(data) {
				// console.log('接收 renderjs 传过来的rtc数据', data)
				var rs = await this.getdatas(data, '/api/chat/sendrtcMsg')
				if (rs.code == 200) {
					// console.log('rtc发送成功')
				}
			}, //计算头像布局
			computedAvatar(avatarList) {
				if (avatarList.length > 4) {
					return "avatarItem--3"
				} else if (avatarList.length > 1) {
					return "avatarItem--2"
				} else {
					return "avatarItem--1"
				}
			},
			Toggle(type) {
				var that = this
				console.log('我被点击了')
				this.myOtherIs = type
				// setTimeout(function() {
				//   // 这里写要延时执行的代码
				//   that.myOtherIs = type
				// }, 1000); // 这里的 1000 表示延时的时间,单位是毫秒
				console.log('我修改了', this.myOtherIs)
			},
		}
	}
</script>

<script module="mettingRtc" lang="renderjs">
	if (navigator.mediaDevices === undefined) {
		navigator.mediaDevices = {};
	}
	if (navigator.mediaDevices.getUserMedia === undefined) {
		navigator.mediaDevices.getUserMedia = function(constraints) {
			const getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator
				.msGetUserMedia || navigator.getUserMedia;
			if (!getUserMedia) {
				return Promise.reject(
					new Error("浏览器不支持访问用户媒体设备,请升级或更换浏览器")
				);
			}
			return new Promise(function(resolve, reject) {
				getUserMedia.call(navigator, constraints, resolve, reject);
			});
		};
	}

	function judgeIosPermissionRecord() {
		var result = false;
		var avaudiosession = plus.ios.import("AVAudioSession");
		var avaudio = avaudiosession.sharedInstance();
		var permissionStatus = avaudio.recordPermission();
		if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
			alert('请在设置中允许使用麦克风');
		} else {
			result = true;
		}
		plus.ios.deleteObject(avaudiosession);
		return result;
	}

	function judgeIosPermissionCamera() {
		var result = false;
		var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
		var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
		if (authStatus == 3) {
			result = true;
		} else {
			alert('请在设置中允许使用相机');
		}
		plus.ios.deleteObject(AVCaptureDevice);
		return result;
	}

	function requestAndroidPermission(permissionID) {
		return new Promise((resolve, reject) => {
			plus.android.requestPermissions(
				[permissionID],
				function(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
					}
					resolve(result);
				},
				function(error) {
					resolve({
						code: error.code,
						message: error.message
					});
				}
			);
		});
	}


	export default {
		data() {
			return {
				map: null,
				linePath: [],
				myuserid: 0,
				msgid: 0,
				localStream: null, //用户媒体流
				rtclist: [],
				os: '',
				oneInit: true,
				_video: null, //摄像头开关
				_audio: null, //语音开关
				udata: null, //当前用户
				RoomType: 0, //单聊/群聊

			}
		},
		mounted() {},
		beforeDestroy() {
			// 页面返回销毁事件
			console.log('mettingRtc组件销毁了')
			// this.videoStateFun(false)
			// this.beforeDestroyClick()

			if (this.localStream) {
				// 关闭摄像头和麦克风
				this.localStream.getTracks().forEach(track => {
					track.stop();
				});
				this.localStream = null
			}

			if (this.rtclist) {
				this.rtclist.map((it, index) => {
					if (it.rtcPeerConnection) {
						let pc = it.rtcPeerConnection
						// 关闭所有已经打开的数据流追踪
						if (pc.getSenders) {
							pc.getSenders().forEach(function(sender) {
								sender.track ? sender.track.stop() : null;
							});
						}

						// 关闭所有已经创建的媒体通道
						pc.getTransceivers().forEach(function(transceiver) {
							if (transceiver.stop) {
								transceiver.stop();
							}
						});

						// 关闭信令机制,并且断开所有的网络连接
						pc.close();
					}
				})
				this.rtclist = []
			}


		},
		methods: {
			msgidChange(newValue, oldValue) {
				if (newValue && newValue > 0) this.msgid = newValue
			},
			myuseridInfo(newValue, oldValue) {

				if (newValue && newValue > 0) this.myuserid = newValue
			},
			roomTypeChange(newValue, oldValue) {

				this.RoomType = newValue
			},
			// 接收逻辑层发送的用户数据
			receiveUsersFun(newValue, oldValue) {
				// this.$ownerInstance.callMethod('reciveMessage', '985saf'); // 向uni-app页面组件发送信息
				console.log('receiveUsersFun', newValue);
				if (newValue.length > 0) {
					this.os = plus.os.name;
					console.log('os', this.os, this.myuserid, this.RoomType);
					newValue.map(it => {
						if (it.state < 2 && this.rtclist.findIndex(a => a.uid == it.ID) < 0) {

							var mVideo = document.createElement('video')
							// console.log('输出成员', it,this.myuserid);
							mVideo.setAttribute('width', '50px');
							mVideo.setAttribute('height', '50px');
							mVideo.setAttribute('object-fit', 'fill');
							// mVideo.setAttribute('transform', 'rotateY(180deg)');
							// mVideo.setAttribute('-webkit-transform', 'rotateY(180deg)');
							// mVideo.setAttribute('-moz-transform', 'rotateY(180deg)');
							if (!this.RoomType) {
								mVideo.setAttribute('width', '100%');
								mVideo.setAttribute('height', '100%');
							}
							// mVideo.setAttribute("muted", "");
							mVideo.setAttribute("poster", it.Avatar);
							mVideo.setAttribute("webkit-playsinline", "");
							mVideo.setAttribute("playsinline", "");
							mVideo.setAttribute("autoplay", "");
							if (this.os == 'iOS') {
								mVideo.setAttribute("controls", "");
								mVideo.setAttribute("x-webkit-airplay", "");
							}
							mVideo.onloadedmetadata = () => {
								mVideo.play();
							};

							mVideo.autoplay = true
							mVideo.playsinline = true
							if (it.ID == this.myuserid) {
								// //本人视频静音
								mVideo.muted = 0;
								mVideo.volume = 0;
							}
							console.log('我进来了', mVideo);
							document.getElementById('viewVideo_' + it.ID).appendChild(mVideo)
							this.rtclist.push({
								uid: it.ID,
								myvideo: mVideo,
								captureRtc: null, // 会议共享屏幕
								rtcPeerConnection: null, // 通话rtc
								state: 0, //状态 0-拨打中 1-已接通 2-未接通 3-已挂断 
								telmin: 0, //通话时长(秒)

							})
						}
					})
					console.log('rtclist', this.rtclist);
				}
			},
			//接收逻辑层发送的rtc数据
			reciveRtcFun_Answer(wdata, oldValue) {
				console.log('通话rtc-Answer数据处理', wdata);
				if (wdata) {
					if (wdata.code == 'Answer') {
						this.receiveAnswer(wdata.sendid, wdata.data)
					}
				}
			},
			//接收逻辑层发送的rtc数据
			reciveRtcFun_Offer(wdata, oldValue) {
				console.log('通话rtc-Offer数据处理', wdata);
				if (wdata) {
					if (wdata.code == 'Offer') {
						this.receiveOffer(wdata.sendid, wdata.data)
					}
				}
			},
			reciveRtcFun_Candidate(cdatas, oldValue) {
				console.log('通话rtc_Candidate数据处理', cdatas);
				if (cdatas.length > 0) {
					cdatas.map(wdata => {
						if (wdata.code == 'Candidate') {
							this.saveIceCandidate(wdata.sendid, wdata.data)
						}
					})
				}
			},

			//发送rtc请求
			async sendrtcFun(uid, rtc_data, code) {
				var rtcdata = JSON.stringify({
					type: 999,
					code: code,
					userid: uid, //接收用户id
					msgid: this.msgid,
					sendid: this.myuserid, //发送用户(我)Id
					data: rtc_data
				});
				var data = {
					UserId: uid,
					MsgContent: rtcdata
				}
				// console.log('sendrtc', data)
				this.$ownerInstance.callMethod('reciveRtcMessage', data); // 向uni-app页面组件发送信息


			},

			async initRtcInfo(newValue) {
				console.log('initRtcInfo', newValue);
				if (newValue) {
					this.checkPermission().then(async () => {
						this.localStream = await this.getUserMedia();

						var index = this.rtclist.findIndex(a => a.uid == this.myuserid)
						console.log('index', this.rtclist, index);
						if (index >= 0) {
							//显示本地视频流

							var localVideo = this.rtclist[index].myvideo
							console.log('localVideo', localVideo)

							if ("srcObject" in localVideo) {
								localVideo.srcObject = this.localStream;
							} else {
								localVideo.src = window.URL.createObjectURL(this.localStream);
							}
							this.videoStateFun(this._video)
							this.audioStateFun(this._audio)
						}


						//创建并发送rtc请求
						this.rtclist.map((it, index) => {
							if (it.uid != this.myuserid) {
								//创建rtc
								this.createPeerConnection(it.uid)
								//创建offter后给用户发送offter请求
								this.createOffer(it.uid, (userid, sdp) => {
									this.sendrtcFun(userid, sdp, 'Offer')
								})
							}
						})
					})
				}
			},

			reciveVideoFun(newavl) {
				console.log('reciveVideoFun数据', newavl);
				if (newavl != null) {
					this._video = newavl
					this.videoStateFun(newavl)
				}
			},
			//开启/关闭摄像头
			videoStateFun(isopen = false) {
				if (this.localStream) {
					console.log('videoStateFun数据', isopen);
					const tracks = this.localStream.getVideoTracks();
					for (let i = 0; i < tracks.length; i++) {
						tracks[i].enabled = isopen
					}

				}
			},
			reciveAudioFun(newavl) {
				console.log('reciveAudioFun数据', newavl);
				if (newavl != null) {
					this._audio = newavl
					this.audioStateFun(newavl)
				}
			},

			//开启/关闭语音
			audioStateFun(isopen = false) {
				if (this.localStream) {
					console.log('audioStateFun数据', isopen);
					const tracks = this.localStream.getAudioTracks();
					for (let i = 0; i < tracks.length; i++) {
						tracks[i].enabled = isopen
					}
				}
			},


			// 创建 PeerConnection 对象
			createPeerConnection(uid) {
				var index = this.rtclist.findIndex(a => a.uid == uid)
				if (index >= 0) {
					//ice服务器信息, 用于创建 SDP 对象
					let iceServers = {
						"iceServers": [{
								"url": "stun:stun.l.google.com:19302"
							},
							//{ "urls": ["stun:159.75.239.36:3478"] },
							//{ "urls": ["turn:159.75.239.36:3478"], "username": "chr", "credential": "123456" },
						]
					};
					var rtcPeerConnection = new RTCPeerConnection(iceServers)
					// console.log('rtcPeerConnection创建', rtcPeerConnection)


					//将 音视频流 添加到 端点 中
					for (const track of this.localStream.getTracks()) {
						rtcPeerConnection.addTrack(track, this.localStream);
					}
					rtcPeerConnection.ontrack = (event) => {
						if (event) {
							console.log('流 的回调', index)
							var stream = event.streams[0]
							//绑定远程视频流

							var remoteVideo = this.rtclist[index].myvideo
							console.log('remoteVideo', remoteVideo)

							if ("srcObject" in remoteVideo) {
								remoteVideo.srcObject = stream;
							} else {
								remoteVideo.src = window.URL.createObjectURL(stream);
							}
						}
					};

					rtcPeerConnection.onconnectionstatechange = () => {
						console.log(`rtc 连接状态-${index}: `, rtcPeerConnection.connectionState);
					};

					rtcPeerConnection.oniceconnectionstatechange = () => {
						console.log(`ICE 连接状态-${index}: `, rtcPeerConnection.iceConnectionState);
					};
					rtcPeerConnection.onicegatheringstatechange = () => {
						console.log(`ICE 收集状态-${index}: `, rtcPeerConnection.iceGatheringState);
					};

					this.rtclist[index].rtcPeerConnection = rtcPeerConnection

					// console.log('创建rtc成功')

					//绑定 收集 candidate 的回调
					this.bindOnIceCandidate(uid, (uid, candidate) => {
						//发送candidate信息,用于交换
						this.sendrtcFun(uid, candidate, 'Candidate')

					})
				}
			},
			//收到offter请求
			async receiveOffer(uid, sdp) {
				console.log('收到offter请求-开始重新创建rtc')
				this.createPeerConnection(uid)
				console.log('收到offter请求')
				this.saveSdp(uid, sdp, () => {
					this.createAnswer(uid, (userid, answer) => {
						this.sendrtcFun(userid, answer, 'Answer')

					})
				})
			},
			//收到Answer请求
			async receiveAnswer(uid, sdp) {
				this.saveSdp(uid, sdp, () => {
					console.log('收到Answer请求保存后执行')
				})
			},
			//创建用于 offer 的 SDP 对象
			createOffer(uid, callback) {
				var index = this.rtclist.findIndex(a => a.uid == uid)
				if (index >= 0) {

					// 创建 offer 的信息
					const offerOptions = {
						iceRestart: true,
						offerToReceiveAudio: true, //由于没有麦克风,所有如果请求音频,会报错,不过不会影响视频流播放
					};
					// 调用PeerConnection的 CreateOffer 方法创建一个用于 offer的SDP对象,SDP对象中保存当前音视频的相关参数。
					this.rtclist[index].rtcPeerConnection.createOffer(offerOptions)
						.then(sdp => {
							// 保存自己的 SDP 对象
							this.rtclist[index].rtcPeerConnection
								.setLocalDescription(sdp)
								.then(() => callback(uid, sdp));
						})
						.catch(() => console.log('createOffer 失败'));
				}
			},
			//创建用于 answer 的 SDP 对象
			createAnswer(uid, callback) {
				var index = this.rtclist.findIndex(a => a.uid == uid)
				if (index >= 0) {
					// 创建 offer 的信息
					const offerOptions = {
						iceRestart: true,
						offerToReceiveAudio: true, //由于没有麦克风,所有如果请求音频,会报错,不过不会影响视频流播放
					};
					// 调用PeerConnection的 CreateAnswer 方法创建一个 answer的SDP对象
					this.rtclist[index].rtcPeerConnection.createAnswer(offerOptions)
						.then(sdp => {
							// 保存自己的 SDP 对象
							this.rtclist[index].rtcPeerConnection
								.setLocalDescription(sdp)
								.then(() => callback(uid, sdp));
						}).catch(() => console.log('createAnswer 失败'))
				}
			},


			//保存远程的 SDP 对象
			saveSdp(uid, answerSdp, callback) {
				var index = this.rtclist.findIndex(a => a.uid == uid)
				if (index >= 0) {
					this.rtclist[index].rtcPeerConnection
						.setRemoteDescription(
							new RTCSessionDescription(answerSdp))
						.then(callback);
				}
			},
			// 、保存 candidate 信息
			saveIceCandidate(uid, candidate) {
				var index = this.rtclist.findIndex(a => a.uid == uid)
				if (index >= 0) {
					var rtc = this.rtclist[index].rtcPeerConnection
					// console.log('rtc.remoteDescription-' + index, rtc.remoteDescription)
					// if (rtc && rtc.remoteDescription) {
					let iceCandidate = new RTCIceCandidate(candidate);
					this.rtclist[index].rtcPeerConnection.addIceCandidate(iceCandidate)
						.then(() => {
							// console.log(`addIceCandidate 成功${index}`, this.rtclist[index].rtcPeerConnection
							// 	.iceConnectionState)
						}).catch((e) => {
							console.log('saveIceCandidate 失败', e)
						});
					// }
				}
			},
			// 收集 candidate 的回调
			bindOnIceCandidate(uid, callback) {
				var index = this.rtclist.findIndex(a => a.uid == uid)
				if (index >= 0) {
					// 绑定 收集 candidate 的回调
					this.rtclist[index].rtcPeerConnection.onicecandidate = (event) => {
						if (event.candidate) {
							callback(uid, event.candidate);
						}
					};
				}
			},
			getUserMedia() {
				return new Promise((resolve, reject) => {
					var mediaConstraints = {
						// 需要摄像头权限,优先使用前置摄像头 environment-后置摄像头
						video: {
							facingMode: "user"
						},
						// 需要麦克风权限,false则不需要
						audio: true,
					};
					navigator.mediaDevices.getUserMedia(mediaConstraints).then((stream) => {
						return resolve(stream);
					}).catch(this.mediaErrorCaptured);
				});
			},


			mediaErrorCaptured(error) {
				console.log("错误信息name打印", error?.name);
				console.log("错误信息message打印", error?.message);
				// 媒体权限失败处理(通用 详细)
				const nameMap = {
					AbortError: "操作中止",
					NotAllowedError: "打开设备权限不足,原因是用户拒绝了媒体访问请求",
					NotFoundError: "找不到满足条件的设备",
					NotReadableError: "系统上某个硬件、浏览器或网页层面发生的错误导致设备无法被访问",
					OverConstrainedError: "指定的要求无法被设备满足",
					SecurityError: "安全错误,使用设备媒体被禁止",
					TypeError: "类型错误",
					NotSupportedError: "不支持的操作",
					NetworkError: "网络错误发生",
					TimeoutError: "操作超时",
					UnknownError: "因未知的瞬态的原因使操作失败)",
					ConstraintError: "条件没满足而导致事件失败的异常操作",
				};
				// 媒体权限失败处理(通用 简单)
				const messageMap = {
					"permission denied": "麦克风、摄像头权限未开启,请检查后重试",
					"requested device not found": "未检测到摄像头",
					"could not start video source": "无法访问到摄像头",
				};
				let nameErrorMsg = nameMap[error.name];
				if (!nameErrorMsg) {
					nameErrorMsg = messageMap[error.message.toLowerCase() || "未知错误"];
				}
				// todo
				console.log("错误信息打印", nameErrorMsg);
				// alert(nameErrorMsg)
			},


			checkPermission(then) {
				return new Promise((resolve, reject) => {
					// alert(this.os)
					if (this.os == 'iOS') {
						// alert('ios权限确认')
						console.log("进入ios权限确认");
						judgeIosPermissionRecord();
						judgeIosPermissionCamera();
						if (typeof then == "function") {
							// alert('ios权限确认完成')
							then();
						} else {
							return resolve();
						}
					} else {
						requestAndroidPermission('android.permission.CAMERA').then(() => {
							requestAndroidPermission('android.permission.RECORD_AUDIO').then(() => {
								if (typeof then == "function") {
									then();
								} else {
									return resolve();
								}
							});
						});
					}
				});
			},
		}
	}
</script>

<style lang="less">
	.movablearea {
		position: fixed;
		height: 100%;
		width: 100%;
		z-index: 999;
		pointer-events: none;

		.movableview {
			
			pointer-events: auto;
			display: flex;
			justify-content: center;
			align-items: center;
			// width: 100rpx;
			// height: 100rpx;
			pointer-events: auto;
			// border-radius: 50%;
			// padding-left: 200rpx;
		}
	}

	.frame {
		// background: #757678 !important;
		// top: 0rpx;
		// left: 0rpx;
		// position: fixed;
		width: 100%;
		height: 100vh;
		overflow: hidden;
		z-index: 999;
		transform: scale(1);
	}
	.frame-min{
		transform: scale(0.35);
	}

	.frame::before {
		content: '';
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		background-image: var(--background-image);
		background-size: cover;
		background-position: center;
		// filter: blur(10px);
		z-index: -1;
		/* Make sure the blur effect stays behind other content */
	}

	.frame-content {
		width: 100vw;
		margin-left: 16rpx;
		top: 12%;
		position: absolute;
	}

	.name {
		margin-top: 32rpx;
		font-weight: 400;
		font-size: 40rpx;
		color: #FFFFFF;
	}

	.text {
		margin-top: 60rpx;
		font-weight: 400;
		font-size: 32rpx;
		color: #C4C4C4;
	}

	.frame-buttom {
		position: fixed;
		top: 80%;
	}

	.img {}

	.frame-icon {
		width: 134rpx;
		height: 134rpx;
		border-radius: 50%;
		background-color: rgba(0, 0, 0, 0.5);
	}

	.avatar {
		width: 750rpx;
		height: 750rpx;
		overflow: hidden;
		overflow: auto;
		display: flex;
		flex-wrap: wrap;


		.avatarImg {
			width: 52px;
			height: 52px;
		}

		.avatarItem--1 {
			width: 98%;
			height: 98%;
		}

		.avatarItem--2 {
			width: 47%;
			height: 47%;
			margin: 1%;
		}

		.avatarItem--3 {
			width: 30%;
			height: 30%;
			margin: 1%;
		}
	}

	.myVideoClass {
		// height: 70vh;
		// width: 100vw;
	}

	.otherVideoClass {
		height: 300rpx;
		width: 300rpx;
		position: fixed;
		// top: 0;
		// right: 0;
		top: 12%;
		right: 10%;
		// transform: scale(0.5);
	}
</style>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

acycwf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值