gradio / js /audio /player /AudioPlayer.svelte
mindmime's picture
Upload folder using huggingface_hub
a03b3ba verified
<script lang="ts">
import { onMount } from "svelte";
import { Music } from "@gradio/icons";
import type { I18nFormatter } from "@gradio/utils";
import WaveSurfer from "wavesurfer.js";
import { skip_audio, process_audio } from "../shared/utils";
import WaveformControls from "../shared/WaveformControls.svelte";
import { Empty } from "@gradio/atoms";
import { resolve_wasm_src } from "@gradio/wasm/svelte";
import type { FileData } from "@gradio/client";
import type { WaveformOptions } from "../shared/types";
import { createEventDispatcher } from "svelte";
export let value: null | FileData = null;
$: url = value?.url;
export let label: string;
export let i18n: I18nFormatter;
export let dispatch_blob: (
blobs: Uint8Array[] | Blob[],
event: "stream" | "change" | "stop_recording"
) => Promise<void> = () => Promise.resolve();
export let interactive = false;
export let editable = true;
export let trim_region_settings = {};
export let waveform_settings: Record<string, any>;
export let waveform_options: WaveformOptions;
export let mode = "";
export let handle_reset_value: () => void = () => {};
let container: HTMLDivElement;
let waveform: WaveSurfer | undefined;
let playing = false;
let timeRef: HTMLTimeElement;
let durationRef: HTMLTimeElement;
let audio_duration: number;
let trimDuration = 0;
let show_volume_slider = false;
const dispatch = createEventDispatcher<{
stop: undefined;
play: undefined;
pause: undefined;
edit: undefined;
end: undefined;
}>();
const formatTime = (seconds: number): string => {
const minutes = Math.floor(seconds / 60);
const secondsRemainder = Math.round(seconds) % 60;
const paddedSeconds = `0${secondsRemainder}`.slice(-2);
return `${minutes}:${paddedSeconds}`;
};
const create_waveform = (): void => {
waveform = WaveSurfer.create({
container: container,
...waveform_settings
});
resolve_wasm_src(value?.url).then((resolved_src) => {
if (resolved_src && waveform) {
return waveform.load(resolved_src);
}
});
};
$: if (container !== undefined) {
if (waveform !== undefined) waveform.destroy();
container.innerHTML = "";
create_waveform();
playing = false;
}
$: waveform?.on("decode", (duration: any) => {
audio_duration = duration;
durationRef && (durationRef.textContent = formatTime(duration));
});
$: waveform?.on(
"timeupdate",
(currentTime: any) =>
timeRef && (timeRef.textContent = formatTime(currentTime))
);
$: waveform?.on("ready", () => {
if (!waveform_settings.autoplay) {
waveform?.stop();
} else {
waveform?.play();
}
});
$: waveform?.on("finish", () => {
playing = false;
dispatch("stop");
});
$: waveform?.on("pause", () => {
playing = false;
dispatch("pause");
});
$: waveform?.on("play", () => {
playing = true;
dispatch("play");
});
const handle_trim_audio = async (
start: number,
end: number
): Promise<void> => {
mode = "";
const decodedData = waveform?.getDecodedData();
if (decodedData)
await process_audio(
decodedData,
start,
end,
waveform_settings.sampleRate
).then(async (trimmedBlob: Uint8Array) => {
await dispatch_blob([trimmedBlob], "change");
waveform?.destroy();
container.innerHTML = "";
});
dispatch("edit");
};
async function load_audio(data: string): Promise<void> {
await resolve_wasm_src(data).then((resolved_src) => {
if (!resolved_src || value?.is_stream) return;
return waveform?.load(resolved_src);
});
}
$: url && load_audio(url);
onMount(() => {
window.addEventListener("keydown", (e) => {
if (!waveform || show_volume_slider) return;
if (e.key === "ArrowRight" && mode !== "edit") {
skip_audio(waveform, 0.1);
} else if (e.key === "ArrowLeft" && mode !== "edit") {
skip_audio(waveform, -0.1);
}
});
});
</script>
{#if value === null}
<Empty size="small">
<Music />
</Empty>
{:else if value.is_stream}
<audio
class="standard-player"
src={value.url}
controls
autoplay={waveform_settings.autoplay}
/>
{:else}
<div
class="component-wrapper"
data-testid={label ? "waveform-" + label : "unlabelled-audio"}
>
<div class="waveform-container">
<div id="waveform" bind:this={container} />
</div>
<div class="timestamps">
<time bind:this={timeRef} id="time">0:00</time>
<div>
{#if mode === "edit" && trimDuration > 0}
<time id="trim-duration">{formatTime(trimDuration)}</time>
{/if}
<time bind:this={durationRef} id="duration">0:00</time>
</div>
</div>
{#if waveform}
<WaveformControls
{container}
{waveform}
{playing}
{audio_duration}
{i18n}
{interactive}
{handle_trim_audio}
bind:mode
bind:trimDuration
bind:show_volume_slider
showRedo={interactive}
{handle_reset_value}
{waveform_options}
{trim_region_settings}
{editable}
/>
{/if}
</div>
{/if}
<style>
.component-wrapper {
padding: var(--size-3);
width: 100%;
}
:global(::part(wrapper)) {
margin-bottom: var(--size-2);
}
.timestamps {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: var(--size-1) 0;
}
#time {
color: var(--neutral-400);
}
#duration {
color: var(--neutral-400);
}
#trim-duration {
color: var(--color-accent);
margin-right: var(--spacing-sm);
}
.waveform-container {
display: flex;
align-items: center;
justify-content: center;
width: var(--size-full);
}
#waveform {
width: 100%;
height: 100%;
position: relative;
}
.standard-player {
width: 100%;
padding: var(--size-2);
}
</style>