Skip to content

Commit a60d66d

Browse files
mvanhornclaude
authored andcommitted
fix: add duration metadata to recorded WebM files
RecordRTC WebM recordings lack duration metadata, making video players unable to show duration or allow seeking. Use fix-webm-duration to inject the correct duration into the WebM blob before downloading. Closes #1847 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2bb691d commit a60d66d

4 files changed

Lines changed: 27 additions & 6 deletions

File tree

packages/client/logic/recording.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Options as RecorderOptions } from 'recordrtc'
33
import type { Ref } from 'vue'
44
import { isTruthy } from '@antfu/utils'
55
import { useDevicesList, useEventListener, useLocalStorage } from '@vueuse/core'
6+
import fixWebmDuration from 'fix-webm-duration'
67
import { nextTick, ref, shallowRef, watch } from 'vue'
78
import { currentCamera, currentMic } from '../state'
89

@@ -78,6 +79,7 @@ export function useRecording() {
7879
const streamCamera: Ref<MediaStream | undefined> = shallowRef()
7980
const streamCapture: Ref<MediaStream | undefined> = shallowRef()
8081
const streamSlides: Ref<MediaStream | undefined> = shallowRef()
82+
let recordingStartTime = 0
8183

8284
const config: RecorderOptions = {
8385
type: 'video',
@@ -183,33 +185,39 @@ export function useRecording() {
183185
)
184186

185187
recorderSlides.value.startRecording()
188+
recordingStartTime = Date.now()
186189
recording.value = true
187190
}
188191

189192
async function stopRecording() {
190193
recording.value = false
194+
const duration = Date.now() - recordingStartTime
195+
191196
recorderCamera.value?.stopRecording(() => {
192197
if (recordCamera.value) {
193198
const blob = recorderCamera.value!.getBlob()
194-
const url = URL.createObjectURL(blob)
195-
download(getFilename('camera', config.mimeType), url)
196-
window.URL.revokeObjectURL(url)
199+
downloadBlob(blob, duration, getFilename('camera', config.mimeType))
197200
}
198201
recorderCamera.value = undefined
199202
if (!showAvatar.value)
200203
closeStream(streamCamera)
201204
})
202205
recorderSlides.value?.stopRecording(() => {
203206
const blob = recorderSlides.value!.getBlob()
204-
const url = URL.createObjectURL(blob)
205-
download(getFilename('screen', config.mimeType), url)
206-
window.URL.revokeObjectURL(url)
207+
downloadBlob(blob, duration, getFilename('screen', config.mimeType))
207208
closeStream(streamCapture)
208209
closeStream(streamSlides)
209210
recorderSlides.value = undefined
210211
})
211212
}
212213

214+
async function downloadBlob(blob: Blob, duration: number, filename: string) {
215+
const fixedBlob = await fixWebmDuration(blob, duration, { logger: false })
216+
const url = URL.createObjectURL(fixedBlob)
217+
download(filename, url)
218+
window.URL.revokeObjectURL(url)
219+
}
220+
213221
function closeStream(stream: Ref<MediaStream | undefined>) {
214222
const s = stream.value
215223
if (!s)

packages/client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"ansis": "catalog:prod",
5050
"drauu": "catalog:frontend",
5151
"file-saver": "catalog:frontend",
52+
"fix-webm-duration": "catalog:frontend",
5253
"floating-vue": "catalog:frontend",
5354
"fuse.js": "catalog:frontend",
5455
"katex": "catalog:frontend",

pnpm-lock.yaml

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ catalogs:
6363
'@vueuse/motion': ^3.0.3
6464
drauu: ^1.0.0
6565
file-saver: ^2.0.5
66+
fix-webm-duration: ^1.0.6
6667
floating-vue: ^5.2.2
6768
fuse.js: ^7.1.0
6869
katex: ^0.16.33

0 commit comments

Comments
 (0)