uniapp-app/pages_chat/chat/message/message-audio.vue
2025-09-16 16:19:29 +08:00

243 lines
5.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div
:class="!msg.isSelf || mode === 'audio-in' ? 'audio-in' : 'audio-out'"
:style="{ width: audioContainerWidth + 'px' }"
@tap="handlePlayAudio"
>
<div class="audio-dur">{{ duration }}s</div>
<div class="audio-icon-wrapper">
<Icon :size="24" :key="audioIconType" :type="audioIconType" />
</div>
</div>
</template>
<script lang="ts" setup>
/** 音频消息组件 */
import { ref, onUnmounted, computed, watch, withDefaults } from 'vue'
import Icon from '@/components/Icon.vue'
import { events } from '@/utils/im/constants'
import { V2NIMMessageForUI } from '@xkit-yx/im-store-v2/dist/types/types'
import { V2NIMMessageAudioAttachment } from 'nim-web-sdk-ng/dist/v2/NIM_UNIAPP_SDK/V2NIMMessageService'
import { onHide, onUnload } from '@dcloudio/uni-app'
import { isHarmonyOs } from '@/utils/im/index'
const props = withDefaults(
defineProps<{
msg: V2NIMMessageForUI
mode?: 'audio-in' | 'audio-out'
broadcastNewAudioSrc?: string
}>(),
{}
)
const audioIconType = ref('icon-yuyin3')
const animationFlag = ref(false)
const isAudioPlaying = ref<boolean>(false)
const audioMap = new Map<string, any>()
const emits = defineEmits(['getGlobalAudioContext'])
/** 格式化音频时长 */
const formatDuration = (duration: number) => {
return Math.round(duration / 1000) || 1
}
/** 音频消息宽度 */
const audioContainerWidth = computed(() => {
//@ts-ignore
const duration = formatDuration(props.msg.attachment?.duration)
const maxWidth = 180
return 50 + 8 * (duration - 1) > maxWidth ? maxWidth : 50 + 8 * (duration - 1)
})
/** 音频时长 */
const duration = computed(() => {
return formatDuration(
(props.msg.attachment as V2NIMMessageAudioAttachment)?.duration
)
})
/**播放音频 */
const handlePlayAudio = () => {
//@ts-ignore
uni.$emit(events.AUDIO_URL_CHANGE, props.msg?.attachment?.url)
const audioContext = getAudio()
if (!audioContext) {
const globalAudioContext = uni.createInnerAudioContext()
audioMap.set('audio', globalAudioContext)
initAudioSrc()
}
toggleAudioPlayState()
}
/** 监听当前的音频播放 是不是当前点击url如果不是就停止 */
watch(
() => props.broadcastNewAudioSrc,
(newSrc: string) => {
//@ts-ignore
if (newSrc !== props.msg?.attachment?.url && isAudioPlaying.value) {
stopAudio()
isAudioPlaying.value = false
}
}
)
/** 播放 */
const toggleAudioPlayState = () => {
if (!isAudioPlaying.value) {
playAudio()
} else {
stopAudio()
}
}
/**停止播放音频 */
const stopAudio = () => {
const audioContext = getAudio()
if (!audioContext) {
return
}
try {
audioContext.stop()
isAudioPlaying.value = false
} catch {
console.log('stop audio error')
}
}
/** 初始化音频实例 */
function initAudioSrc() {
const audioContext = getAudio()
if (!audioContext) {
return
}
//@ts-ignore
audioContext.src = props.msg?.attachment?.url
isAudioPlaying.value = false
audioContext.onPlay(onAudioPlay)
audioContext.onStop(onAudioStop)
audioContext.onEnded(onAudioEnded)
audioContext.onError(onAudioError)
}
/**播放音频 */
function playAudio() {
const audioContext = getAudio()
console.log('audio played', audioContext)
if (!audioContext) {
return
}
try {
audioContext.play()
} catch (error) {
console.log('audio played error', error)
}
}
/** 音频开始播放 */
function onAudioPlay() {
isAudioPlaying.value = true
playAudioAnimation()
}
/** 音频停止播放 */
function onAudioStop() {
animationFlag.value = false
isAudioPlaying.value = false
if (isHarmonyOs) {
const audioContext = getAudio()
audioContext?.destroy?.()
audioMap.delete('audio')
}
}
/** 音频播放结束 */
function onAudioEnded() {
animationFlag.value = false
isAudioPlaying.value = false
}
/** 音频播放失败 */
function onAudioError(error: any) {
animationFlag.value = false
console.warn('audio played error', error)
}
/**获取音频实例 */
const getAudio = () => {
return audioMap.get('audio')
}
/** 播放音频动画 */
const playAudioAnimation = () => {
try {
animationFlag.value = true
let audioIcons = ['icon-yuyin1', 'icon-yuyin2', 'icon-yuyin3']
const handler = () => {
const icon = audioIcons.shift()
if (icon) {
audioIconType.value = icon
if (!audioIcons.length && animationFlag.value) {
audioIcons = ['icon-yuyin1', 'icon-yuyin2', 'icon-yuyin3']
}
if (audioIcons.length) {
setTimeout(handler, 300)
}
}
}
handler()
} catch (error) {
console.log('playAudioAnimation error', error)
}
}
/** 离开当前页面时,停止播放音频 */
const stopAudioOnHide = () => {
const audioContext = getAudio()
if (isAudioPlaying.value) {
stopAudio()
}
audioContext?.destroy?.()
animationFlag.value = false
audioMap.delete('audio')
}
onUnmounted(() => {
stopAudioOnHide()
})
onHide(() => {
stopAudioOnHide()
})
onUnload(() => {
stopAudioOnHide()
})
</script>
<style scoped lang="scss">
.audio-dur {
height: 24px;
line-height: 24px;
}
.audio-in,
.audio-out {
width: 50px;
display: flex;
cursor: pointer;
justify-content: flex-end;
align-items: center;
}
.audio-in {
flex-direction: row-reverse;
.audio-icon-wrapper {
transform: rotate(180deg);
}
}
.audio-icon-wrapper {
height: 24px;
display: flex;
align-items: center;
}
</style>