- rtos设备和小程序voip插件通话 设备或者小程序拨打视频通话 拨打后挂断 重复几次就会出现下面的
[图片][图片] <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>
09-18 - 小程序和设备进行音视频通话功能 微信需要提交录制的视频审核,请问下审核的视频提交入口在哪里?
小程序和设备进行音视频通话功能 微信那边需要提交小程序和设备之间的通话过程的视频,请问下审核的视频提交入口在哪里? 小程序版本提交审核的地方只能上传一个视频啊 我看你们的文档说的支持5个视频的
06-25 - 真机调用微信公众号H5扫-扫功能 失败报错?
微信公众号H5在开发者工具中调用扫一扫是可以的,如图 [图片]但是在手机上(ios系统)就报 scanQRCode:thepermission value is offline verifying 请问这个是咋回事呢
2023-05-16 - websokcet本地正常,真机里无法创建sokect 报invalid http status?
[图片]找个是真机环境 [图片]本地环境可以正常使用
2023-04-06