> 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 et mise en cache avec Supabase

> **Note**
>
> **Guide pratique** · Suppose que vous avez terminé le [guide de démarrage rapide ElevenAPI](/docs/fr/eleven-api/quickstart) et que vous disposez d'un compte Supabase.

## Introduction

Dans ce guide, vous apprendrez à créer une API edge pour générer, diffuser en streaming, stocker et mettre en cache de la parole avec les Edge Functions de Supabase, Supabase Storage et ElevenLabs.

## Prérequis

* Un compte ElevenLabs avec une [clé API](https://elevenlabs.io/app/settings/api-keys).
* Un compte [Supabase](https://supabase.com) ; vous pouvez créer un compte gratuit via [database.new](https://database.new).
* La [CLI Supabase](https://supabase.com/docs/guides/local-development) installée sur votre machine.
* Le [runtime Deno](https://docs.deno.com/runtime/getting_started/installation/) installé sur votre machine et, en option, [configuré dans votre IDE favori](https://docs.deno.com/runtime/getting_started/setup_your_environment).

## Configuration

### Créer un projet Supabase localement

Après avoir installé la [CLI Supabase](https://supabase.com/docs/guides/local-development), exécutez la commande suivante pour créer un projet Supabase localement :

```bash
supabase init
```

### Configurer le bucket de stockage

Vous pouvez configurer la CLI Supabase pour générer automatiquement un bucket de stockage en ajoutant cette configuration au fichier `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**
>
> L'exécution de `supabase start` crée un bucket de stockage dans votre projet Supabase
> local. Pour le transférer vers votre projet Supabase hébergé, exécutez `supabase seed
>   buckets --linked`.

### Configurer les tâches en arrière-plan pour les Edge Functions de Supabase

Pour utiliser des tâches en arrière-plan dans les Edge Functions de Supabase lors du développement local, ajoutez la configuration suivante au fichier `config.toml` :

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

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

> **Note**
>
> Avec la politique `per_worker`, la Function ne se recharge pas automatiquement après les modifications. Vous devez
> la redémarrer manuellement en exécutant `supabase functions serve`.

### Créer une Edge Function Supabase pour la génération de parole

Créez une nouvelle Edge Function en exécutant la commande suivante :

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

Si vous utilisez VS Code ou Cursor, sélectionnez `y` lorsque la CLI vous invite avec « Generate VS Code settings for Deno? \[y/N] ».

### Configurer les variables d'environnement

Dans le répertoire `supabase/functions`, créez un fichier `.env` et ajoutez les variables suivantes :

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

### Dépendances

Le projet utilise quelques dépendances :

* La bibliothèque [@supabase/supabase-js](https://supabase.com/docs/reference/javascript) pour interagir avec la base de données Supabase.
* Le [SDK JavaScript](/docs/fr/eleven-api/quickstart) ElevenLabs pour interagir avec l'API de synthèse vocale.
* La bibliothèque open source [object-hash](https://www.npmjs.com/package/object-hash) pour générer un hash à partir des paramètres de requête.

Comme les Edge Functions de Supabase utilisent le [runtime Deno](https://deno.land/), vous n'avez pas besoin d'installer les dépendances : vous pouvez les [importer](https://docs.deno.com/examples/npm/) via le préfixe `npm:`.

## Programmer l'Edge Function Supabase

Dans le fichier `supabase/functions/text-to-speech/index.ts` que vous venez de créer, ajoutez le code suivant :

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

### Analyse détaillée du code

Quelques éléments du code méritent d'être soulignés. Examinons-les étape par étape.

#### Gérer la requête entrante

Pour gérer la requête entrante, utilisez le gestionnaire `Deno.serve`. Dans la démonstration, nous ne validons pas l'origine de la requête, mais vous pouvez par exemple la valider ou ajouter un jeton d'accès utilisateur et le valider avec [Supabase Auth](https://supabase.com/docs/guides/functions/auth).

À partir de la requête entrante, la fonction extrait les paramètres `text` et `voiceId`. Le paramètre `voiceId` est facultatif et utilise par défaut l'ID ElevenLabs de la voix « Allison ».

À l'aide de la bibliothèque `object-hash`, la fonction génère un hash à partir des paramètres de requête. Ce hash sert à vérifier l'existence de fichiers audio dans 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);

// ...
})
```

#### Vérifier l'existence d'un fichier audio dans Supabase Storage

Supabase Storage intègre un [CDN intelligent](https://supabase.com/docs/guides/storage/cdn/smart-cdn) qui vous permet de mettre facilement vos fichiers en cache et de les diffuser.

Ici, la fonction vérifie l'existence d'un fichier audio dans Supabase Storage. Si le fichier existe, elle le renvoie depuis 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;
}
```

#### Générer la parole sous forme de flux et la diviser en deux branches

Grâce aux capacités de streaming de l'API ElevenLabs, la fonction génère un flux. Même pour des textes plus longs, vous pouvez ainsi commencer immédiatement à diffuser l'audio vers votre utilisateur, puis téléverser le flux vers Supabase Storage en arrière-plan.

Vous offrez ainsi une expérience utilisateur optimale, même les longs blocs de texte paraissant très rapides. Cela se produit à la ligne 17, où la méthode `stream.tee()` divise le flux lisible en deux branches : une pour le navigateur et une pour 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" },
  });
}
```

#### Téléverser le flux audio vers Supabase Storage en arrière-plan

La méthode `EdgeRuntime.waitUntil` de la ligne 20 de l'étape précédente téléverse le flux audio vers Supabase Storage en arrière-plan à l'aide de la fonction `uploadAudioToStorage`. La fonction peut ainsi renvoyer immédiatement la réponse en streaming au navigateur pendant que l'audio est téléversé vers Supabase Storage.

Une fois l'objet de stockage créé, la prochaine fois que vos utilisateurs effectueront une requête avec les mêmes paramètres, la fonction renverra le fichier audio depuis le 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 });
}
```

## Exécuter localement

Pour exécuter la fonction localement, lancez les commandes suivantes :

```bash
supabase start
```

Une fois la pile Supabase locale opérationnelle, exécutez la commande suivante pour démarrer la fonction et consulter les journaux :

```bash
supabase functions serve
```

### Essayer

Accédez à `http://127.0.0.1:54321/functions/v1/text-to-speech?text=hello%20world` pour entendre la fonction en action.

Accédez ensuite à `http://127.0.0.1:54323/project/default/storage/buckets/audio` pour voir le fichier audio dans votre bucket Supabase Storage local.

## Déployer sur Supabase

Si ce n'est pas déjà fait, créez un compte Supabase sur [database.new](https://database.new), puis liez le projet local à votre compte Supabase :

```bash
supabase link
```

Une fois terminé, exécutez la commande suivante pour déployer la fonction :

```bash
supabase functions deploy
```

### Définir les secrets de la fonction

Maintenant que tous vos secrets sont configurés localement, vous pouvez exécuter la commande suivante pour les définir dans votre projet Supabase :

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

## Tester la fonction

La fonction est conçue pour être utilisée directement comme source d'un élément `<audio>`.

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

## Prochaines étapes

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

Diffusez l'audio progressivement sans Supabase pour commencer plus simplement.

#### [Guide de l'API](/docs/fr/api-reference/text-to-speech/stream)

Guide complet de l'API de streaming TTS et de ses paramètres.