record audio node support (#4289)

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
This commit is contained in:
Terry Jia
2025-07-24 03:22:16 -04:00
committed by GitHub
parent bb5aef9275
commit 906bc42f7f
21 changed files with 891 additions and 11 deletions

View File

@@ -1,5 +1,9 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import type { IStringWidget } from '@comfyorg/litegraph/dist/types/widgets'
import type {
IBaseWidget,
IStringWidget
} from '@comfyorg/litegraph/dist/types/widgets'
import { MediaRecorder as ExtendableMediaRecorder } from 'extendable-media-recorder'
import { useChainCallback } from '@/composables/functional/useChainCallback'
import { useNodeDragAndDrop } from '@/composables/node/useNodeDragAndDrop'
@@ -9,6 +13,7 @@ import { t } from '@/i18n'
import type { ResultItemType } from '@/schemas/apiSchema'
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
import type { DOMWidget } from '@/scripts/domWidget'
import { useAudioService } from '@/services/audioService'
import { useToastStore } from '@/stores/toastStore'
import { NodeLocatorId } from '@/types'
import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'
@@ -257,3 +262,167 @@ app.registerExtension({
}
}
})
app.registerExtension({
name: 'Comfy.RecordAudio',
getCustomWidgets() {
return {
AUDIO_RECORD(node, inputName: string) {
const audio = document.createElement('audio')
audio.controls = true
audio.classList.add('comfy-audio')
audio.setAttribute('name', 'media')
const audioUIWidget: DOMWidget<HTMLAudioElement, string> =
node.addDOMWidget(inputName, /* name=*/ 'audioUI', audio)
let mediaRecorder: MediaRecorder | null = null
let isRecording = false
let audioChunks: Blob[] = []
let currentStream: MediaStream | null = null
let recordWidget: IBaseWidget | null = null
let stopPromise: Promise<void> | null = null
let stopResolve: (() => void) | null = null
audioUIWidget.serializeValue = async () => {
if (isRecording && mediaRecorder) {
stopPromise = new Promise((resolve) => {
stopResolve = resolve
})
mediaRecorder.stop()
await stopPromise
}
const audioSrc = audioUIWidget.element.src
if (!audioSrc) {
useToastStore().addAlert(t('g.noAudioRecorded'))
return ''
}
const blob = await fetch(audioSrc).then((r) => r.blob())
return await useAudioService().convertBlobToFileAndSubmit(blob)
}
recordWidget = node.addWidget(
'button',
inputName,
'',
async () => {
if (!isRecording) {
try {
currentStream = await navigator.mediaDevices.getUserMedia({
audio: true
})
mediaRecorder = new ExtendableMediaRecorder(currentStream, {
mimeType: 'audio/wav'
}) as unknown as MediaRecorder
audioChunks = []
mediaRecorder.ondataavailable = (event) => {
audioChunks.push(event.data)
}
mediaRecorder.onstop = async () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' })
useAudioService().stopAllTracks(currentStream)
if (
audioUIWidget.element.src &&
audioUIWidget.element.src.startsWith('blob:')
) {
URL.revokeObjectURL(audioUIWidget.element.src)
}
audioUIWidget.element.src = URL.createObjectURL(audioBlob)
isRecording = false
if (recordWidget) {
recordWidget.label = t('g.startRecording')
}
if (stopResolve) {
stopResolve()
stopResolve = null
stopPromise = null
}
}
mediaRecorder.onerror = (event) => {
console.error('MediaRecorder error:', event)
useAudioService().stopAllTracks(currentStream)
isRecording = false
if (recordWidget) {
recordWidget.label = t('g.startRecording')
}
if (stopResolve) {
stopResolve()
stopResolve = null
stopPromise = null
}
}
mediaRecorder.start()
isRecording = true
if (recordWidget) {
recordWidget.label = t('g.stopRecording')
}
} catch (err) {
console.error('Error accessing microphone:', err)
useToastStore().addAlert(t('g.micPermissionDenied'))
if (mediaRecorder) {
try {
mediaRecorder.stop()
} catch {}
}
useAudioService().stopAllTracks(currentStream)
currentStream = null
isRecording = false
if (recordWidget) {
recordWidget.label = t('g.startRecording')
}
}
} else if (mediaRecorder && isRecording) {
mediaRecorder.stop()
}
},
{ serialize: false }
)
recordWidget.label = t('g.startRecording')
const originalOnRemoved = node.onRemoved
node.onRemoved = function () {
if (isRecording && mediaRecorder) {
mediaRecorder.stop()
}
useAudioService().stopAllTracks(currentStream)
if (audioUIWidget.element.src?.startsWith('blob:')) {
URL.revokeObjectURL(audioUIWidget.element.src)
}
originalOnRemoved?.call(this)
}
return { widget: recordWidget }
}
}
},
async nodeCreated(node) {
if (node.constructor.comfyClass !== 'RecordAudio') return
await useAudioService().registerWavEncoder()
}
})

