add audio areas
This commit is contained in:
parent
7c0d83b8ed
commit
631ba25f9f
2 changed files with 111 additions and 3 deletions
63
app/src/components/AudioArea.vue
Normal file
63
app/src/components/AudioArea.vue
Normal file
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<audio ref="audio"
|
||||
:src="definition.src"
|
||||
loop/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent, ref, watch} from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "AudioArea",
|
||||
props: {
|
||||
definition: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
bbox: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const audio = ref<HTMLAudioElement | null>(null);
|
||||
|
||||
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) {
|
||||
audio.value!.play();
|
||||
audio.value!.volume = (props.definition.radius - distance) / props.definition.radius;
|
||||
} else {
|
||||
audio.value!.pause();
|
||||
}
|
||||
};
|
||||
watch(props.bbox, onBBoxChange, {deep: true});
|
||||
|
||||
return {
|
||||
audio
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export interface AudioAreaDef {
|
||||
cx: number,
|
||||
cy: number,
|
||||
radius: number,
|
||||
src: string
|
||||
}
|
||||
|
||||
export interface BoundingBox {
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
|
@ -3,17 +3,19 @@
|
|||
<div class="video-scrolls">
|
||||
<VideoScroll v-for="scroll in scrolls" :definition="scroll"/>
|
||||
</div>
|
||||
<AudioArea v-for="audio in audioAreas" :definition="audio" :bbox="bbox"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent, onMounted, ref} from "vue";
|
||||
import {defineComponent, onMounted, reactive, ref} from "vue";
|
||||
import createPanZoom, {PanZoom} from "panzoom";
|
||||
import VideoScroll, {VideoScrollDef, VideoScrollDirection} from "@/components/VideoScroll.vue";
|
||||
import AudioArea, {AudioAreaDef} from "@/components/AudioArea.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SVGContent",
|
||||
components: {VideoScroll},
|
||||
components: {AudioArea, VideoScroll},
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
|
@ -26,6 +28,13 @@ export default defineComponent({
|
|||
const anchors = ref<SVGRectElement[]>([]);
|
||||
const scrolls = ref<VideoScrollDef[]>([]);
|
||||
const panToAnchor = ref();
|
||||
const audioAreas = ref<AudioAreaDef[]>([]);
|
||||
const bbox = reactive({
|
||||
x: ref(0),
|
||||
y: ref(0),
|
||||
w: ref(0),
|
||||
h: ref(0)
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const element = root.value as unknown as HTMLDivElement;
|
||||
|
@ -56,6 +65,16 @@ export default defineComponent({
|
|||
});
|
||||
panzoom.value = pz;
|
||||
|
||||
pz.on("transform", function (_) {
|
||||
const transform = pz.getTransform();
|
||||
const currentRatio = svg.clientWidth * transform.scale / svg.viewBox.baseVal.width;
|
||||
|
||||
bbox.x = transform.x / currentRatio * -1;
|
||||
bbox.y = transform.y / currentRatio * -1;
|
||||
bbox.w = window.innerWidth / currentRatio;
|
||||
bbox.h = window.innerHeight / currentRatio;
|
||||
});
|
||||
|
||||
// Edge scrolling
|
||||
const MOVE_EDGE = 75;
|
||||
const MAX_SPEED = 20;
|
||||
|
@ -127,6 +146,9 @@ export default defineComponent({
|
|||
|
||||
// Videoscrolls
|
||||
scrolls.value = await processScrolls(svg);
|
||||
|
||||
// Audio areas
|
||||
audioAreas.value = processAudio(svg);
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -134,7 +156,9 @@ export default defineComponent({
|
|||
panzoom,
|
||||
anchors,
|
||||
panToAnchor,
|
||||
scrolls
|
||||
scrolls,
|
||||
audioAreas,
|
||||
bbox
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -184,10 +208,31 @@ async function processScrolls(svg: XMLDocument): Promise<VideoScrollDef[]> {
|
|||
);
|
||||
}
|
||||
|
||||
function processAudio(svg: XMLDocument): AudioAreaDef[] {
|
||||
const ratio = (svg as any).clientWidth / (svg as any).viewBox.baseVal.width;
|
||||
|
||||
return Array.from(svg.getElementsByTagName("circle"))
|
||||
.filter((el) => Array.from(el.children).some((el) => el.tagName == "desc"))
|
||||
.map((el) => {
|
||||
el.classList.add("svgcontent_audio");
|
||||
|
||||
const descNode = el.children[0]; // to fix
|
||||
const audioSrc = descNode.textContent!.trim();
|
||||
|
||||
return {
|
||||
cx: el.cx.baseVal.value,
|
||||
cy: el.cy.baseVal.value,
|
||||
radius: el.r.baseVal.value,
|
||||
src: `content/${audioSrc}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style>
|
||||
/*.svgcontent_audio*/
|
||||
.svgcontent_anchor {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue