> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Streaming y caché con Supabase

> **Note**
>
> **Guía práctica** · Da por hecho que has completado la [guía de inicio rápido de ElevenAPI](/docs/es/eleven-api/quickstart) y que tienes una cuenta de Supabase.

## Introducción

En esta guía aprenderás a crear una API perimetral para generar, transmitir, almacenar y guardar en caché voz mediante Supabase Edge Functions, Supabase Storage y ElevenLabs.

## Requisitos

* Una cuenta de ElevenLabs con una [clave de API](https://elevenlabs.io/app/settings/api-keys).
* Una cuenta de [Supabase](https://supabase.com) (puedes registrarte gratis a través de [database.new](https://database.new)).
* La [CLI de Supabase](https://supabase.com/docs/guides/local-development) instalada en tu equipo.
* El [runtime de Deno](https://docs.deno.com/runtime/getting_started/installation/) instalado en tu equipo y, opcionalmente, [configurado en tu IDE favorito](https://docs.deno.com/runtime/getting_started/setup_your_environment).

## Configuración

### Crea un proyecto de Supabase localmente

Después de instalar la [CLI de Supabase](https://supabase.com/docs/guides/local-development), ejecuta el siguiente comando para crear un proyecto de Supabase localmente:

```bash
supabase init
```

### Configura el bucket de almacenamiento

Puedes configurar la CLI de Supabase para generar automáticamente un bucket de almacenamiento añadiendo esta configuración al archivo `config.toml`:

**`./supabase/config.toml`**

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

> **Note**
>
> Al ejecutar `supabase start`, se creará un nuevo bucket de almacenamiento en tu proyecto local de Supabase. Si quieres enviar esto a tu proyecto de Supabase alojado, puedes ejecutar `supabase seed
>   buckets --linked`.

### Configura tareas en segundo plano para Supabase Edge Functions

Para usar tareas en segundo plano en Supabase Edge Functions durante el desarrollo local, debes añadir la siguiente configuración al archivo `config.toml`:

**`./supabase/config.toml`**

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

> **Note**
>
> Al ejecutar con la política `per_worker`, Function no se recargará automáticamente al editarla. Tendrás que reiniciarla manualmente ejecutando `supabase functions serve`.

### Crea una Supabase Edge Function para generar voz

Crea una nueva Edge Function ejecutando el siguiente comando:

```bash
supabase functions new text-to-speech
```

Si utilizas VS Code o Cursor, selecciona `y` cuando la CLI te pregunte «Generate VS Code settings for Deno? \[y/N]».

### Configura las variables de entorno

En el directorio `supabase/functions`, crea un archivo `.env` nuevo y añade las siguientes variables:

**`supabase/functions/.env`**

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

### Dependencias

El proyecto utiliza un par de dependencias:

* La biblioteca [@supabase/supabase-js](https://supabase.com/docs/reference/javascript) para interactuar con la base de datos de Supabase.
* El [SDK de JavaScript](/docs/es/eleven-api/quickstart) de ElevenLabs para interactuar con la API de Texto a Voz.
* El proyecto de código abierto [object-hash](https://www.npmjs.com/package/object-hash) para generar un hash a partir de los parámetros de la solicitud.

Como Supabase Edge Function usa el [runtime de Deno](https://deno.land/), no necesitas instalar las dependencias: puedes [importarlas](https://docs.deno.com/examples/npm/) mediante el prefijo `npm:`.

## Programa la Supabase Edge Function

En el archivo `supabase/functions/text-to-speech/index.ts` que acabas de crear, añade el siguiente código:

**`supabase/functions/text-to-speech/index.ts`**

```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" },
    });
  }
});
```

### Análisis detallado del código

Hay un par de aspectos del código que merece la pena destacar. Vamos a verlo paso a paso.

#### Gestiona la solicitud entrante

Para gestionar la solicitud entrante, usa el controlador `Deno.serve`. En la demo no validamos el origen de la solicitud, pero puedes, por ejemplo, validarlo o añadir un token de acceso de usuario y validarlo con [Supabase Auth](https://supabase.com/docs/guides/functions/auth).

A partir de la solicitud entrante, la función extrae los parámetros `text` y `voiceId`. El parámetro `voiceId` es opcional y, de forma predeterminada, usa el ID de ElevenLabs de la voz «Allison».

Mediante la biblioteca `object-hash`, la función genera un hash a partir de los parámetros de la solicitud. Este hash se usa para comprobar si existen archivos de audio en Supabase Storage.

```ts {1,5-8}
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);

// ...
})
```

#### Comprueba si existe un archivo de audio en Supabase Storage

Supabase Storage incluye una [CDN inteligente integrada](https://supabase.com/docs/guides/storage/cdn/smart-cdn) que te permite guardar en caché y servir fácilmente tus archivos.

Aquí, la función comprueba si existe un archivo de audio en Supabase Storage. Si el archivo existe, la función lo devuelve desde Supabase Storage.

```ts {4,9}
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;
}
```

#### Genera voz como flujo y divídelo en dos ramas

Mediante las capacidades de transmisión de la API de ElevenLabs, la función genera un flujo. La ventaja es que, incluso con textos más largos, puedes empezar a transmitir el audio a tu usuario de inmediato y subir el flujo a Supabase Storage en segundo plano.

Esto ofrece la mejor experiencia posible a usuarios y hace que incluso los bloques de texto grandes parezcan increíblemente rápidos. La magia ocurre en la línea 17, donde el método `stream.tee()` divide el flujo legible en dos ramas: una para el navegador y otra para Supabase Storage.

```ts {1,17,20,22-27}
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" },
  });
}
```

#### Sube el flujo de audio a Supabase Storage en segundo plano

El método `EdgeRuntime.waitUntil` de la línea 20 del paso anterior se usa para subir el flujo de audio a Supabase Storage en segundo plano mediante la función `uploadAudioToStorage`. Esto permite que la función devuelva la respuesta en transmisión al navegador inmediatamente mientras el audio se sube a Supabase Storage.

Una vez creado el objeto de almacenamiento, la próxima vez que tus usuarios hagan una solicitud con los mismos parámetros, la función devolverá el archivo de audio desde la CDN de Supabase Storage.

```ts {2,8-10}
// 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 });
}
```

## Ejecuta localmente

Para ejecutar la función localmente, usa los siguientes comandos:

```bash
supabase start
```

Cuando el stack local de Supabase esté en funcionamiento, ejecuta el siguiente comando para iniciar la función y ver los registros:

```bash
supabase functions serve
```

### Pruébalo

Ve a `http://127.0.0.1:54321/functions/v1/text-to-speech?text=hello%20world` para escuchar la función en acción.

Después, ve a `http://127.0.0.1:54323/project/default/storage/buckets/audio` para ver el archivo de audio en tu bucket local de Supabase Storage.

## Despliega en Supabase

Si aún no lo has hecho, crea una cuenta nueva de Supabase en [database.new](https://database.new) y vincula el proyecto local a tu cuenta de Supabase:

```bash
supabase link
```

Cuando termines, ejecuta el siguiente comando para desplegar la función:

```bash
supabase functions deploy
```

### Configura los secretos de la función

Ahora que ya tienes todos los secretos configurados localmente, puedes ejecutar el siguiente comando para configurarlos en tu proyecto de Supabase:

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

## Prueba la función

La función está diseñada para poder usarse directamente como fuente de un elemento `<audio>`.

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

## Próximos pasos

#### [Transmisión de TTS](/docs/es/eleven-api/guides/how-to/text-to-speech/streaming)

Transmite audio de forma progresiva sin Supabase como punto de partida más sencillo.

#### [Referencia de la API](/docs/es/api-reference/text-to-speech/stream)

Referencia completa y parámetros de la API de transmisión de TTS.