微信小程序音视频通话

const log = require("./log");
const util = require('./util');
const crypt = require('./WXBizMsgCrypt');
const wmpfVoip = requirePlugin('wmpf-voip').default

// 判断当前是否为设备端拉起小程序
const isWmpf = (typeof wmpf !== 'undefined');

// 指定接听方使用的小程序版本。formal/正式版(默认);trial/体验版;developer/开发版
const miniprogramState = (() => {
	const accountInfo = wx.getAccountInfoSync();
	if(accountInfo && accountInfo.miniProgram){
		const platform = { develop: 'developer', trial: 'trial', release: 'formal' }
		return platform[accountInfo.miniProgram.envVersion]
	}
})()
console.log('miniprogramState='+miniprogramState)

// 「设备接入」从微信公众平台获取的 model_id
const modelId = 'DSAF58AS5F3SA2FD2SA33DS55';

// 设备名称,用于授权时显示给用户
const deviceName = '好宝宝';

// 通话监听
const onVoipEvent = () => {
	wmpfVoip.onVoipEvent((event) => {
		const eventName = event.eventName;
		// 定义挂断事件
		const filterEvent = ['hangUpVoip', 'cancelVoip', 'timeout', 'rejectVoip'];
		// 挂断动作向设备端发送消息通知
		if(eventName == 'endVoip' || filterEvent.indexOf(eventName) > -1){
			// 每次通话只能推送一次挂断信息
			if(event.groupId && wx.getStorageSync('currcall') != event.groupId){
				sendMsgDevice('EndVoip');
				wx.setStorageSync('currcall', event.groupId)
				log.info('通知设备关闭小程序')
			}
		}
		// 通话记录参数
		const callData = {
			groupId: event.groupId,
			params: {
				eventName: eventName, data: event.data
			},
		}
		// 挂断上报通话记录
		filterEvent.indexOf(eventName) > -1 && updateCallRecord(callData)
		// 为避免上面几种情况执行异常,在结束通话时再次上报,延迟执行是为了避免并发
		if(eventName == 'endVoip'){
			setTimeout(() => {
				updateCallRecord(callData);
			}, 1500)
		}
		// 非通话中,打印调试
		(eventName != 'calling') && log.info(`onVoipEvent`, event);
		// 设备打微信,微信端插件通话页面 onShow
		if(eventName == 'callPageOnShow' && !isWmpf){
			setUIConfig(1)
		}
	})
}

// 设置通话结束跳转地址
const setVoipEndPagePath = (recordId) => {
	if(!isWmpf){
		wmpfVoip.setVoipEndPagePath({
			url: '/pages/call/index',
			key: 'Call',
		})
	}
}

/**
 * 小程序传递消息给设备
 * @param {*} command 
 */
const sendMsgDevice = (command) => {
	if(isWmpf){
		wmpf.Channel.invoke({
			command: command,
			success: function(res) {
				wx.setStorageSync(command+'_invoke', 1)
				setTimeout(() => {
					wx.removeStorageSync(command+'_invoke');
				}, 1000)
				log.info('wmpf.Channel.invoke success:', res.data)
			},
			fail: function(res){
				log.error('wmpf.Channel.invoke fail:', res);
			}
		})
	}
}

/**
 * 微信打设备
 * @param {拨打方用户 openId} openid 
 * @param {拨打对方用户信息 name:拨打方名字,仅显示用;roomType:voice音频房间,video音视频房间;voipToken:从设备获取的 pushToken} contact 
 */
const wechatCallDevice = (contact) => {
	wx.showLoading({title: '呼叫中',mask: true}), setTimeout(() => {wx.hideLoading()}, 3000)
	const initByCaller = async (config, data) => {
		log.info('====>通话请求参数', data, config)
		setUIConfig(2);
		const { groupId, isSuccess, errCode, errMsg } = await wmpfVoip.initByCaller(data)
		wx.hideLoading();
		if (isSuccess) {
			requestCallVoip(groupId, data.roomType, 0, config);
			const callPagePlugin = 'plugin-private://wxf830863afde621eb/pages/call-page-plugin/call-page-plugin'
			wx.redirectTo({
				url: `${callPagePlugin}?isCaller=1&roomType=${data.roomType}&groupId=${groupId}`,
			})
		} else {
			log.error('拨打请求失败:errCode='+errCode+';errMsg='+errMsg, config, data)
			if(errCode == 14){
				return wx.showToast({title: '手机微信拨打硬件设备模式,voipToken 错误',icon: 'error'})
			}
			wx.showToast({title: '拨打失败',icon: 'error'})
		}
	}
	const init = () => {
		checkDeviceAuth(contact.deviceId, ()=>{
			getCallConfigParams(contact.id, contact.relId, (config) => {
				initByCaller(config, {
					caller: {
						id: config.openid, name: config.name
					},
					listener: {
						id: config.deviceId
					},
					roomType: contact.roomtype,
					voipToken: config.pushToken,
					businessType: 2,
					miniprogramState: miniprogramState,
				})
			})
		})
	}
	// 通话结束延长1秒才能再次拉起通话,0.2秒检查一次
	const checkCallEnd = () => {
		if(wx.getStorageSync('EndVoip_invoke')){
			setTimeout(() => {
				checkCallEnd();
			}, 200)
		}else{
			init()
		}
	}
	checkCallEnd();
}

