Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <script setup>
- import { ref, computed } from 'vue'
- import axios from 'axios'
- const captureState = ref('idle') // idle | recording | analyzing
- const showReviewModal = ref(false)
- const transcript = ref('')
- const showTranscript = ref(false)
- const tasks = ref([]) // {id, title, selected}
- const recordingId = ref(null)
- const errorMessage = ref('')
- const isSending = ref(false)
- const statusText = computed(() => {
- switch (captureState.value) {
- case 'recording': return 'Recording... Tap to stop'
- case 'analyzing': return 'Analyzing your thoughts...'
- default: return 'Tap to start recording'
- }
- })
- const selectedCount = computed(() => tasks.value.filter(t => t.selected).length)
- let mediaStream = null
- let mediaRecorder = null
- let chunks = []
- async function handleCapture () {
- if (captureState.value === 'idle') {
- await startRecording()
- } else if (captureState.value === 'recording') {
- await stopRecording()
- }
- }
- async function startRecording () {
- errorMessage.value = ''
- try {
- if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
- throw new Error('Audio recording requires HTTPS')
- }
- if (!navigator.mediaDevices?.getUserMedia) {
- throw new Error('Audio recording not supported in this browser')
- }
- mediaStream = await navigator.mediaDevices.getUserMedia({
- audio: {
- echoCancellation: true,
- noiseSuppression: true,
- autoGainControl: true
- },
- video: false
- })
- const mimeType = getSupportedAudioType()
- mediaRecorder = new MediaRecorder(mediaStream, { mimeType })
- chunks = []
- mediaRecorder.ondataavailable = (e) => {
- if (e.data && e.data.size > 0) chunks.push(e.data)
- }
- mediaRecorder.onstop = async () => {
- try {
- const blob = new Blob(chunks, { type: mimeType })
- if (!blob || blob.size === 0) throw new Error('No audio data recorded')
- captureState.value = 'analyzing'
- await sendRecording(blob)
- } catch (err) {
- errorMessage.value = err?.message || String(err)
- captureState.value = 'idle'
- } finally {
- cleanupMedia()
- }
- }
- mediaRecorder.start()
- captureState.value = 'recording'
- } catch (err) {
- errorMessage.value = err?.message || String(err)
- cleanupMedia()
- captureState.value = 'idle'
- }
- }
- async function stopRecording () {
- try {
- if (mediaRecorder && mediaRecorder.state !== 'inactive') {
- mediaRecorder.stop()
- } else {
- cleanupMedia()
- captureState.value = 'idle'
- }
- } catch (err) {
- errorMessage.value = 'Error stopping recording: ' + (err?.message || String(err))
- cleanupMedia()
- captureState.value = 'idle'
- }
- }
- function cleanupMedia () {
- try {
- mediaRecorder?.stop?.()
- } catch {}
- mediaRecorder = null
- chunks = []
- if (mediaStream) {
- try {
- mediaStream.getTracks().forEach(t => {
- try { t.stop() } catch {}
- })
- } catch {}
- }
- mediaStream = null
- }
- function getSupportedAudioType () {
- const types = [
- 'audio/webm;codecs=opus',
- 'audio/webm',
- 'audio/ogg;codecs=opus',
- 'audio/ogg',
- 'audio/mp4',
- 'audio/mpeg'
- ]
- for (const t of types) {
- if (MediaRecorder.isTypeSupported?.(t)) return t
- }
- return ''
- }
- async function sendRecording (blob) {
- if (!blob) return
- isSending.value = true
- try {
- const fileName = pickFileNameFromType(blob.type)
- const formData = new FormData()
- const file = new File([blob], fileName, { type: blob.type || 'application/octet-stream' })
- formData.append('audio', file)
- const res = await axios.post('/recordings', formData)
- recordingId.value = res.data.recording_id
- transcript.value = res.data.transcript
- showTranscript.value = false
- tasks.value = (res.data.tasks || []).map(t => ({ ...t, selected: true }))
- showReviewModal.value = true
- } catch (e) {
- errorMessage.value = e?.response?.data?.message || e?.message || 'Upload failed'
- } finally {
- isSending.value = false
- captureState.value = 'idle'
- }
- }
- function pickFileNameFromType (type) {
- if (type.includes('webm')) return 'recording.webm'
- if (type.includes('ogg')) return 'recording.ogg'
- if (type.includes('mp4') || type.includes('mpeg')) return 'recording.m4a'
- return 'recording.bin'
- }
- async function addTasks () {
- if (!recordingId.value) return
- isSending.value = true
- try {
- await axios.post(`/recordings/${recordingId.value}/tasks`, { tasks: tasks.value })
- window.location.href = '/tasks'
- } catch (e) {
- errorMessage.value = e.response?.data?.message || e.message
- } finally {
- isSending.value = false
- }
- }
- function resetRecording () {
- showReviewModal.value = false
- transcript.value = ''
- tasks.value = []
- recordingId.value = null
- errorMessage.value = ''
- showTranscript.value = false
- }
- document.addEventListener('visibilitychange', () => {
- if (document.hidden && captureState.value === 'recording') {
- stopRecording()
- }
- })
- window.addEventListener('pagehide', () => {
- if (captureState.value === 'recording') {
- stopRecording()
- } else {
- cleanupMedia()
- }
- })
- </script>
Advertisement
Add Comment
Please, Sign In to add comment