You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
apply-assistant-v3/root/NEUIKit/pages/Chat/message/message-audio.vue

223 lines
4.6 KiB
Vue

<template>
<div
:class="!msg.isSelf || mode === 'audio-in' ? 'audio-in' : 'audio-out'"
:style="{ width: audioContainerWidth + 'px' }"
@tap="onPlayAudio"
>
<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 '../../../utils/transformVue'
import Icon from '../../../components/Icon.vue'
import { events } from '../../../utils/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'
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(() => {
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 onPlayAudio = () => {
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()
}
watch(
() => props.broadcastNewAudioSrc,
(newSrc) => {
if (newSrc !== props.msg?.attachment?.url && isAudioPlaying.value) {
stopAudio()
isAudioPlaying.value = false
}
}
)
const toggleAudioPlayState = () => {
if (!isAudioPlaying.value) {
playAudio()
} else {
stopAudio()
}
}
// 初始化音频实例
function initAudioSrc() {
const audioContext = getAudio()
if (!audioContext) {
return
}
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()
if (!audioContext) {
return
}
audioContext.play()
}
function onAudioPlay() {
isAudioPlaying.value = true
playAudioAnimation()
}
function onAudioStop() {
animationFlag.value = false
isAudioPlaying.value = false
}
function onAudioEnded() {
animationFlag.value = false
isAudioPlaying.value = false
}
function onAudioError() {
animationFlag.value = false
console.warn('audio played error')
}
const getAudio = () => {
return audioMap.get('audio')
}
const stopAudio = () => {
const audioContext = getAudio()
if (!audioContext) {
return
}
try {
audioContext.stop()
} catch {
// console.log('stop audio error')
}
}
// 播放音频动画
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>