- index.html +81 -9
index.html
CHANGED
|
@@ -66,21 +66,30 @@
|
|
| 66 |
this.recordButton.addEventListener('click', () => this.toggleRecording());
|
| 67 |
|
| 68 |
// Replace with your Eleven Labs API key
|
| 69 |
-
this.ELEVEN_LABS_API_KEY = '
|
| 70 |
}
|
| 71 |
|
| 72 |
async setupMediaRecorder() {
|
| 73 |
try {
|
| 74 |
-
const stream = await navigator.mediaDevices.getUserMedia({
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
this.mediaRecorder.ondataavailable = (event) => {
|
| 78 |
this.audioChunks.push(event.data);
|
| 79 |
};
|
| 80 |
|
| 81 |
this.mediaRecorder.onstop = async () => {
|
| 82 |
-
const audioBlob = new Blob(this.audioChunks, { type: 'audio/
|
| 83 |
-
await this.
|
| 84 |
this.audioChunks = [];
|
| 85 |
};
|
| 86 |
|
|
@@ -117,22 +126,85 @@
|
|
| 117 |
this.recordButton.classList.remove('recording');
|
| 118 |
this.statusDiv.textContent = 'Processing audio...';
|
| 119 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
-
async sendToElevenLabs(
|
| 122 |
try {
|
| 123 |
const formData = new FormData();
|
| 124 |
-
formData.append('audio',
|
| 125 |
|
| 126 |
const response = await fetch('https://api.elevenlabs.io/v1/speech-to-text', {
|
| 127 |
method: 'POST',
|
| 128 |
headers: {
|
| 129 |
'xi-api-key': this.ELEVEN_LABS_API_KEY,
|
|
|
|
| 130 |
},
|
| 131 |
body: formData
|
| 132 |
});
|
| 133 |
|
| 134 |
if (!response.ok) {
|
| 135 |
-
|
|
|
|
| 136 |
}
|
| 137 |
|
| 138 |
const data = await response.json();
|
|
@@ -141,7 +213,7 @@
|
|
| 141 |
|
| 142 |
} catch (error) {
|
| 143 |
console.error('Error sending to Eleven Labs:', error);
|
| 144 |
-
this.statusDiv.textContent = 'Error
|
| 145 |
}
|
| 146 |
}
|
| 147 |
}
|
|
|
|
| 66 |
this.recordButton.addEventListener('click', () => this.toggleRecording());
|
| 67 |
|
| 68 |
// Replace with your Eleven Labs API key
|
| 69 |
+
this.ELEVEN_LABS_API_KEY = 'sk_d31f32025dda541dcd4f0aad95d3c7d1b31d36db7116dc1f';
|
| 70 |
}
|
| 71 |
|
| 72 |
async setupMediaRecorder() {
|
| 73 |
try {
|
| 74 |
+
const stream = await navigator.mediaDevices.getUserMedia({
|
| 75 |
+
audio: {
|
| 76 |
+
sampleRate: 16000,
|
| 77 |
+
channelCount: 1
|
| 78 |
+
}
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
this.mediaRecorder = new MediaRecorder(stream, {
|
| 82 |
+
mimeType: 'audio/webm',
|
| 83 |
+
audioBitsPerSecond: 128000
|
| 84 |
+
});
|
| 85 |
|
| 86 |
this.mediaRecorder.ondataavailable = (event) => {
|
| 87 |
this.audioChunks.push(event.data);
|
| 88 |
};
|
| 89 |
|
| 90 |
this.mediaRecorder.onstop = async () => {
|
| 91 |
+
const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });
|
| 92 |
+
await this.convertAndSend(audioBlob);
|
| 93 |
this.audioChunks = [];
|
| 94 |
};
|
| 95 |
|
|
|
|
| 126 |
this.recordButton.classList.remove('recording');
|
| 127 |
this.statusDiv.textContent = 'Processing audio...';
|
| 128 |
}
|
| 129 |
+
|
| 130 |
+
async convertAndSend(audioBlob) {
|
| 131 |
+
try {
|
| 132 |
+
// Convert to audio buffer
|
| 133 |
+
const arrayBuffer = await audioBlob.arrayBuffer();
|
| 134 |
+
const audioContext = new AudioContext();
|
| 135 |
+
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
| 136 |
+
|
| 137 |
+
// Create WAV file
|
| 138 |
+
const wavBuffer = this.audioBufferToWav(audioBuffer);
|
| 139 |
+
const wavBlob = new Blob([wavBuffer], { type: 'audio/wav' });
|
| 140 |
+
|
| 141 |
+
await this.sendToElevenLabs(wavBlob);
|
| 142 |
+
} catch (error) {
|
| 143 |
+
console.error('Error converting audio:', error);
|
| 144 |
+
this.statusDiv.textContent = 'Error converting audio. Please try again.';
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
audioBufferToWav(audioBuffer) {
|
| 149 |
+
const numberOfChannels = 1; // Mono
|
| 150 |
+
const sampleRate = 16000;
|
| 151 |
+
const format = 1; // PCM
|
| 152 |
+
const bitDepth = 16;
|
| 153 |
+
|
| 154 |
+
const length = audioBuffer.length * numberOfChannels * (bitDepth / 8);
|
| 155 |
+
const buffer = new ArrayBuffer(44 + length);
|
| 156 |
+
const view = new DataView(buffer);
|
| 157 |
+
|
| 158 |
+
// Write WAV header
|
| 159 |
+
const writeString = (view, offset, string) => {
|
| 160 |
+
for (let i = 0; i < string.length; i++) {
|
| 161 |
+
view.setUint8(offset + i, string.charCodeAt(i));
|
| 162 |
+
}
|
| 163 |
+
};
|
| 164 |
+
|
| 165 |
+
writeString(view, 0, 'RIFF');
|
| 166 |
+
view.setUint32(4, 36 + length, true);
|
| 167 |
+
writeString(view, 8, 'WAVE');
|
| 168 |
+
writeString(view, 12, 'fmt ');
|
| 169 |
+
view.setUint32(16, 16, true);
|
| 170 |
+
view.setUint16(20, format, true);
|
| 171 |
+
view.setUint16(22, numberOfChannels, true);
|
| 172 |
+
view.setUint32(24, sampleRate, true);
|
| 173 |
+
view.setUint32(28, sampleRate * numberOfChannels * (bitDepth / 8), true);
|
| 174 |
+
view.setUint16(32, numberOfChannels * (bitDepth / 8), true);
|
| 175 |
+
view.setUint16(34, bitDepth, true);
|
| 176 |
+
writeString(view, 36, 'data');
|
| 177 |
+
view.setUint32(40, length, true);
|
| 178 |
+
|
| 179 |
+
// Write audio data
|
| 180 |
+
const channelData = audioBuffer.getChannelData(0);
|
| 181 |
+
let offset = 44;
|
| 182 |
+
for (let i = 0; i < channelData.length; i++) {
|
| 183 |
+
const sample = Math.max(-1, Math.min(1, channelData[i]));
|
| 184 |
+
view.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true);
|
| 185 |
+
offset += 2;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
return buffer;
|
| 189 |
+
}
|
| 190 |
|
| 191 |
+
async sendToElevenLabs(wavBlob) {
|
| 192 |
try {
|
| 193 |
const formData = new FormData();
|
| 194 |
+
formData.append('audio', wavBlob, 'recording.wav');
|
| 195 |
|
| 196 |
const response = await fetch('https://api.elevenlabs.io/v1/speech-to-text', {
|
| 197 |
method: 'POST',
|
| 198 |
headers: {
|
| 199 |
'xi-api-key': this.ELEVEN_LABS_API_KEY,
|
| 200 |
+
'Accept': 'application/json'
|
| 201 |
},
|
| 202 |
body: formData
|
| 203 |
});
|
| 204 |
|
| 205 |
if (!response.ok) {
|
| 206 |
+
const errorText = await response.text();
|
| 207 |
+
throw new Error(`HTTP error! status: ${response.status}, ${errorText}`);
|
| 208 |
}
|
| 209 |
|
| 210 |
const data = await response.json();
|
|
|
|
| 213 |
|
| 214 |
} catch (error) {
|
| 215 |
console.error('Error sending to Eleven Labs:', error);
|
| 216 |
+
this.statusDiv.textContent = 'Error sending to Eleven Labs API. Please check your API key and try again.';
|
| 217 |
}
|
| 218 |
}
|
| 219 |
}
|