105 lines
3.1 KiB
Vue
105 lines
3.1 KiB
Vue
<template>
|
|
<audio ref="audio" :src="audioSrc" loop preload="auto" />
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, PropType, ref, watch } from "vue";
|
|
import { BoundingBox } from "@/components/SVGContent.vue";
|
|
import { queueAudioForLoading } from "@/services/AudioLoader";
|
|
|
|
export default defineComponent({
|
|
name: "AudioArea",
|
|
props: {
|
|
definition: {
|
|
type: Object as PropType<AudioAreaDef>,
|
|
required: true,
|
|
},
|
|
bbox: {
|
|
type: Object as PropType<BoundingBox>,
|
|
required: true,
|
|
},
|
|
},
|
|
setup(props) {
|
|
const audio = ref<HTMLAudioElement | null>(null);
|
|
const audioSrc = ref<string>(""); // Ref to hold audio source after preloading
|
|
const isPreloaded = ref<boolean>(false);
|
|
|
|
console.debug(`[AUDIOAREA] Initializing ${props.definition.src}...`);
|
|
console.debug(props.definition);
|
|
|
|
// Preload the audio file completely to avoid keeping connections open
|
|
// Use the global audio loading queue to throttle concurrent loads
|
|
const preloadAudio = (src: string) => {
|
|
console.debug(`[AUDIOAREA] Queueing audio for preload: ${src}`);
|
|
|
|
// Queue the audio for loading through our global service
|
|
queueAudioForLoading(src)
|
|
.then((blobUrl) => {
|
|
// Use blob URL to avoid keeping connections open
|
|
audioSrc.value = blobUrl;
|
|
isPreloaded.value = true;
|
|
console.debug(`[AUDIOAREA] Successfully preloaded audio: ${src}`);
|
|
})
|
|
.catch((error) => {
|
|
console.error(`[AUDIOAREA] Error preloading audio: ${error}`);
|
|
// Fall back to original source if preloading fails
|
|
audioSrc.value = src;
|
|
});
|
|
};
|
|
|
|
// Start preloading when component is created
|
|
preloadAudio(props.definition.src);
|
|
|
|
const MIN_SCALE = 0.02;
|
|
const MIN_VOLUME_MULTIPLIER = 0.33;
|
|
const vol_x = (1 - MIN_VOLUME_MULTIPLIER) / (1 - MIN_SCALE);
|
|
const vol_b = 1 - vol_x;
|
|
|
|
const onBBoxChange = () => {
|
|
const x = props.bbox.x + props.bbox.w / 2;
|
|
const y = props.bbox.y + props.bbox.h / 2;
|
|
const distance = Math.sqrt(
|
|
Math.pow(x - props.definition.cx, 2) +
|
|
Math.pow(y - props.definition.cy, 2)
|
|
);
|
|
|
|
if (distance < props.definition.radius) {
|
|
if (audio.value!.paused) {
|
|
console.debug(
|
|
`[AUDIOAREA] Entered audio area "${props.definition.src}", starting playback...`
|
|
);
|
|
audio.value!.play();
|
|
}
|
|
const volume =
|
|
(props.definition.radius - distance) / props.definition.radius;
|
|
audio.value!.volume =
|
|
volume * (props.bbox.z < 1 ? props.bbox.z * vol_x + vol_b : 1);
|
|
} else {
|
|
if (!audio.value!.paused) {
|
|
console.debug(
|
|
`[AUDIOAREA] Left audio area "${props.definition.src}", pausing playback...`
|
|
);
|
|
audio.value!.pause();
|
|
}
|
|
}
|
|
};
|
|
watch(props.bbox, onBBoxChange, { deep: true });
|
|
|
|
return {
|
|
audio,
|
|
audioSrc,
|
|
};
|
|
},
|
|
});
|
|
|
|
export interface AudioAreaDef {
|
|
id: string;
|
|
cx: number;
|
|
cy: number;
|
|
radius: number;
|
|
src: string;
|
|
}
|
|
</script>
|
|
|
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
<style scoped></style>
|