diff --git a/src/views/im/home/composables/useVoicePlayer.ts b/src/views/im/home/composables/useVoicePlayer.ts index 7892543f3..a944639c1 100644 --- a/src/views/im/home/composables/useVoicePlayer.ts +++ b/src/views/im/home/composables/useVoicePlayer.ts @@ -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 } } diff --git a/src/views/im/home/index.vue b/src/views/im/home/index.vue index fa04284ff..b342a8798 100644 --- a/src/views/im/home/index.vue +++ b/src/views/im/home/index.vue @@ -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 主壳:主动断 WebSocket(disconnect 内部已清掉 onclose 防自动重连)+ flush 草稿 + 表情缓存 reset + 解绑 unload */ +/** 离开 IM 主壳:主动断 WebSocket(disconnect 内部已清掉 onclose 防自动重连)+ flush 草稿 + 表情缓存 reset + 解绑 unload + 停语音 */ onUnmounted(() => { webSocketStore.disconnect() draftStore.flushPersist() faceStore.reset() + // 模块级单例 audio 不会随视图卸载自动停,主动停掉避免切路由后语音继续响 + voicePlayer.stop() window.removeEventListener('beforeunload', onBeforeUnload) })