View File

@@ -158,6 +158,12 @@
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "Toggle the Custom Nodes Manager Progress Bar"
},
"Comfy_MaskEditor_BrushSize_Decrease": {
"label": "Decrease Brush Size in MaskEditor"
},
"Comfy_MaskEditor_BrushSize_Increase": {
"label": "Increase Brush Size in MaskEditor"
},
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "Open Mask Editor for Selected Node"
},

View File

@@ -134,6 +134,10 @@
"releaseTitle": "{package} {version} Release",
"progressCountOf": "of",
"keybindingAlreadyExists": "Keybinding already exists on",
"startRecording": "Start Recording",
"stopRecording": "Stop Recording",
"micPermissionDenied": "Microphone permission denied",
"noAudioRecorded": "No audio recorded",
"nodesRunning": "nodes running"
},
"manager": {
@@ -973,6 +977,8 @@
"Load Default Workflow": "Load Default Workflow",
"Toggle the Custom Nodes Manager": "Toggle the Custom Nodes Manager",
"Toggle the Custom Nodes Manager Progress Bar": "Toggle the Custom Nodes Manager Progress Bar",
"Decrease Brush Size in MaskEditor": "Decrease Brush Size in MaskEditor",
"Increase Brush Size in MaskEditor": "Increase Brush Size in MaskEditor",
"Open Mask Editor for Selected Node": "Open Mask Editor for Selected Node",
"New": "New",
"Clipspace": "Clipspace",

View File

@@ -158,6 +158,12 @@
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "Alternar diálogo de progreso del administrador"
},
"Comfy_MaskEditor_BrushSize_Decrease": {
"label": "Disminuir tamaño del pincel en MaskEditor"
},
"Comfy_MaskEditor_BrushSize_Increase": {
"label": "Aumentar tamaño del pincel en MaskEditor"
},
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "Abrir editor de máscara para el nodo seleccionado"
},

View File

@@ -331,12 +331,14 @@
"loadingPanel": "Cargando panel {panel}...",
"login": "Iniciar sesión",
"logs": "Registros",
"micPermissionDenied": "Permiso de micrófono denegado",
"migrate": "Migrar",
"missing": "Faltante",
"name": "Nombre",
"newFolder": "Nueva carpeta",
"next": "Siguiente",
"no": "No",
"noAudioRecorded": "No se grabó audio",
"noResultsFound": "No se encontraron resultados",
"noTasksFound": "No se encontraron tareas",
"noTasksFoundMessage": "No hay tareas en la cola.",
@@ -376,7 +378,9 @@
"showReport": "Mostrar informe",
"sort": "Ordenar",
"source": "Fuente",
"startRecording": "Iniciar grabación",
"status": "Estado",
"stopRecording": "Detener grabación",
"success": "Éxito",
"systemInfo": "Información del sistema",
"terminal": "Terminal",
@@ -747,6 +751,7 @@
"Contact Support": "Contactar soporte",
"Convert Selection to Subgraph": "Convertir selección en subgrafo",
"Convert selected nodes to group node": "Convertir nodos seleccionados en nodo de grupo",
"Decrease Brush Size in MaskEditor": "Disminuir tamaño del pincel en MaskEditor",
"Delete Selected Items": "Eliminar elementos seleccionados",
"Desktop User Guide": "Guía de usuario de escritorio",
"Duplicate Current Workflow": "Duplicar flujo de trabajo actual",
@@ -758,6 +763,7 @@
"Give Feedback": "Dar retroalimentación",
"Group Selected Nodes": "Agrupar nodos seleccionados",
"Help": "Ayuda",
"Increase Brush Size in MaskEditor": "Aumentar tamaño del pincel en MaskEditor",
"Interrupt": "Interrumpir",
"Load Default Workflow": "Cargar flujo de trabajo predeterminado",
"Manage group nodes": "Gestionar nodos de grupo",

