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

Add input to rename internal video streams #1359

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 43 additions & 14 deletions src/components/mini-widgets/MiniVideoRecorder.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
variant="text"
@click="widgetStore.miniWidgetManagerVars(miniWidget.hash).configMenuOpen = false"
>
Cancel
Close
</v-btn>
<v-btn
class="bg-[#FFFFFF11] hover:bg-[#FFFFFF33]"
Expand Down Expand Up @@ -123,10 +123,9 @@ const timeNow = useTimestamp({ interval: 100 })
const mediaStream = ref<MediaStream | undefined>()
const isProcessingVideo = ref(false)
const numberOfVideosOnDB = ref(0)
const selectedExternalId = ref<string | undefined>()

const externalStreamId = computed(() => {
return nameSelectedStream.value ? videoStore.externalStreamId(nameSelectedStream.value) : undefined
})
const externalStreamId = computed(() => selectedExternalId.value)

const openVideoLibraryModal = (): void => {
interfaceStore.videoLibraryVisibility = true
Expand All @@ -150,13 +149,43 @@ onBeforeMount(async () => {
}
}
nameSelectedStream.value = miniWidget.value.options.internalStreamName

if (nameSelectedStream.value) {
selectedExternalId.value = videoStore.externalStreamId(nameSelectedStream.value)
}
})

watch(nameSelectedStream, () => {
miniWidget.value.options.internalStreamName = nameSelectedStream.value
mediaStream.value = undefined
})

watch(
() => videoStore.streamsCorrespondency,
(newStreamsCorrespondency) => {
if (!selectedExternalId.value) return

const matchingStream = newStreamsCorrespondency.find((stream) => stream.externalId === selectedExternalId.value)

if (matchingStream) {
if (nameSelectedStream.value !== matchingStream.name) {
nameSelectedStream.value = matchingStream.name
}
} else {
// The externalId no longer exists; handle accordingly
nameSelectedStream.value = undefined
selectedExternalId.value = undefined
}
},
{ deep: true }
)

watch(nameSelectedStream, (newName) => {
selectedExternalId.value = newName ? videoStore.externalStreamId(newName) : undefined
miniWidget.value.options.internalStreamName = newName
mediaStream.value = undefined
})

