|
4 | 4 | class="flex justify-around px-2 py-1 text-center rounded-lg w-40 h-9 align-center bg-slate-800/60" |
5 | 5 | > |
6 | 6 | <div |
7 | | - v-if="!isProcessingVideo" |
8 | 7 | :class="{ |
9 | 8 | 'blob red w-5 opacity-100 rounded-sm': isRecording, |
10 | 9 | 'opacity-30 bg-red-400': isOutside && !isRecording, |
11 | 10 | }" |
12 | 11 | class="w-6 transition-all duration-500 rounded-full aspect-square bg-red-lighten-1 hover:cursor-pointer opacity-70 hover:opacity-90" |
13 | 12 | @click="toggleRecording()" |
14 | 13 | /> |
15 | | - <div v-else> |
16 | | - <v-icon class="w-6 h-6 animate-spin" color="white">mdi-loading</v-icon> |
17 | | - </div> |
18 | | - <template v-if="!isRecording && !isProcessingVideo"> |
| 14 | + <template v-if="!isRecording"> |
19 | 15 | <div |
20 | 16 | v-if="nameSelectedStream" |
21 | 17 | class="flex flex-col max-w-[50%] scroll-container transition-all border-blur cursor-pointer" |
|
25 | 21 | </div> |
26 | 22 | <FontAwesomeIcon v-else icon="fa-solid fa-video" class="h-6 text-slate-100" /> |
27 | 23 | </template> |
28 | | - <div v-if="isRecording && !isProcessingVideo" class="w-16 text-justify text-slate-100"> |
| 24 | + <div v-if="isRecording" class="w-16 text-justify text-slate-100"> |
29 | 25 | {{ timePassedString }} |
30 | 26 | </div> |
31 | | - <div v-else-if="isProcessingVideo" class="w-16 text-justify text-slate-100"> |
32 | | - <div class="text-xs text-center text-white select-none flex-nowrap">Processing video...</div> |
33 | | - </div> |
34 | 27 | <div class="flex justify-center w-6"> |
35 | 28 | <v-divider vertical class="h-6 ml-1" /> |
36 | 29 | <v-badge |
@@ -125,7 +118,6 @@ const isVideoLibraryDialogOpen = ref(false) |
125 | 118 | const isLoadingStream = ref(false) |
126 | 119 | const timeNow = useTimestamp({ interval: 100 }) |
127 | 120 | const mediaStream = ref<MediaStream | undefined>() |
128 | | -const isProcessingVideo = ref(false) |
129 | 121 | const numberOfVideosOnDB = ref(0) |
130 | 122 | const selectedExternalId = ref<string | undefined>() |
131 | 123 |
|
@@ -191,12 +183,63 @@ watch(nameSelectedStream, (newName) => { |
191 | 183 | mediaStream.value = undefined |
192 | 184 | }) |
193 | 185 |
|
194 | | -// Fetch number of temporary videos on storage |
| 186 | +// Fetch number of videos on storage (chunk groups + processed videos without overlap) |
195 | 187 | const fetchNumberOfTempVideos = async (): Promise<void> => { |
196 | | - const keys = await videoStore.videoStorage.keys() |
197 | | - const nProcessedVideos = keys.filter((k) => videoStore.isVideoFilename(k)).length |
198 | | - const nFailedUnprocessedVideos = Object.keys(videoStore.keysFailedUnprocessedVideos).length |
199 | | - numberOfVideosOnDB.value = nProcessedVideos + nFailedUnprocessedVideos |
| 188 | + // Get processed videos from videoStorage |
| 189 | + const processedKeys = await videoStore.videoStorage.keys() |
| 190 | + const processedVideos = processedKeys.filter((k) => videoStore.isVideoFilename(k)) |
| 191 | +
|
| 192 | + // Get chunk groups from tempVideoStorage |
| 193 | + const tempKeys = await videoStore.tempVideoStorage.keys() |
| 194 | + const chunkGroups: Set<string> = new Set() |
| 195 | +
|
| 196 | + for (const key of tempKeys) { |
| 197 | + if (key.includes('thumbnail_')) continue |
| 198 | +
|
| 199 | + const parts = key.split('_') |
| 200 | + if (parts.length < 2) continue |
| 201 | +
|
| 202 | + const hash = parts[0] |
| 203 | + const chunkNumber = parseInt(parts[parts.length - 1], 10) |
| 204 | + if (isNaN(chunkNumber)) continue |
| 205 | +
|
| 206 | + // Check if this chunk actually exists and has content |
| 207 | + try { |
| 208 | + const blob = (await videoStore.tempVideoStorage.getItem(key)) as Blob |
| 209 | + if (blob && blob.size > 0) { |
| 210 | + chunkGroups.add(hash) |
| 211 | + } |
| 212 | + } catch (error) { |
| 213 | + console.warn(`Failed to check chunk ${key}:`, error) |
| 214 | + } |
| 215 | + } |
| 216 | +
|
| 217 | + // Count processed videos that don't have corresponding chunk groups |
| 218 | + const processedVideoHashes = new Set<string>() |
| 219 | + for (const videoKey of processedVideos) { |
| 220 | + // Extract hash from processed video filename |
| 221 | + // Processed videos have format like "MissionName (Date) #hash.webm" |
| 222 | + // We need to extract the hash after the # symbol |
| 223 | + const hashMatch = videoKey.match(/#([a-f0-9]+)\./) |
| 224 | + if (hashMatch && hashMatch[1]) { |
| 225 | + processedVideoHashes.add(hashMatch[1]) |
| 226 | + } |
| 227 | + } |
| 228 | +
|
| 229 | + // Count unique videos: chunk groups + processed videos that don't have chunk groups |
| 230 | + const uniqueVideos = new Set<string>() |
| 231 | +
|
| 232 | + // Add all chunk groups |
| 233 | + chunkGroups.forEach((hash) => uniqueVideos.add(hash)) |
| 234 | +
|
| 235 | + // Add processed videos that don't have corresponding chunk groups |
| 236 | + processedVideoHashes.forEach((hash) => { |
| 237 | + if (!chunkGroups.has(hash)) { |
| 238 | + uniqueVideos.add(hash) |
| 239 | + } |
| 240 | + }) |
| 241 | +
|
| 242 | + numberOfVideosOnDB.value = uniqueVideos.size |
200 | 243 | } |
201 | 244 |
|
202 | 245 | // eslint-disable-next-line jsdoc/require-jsdoc |
@@ -312,20 +355,6 @@ if (widgetStore.isRealMiniWidget(miniWidget.value.hash)) { |
312 | 355 | } |
313 | 356 | onBeforeUnmount(() => clearInterval(streamConnectionRoutine)) |
314 | 357 |
|
315 | | -// Check if there are videos being processed |
316 | | -watch( |
317 | | - () => videoStore.areThereVideosProcessing, |
318 | | - (newValue) => { |
319 | | - isProcessingVideo.value = newValue |
320 | | - fetchNumberOfTempVideos() |
321 | | - } |
322 | | -) |
323 | | -
|
324 | | -watch( |
325 | | - () => videoStore.keysFailedUnprocessedVideos, |
326 | | - () => fetchNumberOfTempVideos() |
327 | | -) |
328 | | -
|
329 | 358 | watch( |
330 | 359 | () => isVideoLibraryDialogOpen.value, |
331 | 360 | async (newValue) => { |
|
0 commit comments