View File

@@ -158,6 +158,12 @@
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "Basculer la boîte de dialogue de progression"
},
"Comfy_MaskEditor_BrushSize_Decrease": {
"label": "Réduire la taille du pinceau dans MaskEditor"
},
"Comfy_MaskEditor_BrushSize_Increase": {
"label": "Augmenter la taille du pinceau dans MaskEditor"
},
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "Ouvrir l'éditeur de masque pour le nœud sélectionné"
},

View File

@@ -331,12 +331,14 @@
"loadingPanel": "Chargement du panneau {panel}...",
"login": "Connexion",
"logs": "Journaux",
"micPermissionDenied": "Permission du microphone refusée",
"migrate": "Migrer",
"missing": "Manquant",
"name": "Nom",
"newFolder": "Nouveau dossier",
"next": "Suivant",
"no": "Non",
"noAudioRecorded": "Aucun audio enregistré",
"noResultsFound": "Aucun résultat trouvé",
"noTasksFound": "Aucune tâche trouvée",
"noTasksFoundMessage": "Il n'y a pas de tâches dans la file d'attente.",
@@ -376,7 +378,9 @@
"showReport": "Afficher le rapport",
"sort": "Trier",
"source": "Source",
"startRecording": "Commencer lenregistrement",
"status": "Statut",
"stopRecording": "Arrêter lenregistrement",
"success": "Succès",
"systemInfo": "Informations système",
"terminal": "Terminal",
@@ -747,6 +751,7 @@
"Contact Support": "Contacter le support",
"Convert Selection to Subgraph": "Convertir la sélection en sous-graphe",
"Convert selected nodes to group node": "Convertir les nœuds sélectionnés en nœud de groupe",
"Decrease Brush Size in MaskEditor": "Réduire la taille du pinceau dans MaskEditor",
"Delete Selected Items": "Supprimer les éléments sélectionnés",
"Desktop User Guide": "Guide de l'utilisateur de bureau",
"Duplicate Current Workflow": "Dupliquer le flux de travail actuel",
@@ -758,6 +763,7 @@
"Give Feedback": "Donnez votre avis",
"Group Selected Nodes": "Grouper les nœuds sélectionnés",
"Help": "Aide",
"Increase Brush Size in MaskEditor": "Augmenter la taille du pinceau dans MaskEditor",
"Interrupt": "Interrompre",
"Load Default Workflow": "Charger le flux de travail par défaut",
"Manage group nodes": "Gérer les nœuds de groupe",

View File

@@ -158,6 +158,12 @@
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "プログレスダイアログの切り替え"
},
"Comfy_MaskEditor_BrushSize_Decrease": {
"label": "マスクエディタでブラシサイズを縮小"
},
"Comfy_MaskEditor_BrushSize_Increase": {
"label": "マスクエディタでブラシサイズを大きくする"
},
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "選択したノードのマスクエディタを開く"
},

View File

