<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>
uni-app利用renderjs+webrtc 实现App音视频通话功能
最新推荐文章于 2026-04-29 05:53:51 发布
6086

被折叠的 条评论
为什么被折叠?



