收藏
回答

rtos设备和小程序voip插件通话 设备或者小程序拨打视频通话 拨打后挂断 重复几次就会出现下面的

问题类型 插件 AppID 插件版本号 AppID 操作系统 微信版本 基础库版本
Bug wxf830863afde621eb 2.4.7 wxa1ac988b47d44c63 iOS 1.06.2504010 3.10.0

<script setup>

import {


onLoad,

onShow,

onUnload

} from '@dcloudio/uni-app'

import {

callMsg,

devNote,

endAtelephoneCall

} from '../request/license.js'

import {

ref,

watch,

computed,

inject,

onMounted,

nextTick

} from 'vue'

import {

authorizationSn,

inquireAuthorizationSn,

checkAndRequestPermission

} from '../utils/wxVoip.js'

import {

useHooks

} from '../hooks/hooks.js'

// 创建基础菜单项

const baseMenuItems = ref([

  {

    title: '定位',

    pagePath: '/pages/location/index', 

    icon: '/static/tabBar/icon-tabbar-no-location.png',

    selectedIcon: '/static/tabBar/icon-tabbar-location.png',

    isTab: true

  },

  {

    title: '应用',

    pagePath: '/pages/home/index',

    icon: '/static/tabBar/icon-tabbar-no-index.png', 

    selectedIcon: '/static/tabBar/icon-tabbar-index.png',

    isTab: true

  },

  {

    title: '健康',

    pagePath: '/pages/health/index',

    icon: '/static/tabBar/icon-tabbar-no-healthy.png',

    selectedIcon: '/static/tabBar/icon-tabbar-healthy.png', 

    isTab: true

  },

  {

    title: '学校', // 新增学校菜单

    pagePath: '/pages/school/index',

    icon: '/static/tabBar/icon-tabbar-no-index.png',

    selectedIcon: '/static/tabBar/icon-tabbar-index.png',

    isTab: true

  },

  {

    title: '我的',

    pagePath: '/pages/my/index',

    icon: '/static/tabBar/icon-tabbar-no-my.png',

    selectedIcon: '/static/tabBar/icon-tabbar-my.png',

    isTab: true

  }

])


const currentIndex = ref(1)

const popup = ref('')

const userImg = inject('userImg')

const list = ref([])

const videocall = inject('videocall')

const pageData = ref({})

const plugin = ref(null)

let isInCall = false

let voipEventOff = null

const showToastFunc = ref(null)

const isTag = ref(null)

const roomNum = ref(null)

const data = ref({})

const showLoader = ref(false)

const props = defineProps({

textIndex: {

type: Number,

default: 0

},

status: {

type: Boolean,

default: false

},

userRole: {  // 新增用户身份属性

    type: String,

    default: 'parent' // 默认为家长身份

  }

})

// 初始化菜单列表

const initializeMenuList = (isTag) => {

  const menuItems = [...baseMenuItems.value]

  // 只有在非教师角色且是微信环境下才添加视频通话按钮

  // #ifdef MP-WEIXIN

  if (isTag && props.userRole !== 'teacher') {

    menuItems.splice(2, 0, {

      title: '',

      pagePath: '',

      icon: '/static/item/shipintonghua.png',

      selectedIcon: '/static/item/shipintonghua.png',

      isCenter: true

    })

  }

  // #endif

  list.value = menuItems

  // 确保初始化后保持正确的选中状态

  if (!props.status) {

    const pages = getCurrentPages()

    const currentPage = pages[pages.length - 1]

    if (currentPage) {

      const currentPath = '/' + currentPage.route

      const tabIndex = menuItems.findIndex(item => item.pagePath === currentPath)

      if (tabIndex !== -1) {

        currentIndex.value = tabIndex

      }

    }

  }

}


// 根据 status 过滤菜单项

const filteredList = computed(() => {

  if (!list.value.length) {

    return filterMenuByRole(baseMenuItems.value)

  }

  return filterMenuByRole(list.value)

})

