feat(im): 初始化消息转发 v0.4:第四次评审(语音播放器资源释放打磨)

agent 三轮复审后的质量打磨,无功能变更。

- useVoicePlayer.stop:audio.removeAttribute('src') + audio.load() 替代 audio.src = '';不会触发空 src 加载的 error 事件,也能让浏览器立即释放底层 decoder buffer
- useVoicePlayer.play:同 key 再点的 stop() 改 stop(key),意图自解释(我想停的就是我自己)
- useVoicePlayer 移除未消费的 currentKey 暴露;调用方都走 isPlaying(key) 派生
- home/index.vue onUnmounted 追加 voicePlayer.stop():模块级单例 audio 不会随视图卸载自动停,补主壳兜底
im
YunaiV 2026-05-07 21:46:33 +08:00
parent 0b07091e79
commit cc93b8a742
2 changed files with 12 additions and 7 deletions

View File

@ -1,4 +1,4 @@
import { computed, ref } from 'vue'
import { ref } from 'vue'
/**
*
@ -37,7 +37,10 @@ function stop(key?: VoiceKey) {
return
}
task.audio.pause()
task.audio.src = ''
// removeAttribute('src') + load() 是 W3C 推荐的释放姿势:不会触发空 src 加载导致的 error 事件,
// 也能让浏览器立即释放底层 decoder buffer比 audio.src = '' 更干净
task.audio.removeAttribute('src')
task.audio.load()
currentTask.value = null
}
@ -52,7 +55,7 @@ function play(key: VoiceKey, url: string) {
return
}
if (currentTask.value?.key === key) {
stop()
stop(key)
return
}
stop()
@ -71,11 +74,9 @@ function play(key: VoiceKey, url: string) {
}
export function useVoicePlayer() {
/** 当前播放的 key给气泡 / 调试用 */
const currentKey = computed(() => currentTask.value?.key ?? null)
/** 指定 key 是否正在播放 */
function isPlaying(key: VoiceKey): boolean {
return currentTask.value?.key === key
}
return { currentKey, isPlaying, play, stop }
return { isPlaying, play, stop }
}

View File

@ -37,6 +37,7 @@ import { useDraftStore } from './store/draftStore'
import { useFaceStore } from './store/faceStore'
import { useMessagePuller } from './composables/useMessagePuller'
import { useMessageSender } from './composables/useMessageSender'
import { useVoicePlayer } from './composables/useVoicePlayer'
import { ImConversationType } from '../utils/constants'
import { StorageKeys } from '../utils/storage'
import type { Conversation } from './types'
@ -56,6 +57,7 @@ const draftStore = useDraftStore()
const faceStore = useFaceStore()
const { pullOnce } = useMessagePuller()
const { readActive, syncPrivateReadStatus } = useMessageSender()
const voicePlayer = useVoicePlayer()
/** 初始化:先吃本地缓存让首屏立即渲染,再远端刷新最新数据,最后建实时通信拉离线消息 */
onMounted(async () => {
@ -135,11 +137,13 @@ function onBeforeUnload() {
}
window.addEventListener('beforeunload', onBeforeUnload)
/** 离开 IM 主壳:主动断 WebSocketdisconnect 内部已清掉 onclose 防自动重连)+ flush 草稿 + 表情缓存 reset + 解绑 unload */
/** 离开 IM 主壳:主动断 WebSocketdisconnect 内部已清掉 onclose 防自动重连)+ flush 草稿 + 表情缓存 reset + 解绑 unload + 停语音 */
onUnmounted(() => {
webSocketStore.disconnect()
draftStore.flushPersist()
faceStore.reset()
// audio
voicePlayer.stop()
window.removeEventListener('beforeunload', onBeforeUnload)
})