Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

772 we should allow users to watch recorded videos (or parts of it) in the video view #838

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/components/VideoPlayer.vue
@@ -0,0 +1,95 @@
<template>
<div v-if="openDialog" class="modal">
<div class="overlay" @click="closeDialog">
<span class="close-icon mdi mdi-close" @click.stop="closeDialog"></span>
</div>
<div class="modal-content">
<video id="video-player" class="w-[100%]" controls autoplay preload="auto">
<source :src="videoFileUrl" type="video/webm" />
</video>
</div>
</div>
</template>

<script lang="ts" setup>
import { ref, watchEffect } from 'vue'

const props = defineProps({
videoUrl: {
type: String,
default: '',
},
openVideoPlayerDialog: Boolean,
})

const emit = defineEmits(['update:openVideoPlayerDialog'])

const videoFileUrl = ref(props.videoUrl)
const openDialog = ref(props.openVideoPlayerDialog)

const closeDialog = (): void => {
openDialog.value = false
emit('update:openVideoPlayerDialog', false)
}

watchEffect(() => {
videoFileUrl.value = props.videoUrl
openDialog.value = props.openVideoPlayerDialog
})
</script>

<style scoped>
.modal {
position: fixed;
top: 50%;
left: 50%;
display: flex;
align-items: center;
justify-content: center;
padding: 30px;
z-index: 1000;
backdrop-filter: blur(10px);
}

.overlay {
position: fixed;
top: 0;
left: 0;
background-color: rgba(255, 255, 255, 0.2);
z-index: 990;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
}

.close-icon {
position: absolute;
top: 0px;
right: 5px;
cursor: pointer;
color: white;
font-size: 24px;
border-radius: 8px;
}

.modal-content {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: rgba(255, 255, 255, 0.5);
border-radius: 8px;
padding: 5px;
width: 1000px;
height: 565px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
z-index: 1200;
}

video {
height: 580px;
}
</style>
12 changes: 9 additions & 3 deletions src/components/mini-widgets/MiniVideoRecorder.vue
Expand Up @@ -34,8 +34,14 @@
</div>
<div v-if="numberOfVideosOnDB > 0" class="flex justify-center w-8">
<v-divider vertical class="h-6" />
<v-badge color="info" :content="numberOfVideosOnDB" :dot="isOutside || isVideoLibraryDialogOpen"
><v-icon class="w-6 h-6 text-slate-100 ml-3" @click="isVideoLibraryDialogOpen = true">
<v-badge
color="info"
:content="numberOfVideosOnDB"
:dot="isOutside || isVideoLibraryDialogOpen"
class="cursor-pointer"
@click="isVideoLibraryDialogOpen = true"
>
<v-icon class="w-6 h-6 text-slate-100 ml-3" @click="isVideoLibraryDialogOpen = true">
mdi-video-box
</v-icon></v-badge
>
Expand Down Expand Up @@ -76,7 +82,7 @@
</div>
</div>
</v-dialog>
<v-dialog v-model="isVideoLibraryDialogOpen" width="auto">
<v-dialog v-model="isVideoLibraryDialogOpen" width="900px">
<ConfigurationVideoView as-video-library />
</v-dialog>
</template>
Expand Down
57 changes: 51 additions & 6 deletions src/views/ConfigurationVideoView.vue
Expand Up @@ -86,20 +86,46 @@
class="max-w-[90%] bg-slate-100/30 rounded-lg p-3 border"
:class="temporaryDbSize === 0 ? 'mb-10' : 'mb-0'"
>
<template #[`item.filename`]="{ item }">
<span
v-if="item.filename.endsWith('.webm')"
class="cursor-pointer hover:underline"
@click="playVideoOnModal([item.filename])"
>
{{ item.filename }}
</span>
<span v-else>
{{ item.filename }}
</span>
</template>
<template #item.size="{ value }">
{{ formatBytes(value) }}
</template>
<template #item.actions="{ item }">
<span
v-if="selectedFilesNames.isEmpty()"
class="mx-1 transition-all cursor-pointer hover:text-slate-500/50 mdi mdi-trash-can"
class="mx-2 transition-all cursor-pointer hover:text-slate-500/50 mdi mdi-trash-can"
style="font-size: 17px"
@click="discardAndUpdateDB([item.filename])"
/>
<span
v-if="selectedFilesNames.isEmpty()"
class="mx-1 transition-all cursor-pointer hover:text-slate-500/50 mdi mdi-download"
class="mx-0 transition-all cursor-pointer hover:text-slate-500/50 mdi mdi-download"
style="font-size: 17px"
@click="downloadAndUpdateDB([item.filename])"
/>
<span
v-if="selectedFilesNames.isEmpty()"
class="mx-1 ml-2 transition-all mdi mdi-play"
:class="{
'hover:text-slate-500/50': item.filename.endsWith('.webm'),
'cursor-pointer': item.filename.endsWith('.webm'),
'cursor-default': !item.filename.endsWith('.webm'),
}"
:style="{ fontSize: '20px', opacity: item.filename.endsWith('.webm') ? '1' : '0.1' }"
@click="item.filename.endsWith('.webm') && playVideoOnModal([item.filename])"
>
</span>
</template>
<template #footer.prepend>
<Transition name="horizontalFade">
Expand All @@ -112,6 +138,10 @@
class="mx-2 text-2xl transition-all cursor-pointer hover:text-slate-500/50 mdi mdi-download"
@click="downloadAndUpdateDB(selectedFilesNames)"
/>
<span
class="mx-2 text-2xl transition-all cursor-pointer hover:text-slate-500/50 mdi mdi-play"
@click="downloadAndUpdateDB(selectedFilesNames)"
/>
</div>
</Transition>
</template>
Expand All @@ -136,6 +166,7 @@
</div>
</template>
</BaseConfigurationView>
<VideoPlayer :video-url="videoFile" :open-video-player-dialog="openVideoPlayerDialog" />
</template>

