File size: 2,750 Bytes
426a708
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { SHYGUY_LABEL, SISTER_LABEL, GIRL_LABEL, BAR_LABEL, DJ_LABEL, WINGMAN_LABEL } from "./constants.js";
import { ELEVENLABS_API_KEY } from "../api.js";

export class ElevenLabsClient {
  constructor() {
    this.apiKey = ELEVENLABS_API_KEY;
    this.baseUrl = "https://api.elevenlabs.io/v1";
  }

  static characterToVoiceIdMapping = {
    [SHYGUY_LABEL]: "bGNROVfU5WbK6F0AyHII",
    [SISTER_LABEL]: "XA4HJz0cEOIlrQq0BGwu",
    [GIRL_LABEL]: "XA4HJz0cEOIlrQq0BGwu",
    [BAR_LABEL]: "XA2bIQ92TabjGbpO2xRr",
    [DJ_LABEL]: "T0pkYhIZ7UMOc26gqqeX",
    [WINGMAN_LABEL]: "XA2bIQ92TabjGbpO2xRr",
  };

  async playAudioForCharacter(character, text) {
    const voiceId = ElevenLabsClient.characterToVoiceIdMapping[character];
    if (!voiceId) {
      throw new Error(`No voice mapping found for character: ${character}`);
    }
    const audioBlob = await this.createSpeech({
      text: text,
      voiceId: voiceId,
    });
    const audioUrl = URL.createObjectURL(audioBlob);
    const audio = new Audio(audioUrl);

    // hack to wait for the audio to finish playing
    return new Promise((res) => {
      audio.play();
      audio.onended = res;
    });
  }

  async createSpeech({
    text,
    voiceId,
    modelId = "eleven_flash_v2",
    outputFormat = "mp3_44100_128",
    voiceSettings = null,
    pronunciationDictionaryLocators = null,
    seed = null,
    previousText = null,
    nextText = null,
    previousRequestIds = null,
    nextRequestIds = null,
    usePvcAsIvc = false,
    applyTextNormalization = "auto",
  }) {
    const url = `${this.baseUrl}/text-to-speech/${voiceId}?output_format=${outputFormat}`;

    const requestBody = {
      text,
      model_id: modelId,
      voice_settings: voiceSettings,
      pronunciation_dictionary_locators: pronunciationDictionaryLocators,
      seed,
      previous_text: previousText,
      next_text: nextText,
      previous_request_ids: previousRequestIds,
      next_request_ids: nextRequestIds,
      use_pvc_as_ivc: usePvcAsIvc,
      apply_text_normalization: applyTextNormalization,
    };

    // Remove null values from request body
    Object.keys(requestBody).forEach((key) => requestBody[key] === null && delete requestBody[key]);

    try {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "xi-api-key": this.apiKey,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(requestBody),
      });

      if (!response.ok) {
        throw new Error(`ElevenLabs API error: ${response.status} ${response.statusText}`);
      }

      // Return audio blob
      return await response.blob();
    } catch (error) {
      throw new Error(`Failed to create speech: ${error.message}`);
    }
  }
}