const filterMenuByRole = (menuList) => {

  if(props.userRole === 'teacher') {

    // 教师只显示学校和我的

    return menuList.filter(item => 

      item.title === '学校' || 

      item.title === '我的'

    )

  }

  // 家长显示除学校外的所有菜单

  return menuList.filter(item => item.title !== '学校')

}


// 监听路由变化

watch(() => {

const pages = getCurrentPages();

return pages[pages.length - 1] && pages[pages.length - 1].route;

}, (newRoute) => {

if (newRoute) {

const currentPath = '/' + newRoute;

const tabIndex = list.value.findIndex(item => item.pagePath === currentPath);

if (tabIndex !== -1) {

currentIndex.value = tabIndex;

emit('tabChange', {

index: tabIndex,

type: 'tab'

});

}

}

}, {

immediate: true

});


// 监听 props.textIndex 的变化

watch(() => props.textIndex, (newValue) => {

if (newValue !== undefined && newValue !== null) {

currentIndex.value = newValue;

}

}, {

immediate: true

});

// 初始化时设置默认选中项

watch(() => props.status, (newValue) => {

if (newValue) {

const applicationIndex = list.value.findIndex(item => item.title === '应用')

if (applicationIndex !== -1) {

currentIndex.value = applicationIndex

}

}

}, {

immediate: true

})

watch(() => videocall.value, (newVal) => {

  if (newVal !== null && props.userRole !== 'teacher') {

    initializeMenuList(newVal)

  } else {

    initializeMenuList(false) // 教师角色强制传false

  }

}, {

  immediate: true

})

const isActive = (filteredIndex, item) => {

if (props.status) {

// 在特殊状态下,根据title判断

return item.title === list.value[currentIndex.value]?.title

}

// 在正常状态下,需要找到实际的索引

const actualIndex = list.value.findIndex(i => i.title === item.title)

return actualIndex === currentIndex.value

}


const emit = defineEmits(['tabChange'])


// 页面导航处理函数

function handleNavigation(item) {

if (item.isTab) {

const pages = getCurrentPages()

const currentPage = pages[pages.length - 1]

const currentPath = '/' + currentPage.route

if (currentPath === item.pagePath) {

return

}

const existingPageIndex = pages.findIndex(page => '/' + page.route === item.pagePath)


if (existingPageIndex !== -1) {

const delta = pages.length - 1 - existingPageIndex

uni.navigateBack({

delta: delta

})

} else {

if (pages.length >= 9) {

uni.redirectTo({

url: item.pagePath

})

} else {

uni.navigateTo({

url: item.pagePath

})

}

}

} else {

const pages = getCurrentPages()

if (pages.length >= 9) {

uni.redirectTo({

url: item.pagePath

})

} else {

uni.navigateTo({

url: item.pagePath

})

}

}

}



//获取用户是否授权

async function getDeviceVoIP(e) {

const res = await inquireAuthorizationSn()

const imei = getApp().globalData.imei

let status = null

if (res.list.length !== 0) {

const y = res.list.find(item => item.sn === imei)

if (!y) {

status = 0

} else {

status = y.status

}


} else {

status = 0

}

// 已授权 or 未授权

showLoader.value = false

if (status === 1) {

getCallMsg(e)

} else {

try {

const noteRes = await devNote()

let data = noteRes.data.data

const authRes = await authorizationSn(data)

getCallMsg(e)

} catch (error) {

if (error.errCode === 10021) {

checkAndRequestPermission()

}

}

}

}


// 通话类型

function btnType(e) {

popup.value.close();

showLoader.value = true

getDeviceVoIP(e)

}

// 结束通话

function endCall() {

console.log('开始结束通话流程, roomId:', roomNum.value);

// 立即重置状态,防止重复调用

const currentRoomId = roomNum.value;

isInCall = false;

roomNum.value = '';

// 取消事件监听

if (voipEventOff) {

try {

voipEventOff();

} catch (error) {

console.error('清理事件监听器失败:', error);

}

voipEventOff = null;

}

// 如果有房间ID,调用结束通话API

if (currentRoomId) {

endAtelephoneCall({

roomId: currentRoomId

}).then(res => {

console.log('结束通话API调用成功:', res);

}).catch(error => {

console.error('结束通话API调用失败:', error);

// API调用失败不影响本地状态重置

});

}

// 重置全局状态

if (getApp().globalData) {

getApp().globalData.isMiniappCalling = false;

}

// 隐藏加载状态

showLoader.value = false;

}

