127 lines
3.4 KiB
Vue
127 lines
3.4 KiB
Vue
<template>
|
|
<video
|
|
v-if="type === 'video'"
|
|
:src="src"
|
|
:style="dynamicStyle"
|
|
class="map-scene--video map-scene"
|
|
ref="video"
|
|
@loadedmetadata="onVideoLoaded"
|
|
autoplay
|
|
loop
|
|
muted
|
|
></video>
|
|
<canvas
|
|
v-else
|
|
:style="dynamicStyle"
|
|
ref="canvas"
|
|
class="map-scene--image map-scene"
|
|
/>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { computed, defineProps, inject, ref, watch } from 'vue';
|
|
import {ReceiverSymbol} from '../../model/injectionSymbols';
|
|
|
|
const video = ref<HTMLVideoElement>();
|
|
const canvas = ref<HTMLCanvasElement>();
|
|
|
|
const props = defineProps<{
|
|
src?: string;
|
|
width: number;
|
|
dpi: number;
|
|
position: {x: number, y: number};
|
|
flipped: boolean;
|
|
}>();
|
|
|
|
const isImage = computed(() => props.src?.endsWith('.png') || props.src?.endsWith('.jpg') || props.src?.endsWith('.jpeg') || props.src?.endsWith('.webp'));
|
|
const isVideo = computed(() => props.src?.endsWith('.mp4') || props.src?.endsWith('.webm'));
|
|
|
|
const mediaDimensions = ref({width: 0, height: 0});
|
|
|
|
|
|
watch(() => props.src, (src) => {
|
|
if (isImage.value) {
|
|
const img = new Image();
|
|
img.src = src!;
|
|
img.onload = () => {
|
|
mediaDimensions.value = {
|
|
width: img.naturalWidth,
|
|
height: img.naturalHeight,
|
|
};
|
|
canvas.value!.width = img.naturalWidth;
|
|
canvas.value!.height = img.naturalHeight;
|
|
const ctx = canvas.value!.getContext('2d')!;
|
|
ctx.drawImage(img, 0, 0);
|
|
|
|
};
|
|
} else if (isVideo.value) {
|
|
video.value!.src = src!;
|
|
}
|
|
});
|
|
|
|
const receiver = inject(ReceiverSymbol);
|
|
receiver?.addEventListener('streamImage', ({detail}) => {
|
|
if (canvas.value) {
|
|
if (canvas.value.width !== detail.canvas.width || canvas.value.height !== detail.canvas.height) {
|
|
const backup = document.createElement('canvas');
|
|
backup.width = canvas.value.width;
|
|
backup.height = canvas.value.height;
|
|
const backupCtx = backup.getContext('2d')!;
|
|
backupCtx.drawImage(canvas.value, 0, 0);
|
|
canvas.value.width = detail.canvas.width;
|
|
canvas.value.height = detail.canvas.height;
|
|
const ctx = canvas.value.getContext('2d')!;
|
|
ctx.drawImage(backup, 0, 0, detail.canvas.width, detail.canvas.height);
|
|
}
|
|
|
|
const imageData = new ImageData(
|
|
new Uint8ClampedArray(new Uint32Array(detail.imageData.data).buffer),
|
|
detail.imageData.width,
|
|
detail.imageData.height
|
|
);
|
|
const ctx = canvas.value.getContext('2d')!;
|
|
ctx.putImageData(imageData, detail.x, detail.y);
|
|
}
|
|
});
|
|
|
|
|
|
const type = computed(() => {
|
|
if (isImage.value) {
|
|
return 'image';
|
|
} else if (isVideo.value) {
|
|
return 'video';
|
|
} else {
|
|
return 'unknown';
|
|
}
|
|
});
|
|
|
|
const dynamicStyle = computed(() => {
|
|
const width = props.dpi * props.width;
|
|
return {
|
|
'--width': `${width}px`,
|
|
'--height': `${(width) * (mediaDimensions.value.height / mediaDimensions.value.width)}px`,
|
|
'--x': `${props.position.x/100}`,
|
|
'--y': `${props.position.y/100}`,
|
|
'--rotate': mediaDimensions.value.width > mediaDimensions.value.height ? (props.flipped ? '180deg' : '0deg') :
|
|
(props.flipped ? '90deg' : '-90deg'),
|
|
};
|
|
});
|
|
|
|
|
|
function onVideoLoaded() {
|
|
mediaDimensions.value = {
|
|
width: video.value!.videoWidth,
|
|
height: video.value!.videoHeight,
|
|
};
|
|
}
|
|
|
|
</script>
|
|
<style>
|
|
.map-scene {
|
|
position: absolute;
|
|
left: calc(var(--x) * 100vw - var(--width) / 2);
|
|
top: calc(var(--y) * 100vh - var(--height) / 2);
|
|
width: var(--width);
|
|
transform: rotate(var(--rotate));
|
|
transform-origin: center;
|
|
}
|
|
</style> |