// Fetch number of temporary videos on storage
const fetchNumberOfTempVideos = async (): Promise<void> => {
const nProcessedVideos = (await videoStore.videoStoringDB.keys()).filter((k) => videoStore.isVideoFilename(k)).length
Expand Down Expand Up @@ -184,39 +213,39 @@ function assertStreamIsSelectedAndAvailable(

const toggleRecording = async (): Promise<void> => {
if (isRecording.value) {
if (externalStreamId.value !== undefined) {
videoStore.stopRecording(externalStreamId.value)
if (selectedExternalId.value) {
videoStore.stopRecording(selectedExternalId.value)
}
return
}

// If there's no stream selected, open the configuration dialog so user can choose the stream which will be recorded
if (nameSelectedStream.value === undefined) {
if (!nameSelectedStream.value) {
widgetStore.miniWidgetManagerVars(miniWidget.value.hash).configMenuOpen = true
return
}

// If there's a stream selected already, try to use it without requiring further user interaction
startRecording()
}

const startRecording = (): void => {
if (externalStreamId.value === undefined) {
if (!selectedExternalId.value) {
showDialog({ title: 'Cannot start recording.', message: 'No stream selected.', variant: 'error' })
return
}
if (!videoStore.getStreamData(externalStreamId.value)?.connected) {

if (!videoStore.getStreamData(selectedExternalId.value)?.connected) {
showDialog({ title: 'Cannot start recording.', message: 'Stream is not connected.', variant: 'error' })
return
}

assertStreamIsSelectedAndAvailable(nameSelectedStream.value)
videoStore.startRecording(externalStreamId.value)
videoStore.startRecording(selectedExternalId.value)
widgetStore.miniWidgetManagerVars(miniWidget.value.hash).configMenuOpen = false
}

const isRecording = computed(() => {
if (externalStreamId.value === undefined) return false
return videoStore.isRecording(externalStreamId.value)
if (!selectedExternalId.value) return false
return videoStore.isRecording(selectedExternalId.value)
})

const timePassedString = computed(() => {
Expand Down
27 changes: 26 additions & 1 deletion src/components/widgets/VideoPlayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,24 @@ const externalStreamId = computed(() => {

watch(
() => videoStore.streamsCorrespondency,
() => (mediaStream.value = undefined),
() => {
mediaStream.value = undefined

if (!nameSelectedStream.value) return

const selectedExternalId = videoStore.externalStreamId(nameSelectedStream.value)
if (!selectedExternalId) return

const newStreamCorr = videoStore.streamsCorrespondency.find((stream) => stream.externalId === selectedExternalId)
if (!newStreamCorr) return

const newInternalName = newStreamCorr.name

if (nameSelectedStream.value !== newInternalName) {
nameSelectedStream.value = newInternalName
widget.value.options.internalStreamName = newInternalName
}
},
{ deep: true }
)

Expand All @@ -169,6 +186,14 @@ const streamConnectionRoutine = setInterval(() => {
streamConnected.value = updatedStreamState
}
}

if (!namesAvailableStreams.value.isEmpty() && !namesAvailableStreams.value.includes(nameSelectedStream.value!)) {
if (videoStore.lastRenamedStreamName !== '') {
nameSelectedStream.value = videoStore.lastRenamedStreamName
return
}
nameSelectedStream.value = namesAvailableStreams.value[0]
}
}, 1000)
onBeforeUnmount(() => clearInterval(streamConnectionRoutine))

Expand Down
30 changes: 30 additions & 0 deletions src/stores/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const useVideoStore = defineStore('video', () => {
const unprocessedVideos = useStorage<{ [key in string]: UnprocessedVideoInfo }>('cockpit-unprocessed-video-info', {})
const timeNow = useTimestamp({ interval: 500 })
const autoProcessVideos = useBlueOsStorage('cockpit-auto-process-videos', true)
const lastRenamedStreamName = ref('')

const namesAvailableStreams = computed(() => mainWebRTCManager.availableStreams.value.map((stream) => stream.name))

Expand Down Expand Up @@ -870,6 +871,33 @@ export const useVideoStore = defineStore('video', () => {
alertStore.pushAlert(new Alert(AlertLevel.Success, `Stopped recording streams: ${streamsThatStopped.join(', ')}.`))
}

const renameStreamInternalNameById = (streamID: string, newInternalName: string): void => {
const streamCorr = streamsCorrespondency.value.find((stream) => stream.externalId === streamID)

if (streamCorr) {
const oldInternalName = streamCorr.name
streamCorr.name = newInternalName

const streamData = activeStreams.value[oldInternalName]
if (streamData) {
activeStreams.value = {
...activeStreams.value,
[newInternalName]: streamData,
}
delete activeStreams.value[oldInternalName]
}
lastRenamedStreamName.value = newInternalName
console.log(`Stream internal name updated from '${oldInternalName}' to '${newInternalName}'.`)
} else {
console.warn(`Stream with ID '${streamID}' not found.`)
showSnackbar({
variant: 'error',
message: `Stream with ID '${streamID}' not found.`,
duration: 3000,
})
}
}

registerActionCallback(
availableCockpitActions.start_recording_all_streams,
useThrottleFn(startRecordingAllStreams, 3000)
Expand Down Expand Up @@ -916,5 +944,7 @@ export const useVideoStore = defineStore('video', () => {
processVideoChunksAndTelemetry,
isVideoFilename,
activeStreams,
renameStreamInternalNameById,
lastRenamedStreamName,
}
})
63 changes: 58 additions & 5 deletions src/views/ConfigurationVideoView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,50 @@
>
<template #headers>
<tr>
<th class="text-center"><p class="text-[16px] font-bold">Internal name</p></th>
<th class="text-center"><p class="text-[16px] font-bold">External name</p></th>
<th class="text-center">
<p class="text-[16px] font-bold">Internal name</p>
</th>
<th class="text-center">
<p class="text-[16px] font-bold">External name</p>
</th>
</tr>
</template>
<template #item="{ item }">
<tr>
<td>
<div class="flex items-center justify-center rounded-xl mx-3">
{{ item.name }}
<div
:id="`internal-name-${item.externalId}`"
class="flex items-center justify-center rounded-xl mx-3"
@mouseover="hoveredStreamId = item.externalId"
@mouseleave="hoveredStreamId = null"
>
<div
v-if="editingStreamId !== item.externalId"
class="flex justify-between items-center cursor-pointer w-[160px] h-[30px]"
@dblclick="editStreamName(item)"
>
<p class="w-[160px] overflow-hidden text-ellipsis text-center whitespace-nowrap">
{{ item.name }}
</p>
<v-btn
v-if="hoveredStreamId === item.externalId"
icon
variant="text"
size="x-small"
class="-mr-8"
@click="editStreamName(item)"
>
<v-icon>mdi-pencil</v-icon>
</v-btn>
</div>
<input
v-else
:id="`edit-input-${item.externalId}`"
v-model="editingStreamName"
class="px-2 py-1 border rounded-sm"
@blur="saveStreamName(item)"
@keyup.enter="saveStreamName(item)"
/>
</div>
</td>
<td>
Expand Down Expand Up @@ -195,11 +230,12 @@

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

import ExpansiblePanel from '@/components/ExpansiblePanel.vue'
import { useAppInterfaceStore } from '@/stores/appInterface'
import { useVideoStore } from '@/stores/video'
import { VideoStreamCorrespondency } from '@/types/video'

import BaseConfigurationView from './BaseConfigurationView.vue'

Expand All @@ -211,6 +247,23 @@ const availableICEProtocols = ['udp', 'tcp']

const videoStore = useVideoStore()
const interfaceStore = useAppInterfaceStore()
const editingStreamId = ref<string | null>(null)
const editingStreamName = ref('')
const hoveredStreamId = ref<string | null>(null)

const editStreamName = (item: VideoStreamCorrespondency): void => {
editingStreamId.value = item.externalId
editingStreamName.value = item.name
setTimeout(() => {
const inputElement = document.getElementById(`edit-input-${item.externalId}`)
inputElement?.focus()
}, 0)
}

const saveStreamName = (item: VideoStreamCorrespondency): void => {
item.name = editingStreamName.value
editingStreamId.value = null
}

onMounted(() => {
if (allowedIceProtocols.value.length === 0) {
Expand Down
Loading