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">
|
<div class="video-scrolls">
|
||||||
<VideoScroll v-for="scroll in scrolls" :definition="scroll"/>
|
<VideoScroll v-for="scroll in scrolls" :definition="scroll"/>
|
||||||
</div>
|
</div>
|
||||||
|
<AudioArea v-for="audio in audioAreas" :definition="audio" :bbox="bbox"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {defineComponent, onMounted, ref} from "vue";
|
import {defineComponent, onMounted, reactive, ref} from "vue";
|
||||||
import createPanZoom, {PanZoom} from "panzoom";
|
import createPanZoom, {PanZoom} from "panzoom";
|
||||||
import VideoScroll, {VideoScrollDef, VideoScrollDirection} from "@/components/VideoScroll.vue";
|
import VideoScroll, {VideoScrollDef, VideoScrollDirection} from "@/components/VideoScroll.vue";
|
||||||
|
import AudioArea, {AudioAreaDef} from "@/components/AudioArea.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "SVGContent",
|
name: "SVGContent",
|
||||||
components: {VideoScroll},
|
components: {AudioArea, VideoScroll},
|
||||||
props: {
|
props: {
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -26,6 +28,13 @@ export default defineComponent({
|
||||||
const anchors = ref<SVGRectElement[]>([]);
|
const anchors = ref<SVGRectElement[]>([]);
|
||||||
const scrolls = ref<VideoScrollDef[]>([]);
|
const scrolls = ref<VideoScrollDef[]>([]);
|
||||||
const panToAnchor = ref();
|
const panToAnchor = ref();
|
||||||
|
const audioAreas = ref<AudioAreaDef[]>([]);
|
||||||
|
const bbox = reactive({
|
||||||
|
x: ref(0),
|
||||||
|
y: ref(0),
|
||||||
|
w: ref(0),
|
||||||
|
h: ref(0)
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const element = root.value as unknown as HTMLDivElement;
|
const element = root.value as unknown as HTMLDivElement;
|
||||||
|
@ -56,6 +65,16 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
panzoom.value = pz;
|
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
|
// Edge scrolling
|
||||||
const MOVE_EDGE = 75;
|
const MOVE_EDGE = 75;
|
||||||
const MAX_SPEED = 20;
|
const MAX_SPEED = 20;
|
||||||
|
@ -127,6 +146,9 @@ export default defineComponent({
|
||||||
|
|
||||||
// Videoscrolls
|
// Videoscrolls
|
||||||
scrolls.value = await processScrolls(svg);
|
scrolls.value = await processScrolls(svg);
|
||||||
|
|
||||||
|
// Audio areas
|
||||||
|
audioAreas.value = processAudio(svg);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -134,7 +156,9 @@ export default defineComponent({
|
||||||
panzoom,
|
panzoom,
|
||||||
anchors,
|
anchors,
|
||||||
panToAnchor,
|
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>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style>
|
<style>
|
||||||
|
/*.svgcontent_audio*/
|
||||||
.svgcontent_anchor {
|
.svgcontent_anchor {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue