AI-translated from English; not yet reviewed by a fluent editor.
# Направите локални гласовни разговор у прегледачу уз OpenAI Realtime
> Направите локални разговор преко микрофона прегледача уз OpenAI Realtime и WebRTC. Потпун код сервера и странице чува API кључ на серверу и показује проверу звука, заустављање микрофона и обраду грешака.
By BIG CHANGE Editorial
Published: 2026-10-19T19:20:34.863Z
Updated: 2026-10-19T19:20:34.863Z
Canonical: https://bigchange.ai/blog/openai-realtime-browser-voice-webrtc-guide

AI-generated conceptual illustration by BIG CHANGE; no actual OpenAI interface, browser test or heard reply is depicted.
OpenAI Realtime API омогућава прегледачу слање звука микрофона и репродукцију говорног одговора преко WebRTC-а. Водич прави један локални разговор уз `gpt-realtime-2.1`. API кључ остаје у Node серверу на вашем рачунару. Потребан је OpenAI API пројекат са приступом моделу и наплатом; ChatGPT претплата не плаћа API употребу. BIG CHANGE је проверио актуелну документацију и пример кода, али није отворио плаћену сесију нити чуо одговор.
## Велика промена
- **Шта се променило:** OpenAI документује WebRTC путању за разговор Realtime у прегледачу, уз локални сервер који чува кључ пројекта и размењује понуду везе прегледача за одговор.
- **Зашто је важно:** Програмер може проверити цео пут од микрофона до говорног одговора на једној локалној страници пре развоја већег говорног производа. Власник пројекта управља кључем и плаћа API.
- **На шта пазити:** Успешна локална размена не доказује спремност за продукцију. Дељена верзија захтева аутентификацију позиваоца и контролу трошка; отказан или истекао захтев је можда већ стигао до OpenAI-а.
## Пре почетка
Инсталирајте Node.js 22.6 или новији и користите актуелан прегледач са исправним микрофоном и звучником. Микрофон захтева безбедан контекст; `http://localhost` одговара овом локалном примеру. Припремите OpenAI API кључ пројекта са приступом `gpt-realtime-2.1` и укљученом наплатом. Најпре проверите ограничења учесталости и трошка. Страница модела наводи да Free ниво није подржан. Чувајте кључ у окружењу серверског процеса кроз уобичајени управљач тајнама или локално shell подешавање. Не стављајте га у `index.html`, не commit-ујте га у `.env` датотеци и не лепите у транскрипт. [OpenAI WebRTC водич](https://developers.openai.com/api/docs/guides/voice-webrtc) документује размену преко поузданог сервера; [страница модела](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) наводи актуелни приступ и цене.
Ово је обједињена `/v1/realtime/calls` путања Realtime API-ја. WebRTC водич описује и GPT-Live преко `/v1/live/sessions`, са другачијим протоколом сесије. Користите наведену путању и садржај захтева заједно. Пример је везан за ваш рачунар; локалне Host и Origin провере не аутентификују кориснике дељеног сервиса.
1. Направите празан директоријум за пример и из њега инсталирајте Express:
```bash
npm init -y
npm install express
```
Инсталација прави `package.json` и додаје зависност за wеб сервер. Сачувајте наредне две датотеке у истом директоријуму. Код користи Node уграђене `fetch`, `FormData` и `AbortSignal.timeout`; проверено подешавање захтева Node.js 22.6 или новији.
2. Сачувајте следеће као `server.mjs`. Сервер прима SDP понуду прегледача (опис предложене WebRTC везе) и шаље је OpenAI-у са Realtime подешавањима. Стандардни API кључ чита се само на овом серверу.
```javascript
import express from "express";
const app = express();
// This demo accepts only the local page, including protection against DNS rebinding.
app.use((req, res, next) => {
const hosts = ["localhost:3000", "127.0.0.1:3000"];
if (!hosts.includes(req.get("host"))) return res.status(403).send("Local host required");
if (req.method === "POST" && req.get("origin") !== `http://${req.get("host")}`) {
return res.status(403).send("Same-origin local page required");
}
next();
});
app.use(express.text({ type: "application/sdp", limit: "64kb" }));
app.get("/", (_req, res) => res.sendFile("index.html", { root: process.cwd() }));
app.post("/session", async (req, res) => {
if (!process.env.OPENAI_API_KEY) return res.status(500).send("Server API key missing");
if (typeof req.body !== "string" || !req.body.startsWith("v=")) {
return res.status(400).send("Expected an SDP offer");
}
const form = new FormData();
form.set("sdp", req.body);
form.set("session", JSON.stringify({
type: "realtime",
model: "gpt-realtime-2.1",
output_modalities: ["audio"],
audio: { output: { voice: "marin" } },
instructions: "Be a concise, helpful voice assistant. Do not claim access to external tools."
}));
try {
const upstream = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
body: form,
signal: AbortSignal.timeout(25000)
});
const answer = await upstream.text();
if (!upstream.ok) {
console.error("Realtime session rejected", upstream.status);
return res.status(502).send(`OpenAI session request failed (${upstream.status})`);
}
res.type("application/sdp").send(answer);
} catch (error) {
console.error("Realtime session network failure", error.name);
res.status(502).send("Could not reach the Realtime API");
}
});
app.listen(3000, "127.0.0.1", () => console.log("Open http://localhost:3000"));
```
Сервер се везује за `127.0.0.1:3000`, прихвата само хост и POST оригин локалне странице и враћа SDP одговор прегледачу. Рок од 25 секунди за захтев ка добављачу избор је овог примера. Истек рока или прекид прегледача не доказује да OpenAI није прихватио захтев; проверите употребу пре понављања неизвесног покретања.
3. Сачувајте следеће као `index.html`. Страница тражи дозволу микрофона након **Start voice**, шаље понуду локалном серверу и репродукује долазни звук. **Stop** затвара везу и зауставља траке микрофона, чак и током повезивања. Тајмер од 30 секунди је такође избор примера, не API гаранција.
```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Local Realtime voice check</title>
<button id="start">Start voice</button>
<button id="stop" disabled>Stop</button>
<p id="status" role="status">Idle</p>
<audio id="reply" autoplay controls></audio>
<script type="module">
const start = document.querySelector("#start");
const stop = document.querySelector("#stop");
const status = document.querySelector("#status");
const reply = document.querySelector("#reply");
let current;
function closeSession(session = current, message = "Stopped") {
if (!session) return;
session.closed = true;
clearTimeout(session.timer);
session.abort.abort();
session.mic?.getTracks().forEach(track => track.stop());
if (session.pc) session.pc.onconnectionstatechange = null;
session.dc?.close();
session.pc?.close();
if (current === session) {
current = null;
reply.srcObject = null;
start.disabled = false;
stop.disabled = true;
status.textContent = message;
}
}
start.onclick = async () => {
const session = { abort: new AbortController(), closed: false };
current = session;
start.disabled = true;
stop.disabled = false;
status.textContent = "Requesting microphone";
session.timer = setTimeout(() => closeSession(session, "Setup timed out; microphone stopped"), 30000);
try {
const mic = await navigator.mediaDevices.getUserMedia({ audio: true });
if (session.closed) { mic.getTracks().forEach(track => track.stop()); return; }
session.mic = mic;
const pc = session.pc = new RTCPeerConnection();
pc.ontrack = event => { if (!session.closed) reply.srcObject = event.streams[0]; };
pc.onconnectionstatechange = () => {
if (session.closed) return;
status.textContent = `Connection: ${pc.connectionState}`;
if (pc.connectionState === "connected") clearTimeout(session.timer);
if (["failed", "closed", "disconnected"].includes(pc.connectionState)) {
closeSession(session, "Connection ended; microphone stopped. Start a new session.");
}
};
pc.addTrack(mic.getAudioTracks()[0], mic);
const dc = session.dc = pc.createDataChannel("oai-events");
dc.onmessage = message => {
if (session.closed) return;
try {
const event = JSON.parse(message.data);
if (event.type === "session.created") status.textContent = "Session ready: speak when connected";
if (event.type === "response.done") {
status.textContent = `Response status: ${event.response?.status || "unknown"}; verify audible playback`;
}
if (event.type === "error") closeSession(session, `Realtime error: ${event.error?.message || "unknown"}; stopped`);
} catch { closeSession(session, "Invalid event; stopped"); }
};
dc.onclose = () => { if (!session.closed) closeSession(session, "Event channel closed; stopped"); };
dc.onerror = () => closeSession(session, "Event channel failed; stopped");
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
if (session.closed) return;
// The official WebRTC example submits the generated offer directly.
const response = await fetch("/session", {
method: "POST",
headers: { "Content-Type": "application/sdp" },
body: offer.sdp,
signal: session.abort.signal
});
if (!response.ok) throw new Error(await response.text());
const answer = await response.text();
if (session.closed) return;
await pc.setRemoteDescription({ type: "answer", sdp: answer });
if (!session.closed && pc.connectionState !== "connected") status.textContent = "Signaling complete; waiting for connection";
} catch (error) {
if (!session.closed) closeSession(session, `Start failed: ${error.message}`);
}
};
stop.onclick = () => closeSession();
window.addEventListener("pagehide", () => closeSession());
</script>
</html>
```
Прегледач затвара сесију и код квара друге стране или канала догађаја и при напуштању странице. Дозвола микрофона може стићи после заустављања или истека рока; закашњела трака се зауставља. Статусни ред приказује `response.done` са пријављеним стањем. Сам догађај не доказује да се одговор чуо нити да је отказан или неуспешан одговор успео.
4. Покрените сервер из директоријума обе датотеке, са `OPENAI_API_KEY` већ подешеним у окружењу тог процеса:
```bash
node server.mjs
```
Отворите `http://localhost:3000`. Треба да видите **Start voice**, **Stop**, стање **Idle** и аудио-плејер. Кликните **Start voice**, дозволите микрофон и изговорите кратко питање без осетљивих података. OpenAI [водич за Realtime разговоре](https://developers.openai.com/api/docs/guides/realtime-conversations) каже да подразумевано препознавање говорне активности може покренути одговор након паузе. За успешно сопствено покретање проверите активан микрофон, везу у стању `connected`, приказан догађај `session.created`, звук говора и да `response.done` пријављује довршен одговор. Кликните **Stop** и проверите да се индикатор микрофона угасио. Ове провере обављате ви; BIG CHANGE није покренуо ову плаћену сесију.
## Ако провера не успе
Ако је микрофон одбијен или нема улазног уређаја, проверите дозволе и уређај, па користите локални `localhost` URL или HTTPS. Ако страница пријави `/session` грешку, проверите серверски HTTP статус, кључ, приступ моделу, наплату, ограничења и endpoint. Сервер намерно не исписује кључ нити OpenAI тело одговора. Статус 502 може бити мрежна грешка или рок 25 секунди; не говори да ли је неизвестан захтев прихваћен.
Ако сигнализација успе, а веза не, проверите статус странице, мрежна ограничења и враћени SDP одговор. За активну али тиху везу проверите микрофон и изабрани излаз. Прегледач може блокирати аутоматску репродукцију: кликните **Play** на аудио-контроли, па по потреби зауставите и отворите нову сесију. При сваком неуспеху користите **Stop** и потврдите угашен индикатор микрофона. Проверите употребу пре поновљених покретања са неизвесним исходом захтева.
## Трошак, приватност и ограничења
OpenAI за `gpt-realtime-2.1` наводи текстуални улаз/излаз без кеша по **$4/$24 за милион токена** и аудио-улаз/излаз по **$32/$64 за милион токена**, проверено 19. октобра 2026. Трошак зависи од токена, укључујући касније потезе са претходним контекстом. Пре употребе проверите [цене и ограничења модела](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) и употребу пројекта; само трајање није довољно за процену рачуна. [Водич за Realtime разговоре](https://developers.openai.com/api/docs/guides/realtime-conversations) наводи највише 60 минута по сесији.
Звук микрофона током сесије иде OpenAI-у. Користите говор који смете да пошаљете и избегавајте приватни или туђи звук. Локална страница нема пријаву ни буџет по кориснику. Дељени сервис захтева аутентификован, ограничен `/session` приступ и безбедносни идентификатор изведен на серверу; локалне Host и Origin провере нису довољне. Пример не позива спољне алате и не телефонира. Нису мерени кашњење, поузданост, квалитет говора, уштеде нити резултат продукционог пуштања.
## Извори и додатна литература
- [OpenAI WebRTC водич](https://developers.openai.com/api/docs/guides/voice-webrtc): обједињена серверска Realtime размена, звук прегледача и засебан GPT-Live протокол.
- [OpenAI водич за Realtime разговоре](https://developers.openai.com/api/docs/guides/realtime-conversations): догађаји сесије, потези, статус одговора и трајање.
- [OpenAI `gpt-realtime-2.1` страница модела](https://developers.openai.com/api/docs/models/gpt-realtime-2.1): приступ моделу, цене токена и ограничења. Поново проверите цене и приступ при покретању.
## Sources
- [OpenAI WebRTC водич](https://developers.openai.com/api/docs/guides/voice-webrtc) — Realtime обједињени calls интерфејс, поуздан кључ, SDP, звук прегледача и засебан GPT-Live протокол.
- [OpenAI Realtime разговори](https://developers.openai.com/api/docs/guides/realtime-conversations) — Догађаји сесије, VAD, статус одговора и трајање 60 минута.
- [OpenAI гпт-реалтиме-2.1 модел](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) — Цене токена и приступ за конкретан модел.
- [Оптимизација OpenAI гласовних трошкова](https://developers.openai.com/api/docs/guides/voice-latency-cost) — Контекст и употреба по одговору.
- [OpenAI API и ChatGPT наплата](https://help.openai.com/en/articles/9039756-managing-billing-for-chatgpt-and-the-api-platform) — Одвојена наплата.
Билтен BIG CHANGE
Шира слика. Вашим темпом.
Недавне приче о вештачкој интелигенцији и роботици, промене вредне праћења и практичне идеје за примену. Изаберите дневни преглед, недељни сажетак или месечну перспективу.
Next scheduled send (UTC): . Your first edition arrives at the next scheduled send after you confirm.
Ваша приватност, ваш избор.
Неопходно складиште доприноси безбедности сајта и памти ваше изборе. Опционални Google Analytics остаје искључен док га не дозволите. Све чланке можете читати само уз неопходно складиштење података. Детаљи о приватности