Hoppa till navigering

Streaming och cachning med Supabase

Generera och streama tal via Supabase Edge Functions. Lagra tal i Supabase Storage och cacha svar via inbyggt CDN.

Guide · Förutsätter att du har slutfört ElevenAPI- snabbstarten och har ett Supabase-konto.

Introduktion

I den här guiden lär du dig att bygga ett edge-API för att generera, streama, lagra och cacha tal med Supabase Edge Functions, Supabase Storage och ElevenLabs.

Krav

Konfiguration

Skapa ett Supabase-projekt lokalt

När du har installerat Supabase CLI kör du följande kommando för att skapa ett nytt Supabase-projekt lokalt:

supabase init

Konfigurera lagrings-bucketen

Du kan konfigurera Supabase CLI så att den automatiskt skapar en lagrings-bucket genom att lägga till följande konfiguration i filen config.toml:

./supabase/config.toml
[storage.buckets.audio]
public = false
file_size_limit = "50MiB"
allowed_mime_types = ["audio/mp3"]
objects_path = "./audio"

När du kör supabase start skapas en ny lagrings-bucket i ditt lokala Supabase- projekt. Om du vill skicka detta till ditt hostade Supabase-projekt kan du köra supabase seed buckets --linked.

Konfigurera bakgrundsaktiviteter för Supabase Edge Functions

För att använda bakgrundsaktiviteter i Supabase Edge Functions vid lokal utveckling behöver du lägga till följande konfiguration i filen config.toml:

./supabase/config.toml
[edge_runtime]
policy = "per_worker"

När du kör med policyn per_worker laddas Function inte om automatiskt vid ändringar. Du måste starta om den manuellt genom att köra supabase functions serve.

Skapa en Supabase Edge Function för talgenerering

Skapa en ny Edge Function genom att köra följande kommando:

supabase functions new text-to-speech

Om du använder VS Code eller Cursor väljer du y när CLI frågar “Generate VS Code settings for Deno? [y/N]”!

Konfigurera miljövariablerna

Skapa en ny .env-fil i katalogen supabase/functions och lägg till följande variabler:

supabase/functions/.env
# Find / create an API key at https://elevenlabs.io/app/settings/api-keys
ELEVENLABS_API_KEY=your_api_key

Beroenden

Projektet använder ett par beroenden:

  • Biblioteket @supabase/supabase-js för att interagera med Supabase-databasen.
  • ElevenLabs JavaScript SDK för att interagera med text-till-tal-API:et.
  • Open source-biblioteket object-hash för att generera en hash från förfrågningsparametrarna.

Eftersom Supabase Edge Function använder Deno runtime behöver du inte installera beroendena. Du kan i stället importera dem med prefixet npm:.

Koda Supabase Edge Function

Lägg till följande kod i den nyligen skapade filen supabase/functions/text-to-speech/index.ts:

