Skip to content

Commit cdf77ef

Browse files
authored
1 parent 148bc12 commit cdf77ef

File tree

1 file changed

+282
-0
lines changed

1 file changed

+282
-0
lines changed

openai-audio.html

+282
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>OpenAI Audio</title>
7+
<style>
8+
body {
9+
font-family: Arial, sans-serif;
10+
display: flex;
11+
justify-content: center;
12+
align-items: center;
13+
min-height: 100vh;
14+
margin: 0;
15+
background-color: #f0f0f0;
16+
}
17+
.container {
18+
text-align: center;
19+
background-color: white;
20+
padding: 2rem;
21+
border-radius: 8px;
22+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
23+
max-width: 600px;
24+
width: 100%;
25+
}
26+
button {
27+
font-size: 1rem;
28+
padding: 0.5rem 1rem;
29+
margin: 0.5rem;
30+
cursor: pointer;
31+
}
32+
#timer {
33+
font-size: 1.5rem;
34+
margin: 1rem 0;
35+
}
36+
#audioPlayback, #prompt {
37+
margin-top: 1rem;
38+
width: 100%;
39+
}
40+
#prompt {
41+
height: 100px;
42+
resize: vertical;
43+
}
44+
#apiResponse {
45+
margin-top: 1rem;
46+
text-align: left;
47+
white-space: pre-wrap;
48+
background-color: #f8f8f8;
49+
padding: 1rem;
50+
border-radius: 4px;
51+
overflow-x: auto;
52+
}
53+
</style>
54+
</head>
55+
<body>
56+
<div class="container">
57+
<h1>OpenAI Audio</h1>
58+
<button id="recordButton">Start Recording</button>
59+
<div id="timer">00:00</div>
60+
<audio id="audioPlayback" controls></audio>
61+
<textarea id="prompt" placeholder="Enter your prompt here"></textarea>
62+
<button id="submitButton">Submit to API</button>
63+
<div id="apiResponse"></div>
64+
</div>
65+
66+
<script>
67+
let audioContext;
68+
let recorder;
69+
let audioChunks = [];
70+
let startTime;
71+
let timerInterval;
72+
let audioBlob;
73+
let isRecording = false;
74+
const recordButton = document.getElementById('recordButton');
75+
const timer = document.getElementById('timer');
76+
const audioPlayback = document.getElementById('audioPlayback');
77+
const promptTextarea = document.getElementById('prompt');
78+
const submitButton = document.getElementById('submitButton');
79+
const apiResponse = document.getElementById('apiResponse');
80+
81+
recordButton.addEventListener('click', toggleRecording);
82+
submitButton.addEventListener('click', submitToAPI);
83+
84+
async function toggleRecording() {
85+
if (!isRecording) {
86+
await startRecording();
87+
} else {
88+
stopRecording();
89+
}
90+
}
91+
92+
async function startRecording() {
93+
try {
94+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
95+
audioContext = new (window.AudioContext || window.webkitAudioContext)();
96+
const source = audioContext.createMediaStreamSource(stream);
97+
const processor = audioContext.createScriptProcessor(1024, 1, 1);
98+
99+
source.connect(processor);
100+
processor.connect(audioContext.destination);
101+
102+
audioChunks = [];
103+
104+
processor.onaudioprocess = (e) => {
105+
const inputData = e.inputBuffer.getChannelData(0);
106+
audioChunks.push(new Float32Array(inputData));
107+
};
108+
109+
isRecording = true;
110+
startTime = Date.now();
111+
updateTimer();
112+
timerInterval = setInterval(updateTimer, 1000);
113+
recordButton.textContent = 'Stop Recording';
114+
} catch (error) {
115+
console.error('Error starting recording:', error);
116+
alert('Error starting recording. Please make sure you have given permission to use the microphone.');
117+
}
118+
}
119+
120+
function stopRecording() {
121+
if (audioContext) {
122+
audioContext.close();
123+
audioContext = null;
124+
}
125+
clearInterval(timerInterval);
126+
recordButton.textContent = 'Start Recording';
127+
isRecording = false;
128+
129+
// Convert to WAV
130+
const wavBlob = createWavBlob(audioChunks);
131+
audioBlob = wavBlob;
132+
133+
const audioUrl = URL.createObjectURL(audioBlob);
134+
audioPlayback.src = audioUrl;
135+
}
136+
137+
function updateTimer() {
138+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
139+
const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0');
140+
const seconds = (elapsed % 60).toString().padStart(2, '0');
141+
timer.textContent = `${minutes}:${seconds}`;
142+
}
143+
144+
function createWavBlob(audioChunks) {
145+
const sampleRate = 44100;
146+
const numChannels = 1;
147+
const bitsPerSample = 16;
148+
const bytesPerSample = bitsPerSample / 8;
149+
const blockAlign = numChannels * bytesPerSample;
150+
151+
const buffer = mergeAudioBuffers(audioChunks);
152+
const dataLength = buffer.length * bytesPerSample;
153+
const wavDataLength = 36 + dataLength;
154+
155+
const headerBuffer = new ArrayBuffer(44);
156+
const view = new DataView(headerBuffer);
157+
158+
writeString(view, 0, 'RIFF');
159+
view.setUint32(4, wavDataLength, true);
160+
writeString(view, 8, 'WAVE');
161+
writeString(view, 12, 'fmt ');
162+
view.setUint32(16, 16, true);
163+
view.setUint16(20, 1, true);
164+
view.setUint16(22, numChannels, true);
165+
view.setUint32(24, sampleRate, true);
166+
view.setUint32(28, sampleRate * blockAlign, true);
167+
view.setUint16(32, blockAlign, true);
168+
view.setUint16(34, bitsPerSample, true);
169+
writeString(view, 36, 'data');
170+
view.setUint32(40, dataLength, true);
171+
172+
const wavBuffer = new Int16Array(headerBuffer.byteLength + dataLength);
173+
wavBuffer.set(new Int16Array(headerBuffer));
174+
wavBuffer.set(convertToInt16(buffer), headerBuffer.byteLength / 2);
175+
176+
return new Blob([wavBuffer], { type: 'audio/wav' });
177+
}
178+
179+
function writeString(view, offset, string) {
180+
for (let i = 0; i < string.length; i++) {
181+
view.setUint8(offset + i, string.charCodeAt(i));
182+
}
183+
}
184+
185+
function mergeAudioBuffers(buffers) {
186+
let totalLength = 0;
187+
for (let buffer of buffers) {
188+
totalLength += buffer.length;
189+
}
190+
const result = new Float32Array(totalLength);
191+
let offset = 0;
192+
for (let buffer of buffers) {
193+
result.set(buffer, offset);
194+
offset += buffer.length;
195+
}
196+
return result;
197+
}
198+
199+
function convertToInt16(float32Array) {
200+
const int16Array = new Int16Array(float32Array.length);
201+
for (let i = 0; i < float32Array.length; i++) {
202+
const s = Math.max(-1, Math.min(1, float32Array[i]));
203+
int16Array[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
204+
}
205+
return int16Array;
206+
}
207+
208+
async function submitToAPI() {
209+
if (!audioBlob) {
210+
alert('Please record audio first.');
211+
return;
212+
}
213+
214+
const apiKey = getAPIKey();
215+
if (!apiKey) {
216+
alert('API Key is required.');
217+
return;
218+
}
219+
220+
const base64Audio = await blobToBase64(audioBlob);
221+
const prompt = promptTextarea.value;
222+
223+
const payload = {
224+
model: "gpt-4o-audio-preview",
225+
modalities: ["text"],
226+
messages: [
227+
{
228+
role: "user",
229+
content: [
230+
{type: "text", text: prompt},
231+
{
232+
type: "input_audio",
233+
input_audio: {
234+
data: base64Audio,
235+
format: "wav"
236+
}
237+
}
238+
]
239+
}
240+
]
241+
};
242+
243+
try {
244+
const response = await fetch('https://api.openai.com/v1/chat/completions', {
245+
method: 'POST',
246+
headers: {
247+
'Content-Type': 'application/json',
248+
'Authorization': `Bearer ${apiKey}`
249+
},
250+
body: JSON.stringify(payload)
251+
});
252+
253+
const data = await response.json();
254+
apiResponse.textContent = JSON.stringify(data, null, 2);
255+
} catch (error) {
256+
console.error('Error:', error);
257+
apiResponse.textContent = `Error: ${error.message}`;
258+
}
259+
}
260+
261+
function getAPIKey() {
262+
let apiKey = localStorage.getItem('openai_api_key');
263+
if (!apiKey) {
264+
apiKey = prompt('Please enter your OpenAI API Key:');
265+
if (apiKey) {
266+
localStorage.setItem('openai_api_key', apiKey);
267+
}
268+
}
269+
return apiKey;
270+
}
271+
272+
function blobToBase64(blob) {
273+
return new Promise((resolve, reject) => {
274+
const reader = new FileReader();
275+
reader.onloadend = () => resolve(reader.result.split(',')[1]);
276+
reader.onerror = reject;
277+
reader.readAsDataURL(blob);
278+
});
279+
}
280+
</script>
281+
</body>
282+
</html>

0 commit comments

Comments
 (0)