// 开始通话

async function getCallMsg(e) {

try {

console.log('开始发起通话, 类型:', e === 0 ? '语音' : '视频');

// 检查是否在通话中

if (isInCall) {

console.log('已在通话中,拒绝重复发起');

uni.showToast({

title: '已在通话中',

icon: 'none'

});

showLoader.value = false;

return;

}

// 确保状态清理

if (voipEventOff) {

try {

voipEventOff();

} catch (error) {

console.error('清理旧事件监听器失败:', error);

}

voipEventOff = null;

}


// 准备通话消息

const msg = {

callType: e === 0 ? 'SWV' : 'SW'

};


// 获取通话所需参数

const response = await callMsg(msg);

if(!response || response.data.code !== 0) {

console.error('获取通话参数失败:', response);

uni.showToast({

title: '通话参数异常',

icon: 'none'

});

showLoader.value = false;

return;

}

const number = response.data.data;

const {

channelName,

robotUID,

deviceUID,

token,

license,

appId,

openid,

deviceId,

modelId,

roomId,

screenHeight,

screenWidth

} = number;

// 验证必要参数

if (!channelName || !robotUID || !deviceUID || !token || !license || !appId || !openid || !deviceId || !modelId || !roomId) {

console.error('通话参数不完整:', number);

uni.showToast({

title: '通话参数不完整',

icon: 'none'

});

showLoader.value = false;

return;

}

roomNum.value = roomId;

console.log('通话参数获取成功, roomId:', roomId);

// 准备 Agora 配置

const agoraConfig = {

agoraVoIP: {

trigger: 'MiniApp',

version: 'v2',

transmission: {

agora: {

channelName,

robotUID,

deviceUID,

token,

license,

appId,

encryptionMode: 'NONE',

encryptionKey: '',

encryptionKdfSalt: '',

source: {

videoCodec: 'JPEG',

videoWidth: screenWidth,

videoHeight: screenHeight,

videoFrameRate: 10

},

sink: {

audioCodec: 'G722',

videoTranscode: true,

videoCodec: 'JPEG',

videoWidth: screenWidth,

videoHeight: screenHeight,

videoFrameRate: 10

}

},

wechat: {

openId: openid,

appId: 'wxa1ac988b47d44c63', // wxa1ac988b47d44c63   太阳树wx0ad0b6dda50c3cde

deviceId: deviceId,

modelId: modelId,

landscape: false,

payload: '',

subscribeVideoLength: (screenHeight - screenWidth) > 0 ? screenHeight : screenWidth,

roomType: e === 0 ? 'voice' : 'video',

listenerName: "m",

versionType: 2, //版本环境  2 体验 0 正式 1开发

query: "a=b",

callerCameraStatus: 0,

listenerCameraStatus: 0,

encodeVideoRotation: 1,

source: {

videoCodec: "H264",

videoWidth: screenWidth,

videoHeight: screenHeight,

videoFrameRate: 10,

// 240 284

},

sink: {

videoTranscode: true,

videoCodec: "H264",

// audiocodec:'G722',//string PCMU、PCMA、G722、AAC

videoWidth: screenWidth,

videoHeight: screenHeight,

// 240 320

videoFrameRate: 10

}

}

}

}

};


// 准备呼叫选项

const options = {

roomType: e === 0 ? 'voice' : 'video',

sn: deviceId,

modelId,

payload: JSON.stringify(agoraConfig),

isCloud: true,

encodeVideoRotation: 1,

};

// .replace(/"/g, '\\"')

// 清除旧的事件监听

if (voipEventOff) {

voipEventOff();

voipEventOff = null;

}


// 在使用插件前进行健壮性校验

if (!plugin.value) {

uni.showToast({

title: '通话组件未初始化',

icon: 'none'

});

return;

}

const requiredMethods = ['onVoipEvent','getPluginEnterOptions','getPluginOnloadOptions','setUIConfig','callDevice'];

const missing = requiredMethods.find(m => typeof plugin.value[m] !== 'function');

if (missing) {

uni.showToast({

title: '通话能力缺失:' + missing,

icon: 'none'

});

return;

}


// 设置新的事件监听

voipEventOff = plugin.value.onVoipEvent((event) => {

console.log('收到voip事件:', event.eventName, event);

switch (event.eventName) {

case 'endVoip':

case 'finishVoip':

case 'cancelVoip':

case 'hangUpVoip':

console.log('通话结束事件触发:', event.eventName);

endCall();

break;

case 'acceptVoip':

console.log('通话被接受');

break;

case 'rejectVoip':

console.log('通话被拒绝');

endCall();

break;

case 'errorVoip':

console.error('voip错误事件:', event);

endCall();

break;

default:

console.log('未处理的voip事件:', event.eventName);

break;

}

});

const query = plugin.value.getPluginEnterOptions()

const queryB = plugin.value.getPluginOnloadOptions()

// 小程序主动呼叫设备时,通知App.vue设置正确的配置

if (!queryB || Object.keys(queryB).length === 0) {

// 通知App.vue这是小程序主动呼叫

getApp().globalData.isMiniappCalling = true

// 立即设置配置,确保在通话发起前配置正确

getApp().setVoipUIConfig()

} else {

getApp().globalData.isMiniappCalling = false

getApp().setVoipUIConfig()

}

// 发起通话

try {

console.log('开始调用callDevice, options:', options);

const callResult = await plugin.value.callDevice(options);

console.log('callDevice调用结果:', callResult);

if (callResult) {

isInCall = true;

console.log('通话发起成功,准备跳转页面');

// 跳转到通话页面

const callPage = plugin.value.CALL_PAGE_PATH;

if (!callPage) {

console.error('通话页面未配置');

endCall();

uni.showToast({ title: '通话页面未配置', icon: 'none' });

return;

}

const pages = getCurrentPages();

const nav = pages.length >= 9 ? uni.redirectTo : uni.navigateTo;

nav({

url: callPage,

success: () => {

console.log('成功跳转到通话页面');

showLoader.value = false;

},

fail: (error) => {

console.error('跳转通话页面失败:', error);

endCall();

uni.showToast({ title: '打开通话页面失败', icon: 'none' });

}

});

} else {

throw new Error('呼叫失败:未收到有效响应');

}


} catch (callError) {

console.error('callDevice调用失败:', callError);

endCall();

uni.showToast({

title: '呼叫失败',

icon: 'error'

});

}


} catch (error) {

console.error('发起通话过程中发生错误:', error);

endCall();

uni.showToast({

title: '发起通话失败',

icon: 'error'

});

} finally {

showLoader.value = false;

}

}