<script setup lang="ts">
Expand All @@ -145,6 +176,7 @@ import { computed, onMounted, ref, watch, watchEffect } from 'vue'
import type { VDataTable } from 'vuetify/components'

import Button from '@/components/Button.vue'
import VideoPlayer from '@/components/VideoPlayer.vue'
import { formatBytes } from '@/libs/utils'
import { useVideoStore } from '@/stores/video'

Expand All @@ -161,7 +193,13 @@ const props = defineProps<{
asVideoLibrary?: boolean
}>()

/* eslint-enable jsdoc/require-jsdoc */
const availableVideosAndLogs = ref<VideoStorageFile[] | undefined>()
const isVideoLibraryOnly = ref(props.asVideoLibrary)
const videoFile = ref()
const openVideoPlayerDialog = ref<boolean>(false)
const temporaryDbSize = ref(0)
const selectedFilesNames = ref<string[]>([])

watchEffect(() => {
isVideoLibraryOnly.value = props.asVideoLibrary ?? false
Expand All @@ -173,10 +211,6 @@ interface VideoStorageFile {
filename: string
size: number
}
/* eslint-enable jsdoc/require-jsdoc */
const availableVideosAndLogs = ref<VideoStorageFile[] | undefined>()
const temporaryDbSize = ref(0)
const selectedFilesNames = ref<string[]>([])

onMounted(async () => {
await fetchVideoAndLogsData()
Expand Down Expand Up @@ -217,6 +251,17 @@ const downloadAndUpdateDB = async (filenames: string[]): Promise<void> => {
selectedFilesNames.value = []
}

async function playVideoOnModal(videoFileName: string[]): Promise<void> {
const videoBlob = await videoStore.videoStoringDB.getItem(videoFileName[0])
if (!(videoBlob instanceof Blob)) {
console.error('Video data is not a Blob:', videoBlob)
return
}
const tempFileUrl = URL.createObjectURL(videoBlob)
videoFile.value = tempFileUrl
openVideoPlayerDialog.value = true
}

const clearTemporaryVideoFiles = async (): Promise<void> => {
const videosBeingRecorded = videoStore.keysAllUnprocessedVideos.length > videoStore.keysFailedUnprocessedVideos.length

Expand Down