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 { 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') } }