/**
 * 设备拨打微信
 */
const deviceCallWechat = () => {
	const { query } = wmpfVoip.getPluginEnterOptions()
	log.info('====>设备拨打微信传参:', query)
	setUIConfig(query.isCaller === '1' ? 3 : 4);
	if(query.isPreLaunch == 'false' && query.isCaller === '1' && query.contactId){
		var contactId = query.contactId;
		var relId = query.deviceId;
		if(query.isRecord == '1'){
			contactId = -2;
			relId = query.contactId;
		}
		wx.setStorageSync('token', query.token);
		getCallConfigParams(contactId, relId, (config) => {
			wmpfVoip.initByCaller({
				caller: {
					id: query.deviceId
				},
				listener: {
					id: config.openid,name: config.name
				},
				roomType: query.roomType,
				voipToken: query.voipToken,
				businessType: 1,
				miniprogramState: miniprogramState,
			}).then((res)=>{
				log.info('===========>设备打微信initByCaller success', res);
				res.isSuccess && requestCallVoip(res.groupId, query.roomType, 1, config);
				if(!res.isSuccess){
					if(res.errCode == 9){
						wx.showToast({title: '未授权设备无法使用通话功能',icon: 'error'})
					}
					if(res.errCode == 13){
						// 传递参数给设备处理
						sendMsgDevice('VoipTokenErr13');
					}
					sendMsgDevice('errorEnd');
				}
			}).catch((e) => {
				sendMsgDevice('errorEnd');
				log.error('==========>设备打微信initByCaller fail', e);
			})
		}, () => {
			sendMsgDevice('errorEnd');
		})
	}
}

/**
 * 获取通话配置参数
 * @param {通讯录id} contactId 
 */
const getCallConfigParams = (contactId, relId, callback, errback) => {
	util.rqt({
		url: '/eeop/wechatcall/getCallConfig',
		data: {
			id: contactId,appid: crypt.appid,relId: relId
		},
		callBack(res){
			const encryptOpenid = res.data.encryptOpenid;
			if(!encryptOpenid){
				errback && errback();
				log.error('获取配置参数openid失败', res.data);
				return;
			}
			res.data.openid = crypt.decrypt(encryptOpenid.TimeStamp, encryptOpenid.Nonce, encryptOpenid.Encrypt, encryptOpenid.MsgSignature);
			callback && callback(res.data);
		},
		errCallBack(res){
			errback && errback();
			log.error('请求后台配置出现异常', res);
		}
	})
}

/**
 * 拨打电话
 */
const requestCallVoip = (groupId, roomType, callWay, config) => {
	updateCallRecord({
		groupId: groupId,
		userId: config.userId,
		deviceId: config.deviceId,
		callType: (roomType == 'video' ? 1 : 0),
		callWay: callWay,
		params: {
			eventName: 'startVoip'
		}
	})
}

const updateCallRecord = (data) => {
	util.rqt({
		url: '/eeop/wechatcall/updateCallRecord',
		method: 'POST',
		data: data,
		callBack(res){
			log.info('上报通话返回结果', res, data);
		},
		errCallBack(res){
			log.error('上报通话数据返回异常', res, data)
		}
	})
}

/**
 * 添加联系人
 * @param {*} options 
 * @param {*} callback 
 */
const addContact = (options, callback) => {
	if(!options.deviceId || !options.code){
		return wx.showToast({title: '扫描失败',icon: 'none'})
	}
	checkDeviceAuth(options.deviceId, () => {
		util.rqt({
			url: '/eeop/wechatcall/bind',
			data: options,
			callBack(res){
				open('添加成功', true);
			},
			errCallBack(res){
				if(res.code == 3010){
					return open('二维码已过期', false);
				}
				open(res.msg, false);
			}
		})
		const open = (content, status) => {
			wx.showToast({
				title: content, icon: 'none',
				success(res){
					setTimeout(() => {
						callback && callback(status);
					}, 1500)
				}
			})
		}
	})
}

/**
 * 更新token
 */
