这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

视频通话SDK用的即构的,uniapp插件市场地址
推送用的极光的,uniapp插件市场地址

即构音视频SDK

uniapp插件市场的貌似是有些问题,导入不进项目,直接去官网下载,然后放到项目下的 nativeplugins 目录下,在配置文件中填入即构后台的appID和AppSign,接下来就可以开干了

准备两个页面

首页:/pages/index/index
// 新建一个按钮
<button @click="sendVideo">发送视频邀请</button>

// 发送事件,主动发送直接进入下一个页面即可
sendVideo(){
    uni.navigateTo({
        url: '/pages/call/call'
    })
}
通话页:pages/call/call 这个页面会复杂一点
注意这个页面为 nvue 页面
先把所有代码都列出来,再一一做说明
<template>
    <view>
        <view v-if="status === 1" class="switch-bg" :style="{'height': pageH + 'px'}">
            <view class="top-info u-flex" style="flex-direction: row;">
                <image src="http://cdn.u2.huluxia.com/g3/M02/32/81/wKgBOVwN9CiARK1lAAFT4MSyQ3863.jpeg" class="avatar">
                </image>
                <view class="info u-flex u-flex-col u-col-top">
                    <text class="text">值班中心</text>
                    <text class="text" style="margin-top: 10rpx;">正在呼叫</text>
                </view>
            </view>
                <view class="switch-handle u-flex u-row-center" style="flex-direction: row; justify-content: center;">
                    <image src="/static/hang_up.png" class="img" @click="hangUp"></image>
                </view>
        </view>
        <view v-if="status === 2" class="switch-bg" :style="{'height': pageH + 'px'}">
            <view class="top-info u-flex" style="flex-direction: row;">
                <image src="http://cdn.u2.huluxia.com/g3/M02/32/81/wKgBOVwN9CiARK1lAAFT4MSyQ3863.jpeg" class="avatar">
                        </image>
                    <view class="info u-flex u-flex-col u-col-top">
                        <text class="text">值班中心</text>
                        <text class="text" style="margin-top: 10rpx;">邀请您视频聊天</text>
                    </view>
                </view>
                <view class="switch-handle">
                    <view class="u-flex" style="justify-content: flex-end; flex-direction: row; padding-right: 10rpx; padding-bottom: 30rpx;">
                        <text style="font-size: 26rpx; color: #fff; margin: 10rpx;">切到语音接听</text>
                        <image src="/static/notice.png" style="width: 64rpx; height: 52rpx;"></image>
                    </view>
                        <view class="u-flex u-row-center u-row-between" style="flex-direction: row; justify-content: space-between;">
                            <image src="/static/hang_up.png" class="img" @click="hangUp"></image>
                            <image src="/static/switch_on.png" class="img" @click="switchOn"></image>
                        </view>
                </view>
        </view>
        <view v-if="status === 3" style="background-color: #232323;" :style="{'height': pageH + 'px'}">
            <view style="flex-direction: row; flex-wrap: wrap;">
                <zego-preview-view class="face" style="width: 375rpx; height: 335rpx;"></zego-preview-view>
                <view v-for="(stream, index) in streamList" :key="index" style="flex-direction: row; flex-wrap: wrap;">
                    <zego-view :streamID="stream.streamID" style="width: 375rpx; height: 335rpx;"></zego-view>
                </view>
                </view>
                <view class="switch-handle">
                        <view style="flex-direction: row; justify-content: center; padding-bottom: 30rpx;">
                                <text style="font-size: 26rpx; color: #fff; margin: 10rpx;">{{minute}}:{{seconds}}</text>
                        </view>
                        <view style="flex-direction: row; justify-content: space-between;">
                                <view style="align-items: center;">
                                        <view class="icon-round">
                                                <image src="/static/notice.png" class="icon1" mode=""></image>
                                        </view>
                                        <text class="h-text">切到语音通话</text>
                                </view>
                                <view style="align-items: center;">
                                        <image src="/static/hang_up.png" class="img" @click="hangUp"></image>
                                        <text class="h-text">挂断</text>
                                </view>
                                <view style="align-items: center;">
                                        <view class="icon-round" @click="changeCamera">
                                                <image src="/static/change_camera.png" class="icon2" mode=""></image>
                                        </view>
                                        <text class="h-text">转换摄像头</text>
                                </view>
                        </view>
                </view>
        </view>
    </view>
</template>

