记录–uniapp开发安卓APP视频通话模块初实践

  • 记录–uniapp开发安卓APP视频通话模块初实践已关闭评论
  • 141 次浏览
  • A+
所属分类:Web前端
摘要

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


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

记录--uniapp开发安卓APP视频通话模块初实践

视频通话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" alt="记录--uniapp开发安卓APP视频通话模块初实践" 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" alt="记录--uniapp开发安卓APP视频通话模块初实践" 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" alt="记录--uniapp开发安卓APP视频通话模块初实践" class="img" @click="hangUp"></image>                             <image src="/static/switch_on.png" alt="记录--uniapp开发安卓APP视频通话模块初实践" 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" alt="记录--uniapp开发安卓APP视频通话模块初实践" class="icon1" mode=""></image>                                         </view>                                         <text class="h-text">切到语音通话</text>                                 </view>                                 <view style="align-items: center;">                                         <image src="/static/hang_up.png" alt="记录--uniapp开发安卓APP视频通话模块初实践" 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" alt="记录--uniapp开发安卓APP视频通话模块初实践" 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

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

 记录--uniapp开发安卓APP视频通话模块初实践