225 lines
6.0 KiB
Vue
225 lines
6.0 KiB
Vue
<template>
|
|
<h1>AUDiO Tag mit dem volume: {{ safeVolume }}</h1>
|
|
<p>audioStatus {{ audioStatus }}</p>
|
|
<button @click="muteAudio">muteAudio</button>
|
|
<audio
|
|
v-if="src"
|
|
ref="audioElement"
|
|
:src="src"
|
|
loop="true"
|
|
:volume="safeVolume"
|
|
mediagroup="noiseGroup"
|
|
sinkId=""
|
|
@canplay="()=> $emit('canplay')"
|
|
/>
|
|
</template>
|
|
<script lang="ts">
|
|
import type { Logger } from 'pino'
|
|
import { defineComponent, ref, watch, computed } from 'vue'
|
|
import { ensureAudio, useAudioStore } from '~/stores/audio'
|
|
import { useUserStore } from '~/stores/user'
|
|
export default defineComponent({
|
|
name: 'AudioTag',
|
|
props: {
|
|
src: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
volume: {
|
|
type: [Number, String],
|
|
default: 0.23,
|
|
validator: (value:any) => {
|
|
const num = parseFloat(value)
|
|
return num >= -3.4 && num <= 3.4
|
|
}
|
|
},
|
|
play: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
},
|
|
emits: ['canplay'],
|
|
setup (props, { emit: $emit }) {
|
|
const logger = useNuxtApp().$logger as Logger
|
|
const audioElement = ref<HTMLAudioElement | null>(null)
|
|
const audioStatus = ref<'stopped' | 'playing' | 'paused'>('stopped')
|
|
|
|
const safeVolume = computed(() => {
|
|
const vol = parseFloat(props.volume.toString())
|
|
return Math.max(0, Math.min(1, isNaN(vol) ? 1 : vol))
|
|
})
|
|
|
|
const muteAudio = () => {
|
|
if (audioElement.value) {
|
|
audioElement.value.muted = !audioElement.value.muted
|
|
}
|
|
}
|
|
|
|
// kleine Hilfsfunktion für Volume-Fading
|
|
const fadeVolume = (audio: HTMLAudioElement, from: number, to: number, duration: number) => {
|
|
const steps = 20
|
|
const stepTime = duration / steps
|
|
let currentStep = 0
|
|
|
|
const fadeInterval = setInterval(() => {
|
|
currentStep++
|
|
const progress = currentStep / steps
|
|
const newVolume = from + (to - from) * progress
|
|
audio.volume = Math.max(0, Math.min(1, newVolume))
|
|
|
|
if (currentStep >= steps) {
|
|
clearInterval(fadeInterval)
|
|
audio.volume = to
|
|
}
|
|
}, stepTime)
|
|
}
|
|
|
|
const startPlaying = async () => {
|
|
const audioStore = useAudioStore()
|
|
await ensureAudio()
|
|
const sink = useUserStore().audioOutputDevice as MediaDeviceInfo
|
|
audioElement.value?.setSinkId(sink.deviceId)
|
|
if (audioElement.value && audioStore.isPlaying()) {
|
|
try {
|
|
const audio = audioElement.value
|
|
audio.currentTime = 0
|
|
audio.volume = 0
|
|
audio.muted = false
|
|
|
|
await audio.play().catch((error) => {
|
|
logger.warn('Error playing audio:', error)
|
|
})
|
|
|
|
fadeVolume(audio, 0, 0.3, 8000) // weiches Fade-In
|
|
audioStatus.value = 'playing'
|
|
} catch (error) {
|
|
logger.warn('Error starting audio:', error)
|
|
}
|
|
} else {
|
|
logger.info('Audio Playback has not started, audioStore is not playing')
|
|
}
|
|
}
|
|
|
|
const pausePlaying = () => {
|
|
if (audioElement.value && audioStatus.value === 'playing') {
|
|
try {
|
|
const audio = audioElement.value
|
|
const initialVolume = audio.volume
|
|
|
|
fadeVolume(audio, initialVolume, 0, 300)
|
|
|
|
setTimeout(() => {
|
|
audio.pause()
|
|
audioStatus.value = 'paused'
|
|
}, 350) // minimal länger als Fade
|
|
} catch (error) {
|
|
logger.warn('Error pausing audio:', error)
|
|
}
|
|
}
|
|
}
|
|
|
|
const stopAndResetPlaying = () => {
|
|
if (audioElement.value) {
|
|
try {
|
|
const audio = audioElement.value
|
|
const sink = useUserStore().audioOutputDevice as MediaDeviceInfo
|
|
audio.setSinkId(sink.deviceId)
|
|
audio.pause()
|
|
audio.currentTime = 0
|
|
audio.volume = safeVolume.value
|
|
audio.muted = true
|
|
audioStatus.value = 'stopped'
|
|
} catch (error) {
|
|
logger.warn('Error stopping audio:', error)
|
|
}
|
|
}
|
|
}
|
|
|
|
const resumePlaying = async () => {
|
|
const audioStore = useAudioStore()
|
|
await ensureAudio()
|
|
|
|
if (audioElement.value && audioStore.isPlaying() && audioStatus.value === 'paused') {
|
|
try {
|
|
const audio = audioElement.value
|
|
audio.muted = false
|
|
audio.volume = 0
|
|
|
|
await audio.play().catch((error) => {
|
|
logger.warn('Error resuming audio:', error)
|
|
})
|
|
|
|
fadeVolume(audio, 0, safeVolume.value, 500) // kürzeres Fade-In
|
|
audioStatus.value = 'playing'
|
|
} catch (error) {
|
|
logger.warn('Error resuming audio:', error)
|
|
}
|
|
} else {
|
|
logger.info('Audio Resume not possible: wrong audio state')
|
|
}
|
|
}
|
|
|
|
const stopPlaying = () => {
|
|
if (audioElement.value) {
|
|
try {
|
|
const audio = audioElement.value
|
|
const initialVolume = audio.volume
|
|
|
|
fadeVolume(audio, initialVolume, 0, 500)
|
|
|
|
setTimeout(() => {
|
|
audio.pause()
|
|
audio.currentTime = 0
|
|
audio.volume = safeVolume.value
|
|
audio.muted = true
|
|
audioStatus.value = 'stopped'
|
|
}, 550)
|
|
} catch (error) {
|
|
logger.warn('Error stopping and fading audio:', error)
|
|
}
|
|
}
|
|
}
|
|
|
|
watch(() => props.src, () => {
|
|
stopAndResetPlaying()
|
|
if (audioElement.value) {
|
|
audioElement.value.load()
|
|
}
|
|
})
|
|
|
|
watch(() => props.play, (newPlay) => {
|
|
setTimeout(() => {
|
|
if (newPlay === false) {
|
|
pausePlaying()
|
|
} else if (audioStatus.value === 'paused') {
|
|
resumePlaying()
|
|
} else {
|
|
startPlaying()
|
|
}
|
|
}, 500)
|
|
})
|
|
|
|
watch(() => safeVolume.value, (newVolume) => {
|
|
if (audioElement.value && audioStatus.value === 'playing') {
|
|
try {
|
|
audioElement.value.volume = newVolume
|
|
} catch (error) {
|
|
logger.warn('Error updating audio volume:', error)
|
|
}
|
|
}
|
|
})
|
|
|
|
return {
|
|
audioElement,
|
|
startPlaying,
|
|
pausePlaying,
|
|
stopPlaying,
|
|
resumePlaying,
|
|
safeVolume,
|
|
muteAudio,
|
|
audioStatus
|
|
}
|
|
}
|
|
})
|
|
</script>
|