<script>
	// #ifdef APP-PLUS
	var jpushModule = uni.requireNativePlugin("JG-JPush")
	import ZegoExpressEngine from '../../zego-express-video-uniapp/ZegoExpressEngine';
	import {ZegoScenario} from '../../zego-express-video-uniapp/impl/ZegoExpressDefines'
	import {AppID,AppSign} from '../../zegoKey.js'
	var instance = ZegoExpressEngine.createEngine(AppID, AppSign, true, 0);
	// #endif
	export default {
		data() {
			return {
				status: 1, // 1: 主动呼叫;2: 被呼叫
				pageH: '',	// 页面高度
				innerAudioContext: null, //	音乐对象
				streamList: [],
				msg_id: '',		// 推送消息id
				msg_cid: '',		// 推送cid
				roomID: 'dfmily110001',
				publishStreamID: uni.getStorageSync('userinfo').nickname,
				userID: uni.getStorageSync('userinfo').nickname, 
				userName: uni.getStorageSync('userinfo').nickname, 
				camera_dir: 'before', // 摄像头 before 前置,after 后置
			};
		},
		destroyed: () => {
			console.log('destroyed');
			ZegoExpressEngine.destroyEngine();
		},
		mounted() {
			var client = uni.getSystemInfoSync()
			if (client.platform == 'android') {
				//安卓事先请求摄像头、麦克风权限
				var nativeEngine = uni.requireNativePlugin('zego-ZegoExpressUniAppSDK_ZegoExpressUniAppEngine');
				nativeEngine.requestCameraAndAudioPermission();
			}
		},
		onLoad(opt) {
			this.getSysInfo();
			this.playAudio();
			
			if(opt.status == 2){		// 带参数 status=2时代表被呼叫
				this.status = parseInt(opt.status)
			}
			if(!opt.status){			// 主动呼叫、需要发推送消息
				this.getPushCid();
			}
			this.initZegoExpress();
		},
		onBackPress() {
			// return true;
			this.innerAudioContext.stop();
			this.logout();
		},
		methods: {
			getSysInfo() { // 获取手机信息
				let sys = uni.getSystemInfoSync()
				this.pageH = sys.windowHeight
			},
			playAudio() { // 播放音乐
				this.innerAudioContext = uni.createInnerAudioContext();
				this.innerAudioContext.autoplay = true;
				this.innerAudioContext.src = '/static/message.mp3';
				this.innerAudioContext.onPlay(() => {
					console.log('开始播放');
				});
			},
			stopAudio(){		// 停止播放音乐
				if (this.innerAudioContext) {
					this.innerAudioContext.stop()
				}
			},
			hangUp() { // 挂断
				this.stopAudio();
				this.sendCustomCommand(500)
				this.revocationPushMsg();
				this.logout();
				uni.navigateBack({
					delta:1
				})
			},
			switchOn() { // 接通
				this.stopAudio();
				this.status = 3
				this.sendCustomCommand(200)
			},
			changeCamera() { // 切换摄像头
				var instance = ZegoExpressEngine.getInstance();
				if (this.camera_dir == 'before') {
					instance.useFrontCamera(false)
					this.camera_dir = 'after'
				} else if (this.camera_dir == 'after') {
					instance.useFrontCamera(true)
					this.camera_dir = 'before'
				}
			},
			sendCustomCommand(msg){		// 发送自定义信令
				var instance = ZegoExpressEngine.getInstance();
				instance.sendCustomCommand(this.roomID, msg, [{
					"userID": this.userID,
					"userName": this.userName
				}], res => {
					console.log(res)
				});
			},
			getPushCid(){			// 极光推送cid获取
				uni.request({
					url: 'https://api.jpush.cn/v3/push/cid',
					header: {
						'Authorization': 'Basic ' + this.encode(
							'appKey:masterSecret')
					},
					success: (res) => {
						this.msg_cid = res.data.cidlist[0]
						this.sendPushMsg();
					}
				})
			},
			revocationPushMsg(){		// 撤销推送
				uni.request({
					url: 'https://api.jpush.cn/v3/push/' + this.msg_id,
					method: 'DELETE',
					header: {
						'Authorization': 'Basic ' + this.encode(
							'appKey:masterSecret')
					},
					success: (res) => {
						console.log(res)
					}
				})
			},
			sendPushMsg(idArr) {
				uni.request({
					url: 'https://api.jpush.cn/v3/push',
					method: 'POST',
					header: {
						'Authorization': 'Basic ' + this.encode(
							'appKey:masterSecret')
					},
					data: {
						"cid": this.msg_cid,
						"platform": "all",
						"audience": {
							"registration_id": ['160a3797c8ae473a331']
						},
						"notification": {
							"alert": "邀请通话",
							"android": {},
							"ios": {
								"extras": {
									"newsid": 321
								}
							}
						}
					},
					success: (res) => {
						this.msg_id = res.data.msg_id
					}
				})
			},
			initZegoExpress(){		// 初始化
				// instance.startPreview();
				instance.on('roomStateUpdate', result => {
					console.log('From Native roomStateUpdate:' + JSON.stringify(result));
					if (result['state'] == 0) {
						console.log('房间断开')
					} else if (result['state'] == 1) {
						console.log('房间连接中')
					} else if (result['state'] == 2) {
						console.log('房间连接成功')
					}
				});
				instance.on('engineStateUpdate', result => {
					if (result == 0) {
						console.log('引擎启动')
					} else if (result['state'] == 1) {
						console.log('引擎停止')
					}
				});
				instance.on('roomStreamUpdate', result => {
					var updateType = result['updateType'];
					if (updateType === 0) {
						var addedStreamList = result['streamList'];
						this.streamList = this.streamList.concat(addedStreamList);
						for (let i = 0; i < addedStreamList.length; i++) {
							console.log('***********&&&&', addedStreamList[i].streamID)
							var streamID = addedStreamList[i].streamID;
							var instance = ZegoExpressEngine.getInstance();
							instance.startPlayingStream(streamID);
						}
					} else if (updateType === 1) {
						this.removeStreams(result['streamList']);
					}
				});
				instance.on('roomUserUpdate', result => {
					var updateType = result['updateType'];
					if (updateType === 0) {
						this.userID = result.userList[0].userID
						this.userName = result.userList[0].userName
						// this.userList = this.userList.concat(result['userList']);
					} else if (updateType === 1) {
						// this.removeUsers(result['userList']);
					}
				});
				instance.on('IMRecvCustomCommand', result => {
					var fromUser = result['fromUser'];
					var command = result['command'];
					// console.log(`收到${fromUser.userID}的消息:${JSON.stringify(result)}`)
					if(result.command == 200){
						console.log('接听视频通话')
						this.status = 3
						this.stopAudio();
					}else if(result.command == 500){
						console.log('拒绝通话')
						uni.navigateBack({
							delta: 1
						})
					}
				});
				this.login();
				this.publish();
			},
			login() {		// 登录房间
				var instance = ZegoExpressEngine.getInstance();
				instance.loginRoom(this.roomID, {
					'userID': this.userID,
					'userName': this.userName
				});
			},
			logout() {		// 退出房间
				var instance = ZegoExpressEngine.getInstance();
				instance.logoutRoom(this.roomID);
				this.destroyEngine();
			},
			publish() {		// 推流
				var instance = ZegoExpressEngine.getInstance();
				instance.startPublishingStream(this.publishStreamID);
				instance.setVideoConfig({
					encodeWidth: 375,
					encodeHeight: 336
				})
			},
			destroyEngine() {
				ZegoExpressEngine.destroyEngine(boolResult => {
					this.streamList = [];
				});
			},
			removeStreams(removedStreams) {		// 删除流
				let leg = this.streamList.length
				for (let i = leg - 1; i >= 0; i--) {
					for (let j = 0; j < removedStreams.length; j++) {
						if (this.streamList[i]) {
							if (this.streamList[i].streamID === removedStreams[j].streamID) {
								this.streamList.splice(i, 1)
								continue; //结束当前本轮循环,开始新的一轮循环
							}
						}
					}
				}
			},
			
			
			
			encode: function(str) {
				// 对字符串进行编码
				var encode = encodeURI(str);
				// 对编码的字符串转化base64
				var base64 = btoa(encode);
				return base64;
			},
		}
	}