@@ -331,12 +331,14 @@
"loadingPanel": "{panel} パネルを読み込み中...",
"login": "ログイン",
"logs": "ログ",
"micPermissionDenied": "マイクの許可が拒否されました",
"migrate": "移行する",
"missing": "不足している",
"name": "名前",
"newFolder": "新しいフォルダー",
"next": "次へ",
"no": "いいえ",
"noAudioRecorded": "音声が録音されていません",
"noResultsFound": "結果が見つかりません",
"noTasksFound": "タスクが見つかりません",
"noTasksFoundMessage": "キューにタスクがありません。",
@@ -376,7 +378,9 @@
"showReport": "レポートを表示",
"sort": "並び替え",
"source": "ソース",
"startRecording": "録音開始",
"status": "ステータス",
"stopRecording": "録音停止",
"success": "成功",
"systemInfo": "システム情報",
"terminal": "ターミナル",
@@ -747,6 +751,7 @@
"Contact Support": "サポートに連絡",
"Convert Selection to Subgraph": "選択範囲をサブグラフに変換",
"Convert selected nodes to group node": "選択したノードをグループノードに変換",
"Decrease Brush Size in MaskEditor": "マスクエディタでブラシサイズを小さくする",
"Delete Selected Items": "選択したアイテムを削除",
"Desktop User Guide": "デスクトップユーザーガイド",
"Duplicate Current Workflow": "現在のワークフローを複製",
@@ -758,6 +763,7 @@
"Give Feedback": "フィードバックを送る",
"Group Selected Nodes": "選択したノードをグループ化",
"Help": "ヘルプ",
"Increase Brush Size in MaskEditor": "マスクエディタでブラシサイズを大きくする",
"Interrupt": "中断",
"Load Default Workflow": "デフォルトワークフローを読み込む",
"Manage group nodes": "グループノードを管理",

View File

@@ -158,6 +158,12 @@
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "진행 상황 대화 상자 전환"
},
"Comfy_MaskEditor_BrushSize_Decrease": {
"label": "MaskEditor에서 브러시 크기 줄이기"
},
"Comfy_MaskEditor_BrushSize_Increase": {
"label": "MaskEditor에서 브러시 크기 늘리기"
},
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "선택한 노드 마스크 편집기 열기"
},

View File

@@ -331,12 +331,14 @@
"loadingPanel": "{panel} 패널 불러오는 중...",
"login": "로그인",
"logs": "로그",
"micPermissionDenied": "마이크 권한이 거부되었습니다",
"migrate": "이전(migrate)",
"missing": "누락됨",
"name": "이름",
"newFolder": "새 폴더",
"next": "다음",
"no": "아니오",
"noAudioRecorded": "녹음된 오디오가 없습니다",
"noResultsFound": "결과를 찾을 수 없습니다.",
"noTasksFound": "작업을 찾을 수 없습니다.",
"noTasksFoundMessage": "대기열에 작업이 없습니다.",
@@ -376,7 +378,9 @@
"showReport": "보고서 보기",
"sort": "정렬",
"source": "소스",
"startRecording": "녹음 시작",
"status": "상태",
"stopRecording": "녹음 중지",
"success": "성공",
"systemInfo": "시스템 정보",
"terminal": "터미널",
@@ -747,6 +751,7 @@
"Contact Support": "고객 지원 문의",
"Convert Selection to Subgraph": "선택 영역을 서브그래프로 변환",
"Convert selected nodes to group node": "선택한 노드를 그룹 노드로 변환",
"Decrease Brush Size in MaskEditor": "MaskEditor에서 브러시 크기 줄이기",
"Delete Selected Items": "선택한 항목 삭제",
"Desktop User Guide": "데스크톱 사용자 가이드",
"Duplicate Current Workflow": "현재 워크플로 복제",
@@ -758,6 +763,7 @@
"Give Feedback": "피드백 제공",
"Group Selected Nodes": "선택한 노드 그룹화",
"Help": "도움말",
"Increase Brush Size in MaskEditor": "MaskEditor에서 브러시 크기 늘리기",
"Interrupt": "중단",
"Load Default Workflow": "기본 워크플로 불러오기",
"Manage group nodes": "그룹 노드 관리",

View File

@@ -158,6 +158,12 @@
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "Переключить диалоговое окно прогресса"
},
"Comfy_MaskEditor_BrushSize_Decrease": {
"label": "Уменьшить размер кисти в MaskEditor"
},
"Comfy_MaskEditor_BrushSize_Increase": {
"label": "Увеличить размер кисти в MaskEditor"
},
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "Открыть редактор масок для выбранной ноды"
},