const updatePushToken = () => {
	// 根据设备deviceId更新token信息到后台服务器
	const updatePushToken = (deviceId, pushToken) => {
		util.rqt({
			url: '/eeop/wechatcall/pushToken',
			data: {
				deviceId: deviceId,pushToken: pushToken
			},
			callBack(result){
				log.info('=====>设备端拉起小程序时向后台请求更新token返回结果callBack:deviceId='+deviceId+';pushToken='+pushToken, result);
			},
			errCallBack(result){
				log.info('=====>设备端拉起小程序时向后台请求更新token返回结果errCallBack:deviceId='+deviceId+';pushToken='+pushToken, result);
			}
		});
	}
	// 获取pushToken
	const getWmpfPushToken = (deviceId) => {
		log.info('=====>获取pushToken', deviceId);
		wmpf.getWmpfPushToken({
			success(res){
				log.info('=====>wmpf.getWmpfPushToken success', res);
				const pushToken = res.token;
				if(pushToken && pushToken != 'undefined'){
					updatePushToken(deviceId, pushToken);
				}
			},
			fail(res){
				log.error('=====>wmpf.getWmpfPushToken fail', res);
			}
		})
	}
	// 获取设备deviceId
	const { query } = wmpfVoip.getPluginEnterOptions()
	log.info('设备拉起小程序获取参数:', query);
	query.token && wx.setStorageSync('token', query.token);
	query.deviceId && getWmpfPushToken(query.deviceId);
}

/**
 * 检测是否授权
 * @param {用户信息} contact 
 */
const checkDeviceAuth = (deviceId, callback) => {
	const requestDeviceVoIP = (snTicket) => {
		wx.hideLoading();
		wx.requestDeviceVoIP({
			sn: deviceId, // 向用户发起通话的设备 sn(需要与设备注册时一致)
			snTicket: snTicket, // 获取的 snTicket
			modelId: modelId, // 「设备接入」从微信公众平台获取的 model_id
			deviceName: deviceName, // 设备名称,用于授权时显示给用户
			success(res) {
				callback && callback();
				log.info(`requestDeviceVoIP success:`, res)
			},
			fail(err) {
				if(err.errCode == 10021){
					wx.showModal({
						title: '是否要打开设置页面',
						content: '需要获取您的音视频通话授权信息,请到小程序的设置中打开授权',
						success(res) {
							res.confirm && wx.openSetting()
						}
					})
				}
				log.error(`requestDeviceVoIP fail:`, err)
			},
		})
	}
	const getSnTicket = () => {
		util.rqt({
			url: '/eeop/wechatcall/setting/getSnTicket',
			data: {
				deviceId: deviceId
			},
			callBack(res){
				requestDeviceVoIP(res.data.snTicket)
			},
			errCallBack(res){
				log.error(`getSnTicket fail:`, err)
			}
		})
	}
	util.checkWechatVersion('2.30.3') && wx.getDeviceVoIPList({
		success(res) {
			log.info('当前用户授权的设备:', res.list)
			const isAuth = res.list.some(element => {
				return element.sn == deviceId && element.status == 1;
			})
			if(isAuth){
				callback && callback()
			}else{
				getSnTicket();
			}
		}
	})
}

/**
 * 自定义UI相关配置
 * @param {拨打场景: 1设备打微信,微信端接听,微信端设置;2微信打设备,微信端设置;3设备打微信,设备端设置;4微信打设备,设备端接听,设备端设置}  scene
 */
const setUIConfig = (scene) => {
	return;
	// 显示设备端
	var UI_1 = {
		cameraRotation: 0,  // caller的视频画面旋转角度,有效值为 0, 90, 180, 270。默认 0
		aspectRatio: 3/4, // 纵横比,caller的视频画面会进行适配比例,有效值 数字。默认 4/3
		horMirror: false, // 横向镜像,boolean 值,默认 false
		vertMirror: false, // 竖直镜像,同上
		enableToggleCamera: false, // 是否支持切换摄像头,false 则不显示「摄像头开关」按钮。默认false 【该配置项在wmpf无效,wmpf默认开摄像头,且不显示开关按钮】
	}
	// 显示微信端
	var UI_2 = {
		cameraRotation: 0,
		aspectRatio: 4/3,
		horMirror: false,
		vertMirror: false,
		enableToggleCamera: false,
	}
	if(scene == 1){
		wmpfVoip.setUIConfig({
			btnText: null,
			callerUI: UI_1,
			listenerUI: UI_2
		})
	}
	if(scene == 2){
		wmpfVoip.setUIConfig({
			btnText: null,
			callerUI: UI_1,
			listenerUI: UI_2
		})
	}
	if(scene == 3){
		wmpfVoip.setUIConfig({
			btnText: null,
			callerUI: UI_1,
			listenerUI: UI_2
		})
	}
	if(scene == 4){
		wmpfVoip.setUIConfig({
			btnText: null,
			callerUI: UI_2,
			listenerUI: UI_1
		})
	}
}

module.exports = {
	isWmpf, updatePushToken, wechatCallDevice, deviceCallWechat, checkDeviceAuth, onVoipEvent, setVoipEndPagePath, addContact
}

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值