</script>

<style lang="scss">
	.switch-bg {
		position: relative;
		background-color: #6B6B6B;
	}

	.top-info {
		padding: 150rpx 35rpx;
		flex-direction: row;
		align-items: center;

		.avatar {
			width: 150rpx;
			height: 150rpx;
			border-radius: 10rpx;
		}

		.info {
			padding-left: 18rpx;

			.text {
				color: #fff;
				font-size: 26rpx;
			}
		}
	}

	.switch-handle {
		position: absolute;
		bottom: 100rpx;
		left: 0;
		right: 0;
		padding: 0 85rpx;

		.img {
			width: 136rpx;
			height: 136rpx;
		}

		.icon-round {
			align-items: center;
			justify-content: center;
			width: 136rpx;
			height: 136rpx;
			border: 1rpx solid #fff;
			border-radius: 50%;

			.icon1 {
				width: 64rpx;
				height: 52rpx;
			}

			.icon2 {
				width: 60rpx;
				height: 60rpx;
			}
		}

		.h-text {
			margin-top: 10rpx;
			font-size: 26rpx;
			color: #fff;
		}
	}
</style>

说明:

代码中的masterSecret需要修改为极光后台的masterSecretappKey需要修改为极光后台的appKey

view 部分:

status=1 中的为主动呼叫方进入页面是初始显示内容,最重要的是 hangUp 方法,用来挂断当前邀请
status=2 中的为被邀请者进入页面初始显示的内容,有两个按钮,一个hangUp挂断,一个switchOn 接听
status=3中为接听后显示的内容(显示自己与对方视频画面)

