feat(im): 振铃超时 Job 单人粒度标 NO_ANSWER + 独立 NO_ANSWER 信令推送

 feat(im): 处理 RTC_CALL(NO_ANSWER) 信令;私聊气泡显示「未接听」
im
YunaiV 2026-05-18 09:45:32 +08:00
parent f58d1d88c8
commit 8329a6a885
3 changed files with 48 additions and 7 deletions

View File

@ -74,9 +74,9 @@ export const leaveCall = (room: string) => {
return request.post<boolean>({ url: '/im/rtc/leave', params: { room } })
}
// 重新签发 Token客户端重连或 Token 过期续期
export const refreshCallToken = (room: string) => {
return request.get<ImRtcCallRespVO>({ url: '/im/rtc/refresh-token', params: { room } })
// 振铃超时检查RUNNING 端 timer 兜底,触发后端立即扫描该 room 的超时 INVITING接口静默
export const noAnswerCallCheck = (room: string) => {
return request.post<boolean>({ url: '/im/rtc/no-answer-call-check', params: { room } })
}
// 查询当前进行中的通话;目前仅群聊场景(胶囊条),返回 null 表示无活跃通话

View File

@ -56,6 +56,7 @@
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import { useIntervalFn } from '@vueuse/core'
import { useMessage } from '@/hooks/web/useMessage'
import { useRtcStore } from '../../store/rtcStore'
import { useLiveKitRoom } from '../../composables/useLiveKitRoom'
@ -64,13 +65,15 @@ import {
rejectCall,
acceptCall,
leaveCall,
inviteCall
inviteCall,
noAnswerCallCheck
} from '@/api/im/home/rtc'
import {
ImRtcCallMediaType,
ImRtcCallStage,
ImConversationType
} from '@/views/im/utils/constants'
import { RTC_NO_ANSWER_CALL_CHECK_INTERVAL_MS } from '@/views/im/utils/config'
import { getCurrentUserId } from '@/views/im/utils/storage'
import { getSenderAvatar, getSenderDisplayName } from '@/views/im/utils/user'
import { Track } from 'livekit-client'
@ -363,9 +366,39 @@ async function handleHangup() {
/** LiveKit Room 异常断开;多见于网络中断 */
function handlePeerDisconnected() {
if (!rtcStore.isActive) return
message.warning('通话已断开')
rtcStore.reset()
if (!rtcStore.isActive) {
return
}
// RTC_CALL_END WebSocket / endSession RTC_CALL_END
// "" / "" reset toast
setTimeout(() => {
if (!rtcStore.isActive) {
return
}
message.warning('通话已断开')
rtcStore.reset()
}, 100)
}
// ==================== ====================
/** 通话存活期间INVITING / INCOMING / RUNNING周期性触发后端扫该 room 的超时 INVITING保持 timer 是为了 inviteCall 追加新人后也能覆盖;阈值由后端配置决定,前端只负责 trigger */
const { resume: resumeNoAnswerTimer, pause: pauseNoAnswerTimer } = useIntervalFn(
triggerNoAnswerCallCheck, RTC_NO_ANSWER_CALL_CHECK_INTERVAL_MS, { immediate: false }
)
watch(
() => rtcStore.isActive,
(active) => (active ? resumeNoAnswerTimer() : pauseNoAnswerTimer()),
{ immediate: true }
)
/** 本地仍有 pending 才调INVITING / RUNNING 取 call、INCOMING 取 incomingPayload接口静默错误 fire-and-forget */
function triggerNoAnswerCallCheck() {
const source = rtcStore.call ?? rtcStore.incomingPayload
if (!source?.room || !source.inviteeIds?.length) {
return
}
noAnswerCallCheck(source.room).catch(() => undefined)
}
// ==================== ====================

View File

@ -75,3 +75,11 @@ export const MESSAGE_MERGE_PREVIEW_LINES = 3
/** 最近转发会话 key 列表的最大保留数量(对齐微信 PC 横向头像区可见容量) */
export const CONVERSATION_RECENT_FORWARD_MAX = 12
// ==================== 前端独有RTC 通话兜底 ====================
/**
* timer noAnswerCallCheck
* yudao.im.rtc.invite-timeout-minutes
*/
export const RTC_NO_ANSWER_CALL_CHECK_INTERVAL_MS = 60 * 1000