深圳幻海软件技术有限公司 欢迎您!

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

2023-03-02

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助视频通话SDK用的即构的,uniapp插件市场地址推送用的极光的,uniapp插件市场地址即构音视频SDKuniapp插件市场的貌似是有些问题,导入不进项目,直接去官网下载,然后放到项目下的nativeplugins目录下,在配置文件中填入

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

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

推送用的极光的,uniapp插件市场地址

即构音视频SDK

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

准备两个页面

首页:/pages/index/index

?
1
2
3
4
5
6
7
8
9
// 新建一个按钮
<button @click="sendVideo">发送视频邀请</button>
 
// 发送事件,主动发送直接进入下一个页面即可
sendVideo(){
    uni.navigateTo({
        url: '/pages/call/call'
    })
}

通话页:pages/call/call 这个页面会复杂一点

注意这个页面为 nvue 页面

先把所有代码都列出来,再一一做说明

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
<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 中有一个判断语句,这个就是用于判断进入页面时是主动呼叫方还是被动答应方的,显示不同内容

?
1
2
3
4
5
6
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中进行极光推送的初始化

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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的状态下

?
1
2
3
4
5
6
7
8
9
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

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

 


__EOF__

  • 本文作者: 林恒
  • 本文链接: https://www.cnblogs.com/smileZAZ/p/16900022.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。