// 强制结束通话

function forceEndCall() {

console.log('强制结束通话');

if (plugin.value && typeof plugin.value.forceHangUpVoip === 'function') {

try {

plugin.value.forceHangUpVoip();

} catch (error) {

console.error('强制结束通话失败:', error);

}

}

// 确保状态被重置

endCall();

}

function itemBtn(filteredIndex, item) {

// 视频通话按钮的处理

if (!item.pagePath) {

if (!showToastFunc.value(uni.getStorageSync('isOnline'))) return;

popup.value.open();

return;

}


// 找到在完整列表中的实际索引

const actualIndex = list.value.findIndex(i => i.title === item.title);

if (item.isCenter) {

emit('tabChange', {

index: actualIndex,

type: 'center'

});

return;

}

// 避免重复点击当前项

if (currentIndex.value === actualIndex) return;


// 更新当前索引

currentIndex.value = actualIndex;


emit('tabChange', {

index: actualIndex,

type: 'tab'

});

handleNavigation(item);

}

// UI配置已统一在App.vue中处理,这里不再需要定义

// 初始化插件

function initPlugin() {

if (!plugin.value) {

plugin.value = getApp().globalData.plugin

}

// UI配置统一在App.vue中处理,这里只初始化插件

}

// 重置所有voip相关状态