supabase/functions/text-to-speech/index.ts
// Setup type definitions for built-in Supabase Runtime APIs
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "jsr:@supabase/supabase-js@2";
import { ElevenLabsClient } from "npm:elevenlabs";
import * as hash from "npm:object-hash";
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);
const elevenlabs = new ElevenLabsClient({
apiKey: Deno.env.get("ELEVENLABS_API_KEY"),
});
// Upload audio to Supabase Storage in a background task
async function uploadAudioToStorage(stream: ReadableStream, requestHash: string) {
const { data, error } = await supabase.storage
.from("audio")
.upload(`${requestHash}.mp3`, stream, {
contentType: "audio/mp3",
});
console.log("Storage upload result", { data, error });
}
Deno.serve(async (req) => {
// To secure your function for production, you can for example validate the request origin,
// or append a user access token and validate it with Supabase Auth.
console.log("Request origin", req.headers.get("host"));
const url = new URL(req.url);
const params = new URLSearchParams(url.search);
const text = params.get("text");
const voiceId = params.get("voiceId") ?? "JBFqnCBsd6RMkjVDRZzb";
const requestHash = hash.MD5({ text, voiceId });
console.log("Request hash", requestHash);
// Check storage for existing audio file
const { data } = await supabase.storage.from("audio").createSignedUrl(`${requestHash}.mp3`, 60);
if (data) {
console.log("Audio file found in storage", data);
const storageRes = await fetch(data.signedUrl);
if (storageRes.ok) return storageRes;
}
if (!text) {
return new Response(JSON.stringify({ error: "Text parameter is required" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
try {
console.log("ElevenLabs API call");
const response = await elevenlabs.textToSpeech.stream(voiceId, {
output_format: "mp3_44100_128",
model_id: "eleven_multilingual_v2",
text,
});
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of response) {
controller.enqueue(chunk);
}
controller.close();
},
});
// Branch stream to Supabase Storage
const [browserStream, storageStream] = stream.tee();
// Upload to Supabase Storage in the background
EdgeRuntime.waitUntil(uploadAudioToStorage(storageStream, requestHash));
// Return the streaming response immediately
return new Response(browserStream, {
headers: {
"Content-Type": "audio/mpeg",
},
});
} catch (error) {
console.log("error", { error });
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
});

Djupdykning i koden

Det finns ett par saker i koden som är värda att notera. Vi går igenom den steg för steg.

1

Hantera den inkommande förfrågan

Använd hanteraren Deno.serve för att hantera den inkommande förfrågan. I demon validerar vi inte förfrågans ursprung, men du kan till exempel validera ursprunget eller lägga till en användaråtkomsttoken och validera den med Supabase Auth.

Från den inkommande förfrågan extraherar funktionen parametrarna text och voiceId. Parametern voiceId är valfri och använder som standard ElevenLabs-ID:t för rösten “Allison”.

Med biblioteket object-hash genererar funktionen en hash från förfrågningsparametrarna. Den här hashen används för att kontrollera om det finns befintliga ljudfiler i Supabase Storage.

Deno.serve(async (req) => {
// To secure your function for production, you can for example validate the request origin,
// or append a user access token and validate it with Supabase Auth.
console.log("Request origin", req.headers.get("host"));
const url = new URL(req.url);
const params = new URLSearchParams(url.search);
const text = params.get("text");
const voiceId = params.get("voiceId") ?? "JBFqnCBsd6RMkjVDRZzb";
const requestHash = hash.MD5({ text, voiceId });
console.log("Request hash", requestHash);
// ...
})
2

Kontrollera om det finns en befintlig ljudfil i Supabase Storage

Supabase Storage har ett smart CDN inbyggt som gör det enkelt att cacha och leverera dina filer.

Här kontrollerar funktionen om det finns en befintlig ljudfil i Supabase Storage. Om filen finns returnerar funktionen filen från Supabase Storage.

const { data } = await supabase
.storage
.from("audio")
.createSignedUrl(`${requestHash}.mp3`, 60);
if (data) {
console.log("Audio file found in storage", data);
const storageRes = await fetch(data.signedUrl);
if (storageRes.ok) return storageRes;
}
3

Generera tal som en ström och dela upp den i två grenar

Med streamingfunktionerna i ElevenLabs API genererar funktionen en ström. Fördelen är att du kan börja streama ljudet tillbaka till användaren direkt, även för längre text, och sedan ladda upp strömmen till Supabase Storage i bakgrunden.

Det ger bästa möjliga användarupplevelse och gör att även stora textblock känns magiskt snabba. Det sker på rad 17, där metoden stream.tee() delar upp läsströmmen i två grenar: en för webbläsaren och en för Supabase Storage.

try {
const response = await elevenlabs.textToSpeech.stream(voiceId, {
output_format: "mp3_44100_128",
model_id: "eleven_multilingual_v2",
text,
});
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of response) {
controller.enqueue(chunk);
}
controller.close();
},
});
// Branch stream to Supabase Storage
const [browserStream, storageStream] = stream.tee();
// Upload to Supabase Storage in the background
EdgeRuntime.waitUntil(uploadAudioToStorage(storageStream, requestHash));
// Return the streaming response immediately
return new Response(browserStream, {
headers: {
"Content-Type": "audio/mpeg",
},
});
} catch (error) {
console.log("error", { error });
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
4

Ladda upp ljudströmmen till Supabase Storage i bakgrunden

Metoden EdgeRuntime.waitUntil på rad 20 i föregående steg används för att ladda upp ljudströmmen till Supabase Storage i bakgrunden med funktionen uploadAudioToStorage. Det gör att funktionen kan returnera streamingsvaret direkt till webbläsaren medan ljudet laddas upp till Supabase Storage.

När lagringsobjektet har skapats kommer funktionen nästa gång dina användare gör en förfrågan med samma parametrar att returnera ljudfilen från Supabase Storage CDN.

// Upload audio to Supabase Storage in a background task
async function uploadAudioToStorage(
stream: ReadableStream,
requestHash: string,
) {
const { data, error } = await supabase.storage
.from("audio")
.upload(`${requestHash}.mp3`, stream, {
contentType: "audio/mp3",
});
console.log("Storage upload result", { data, error });
}

Kör lokalt

Kör följande kommandon för att köra funktionen lokalt:

supabase start

När den lokala Supabase-stacken är igång kör du följande kommando för att starta funktionen och se loggarna:

supabase functions serve

Prova själv

Gå till http://127.0.0.1:54321/functions/v1/text-to-speech?text=hello%20world för att höra funktionen i praktiken.

Gå sedan till http://127.0.0.1:54323/project/default/storage/buckets/audio för att se ljudfilen i din lokala Supabase Storage-bucket.

Distribuera till Supabase

Om du inte redan har gjort det skapar du ett nytt Supabase-konto på database.new och länkar det lokala projektet till ditt Supabase-konto:

supabase link

Kör sedan följande kommando för att distribuera funktionen:

supabase functions deploy

Ange funktionens hemligheter

Nu när du har angett alla hemligheter lokalt kan du köra följande kommando för att ange hemligheterna i ditt Supabase-projekt:

supabase secrets set --env-file supabase/functions/.env

Testa funktionen

Funktionen är utformad så att den kan användas direkt som källa för ett <audio>-element.

<audio
src="https://${SUPABASE_PROJECT_REF}.supabase.co/functions/v1/text-to-speech?text=Hello%2C%20world!&voiceId=JBFqnCBsd6RMkjVDRZzb"
controls
/>

Nästa steg