> 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 e cache com Supabase

> **Note**
>
> **Guia prático** · Pressupõe que você tenha concluído o [guia de início rápido da ElevenAPI ](/docs/pt/eleven-api/quickstart) e tenha uma conta no Supabase.

## Introdução

Neste guia, você vai aprender a criar uma API de borda para gerar, transmitir, armazenar e armazenar em cache áudio de fala usando Supabase Edge Functions, Supabase Storage e ElevenLabs.

## Requisitos

* Uma conta da ElevenLabs com uma [chave de API](https://elevenlabs.io/app/settings/api-keys).
* Uma conta no [Supabase](https://supabase.com) (você pode criar uma conta gratuita em [database.new](https://database.new)).
* A [CLI do Supabase](https://supabase.com/docs/guides/local-development) instalada na sua máquina.
* O [runtime Deno](https://docs.deno.com/runtime/getting_started/installation/) instalado na sua máquina e, opcionalmente, [configurado na sua IDE favorita](https://docs.deno.com/runtime/getting_started/setup_your_environment).

## Configuração

### Criar um projeto Supabase localmente

Depois de instalar a [CLI do Supabase](https://supabase.com/docs/guides/local-development), execute o comando a seguir para criar um novo projeto Supabase localmente:

```bash
supabase init
```

### Configurar o bucket de armazenamento

Você pode configurar a CLI do Supabase para gerar automaticamente um bucket de armazenamento adicionando esta configuração ao arquivo `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**
>
> Ao executar `supabase start`, um novo bucket de armazenamento será criado no seu projeto Supabase
> local. Se quiser enviar isso para seu projeto Supabase hospedado, execute `supabase seed
>   buckets --linked`.

### Configurar tarefas em segundo plano para Supabase Edge Functions

Para usar tarefas em segundo plano nas Supabase Edge Functions durante o desenvolvimento local, você precisa adicionar a seguinte configuração ao arquivo `config.toml`:

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

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

> **Note**
>
> Ao executar com a política `per_worker`, a função não será recarregada automaticamente após edições. Você precisará
> reiniciá-la manualmente executando `supabase functions serve`.

### Criar uma Supabase Edge Function para geração de fala

Crie uma nova Edge Function executando o seguinte comando:

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

Se você usa VS Code ou Cursor, selecione `y` quando a CLI perguntar "Generate VS Code settings for Deno? \[y/N]"!

### Configurar as variáveis de ambiente

No diretório `supabase/functions`, crie um novo arquivo `.env` e adicione as seguintes variáveis:

**`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
```

### Dependências

O projeto usa algumas dependências:

* A biblioteca [@supabase/supabase-js](https://supabase.com/docs/reference/javascript) para interagir com o banco de dados do Supabase.
* O [SDK JavaScript](/docs/pt/eleven-api/quickstart) da ElevenLabs para interagir com a API de Text to Speech.
* O [object-hash](https://www.npmjs.com/package/object-hash) de código aberto para gerar um hash a partir dos parâmetros da solicitação.

Como a Supabase Edge Function usa o [runtime Deno](https://deno.land/), você não precisa instalar as dependências; basta [importá-las](https://docs.deno.com/examples/npm/) pelo prefixo `npm:`.

## Programar a Supabase Edge Function

No arquivo `supabase/functions/text-to-speech/index.ts` que você acabou de criar, adicione o código a seguir:

**`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álise detalhada do código

Há alguns pontos importantes sobre o código. Vamos analisá-lo passo a passo.

#### Processar a solicitação recebida

Para processar a solicitação recebida, use o handler `Deno.serve`. Na demonstração, não validamos a origem da solicitação, mas você pode, por exemplo, validar a origem da solicitação ou adicionar um token de acesso do usuário e validá-lo com o [Supabase Auth](https://supabase.com/docs/guides/functions/auth).

A partir da solicitação recebida, a função extrai os parâmetros `text` e `voiceId`. O parâmetro `voiceId` é opcional e, por padrão, usa o ID da ElevenLabs para a voz "Allison".

Usando a biblioteca `object-hash`, a função gera um hash a partir dos parâmetros da solicitação. Esse hash é usado para verificar se há arquivos de áudio existentes no 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);

// ...
})
```

#### Verificar se há um arquivo de áudio existente no Supabase Storage

O Supabase Storage inclui uma [CDN inteligente integrada](https://supabase.com/docs/guides/storage/cdn/smart-cdn), permitindo armazenar em cache e disponibilizar seus arquivos facilmente.

Aqui, a função verifica se há um arquivo de áudio existente no Supabase Storage. Se o arquivo existir, a função o retorna do 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;
}
```

#### Gerar fala como stream e dividi-la em duas ramificações

Usando os recursos de streaming da API da ElevenLabs, a função gera um stream. A vantagem é que, mesmo para textos maiores, você pode começar a transmitir o áudio de volta ao usuário imediatamente e enviar o stream para o Supabase Storage em segundo plano.

Isso proporciona a melhor experiência possível ao usuário, fazendo com que até grandes blocos de texto pareçam surpreendentemente rápidos. A mágica acontece na linha 17, em que o método `stream.tee()` divide o stream legível em duas ramificações: uma para o navegador e outra para o 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" },
  });
}
```

#### Enviar o stream de áudio para o Supabase Storage em segundo plano

O método `EdgeRuntime.waitUntil` na linha 20 da etapa anterior é usado para enviar o stream de áudio ao Supabase Storage em segundo plano usando a função `uploadAudioToStorage`. Isso permite que a função retorne imediatamente a resposta em streaming ao navegador enquanto o áudio é enviado ao Supabase Storage.

Depois que o objeto de armazenamento for criado, na próxima vez que seus usuários fizerem uma solicitação com os mesmos parâmetros, a função retornará o arquivo de áudio da CDN do 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 });
}
```

## Executar localmente

Para executar a função localmente, execute os comandos a seguir:

```bash
supabase start
```

Quando a stack local do Supabase estiver em execução, execute o comando a seguir para iniciar a função e acompanhar os logs:

```bash
supabase functions serve
```

### Experimente

Acesse `http://127.0.0.1:54321/functions/v1/text-to-speech?text=hello%20world` para ouvir a função em ação.

Em seguida, acesse `http://127.0.0.1:54323/project/default/storage/buckets/audio` para ver o arquivo de áudio no seu bucket local do Supabase Storage.

## Implantar no Supabase

Se ainda não fez isso, crie uma nova conta no Supabase em [database.new](https://database.new) e vincule o projeto local à sua conta do Supabase:

```bash
supabase link
```

Depois, execute o comando a seguir para implantar a função:

```bash
supabase functions deploy
```

### Definir os segredos da função

Agora que todos os seus segredos estão configurados localmente, você pode executar o comando a seguir para defini-los no seu projeto Supabase:

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

## Testar a função

A função foi projetada para poder ser usada diretamente como fonte de um elemento `<audio>`.

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

## Próximas etapas

#### [Streaming de TTS](/docs/pt/eleven-api/guides/how-to/text-to-speech/streaming)

Transmita áudio progressivamente sem o Supabase como um ponto de partida mais simples.

#### [Referência da API](/docs/pt/api-reference/text-to-speech/stream)

Referência completa da API de streaming de TTS e seus parâmetros.