script 部分:

最开始五行是引入相关SDK的。极光推送、即构音视频
onLoad 中有一个判断语句,这个就是用于判断进入页面时是主动呼叫方还是被动答应方的,显示不同内容
if(opt.status == 2){		// 带参数 status=2时代表被呼叫
    this.status = parseInt(opt.status)
}
if(!opt.status){			// 主动呼叫、需要发推送消息
    this.getPushCid();
}
sendCustomCommand 是用来在房间内发送自定义信令的,用于通知另一个人是接听了还是挂断了通话
getPushCid 是获取极光推送的cid,避免重复发送推送消息(极光推送)
changeCamera 切换摄像头
revocationPushMsg 撤销推送(主动呼叫方挂断通话)
sendPushMsg 发推送消息
initZegoExpress 初始化即构音视频SDK相关,与官网demo,此处我做了小改动
login 登录即构房间
logout 退出即构房间
publish 推流
destroyEngine 销毁音视频实例
removeStreams 删除流
encode base64转码

在App.vue中进行极光推送的初始化

onLaunch: function() {
    console.log('App Launch')
    // #ifdef APP-PLUS
    if (uni.getSystemInfoSync().platform == "ios") {
        // 请求定位权限
        let locationServicesEnabled = jpushModule.locationServicesEnabled()
        let locationAuthorizationStatus = jpushModule.getLocationAuthorizationStatus()
        console.log('locationAuthorizationStatus', locationAuthorizationStatus)
        if (locationServicesEnabled == true && locationAuthorizationStatus < 3) {
            jpushModule.requestLocationAuthorization((result) => {
                console.log('定位权限', result.status)
            })
        }


        jpushModule.requestNotificationAuthorization((result) => {
            let status = result.status
            if (status < 2) {
                uni.showToast({
                    icon: 'none',
                    title: '您还没有打开通知权限',
                    duration: 3000
                })
            }
        })

        jpushModule.addGeofenceListener(result => {
            let code = result.code
            let type = result.type
            let geofenceId = result.geofenceId
            let userInfo = result.userInfo
            uni.showToast({
                icon: 'none',
                title: '触发地理围栏',
                duration: 3000
            })
        })

        jpushModule.setIsAllowedInMessagePop(true)
        jpushModule.pullInMessage(result => {
            let code = result.code
            console.log(code)
        })

        jpushModule.addInMessageListener(result => {
            let eventType = result.eventType
            let messageType = result.messageType
            let content = result.content
            console.log('inMessageListener', eventType, messageType, content)

            uni.showToast({
                icon: 'none',
                title: JSON.stringify(result),
                duration: 3000
            })
        })

    }

    jpushModule.initJPushService();
    jpushModule.setLoggerEnable(true);
    jpushModule.addConnectEventListener(result => {
        let connectEnable = result.connectEnable
        uni.$emit('connectStatusChange', connectEnable)
    });

    jpushModule.addNotificationListener(result => {
        let notificationEventType = result.notificationEventType
        let messageID = result.messageID
        let title = result.title
        let content = result.content
        let extras = result.extras
        console.log(result)
        this.$util.router(`/pages/public/answer?status=2`)
    });

    jpushModule.addCustomMessageListener(result => {
        let type = result.type
        let messageType = result.messageType
        let content = result.content
        console.log(result)
        uni.showToast({
            icon: 'none',
            title: JSON.stringify(result),
            duration: 3000
        })
    })

    jpushModule.addLocalNotificationListener(result => {
        let messageID = result.messageID
        let title = result.title
        let content = result.content
        let extras = result.extras
        console.log(result)
        uni.showToast({
            icon: 'none',
            title: JSON.stringify(result),
            duration: 3000
        })
    })
    // #endif
},
不要忘了在最开始引入极光推送的插件

var jpushModule = uni.requireNativePlugin("JG-JPush")

官方demo的代码,直接拿过来了。。
其中最重要的就是下面这段,用来监听获取推送消息的,这里如果收到推送消息自动跳转至通话页面,也就是上面status=2的状态下
jpushModule.addNotificationListener(result => {
    let notificationEventType = result.notificationEventType
    let messageID = result.messageID
    let title = result.title
    let content = result.content
    let extras = result.extras
    console.log(result)
    this.$util.router(`/pages/call/call?status=2`)
});

https://juejin.cn/post/6954172658195906567

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