Add voice memos to live journal and event log.
Record short E2E-encrypted audio attachments from the live log, link them to events via __live:voice markers, and play them back in the stream and chronological event table. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
export const VOICE_MEMO_MAX_DURATION_SEC = 60
|
||||
export const VOICE_MEMO_MAX_BLOB_BYTES = 800_000
|
||||
|
||||
const MIME_CANDIDATES = [
|
||||
'audio/webm;codecs=opus',
|
||||
'audio/webm',
|
||||
'audio/mp4',
|
||||
'audio/ogg;codecs=opus'
|
||||
]
|
||||
|
||||
export function pickMediaRecorderMimeType(): string | undefined {
|
||||
if (typeof MediaRecorder === 'undefined') return undefined
|
||||
for (const mime of MIME_CANDIDATES) {
|
||||
if (MediaRecorder.isTypeSupported(mime)) return mime
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function blobToAudioDataUrl(blob: Blob): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => resolve(String(reader.result))
|
||||
reader.onerror = () => reject(new Error('audio_read_failed'))
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
}
|
||||
|
||||
export function formatVoiceDuration(seconds: number): string {
|
||||
const s = Math.max(0, Math.floor(seconds))
|
||||
const m = Math.floor(s / 60)
|
||||
const r = s % 60
|
||||
return `${m}:${String(r).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
export function assertVoiceMemoBlobSize(blob: Blob): void {
|
||||
if (blob.size > VOICE_MEMO_MAX_BLOB_BYTES) {
|
||||
throw new Error('VOICE_MEMO_TOO_LARGE')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user