function resetVoipState() {

console.log('重置voip状态');

isInCall = false;

roomNum.value = '';

showLoader.value = false;

if (voipEventOff) {

try {

voipEventOff();

} catch (error) {

console.error('重置时清理事件监听器失败:', error);

}

voipEventOff = null;

}

if (getApp().globalData) {

getApp().globalData.isMiniappCalling = false;

}

}

// 更新当前标签页索引

function updateCurrentIndex() {

if (!props.status) {

const pages = getCurrentPages()

const currentPage = pages[pages.length - 1]

const currentPath = '/' + currentPage.route

const tabIndex = list.value.findIndex(item => item.pagePath === currentPath)

if (tabIndex !== -1) {

currentIndex.value = tabIndex

}

}

}

// 3. 修改 watch 相关逻辑,确保正确的导航状态

watch(() => props.status, (newValue) => {

if (newValue) {

const applicationIndex = list.value.findIndex(item => item.title === '应用');

if (applicationIndex !== -1) {

currentIndex.value = applicationIndex;

emit('tabChange', {

index: applicationIndex,

type: 'tab',

title: '应用'

});

}

}

}, {

immediate: true

});

// 监听路由变化,确保选中状态正确

watch(() => {

const pages = getCurrentPages()

return pages[pages.length - 1] && pages[pages.length - 1].route

}, (newRoute) => {

if (!props.status && newRoute) {

const currentPath = '/' + newRoute

const tabIndex = list.value.findIndex(item => item.pagePath === currentPath)

if (tabIndex !== -1) {

currentIndex.value = tabIndex

}

}

}, {

immediate: true

})

watch(() => props.status, (newValue) => {

if (newValue) {

const applicationIndex = list.value.findIndex(item => item.title === '应用')

if (applicationIndex !== -1) {

currentIndex.value = applicationIndex

// 只发送切换事件,不跳转页面

emit('tabChange', {

index: applicationIndex,

type: 'tab',

title: '应用'

})

}

}

}, {

immediate: true

})


onUnload(() => {

// #ifdef MP-WEIXIN

console.log('组件卸载,清理voip相关状态');

if (voipEventOff) {

try {

voipEventOff();

} catch (error) {

console.error('卸载时清理事件监听器失败:', error);

}

voipEventOff = null;

}

isInCall = false;

roomNum.value = '';

showLoader.value = false;

// #endif 

})

onMounted(() => {

initializeMenuList(videocall.value)

})

onLoad(() => {

// 初始化其他功能

showToastFunc.value = useHooks().showToastFunc

// 设置初始索引

if (!props.status && props.textIndex !== undefined) {

currentIndex.value = props.textIndex

} else {

currentIndex.value = 1 // 确保默认是1

}

})



onShow(() => {

// 初始化插件

// #ifdef MP-WEIXIN

initPlugin()

// #endif 

// UI配置统一在App.vue中处理

// 更新当前标签页索引

updateCurrentIndex()

})


let tabRefs = [] // 普通数组

const sliderLeft = ref(0)

const sliderWidth = ref(0)


// 计算滑块样式

const sliderStyle = computed(() => {

  return {

    left: sliderLeft.value + 'px',

    width: sliderWidth.value + 'px',

    transition: 'left 0.3s cubic-bezier(.4,0,.2,1),width 0.3s cubic-bezier(.4,0,.2,1)'

  }

})


function updateSlider() {

  nextTick(() => {

    const tabEls = tabRefs

    const activeIdx = filteredList.value.findIndex((item, idx) => isActive(idx, item))

    if (tabEls && tabEls[activeIdx]) {

      const el = tabEls[activeIdx]

      if (el && el.offsetLeft !== undefined) {

        sliderLeft.value = el.offsetLeft

        sliderWidth.value = el.offsetWidth

      }

    }

  })

}


watch(filteredList, () => {

  tabRefs = []

  updateSlider()

}, { immediate: true })

watch(() => currentIndex.value, updateSlider)


onMounted(() => {

  tabRefs = []

  updateSlider()

})

</script>

回答关注问题邀请回答
收藏
登录 后发表内容