|
@@ -1,9 +1,28 @@
|
|
|
<template>
|
|
|
<div class="annotated-image" v-on="events">
|
|
|
- <img alt="media" ref="image" draggable="false"
|
|
|
- :src="mediaUrl" :srcset="mediaUrlSet" :sizes="mediaUrlSizes"/>
|
|
|
+ <img v-if="data.type === 'image'"
|
|
|
+ alt="media" ref="image" draggable="false"
|
|
|
+ :src="mediaUrl" :srcset="mediaUrlSet" :sizes="mediaUrlSizes"
|
|
|
+ v-on:load="resizeEvent" v-on:loadedmetadata="resizeEvent" v-on:loadeddata="resizeEvent"/>
|
|
|
|
|
|
- <div style="position: absolute; top: 0.5rem; left: 0.5rem">{{ data }}</div>
|
|
|
+ <template v-if="data.type === 'video'">
|
|
|
+ <video ref="image" draggable="false"
|
|
|
+ preload="auto" :src="mediaUrl"
|
|
|
+ v-on:touchstart.prevent v-on:touchmove.prevent v-on:touchend.prevent
|
|
|
+ v-on:mousedown.prevent v-on:mousemove.prevent v-on:mouseup.prevent
|
|
|
+ v-on:click.prevent v-on:dragstart.prevent
|
|
|
+ v-on:load="resizeEvent" v-on:canplay="resizeEvent" v-on:loadeddata="resizeEvent"
|
|
|
+ v-on:timeupdate="videoProgress">
|
|
|
+ </video>
|
|
|
+
|
|
|
+ <video-control :video="video" :data="data"
|
|
|
+ v-on:touchstart.prevent.stop v-on:touchmove.prevent.stop v-on:touchend.prevent.stop
|
|
|
+ v-on:mousedown.prevent.stop v-on:mousemove.prevent.stop v-on:mouseup.prevent.stop
|
|
|
+ v-on:click.prevent.stop v-on:dragstart.prevent.stop
|
|
|
+ @play="videoPlay" @jump="videoJump"/>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- <div style="position: absolute; top: 0.5rem; left: 0.5rem">{{ video }}</div> -->
|
|
|
|
|
|
<annotation-box v-if="current"
|
|
|
:image="image"
|
|
@@ -27,37 +46,50 @@
|
|
|
|
|
|
<script>
|
|
|
import AnnotationBox from "@/components/media/annotation-box";
|
|
|
+import VideoControl from "@/components/media/video-control";
|
|
|
|
|
|
export default {
|
|
|
name: "annotated-image",
|
|
|
- components: {AnnotationBox},
|
|
|
+ components: {VideoControl, AnnotationBox},
|
|
|
props: ['data', 'project', 'socket'],
|
|
|
mounted: function () {
|
|
|
window.addEventListener("resize", this.resizeEvent);
|
|
|
+ this.resizeEvent();
|
|
|
|
|
|
- if (this.$refs.image.complete)
|
|
|
- this.resizeEvent();
|
|
|
- else
|
|
|
- this.$refs.image.addEventListener('load', this.resizeEvent);
|
|
|
+ // TODO dirty workaround
|
|
|
+ // this works around a bug which is probably caused by partly loaded
|
|
|
+ // videos and their received dimensions
|
|
|
+ this.watcher = setInterval(this.resizeEvent, 400);
|
|
|
},
|
|
|
destroyed() {
|
|
|
window.removeEventListener("resize", this.resizeEvent);
|
|
|
+ clearInterval(this.watcher);
|
|
|
},
|
|
|
watch: {
|
|
|
data: function () {
|
|
|
this.current = false;
|
|
|
+ this.video = {
|
|
|
+ frame: 0,
|
|
|
+ play: false
|
|
|
+ };
|
|
|
+
|
|
|
this.resizeEvent();
|
|
|
}
|
|
|
},
|
|
|
data: function () {
|
|
|
return {
|
|
|
sizes: [600, 800, 1200, 1600, 2000, 3000],
|
|
|
+ watcher: 0,
|
|
|
image: {
|
|
|
left: 0,
|
|
|
top: 0,
|
|
|
width: 0,
|
|
|
height: 0
|
|
|
},
|
|
|
+ video: {
|
|
|
+ frame: 0,
|
|
|
+ play: false
|
|
|
+ },
|
|
|
start: false,
|
|
|
fixed: false,
|
|
|
current: false,
|
|
@@ -75,9 +107,14 @@ export default {
|
|
|
return this.sizes.map(e => '(max-width: ' + e + 'px) ' + e + 'px').join(',');
|
|
|
},
|
|
|
predictions: function () {
|
|
|
- return Object.keys(this.data.predictionResults)
|
|
|
+ const predictions = Object.keys(this.data.predictionResults)
|
|
|
.map(k => this.data.predictionResults[k])
|
|
|
.filter(k => 'x' in k);
|
|
|
+
|
|
|
+ if (this.data.type === 'video')
|
|
|
+ return predictions.filter(k => k.frame === this.video.frame);
|
|
|
+ else
|
|
|
+ return predictions;
|
|
|
},
|
|
|
events: function () {
|
|
|
if (this.project.model.supports.includes('bounding-boxes')
|
|
@@ -106,6 +143,22 @@ export default {
|
|
|
this.image.width = element.width;
|
|
|
this.image.height = element.height;
|
|
|
},
|
|
|
+ videoPlay: function (value) {
|
|
|
+ this.video.play = value;
|
|
|
+ if (value)
|
|
|
+ this.$refs.image.play();
|
|
|
+ else
|
|
|
+ this.$refs.image.pause();
|
|
|
+ },
|
|
|
+ videoJump: function (value) {
|
|
|
+ value = Math.max(0, Math.min(this.data.frameCount, value));
|
|
|
+ const diff = value - this.video.frame;
|
|
|
+
|
|
|
+ this.$refs.image.currentTime += diff / this.data.fps;
|
|
|
+ },
|
|
|
+ videoProgress: function (event) {
|
|
|
+ this.video.frame = Math.floor(event.target.currentTime * this.data.fps);
|
|
|
+ },
|
|
|
get: function (event) {
|
|
|
if ('clientX' in event)
|
|
|
return event;
|
|
@@ -137,12 +190,17 @@ export default {
|
|
|
hy = this.fixed && 'hy' in this.fixed ? this.fixed.hy : Math.min(Math.max(y1, y2), 1);
|
|
|
}
|
|
|
|
|
|
- return {
|
|
|
+ const rectangle = {
|
|
|
x: lx,
|
|
|
y: ly,
|
|
|
w: hx - lx,
|
|
|
h: hy - ly
|
|
|
- };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.data.type === 'video')
|
|
|
+ rectangle.frame = this.video.frame;
|
|
|
+
|
|
|
+ return rectangle;
|
|
|
},
|
|
|
press: function (event) {
|
|
|
this.start = {
|
|
@@ -222,8 +280,14 @@ export default {
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-img {
|
|
|
+img,
|
|
|
+video {
|
|
|
max-width: 100%;
|
|
|
max-height: 100%;
|
|
|
}
|
|
|
+
|
|
|
+.video-control {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+}
|
|
|
</style>
|