View File

@@ -331,12 +331,14 @@
"loadingPanel": "Загрузка панели {panel}...",
"login": "Вход",
"logs": "Логи",
"micPermissionDenied": "Доступ к микрофону запрещён",
"migrate": "Мигрировать",
"missing": "Отсутствует",
"name": "Имя",
"newFolder": "Новая папка",
"next": "Далее",
"no": "Нет",
"noAudioRecorded": "Аудио не записано",
"noResultsFound": "Результатов не найдено",
"noTasksFound": "Задачи не найдены",
"noTasksFoundMessage": "В очереди нет задач.",
@@ -376,7 +378,9 @@
"showReport": "Показать отчёт",
"sort": "Сортировать",
"source": "Источник",
"startRecording": "Начать запись",
"status": "Статус",
"stopRecording": "Остановить запись",
"success": "Успех",
"systemInfo": "Информация о системе",
"terminal": "Терминал",
@@ -747,6 +751,7 @@
"Contact Support": "Связаться с поддержкой",
"Convert Selection to Subgraph": "Преобразовать выделенное в подграф",
"Convert selected nodes to group node": "Преобразовать выбранные ноды в групповую ноду",
"Decrease Brush Size in MaskEditor": "Уменьшить размер кисти в MaskEditor",
"Delete Selected Items": "Удалить выбранные элементы",
"Desktop User Guide": "Руководство пользователя для настольных ПК",
"Duplicate Current Workflow": "Дублировать текущий рабочий процесс",
@@ -758,6 +763,7 @@
"Give Feedback": "Оставить отзыв",
"Group Selected Nodes": "Сгруппировать выбранные ноды",
"Help": "Помощь",
"Increase Brush Size in MaskEditor": "Увеличить размер кисти в MaskEditor",
"Interrupt": "Прервать",
"Load Default Workflow": "Загрузить стандартный рабочий процесс",
"Manage group nodes": "Управление групповыми нодами",

View File

@@ -158,6 +158,12 @@
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "切換自訂節點管理器進度條"
},
"Comfy_MaskEditor_BrushSize_Decrease": {
"label": "減少 MaskEditor 畫筆大小"
},
"Comfy_MaskEditor_BrushSize_Increase": {
"label": "增加 MaskEditor 畫筆大小"
},
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "為選取的節點開啟 Mask 編輯器"
},

View File

@@ -331,12 +331,14 @@
"loadingPanel": "正在載入{panel}面板...",
"login": "登入",
"logs": "日誌",
"micPermissionDenied": "麥克風權限被拒絕",
"migrate": "遷移",
"missing": "缺少",
"name": "名稱",
"newFolder": "新資料夾",
"next": "下一步",
"no": "否",
"noAudioRecorded": "沒有錄製到音訊",
"noResultsFound": "找不到結果",
"noTasksFound": "找不到任務",
"noTasksFoundMessage": "佇列中沒有任務。",
@@ -376,7 +378,9 @@
"showReport": "顯示報告",
"sort": "排序",
"source": "來源",
"startRecording": "開始錄音",
"status": "狀態",
"stopRecording": "停止錄音",
"success": "成功",
"systemInfo": "系統資訊",
"terminal": "終端機",
@@ -747,6 +751,7 @@
"Contact Support": "聯絡支援",
"Convert Selection to Subgraph": "將選取內容轉為子圖",
"Convert selected nodes to group node": "將選取節點轉為群組節點",
"Decrease Brush Size in MaskEditor": "在 MaskEditor 中減小筆刷大小",
"Delete Selected Items": "刪除選取項目",
"Desktop User Guide": "桌面應用程式使用指南",
"Duplicate Current Workflow": "複製目前工作流程",
@@ -758,6 +763,7 @@
"Give Feedback": "提供意見回饋",
"Group Selected Nodes": "群組選取節點",
"Help": "說明",
"Increase Brush Size in MaskEditor": "在 MaskEditor 中增大筆刷大小",
"Interrupt": "中斷",
"Load Default Workflow": "載入預設工作流程",
"Manage group nodes": "管理群組節點",

