Spaces:
Runtime error
Runtime error
<script lang="ts"> | |
import { onMount } from "svelte"; | |
import type { I18nFormatter } from "@gradio/utils"; | |
import WaveSurfer from "wavesurfer.js"; | |
import { skipAudio, process_audio } from "../shared/utils"; | |
import Record from "wavesurfer.js/dist/plugins/record.js"; | |
import WaveformControls from "../shared/WaveformControls.svelte"; | |
import WaveformRecordControls from "../shared/WaveformRecordControls.svelte"; | |
import RecordPlugin from "wavesurfer.js/dist/plugins/record.js"; | |
export let mode: string; | |
export let i18n: I18nFormatter; | |
export let dispatch: (event: any, detail?: any) => void; | |
export let dispatch_blob: ( | |
blobs: Uint8Array[] | Blob[], | |
event: "stream" | "change" | "stop_recording" | |
) => Promise<void> | undefined; | |
export let waveform_settings = {}; | |
export let handle_reset_value: () => void; | |
let micWaveform: WaveSurfer; | |
let recordingWaveform: WaveSurfer; | |
let playing = false; | |
let container: HTMLDivElement; | |
let record: Record; | |
let recordedAudio: string | null = null; | |
// timestamps | |
let timeRef: HTMLTimeElement; | |
let durationRef: HTMLTimeElement; | |
let audioDuration: number; | |
let seconds = 0; | |
let interval: NodeJS.Timeout; | |
let timing = false; | |
// trimming | |
let trimDuration = 0; | |
const start_interval = (): void => { | |
clearInterval(interval); | |
interval = setInterval(() => { | |
seconds++; | |
}, 1000); | |
}; | |
const format_time = (seconds: number): string => { | |
const minutes = Math.floor(seconds / 60); | |
const secondsRemainder = Math.round(seconds) % 60; | |
const paddedSeconds = `0${secondsRemainder}`.slice(-2); | |
return `${minutes}:${paddedSeconds}`; | |
}; | |
$: record?.on("record-start", () => { | |
start_interval(); | |
timing = true; | |
dispatch("start_recording"); | |
}); | |
$: record?.on("record-end", async (blob) => { | |
seconds = 0; | |
timing = false; | |
clearInterval(interval); | |
const array_buffer = await blob.arrayBuffer(); | |
const context = new AudioContext(); | |
const audio_buffer = await context.decodeAudioData(array_buffer); | |
if (audio_buffer) | |
await process_audio(audio_buffer).then( | |
async (trimmedBlob: Uint8Array) => { | |
await dispatch_blob([trimmedBlob], "change"); | |
await dispatch_blob([trimmedBlob], "stop_recording"); | |
} | |
); | |
}); | |
$: record?.on("record-pause", () => { | |
dispatch("pause_recording"); | |
clearInterval(interval); | |
}); | |
$: record?.on("record-resume", () => { | |
start_interval(); | |
}); | |
$: recordingWaveform?.on("decode", (duration: any) => { | |
audioDuration = duration; | |
durationRef && (durationRef.textContent = format_time(duration)); | |
}); | |
$: recordingWaveform?.on( | |
"timeupdate", | |
(currentTime: any) => | |
timeRef && (timeRef.textContent = format_time(currentTime)) | |
); | |
$: recordingWaveform?.on("pause", () => { | |
dispatch("pause"); | |
playing = false; | |
}); | |
$: recordingWaveform?.on("play", () => { | |
dispatch("play"); | |
playing = true; | |
}); | |
$: recordingWaveform?.on("finish", () => { | |
dispatch("stop"); | |
dispatch("end"); | |
playing = false; | |
}); | |
const create_mic_waveform = (): void => { | |
const recorder = document.getElementById("microphone"); | |
if (recorder) recorder.innerHTML = ""; | |
if (micWaveform !== undefined) micWaveform.destroy(); | |
if (!recorder) return; | |
micWaveform = WaveSurfer.create({ | |
...waveform_settings, | |
container: recorder | |
}); | |
record = micWaveform.registerPlugin(RecordPlugin.create()); | |
record.startMic(); | |
}; | |
const create_recording_waveform = (): void => { | |
let recording = document.getElementById("recording"); | |
if (!recordedAudio || !recording) return; | |
recordingWaveform = WaveSurfer.create({ | |
container: recording, | |
url: recordedAudio, | |
...waveform_settings | |
}); | |
}; | |
$: record?.on("record-end", (blob) => { | |
recordedAudio = URL.createObjectURL(blob); | |
const microphone = document.getElementById("microphone"); | |
const recording = document.getElementById("recording"); | |
if (microphone) microphone.style.display = "none"; | |
if (recording && recordedAudio) { | |
recording.innerHTML = ""; | |
create_recording_waveform(); | |
} | |
}); | |
const handle_trim_audio = async ( | |
start: number, | |
end: number | |
): Promise<void> => { | |
mode = "edit"; | |
const decodedData = recordingWaveform.getDecodedData(); | |
if (decodedData) | |
await process_audio(decodedData, start, end).then( | |
async (trimmedAudio: Uint8Array) => { | |
await dispatch_blob([trimmedAudio], "change"); | |
recordingWaveform.destroy(); | |
create_recording_waveform(); | |
} | |
); | |
dispatch("edit"); | |
}; | |
onMount(() => { | |
create_mic_waveform(); | |
window.addEventListener("keydown", (e) => { | |
if (e.key === "ArrowRight") { | |
skipAudio(recordingWaveform, 0.1); | |
} else if (e.key === "ArrowLeft") { | |
skipAudio(recordingWaveform, -0.1); | |
} | |
}); | |
}); | |
</script> | |
<div class="component-wrapper"> | |
<div id="microphone" data-testid="microphone-waveform" /> | |
{#if micWaveform && !recordedAudio} | |
<WaveformRecordControls bind:record {dispatch} /> | |
{/if} | |
{#if recordingWaveform && recordedAudio} | |
<WaveformControls | |
bind:waveform={recordingWaveform} | |
{container} | |
{playing} | |
{audioDuration} | |
{i18n} | |
interactive={true} | |
{handle_trim_audio} | |
bind:trimDuration | |
bind:mode | |
showRedo | |
{handle_reset_value} | |
{waveform_settings} | |
/> | |
{/if} | |
</div> | |
<style> | |
#microphone { | |
width: 100%; | |
display: none; | |
} | |
.component-wrapper { | |
padding: var(--size-3); | |
} | |
</style> | |