夜雨聆风学习资料网

ARTICLE · 988158

微信小程序 VoIP 插件(二期,实战代码)

微信小程序 VoIP 插件(二期,实战代码)

VoIP 对接代码整理

配置 manifest.json

{  ”mp-weixin”: {    ”plugins”: {      ”wmpf-voip”: {        ”version”: ”2.5.3”,        ”provider”: ”wx***************”      }    },    ”permission”: {      ”scope.camera”: { ”desc”: ”用于人脸识别验证和音视频通话” },      ”scope.record”: { ”desc”: ”用于音视频通话时采集麦克风声音” }    },    ”optimization”: {      ”subPackages”: true    }  }}

不要加:"lazyCodeLoading": "requiredComponents"(白屏的关键)


App.vue

import wmpfVoipMixin from '@/common/mixin/wmpf-voip-mixin'export default {  mixins: [wmpfVoipMixin],  onLaunch(res) {    // #ifdef MP-WEIXIN    const pluginLaunch = this.isVoipPluginLaunch(res)    this.ensureVoipRuntimeReady()    if (pluginLaunch) {      setTimeout(() => this.logVoipColdStart('app.onLaunch', res), 0)    } else {      this.logVoipColdStart('app.onLaunch', res)    }    // #endif  },  onShow(res) {    // #ifdef MP-WEIXIN    this.initVoip()    setTimeout(() => this.logVoipColdStart('app.onShow', res), 0)    // #endif  }}

common/mixin/wmpf-voip-mixin.js

import * as env from '@/config/env.js'import NetworkMonitor from '@/common/js/networkMonitor.js'import { getConfig } from '@/api/auth'let wmpfVoip = nulllet voipRealtimeLogger = nullconst VOIP_REALTIME_SKIP = { calling: true }const VOIP_REALTIME_WARN = {  joinFailListener: true,  joinFailCaller: true,  abortVoip: true,  rejectVoip: true,  timeout: true,  busy: true,  forceHangUpVoip: true}const VOIP_REALTIME_ERROR = {  pluginMissing: true,  initByCallerFail: true,  setVoipEndPagePathFail: true}// #ifdef MP-WEIXINtry {  const plugin = requirePlugin('wmpf-voip')  wmpfVoip = plugin && plugin.default ? plugin.default : plugin} catch (e) {  wmpfVoip = null}try {  if (typeof wx !== 'undefined' && wx.getRealtimeLogManager) {    voipRealtimeLogger = wx.getRealtimeLogManager()  }} catch (e) {  voipRealtimeLogger = null}// #endiffunction isWmpfDeviceEnv() {  // #ifdef MP-WEIXIN  return typeof wmpf !== 'undefined' && wmpf.Channel && wmpf.Channel.invoke  // #endif  // #ifndef MP-WEIXIN  return false  // #endif}function getVoipPluginOptions(getterName) {  if (!wmpfVoip || typeof wmpfVoip[getterName] !== 'function') return {}  try {    return wmpfVoip[getterName]() || {}  } catch (e) {    return {}  }}function pickDefinedVoipFields(src) {  const out = {}  if (!src || typeof src !== 'object') return out  Object.keys(src).forEach((key) => {    if (key === 'path' || key === 'query') return    const value = src[key]    if (value === undefined || value === null || value === '') return    out[key] = value  })  return out}function getVoipQueryObject(src) {  if (src && src.query && typeof src.query === 'object') return src.query  return {}}function isVoipPluginPath(path) {  return typeof path === 'string' && path.indexOf('call-page-plugin') !== -1}function writeVoipRealtimeLog(level, payload) {  const role = isWmpfDeviceEnv() ? 'caller' : 'listener'  const msg = Object.assign({ role }, payload || {})  console.log('[voip]', level, msg)  if (!voipRealtimeLogger) return  try {    voipRealtimeLogger.setFilterMsg('voip')    const text = JSON.stringify(msg)    if (level === 'error' && voipRealtimeLogger.error) voipRealtimeLogger.error(text)    else if (level === 'warn' && voipRealtimeLogger.warn) voipRealtimeLogger.warn(text)    else if (voipRealtimeLogger.info) voipRealtimeLogger.info(text)  } catch (e) {    console.error('[voip] realtime log fail', e)  }}export default {  data() {    return {      callOptions: {},      deviceKeepTime: -1, // -1 不限时;>0 才限时。默认不能是 0      voipEventListener: null,      voipRuntimeReady: false,      voipSessionPhase: 'idle', // idle | ringing | active      voipRoomId: '',      lastHandledCallSeq: '',      voipInitByCallerInFlight: false,      networkState: { connected: true, type: '', available: true },      networkMonitor: null    }  },  mounted() {    if (wmpfVoip && isWmpfDeviceEnv()) {      this.initNetWorkMonitor()    }  },  methods: {    isWmpfDevice() {      return isWmpfDeviceEnv()    },    isVoipPluginLaunch(launchRes) {      const enterOptions = getVoipPluginOptions('getPluginEnterOptions')      const onloadOptions = getVoipPluginOptions('getPluginOnloadOptions')      const path = (launchRes && launchRes.path) || enterOptions.path || onloadOptions.path || ''      return isVoipPluginPath(path)    },    getVoipSourceQuery(getterName) {      const options = getVoipPluginOptions(getterName)      return Object.assign(        {},        pickDefinedVoipFields(options),        pickDefinedVoipFields(getVoipQueryObject(options))      )    },    getVoipEnterQuery() {      return this.getVoipSourceQuery('getPluginEnterOptions')    },    getVoipOnloadQuery() {      return this.getVoipSourceQuery('getPluginOnloadOptions')    },    getVoipPluginQuery() {      return Object.assign({}, this.getVoipEnterQuery(), this.getVoipOnloadQuery())    },    isVoipPreLaunchQuery(query) {      const q = query || {}      return q.isPreLaunch === true || q.isPreLaunch === 'true' || q.isPreLaunch === '1'    },    isWmpfCallerPluginQuery(query) {      const q = query || {}      if (this.isVoipPreLaunchQuery(q)) return false      if (!q.callSeq) return false      if (q.isCaller === '0' || q.isCaller === 0 || q.isCaller === false) return false      if (String(q.isCaller) === '1') return true      return !!(q.sn && q.openId)    },    ensureVoipRuntimeReady() {      // #ifdef MP-WEIXIN      if (this.voipRuntimeReady) return true      if (!wmpfVoip) {        this.logVoipEvent('pluginMissing')        return false      }      this.setupVoipEventListener()      this.syncKeepTimeFromPlugin()      try {        wmpfVoip.setVoipEndPagePath({          url: '/pages/home/home',          key: 'Call',          routeType: 'switchTab'        })      } catch (e) {        this.logVoipEvent('setVoipEndPagePathFail', {          data: { errMsg: e && e.message ? e.message : String(e) }        })      }      this.voipRuntimeReady = true      return true      // #endif      // #ifndef MP-WEIXIN      return false      // #endif    },    initVoip() {      // #ifdef MP-WEIXIN      return new Promise((resolve) => {        const ready = this.ensureVoipRuntimeReady()        if (!ready || !wmpfVoip) {          resolve(false)          return        }        // 被叫:立刻让路        if (!this.isWmpfDevice()) {          resolve(true)          return        }        const enterQuery = this.getVoipEnterQuery()        if (this.isVoipPreLaunchQuery(enterQuery)) {          resolve(false)          return        }        // 主叫参数齐全:直接拨        if (this.isWmpfCallerPluginQuery(enterQuery)) {          this.startCallerFromPluginQuery(enterQuery)            .then(resolve)            .catch(() => resolve(false))          return        }        // query 未齐:等 callPageOnShow        if (this.isVoipPluginLaunch()) {          resolve(true)          return        }        // 兜底        getConfig().then((res) => {          if (!res.isShowFace) {            resolve(false)            return          }          uni.getNetworkType({            success: (netRes) => {              if (netRes.networkType === 'none') {                this.$util.msg('当前设备没有网络')                resolve(false)                return              }              const query = this.getVoipPluginQuery()              this.syncKeepTimeFromPlugin()              if (this.isVoipPreLaunchQuery(query)) {                resolve(false)                return              }              if (query.isCaller === '1') {                this.startCallerFromPluginQuery(query)                  .then(resolve)                  .catch(() => resolve(false))              } else {                resolve(true)              }            }          })        }).catch(() => resolve(false))      })      // #endif      // #ifndef MP-WEIXIN      return Promise.resolve(false)      // #endif    },    startCallerFromPluginQuery(query) {      const q = query || this.getVoipEnterQuery()      if (!this.isWmpfDevice() || !this.isWmpfCallerPluginQuery(q)) {        return Promise.resolve()      }      const callSeq = String(q.callSeq)      if (this.lastHandledCallSeq && this.lastHandledCallSeq === callSeq) {        return Promise.resolve()      }      if (this.voipInitByCallerInFlight) {        return Promise.resolve()      }      this.voipInitByCallerInFlight = true      this.syncKeepTimeFromPlugin()      if (q.sn) this.deviceSn = q.sn      if (q.roomType) this.roomType = q.roomType      try {        wmpfVoip.setUIConfig({ handsFree: false })      } catch (e) {}      return this.checkCallerAuthorization(Object.assign({}, q))        .then((ok) => {          this.lastHandledCallSeq = callSeq          return ok        })        .catch((err) => {          this.logVoipEvent('initByCallerFail', {            data: { errMsg: err && err.errMsg ? err.errMsg : String(err) }          })        })        .then(() => {          this.voipInitByCallerInFlight = false        })    },    checkCallerAuthorization(options) {      const { sn, openId, roomType, callName, famName } = options || {}      // #ifdef MP-WEIXIN      return new Promise((resolve, reject) => {        if (!wmpfVoip) {          resolve(false)          return        }        let miniprogramState = 'trial'        if (env.IS_TRIAL) miniprogramState = 'trial'        else if (env.IS_RELEASE) miniprogramState = 'formal'        wmpfVoip.getIotBindContactList({          sn,          model_id: env.DEVICE_ID,          openid_list: [openId]        }).then((res) => {          const contactList = res.contact_list || []          if (!(contactList.length > 0 && contactList[0].status == 1)) {            this.sendVoipEventToDevice('callError', { msg: '该家长未对此设备授权' })            reject(new Error('contact unauthorized'))            return          }          wmpfVoip.initByCaller({            caller: { id: sn, name: callName },            listener: { id: openId, name: famName },            roomType,            businessType: 1,            miniprogramState          }).then((roomRes) => {            const { groupId, roomId, isSuccess } = roomRes            if (!isSuccess) {              this.sendVoipEventToDevice('callError', { msg: '拨打失败' })              reject(new Error('initByCaller isSuccess false'))              return            }            this.rememberVoipRoomId(roomId || groupId)            this.setVoipPhase('ringing')            resolve(true)          }).catch((err) => {            this.sendVoipEventToDevice('callError', { msg: '创建房间失败' })            this.sendVoipEventToDevice('endVoip', { reason: 'initByCallerFail' })            this.forceHangUpVoipSession('initByCallerFail')            this.resetVoipSession()            reject(err)          })        }).catch((err) => {          this.sendVoipEventToDevice('callError', { msg: '该家长未对此设备授权' })          reject(err)        })      })      // #endif      // #ifndef MP-WEIXIN      return Promise.resolve(false)      // #endif    },    setupVoipEventListener() {      // #ifdef MP-WEIXIN      if (!wmpfVoip) return      if (this.voipEventListener) return // 只绑一次      this.voipEventListener = wmpfVoip.onVoipEvent((event) => {        const { eventName, data, roomId, groupId } = event        this.rememberVoipRoomId(roomId || groupId)        if (!VOIP_REALTIME_SKIP[eventName]) {          this.logVoipEvent(eventName, { data: data || {}, roomId, groupId })        }        this.sendVoipEventToDevice(eventName, data)        switch (eventName) {          case 'startVoip':            this.setVoipPhase('active')            break          case 'inviteVoip':            this.setVoipPhase('ringing')            break          case 'acceptVoip':          case 'hangOnVoip':            this.setVoipPhase('active')            break          case 'calling':            this.handleCalling(data)            break          case 'callPageOnShow':            this.handleCallPageOnShow(data)            break          case 'joinedRoomByCaller':            if (this.voipSessionPhase === 'idle') this.setVoipPhase('ringing')            break          case 'abortVoip':          case 'cancelVoip':          case 'rejectVoip':          case 'hangUpVoip':          case 'endVoip':          case 'busy':          case 'timeout':          case 'joinFailCaller':          case 'joinFailListener':          case 'finishVoip':            this.resetVoipSession()            break        }      })      // #endif    },    handleCallPageOnShow(data) {      if (!this.isWmpfDevice()) {        if (this.voipSessionPhase === 'idle') this.setVoipPhase('ringing')        setTimeout(() => this.logVoipColdStart('plugin.callPageOnShow', data), 0)        return      }      const onloadQuery = this.getVoipOnloadQuery()      this.syncKeepTimeFromPlugin()      if (this.isWmpfCallerPluginQuery(onloadQuery)) {        this.startCallerFromPluginQuery(onloadQuery)        return      }      if (this.voipSessionPhase === 'idle') this.setVoipPhase('ringing')      setTimeout(() => this.logVoipColdStart('plugin.callPageOnShow', data), 0)    },    handleCalling(data) {      this.setVoipPhase('active')      const keepTime = data.keepTime * 1      if (!(this.deviceKeepTime > 0)) this.syncKeepTimeFromPlugin()      if (!(this.deviceKeepTime > 0)) return      if ((this.deviceKeepTime - keepTime) === 30) {        this.$util.msg('剩余通话时长不足30秒')      }      if (keepTime >= this.deviceKeepTime) {        this.sendVoipEventToDevice('endVoip', data)        this.forceHangUpVoipSession('timeLimit')        this.resetVoipSession()      }    },    syncKeepTimeFromPlugin() {      // #ifdef MP-WEIXIN      try {        const query = this.getVoipPluginQuery()        if (query.keepTime === undefined || query.keepTime === null || query.keepTime === '') return        const n = query.keepTime * 1        if (!Number.isNaN(n)) this.deviceKeepTime = n      } catch (e) {}      // #endif    },    setVoipPhase(phase) {      this.voipSessionPhase = phase    },    resetVoipSession() {      this.voipSessionPhase = 'idle'      this.voipRoomId = ''    },    rememberVoipRoomId(roomId) {      if (roomId) this.voipRoomId = roomId    },    forceHangUpVoipSession(reason) {      // #ifdef MP-WEIXIN      if (!wmpfVoip) return      try {        if (this.voipRoomId) wmpfVoip.forceHangUpVoip(this.voipRoomId)        else wmpfVoip.forceHangUpVoip()        this.logVoipEvent('forceHangUpVoip', {          data: { origin: 'forceFromApp', errMsg: reason }        })      } catch (e) {}      // #endif    },    sendVoipEventToDevice(eventType, eventData) {      // #ifdef MP-WEIXIN      try {        if (typeof wmpf !== 'undefined' && wmpf.Channel && wmpf.Channel.invoke) {          wmpf.Channel.invoke({            command: eventType,            data: JSON.stringify(eventData || {}),            success() {},            fail() {}          })        }      } catch (e) {}      // #endif    },    logVoipEvent(eventName, extra) {      const data = (extra && extra.data) || extra || {}      let level = 'info'      if (VOIP_REALTIME_ERROR[eventName]) level = 'error'      else if (VOIP_REALTIME_WARN[eventName]) level = 'warn'      writeVoipRealtimeLog(level, {        eventName,        roomId: (extra && extra.roomId) || this.voipRoomId || '',        groupId: (extra && extra.groupId) || '',        errMsg: data.errMsg || (data.error && data.error.errMsg) || '',        status: data.status || '',        origin: data.origin || ''      })    },    logVoipColdStart(stage, launchRes) {      const enterOptions = getVoipPluginOptions('getPluginEnterOptions')      const onloadOptions = getVoipPluginOptions('getPluginOnloadOptions')      writeVoipRealtimeLog('info', {        eventName: 'coldStart',        stage,        hasWmpf: isWmpfDeviceEnv(),        path: (launchRes && launchRes.path) || '',        scene: (launchRes && launchRes.scene) || '',        pluginPath: enterOptions.path || '',        pluginQuery: enterOptions.query || {},        onloadPath: onloadOptions.path || '',        onloadQuery: onloadOptions.query || {}      })    },    initNetWorkMonitor() {      this.networkMonitor = new NetworkMonitor()      this.networkMonitor.setCallback((state) => {        this.networkState = state        if (!state.available) {          this.$util.msg('当前设备没有网络,请重新启动')          setTimeout(() => {            if (this.voipSessionPhase !== 'idle') {              this.forceHangUpVoipSession('networkUnavailable')              this.resetVoipSession()            }            if (this.networkMonitor) this.networkMonitor.destroy()          }, 300)        }      })    },    destroyVoipEventListener() {      // #ifdef MP-WEIXIN      if (this.voipEventListener) {        this.voipEventListener()        this.voipEventListener = null        this.voipRuntimeReady = false      }      // #endif    }  },  beforeDestroy() {    this.destroyVoipEventListener()    if (this.networkMonitor) this.networkMonitor.destroy()  },  onHide() {    // #ifdef MP-WEIXIN    if (!this.isWmpfDevice() || !wmpfVoip) return    if (this.voipSessionPhase === 'idle') return    this.forceHangUpVoipSession('onHide')    this.resetVoipSession()    // #endif  }}

调用链

设备主叫:onLaunch → ensureVoipRuntimeReadyonShow → initVoip → startCallerFromPluginQuery → getIotBindContactList → initByCallerquery 未齐时 → callPageOnShow → getVoipOnloadQuery → startCallerFromPluginQuery手机被叫:onLaunch → ensureVoipRuntimeReady(只绑事件)onShow → initVoip → 非设备端立刻 return插件自己进房 → hangOnVoip

query 字段

callSeq / isCaller / isPreLaunch / sn / openId / roomType / keepTime / callName / famName

注意

deviceKeepTime 默认 -1,不能是 0onVoipEvent 只绑一次被叫 initVoip 立刻 return结束页 setVoipEndPagePath → 你们的 tab 首页实时日志 FilterMsg = voip
本期直接上干货代码(由AI辅助生成,实际场景使用请自行测试)

相关学习资料