View File

@@ -158,6 +158,12 @@
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "切换进度对话框"
},
"Comfy_MaskEditor_BrushSize_Decrease": {
"label": "減小 MaskEditor 中的筆刷大小"
},
"Comfy_MaskEditor_BrushSize_Increase": {
"label": "增加 MaskEditor 畫筆大小"
},
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "打开选中节点的遮罩编辑器"
},

View File

@@ -331,12 +331,14 @@
"loadingPanel": "正在加载{panel}面板...",
"login": "登录",
"logs": "日志",
"micPermissionDenied": "麦克风权限被拒绝",
"migrate": "迁移",
"missing": "缺失",
"name": "名称",
"newFolder": "新文件夹",
"next": "下一个",
"no": "否",
"noAudioRecorded": "未录制音频",
"noResultsFound": "未找到结果",
"noTasksFound": "未找到任务",
"noTasksFoundMessage": "队列中没有任务。",
@@ -376,7 +378,9 @@
"showReport": "显示报告",
"sort": "排序",
"source": "来源",
"startRecording": "开始录音",
"status": "状态",
"stopRecording": "停止录音",
"success": "成功",
"systemInfo": "系统信息",
"terminal": "终端",
@@ -747,6 +751,7 @@
"Contact Support": "联系支持",
"Convert Selection to Subgraph": "将选中内容转换为子图",
"Convert selected nodes to group node": "将选中节点转换为组节点",
"Decrease Brush Size in MaskEditor": "在 MaskEditor 中減小筆刷大小",
"Delete Selected Items": "删除选定的项目",
"Desktop User Guide": "桌面端用户指南",
"Duplicate Current Workflow": "复制当前工作流",
@@ -758,6 +763,7 @@
"Give Feedback": "提供反馈",
"Group Selected Nodes": "将选中节点转换为组节点",
"Help": "帮助",
"Increase Brush Size in MaskEditor": "在 MaskEditor 中增大筆刷大小",
"Interrupt": "中断",
"Load Default Workflow": "加载默认工作流",
"Manage group nodes": "管理组节点",

View File

@@ -0,0 +1,84 @@
import { register } from 'extendable-media-recorder'
import { connect } from 'extendable-media-recorder-wav-encoder'
import { api } from '@/scripts/api'
import { useToastStore } from '@/stores/toastStore'
export interface AudioRecordingError {
type: 'permission' | 'not_supported' | 'encoder' | 'recording' | 'unknown'
message: string
originalError?: unknown
}
let isEncoderRegistered: boolean = false
export const useAudioService = () => {
const handleError = (
type: AudioRecordingError['type'],
message: string,
originalError?: unknown
) => {
console.error(`Audio Service Error (${type}):`, message, originalError)
}
const stopAllTracks = (currentStream: MediaStream | null) => {
if (currentStream) {
currentStream.getTracks().forEach((track) => {
track.stop()
})
currentStream = null
}
}
const registerWavEncoder = async (): Promise<void> => {
if (isEncoderRegistered) {
return
}
try {
await register(await connect())
isEncoderRegistered = true
} catch (err) {
if (
err instanceof Error &&
err.message.includes('already an encoder stored')
) {
isEncoderRegistered = true
} else {
handleError('encoder', 'Failed to register WAV encoder', err)
}
}
}
const convertBlobToFileAndSubmit = async (blob: Blob): Promise<string> => {
const name = `recording-${Date.now()}.wav`
const file = new File([blob], name, { type: blob.type || 'audio/wav' })
const body = new FormData()
body.append('image', file)
body.append('subfolder', 'audio')
body.append('type', 'temp')
const resp = await api.fetchApi('/upload/image', {
method: 'POST',
body
})
if (resp.status !== 200) {
const err = `Error uploading temp file: ${resp.status} - ${resp.statusText}`
useToastStore().addAlert(err)
throw new Error(err)
}
const tempAudio = await resp.json()
return `audio/${tempAudio.name} [temp]`
}
return {
// Methods
convertBlobToFileAndSubmit,
registerWavEncoder,
stopAllTracks
}
}