首页 > 其他分享 >记录--uniapp开发安卓APP视频通话模块初实践

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

时间:2022-11-17 17:01:35浏览次数:74  
标签:status uniapp console log -- 安卓 let result uni

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

视频通话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

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

 

标签:status,uniapp,console,log,--,安卓,let,result,uni
From: https://www.cnblogs.com/smileZAZ/p/16900022.html

相关文章

  • 黏包、udp协议、多道技术、进程并行与并发
    目录黏包现象struct模块黏包代码实战UDP协议(了解)并发编程理论多道技术进程理论进程的并行与并发进程的三状态黏包现象1.服务端连续执行三次recv2.客户端连续执行三次sen......
  • 记一次分布式锁失效的生产事故
    项目背景:在给某项目做业务开发的时候,有一个任务派发的定时任务,该定时任务通过算法,把系统远远不断的任务每隔一分钟派发给不同的审核员进行审核。因为考虑到分布式任务......
  • linux 常用知识
    linux是一个开源、免费的操作系统,其稳定性、安全性、处理多并发已经得到业界的认可,目前很多中型,大型甚至是巨型项目都在使用linux。linux内核:redhat、红旗linux、ubuntu、......
  • NSSCTF-SWPUCTF2021新生赛之fakerandom
    Day1NSSCTF[SWPUCTF2021新生赛]fakerandom下载源件之后是一个py文件,打开后源码如下随后看到了一个随机数种子,关于随机数种子的知识我了解的不多,只在学校的石头剪刀......
  • Rundeck部署和基本使用【转】
    rundeck介绍Rundeck是一款能在数据中心或云环境中的日常业务中使程序自己主动化的开源软件。Rundeck 提供了大量功能。能够减轻耗时繁重的体力劳动。团队能够相互协作......
  • 常见的JVM性能调优场景
    借鉴并分享一篇好文章一、cpu占用过高cpu占用过高要分情况讨论,是不是业务上在搞活动,突然有大批的流量进来,而且活动结束后cpu占用率就下降了,如果是这种情况其实可以不用......
  • 96. 不同的二叉搜索树 ----- 动态规划
    给你一个整数n,求恰由n个节点组成且节点值从1到n互不相同的二叉搜索树有多少种?返回满足题意的二叉搜索树的种数。 示例1:  输入:n=3输出:5示例2:输入:n......
  • LNMP搭建
    一、编译安装Nginx服务1、编译安装Nginx服务的操作步骤systemctlstopfirewalldsystemctldisablefirewalldsetenforce01.1安装依赖包yum-yinstallpcre-d......
  • JavaScript对象RegExp1和GloBal
    JavaScript对象_RegExp1RegExp:正则表达式对象1.正则表达式:定义字符串的组成规则1.单个字符:[]如:[a][ab][a-zA-Z0-9]......
  • app上架应用市场-修改标题、副标题规则
    近期在弄应用市场上架总结:一、上架前:华为资质审核要求:https://developer.huawei.com/consumer/cn/doc/distribution/app/80301上架所需纸质版资料:(1)ICP备案或《增值电信......