DUNIA TERUS BERGERAK.RSS
BIG CHANGE.

Edisi Markdown

AI-translated from English; not yet reviewed by a fluent editor.

# Buat percakapan suara lokal di browser dengan OpenAI Realtime

> Buat percakapan mikrofon browser lokal memakai OpenAI Realtime melalui WebRTC. Kode server dan halaman lengkap menyimpan kunci API di server serta menunjukkan pemeriksaan suara, penghentian mikrofon, dan penanganan kegagalan.

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

![Conceptual charcoal illustration of a desktop microphone beside a blank browser on a monitor and a small speaker.](https://bigchange.ai/api/media/file/realtime-browser-voice-hero-v1.png)
AI-generated conceptual illustration by BIG CHANGE; no actual OpenAI interface, browser test or heard reply is depicted.

Realtime API OpenAI memungkinkan browser mengirim audio mikrofon dan memutar jawaban lisan melalui WebRTC. Panduan ini membuat satu percakapan lokal dengan `gpt-realtime-2.1`. Kunci API tetap di server Node pada komputer Anda. Dibutuhkan proyek OpenAI API dengan akses model dan penagihan; langganan ChatGPT tidak membayar penggunaan API. BIG CHANGE memeriksa dokumentasi terkini dan kode contoh, tetapi belum membuka sesi berbayar atau mendengar jawaban.

## Perubahan besar

- **Apa yang berubah:** OpenAI mendokumentasikan jalur WebRTC browser untuk percakapan Realtime langsung, dengan server lokal menyimpan kunci proyek dan menukar tawaran koneksi browser dengan jawaban.
- **Mengapa penting:** Pengembang dapat memeriksa seluruh jalur mikrofon hingga jawaban lisan pada satu halaman lokal sebelum merancang produk suara lebih besar. Pemilik proyek mengendalikan kunci dan membayar penggunaan API.
- **Yang perlu diperhatikan:** Pertukaran lokal yang berhasil tidak membuktikan kesiapan produksi. Versi bersama memerlukan autentikasi pemanggil dan kontrol belanja; permintaan batal atau kedaluwarsa mungkin sudah mencapai OpenAI.

## Sebelum memulai

Pasang Node.js 22.6 atau lebih baru dan gunakan browser terkini dengan mikrofon serta pengeras suara yang berfungsi. Akses mikrofon membutuhkan konteks aman; `http://localhost` memenuhi syarat pada contoh lokal ini. Siapkan kunci proyek OpenAI API dengan akses `gpt-realtime-2.1` dan penagihan aktif. Periksa batas laju dan belanja dahulu. Halaman model menyatakan tingkat Free tidak didukung. Simpan kunci dalam lingkungan proses server melalui pengelola rahasia atau pengaturan shell lokal biasa. Jangan letakkan di `index.html`, commit ke berkas `.env`, atau tempel ke transkrip. [Panduan WebRTC OpenAI](https://developers.openai.com/api/docs/guides/voice-webrtc) mendokumentasikan pertukaran server tepercaya; [halaman model](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) mencantumkan akses dan harga terkini.

Ini adalah rute terpadu `/v1/realtime/calls` pada Realtime API. Panduan WebRTC juga menjelaskan GPT-Live melalui `/v1/live/sessions`, dengan protokol sesi berbeda. Gunakan rute dan muatan berikut bersama. Contoh dibatasi ke komputer sendiri; pemeriksaan Host dan Origin lokal tidak mengautentikasi pengguna layanan bersama.

1. Buat direktori kosong untuk demo dan pasang Express dari sana:

```bash
npm init -y
npm install express
```

Instalasi membuat `package.json` dan menambah dependensi server web. Simpan dua berkas berikut di direktori yang sama. Kode memakai bawaan Node `fetch`, `FormData`, dan `AbortSignal.timeout`; pengaturan yang ditinjau memerlukan Node.js 22.6 atau lebih baru.

2. Simpan berikut sebagai `server.mjs`. Server menerima tawaran SDP browser (deskripsi koneksi WebRTC yang diusulkan) dan mengirimkannya bersama pengaturan sesi Realtime ke OpenAI. Kunci API standar hanya dibaca di server ini.

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

Server terikat pada `127.0.0.1:3000`, hanya menerima host dan POST origin halaman lokal, lalu mengembalikan jawaban SDP. Batas permintaan hulu 25 detik adalah pilihan contoh. Waktu habis atau browser terputus tidak membuktikan OpenAI menolak permintaan; tinjau penggunaan sebelum mengulangi awal yang tidak pasti.

3. Simpan berikut sebagai `index.html`. Halaman meminta izin mikrofon setelah **Start voice**, mengirim tawaran ke server lokal, dan memutar audio masuk. **Stop** menutup koneksi dan menghentikan trek mikrofon meski pengaturan masih berjalan. Pengatur waktu 30 detik juga pilihan contoh, bukan jaminan 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>
```

Browser menutup sesi ketika peer atau kanal peristiwa gagal dan saat meninggalkan halaman. Izin mikrofon dapat datang setelah berhenti atau waktu habis; trek terlambat dihentikan. Baris status menampilkan `response.done` bersama status yang dilaporkan. Peristiwa itu sendiri tidak membuktikan jawaban terdengar atau respons batal/gagal berhasil.

4. Mulai server dari direktori kedua berkas, dengan `OPENAI_API_KEY` sudah ditetapkan di lingkungan proses server:

```bash
node server.mjs
```

Buka `http://localhost:3000`. Anda seharusnya melihat **Start voice**, **Stop**, status **Idle**, dan pemutar audio. Klik **Start voice**, izinkan mikrofon, dan ucapkan pertanyaan singkat tanpa data sensitif. Menurut [panduan percakapan Realtime](https://developers.openai.com/api/docs/guides/realtime-conversations), deteksi aktivitas suara bawaan dapat mengubah jeda setelah bicara menjadi respons. Periksa keberhasilan Anda: mikrofon aktif, koneksi mencapai `connected`, peristiwa `session.created` muncul, suara terdengar, dan `response.done` melaporkan respons selesai. Klik **Stop** dan pastikan indikator mikrofon mati. Pemeriksaan ini untuk Anda lakukan; BIG CHANGE belum menjalankan sesi browser berbayar ini.

## Jika pemeriksaan gagal

Jika izin ditolak atau perangkat tidak muncul, periksa izin browser dan perangkat, lalu gunakan URL lokal `localhost` atau HTTPS. Jika halaman melaporkan kegagalan `/session`, periksa status HTTP server, keberadaan kunci, akses model proyek, penagihan, batas laju, dan endpoint. Server sengaja tidak mencetak kunci atau isi respons OpenAI. 502 juga bisa berarti kegagalan jaringan atau tenggat 25 detik; tidak menjelaskan apakah permintaan hulu yang tak pasti diterima.

Jika pensinyalan berhasil tetapi koneksi gagal, periksa status halaman, pembatasan jaringan, dan jawaban SDP. Jika terhubung tetapi senyap, periksa masukan mikrofon dan keluaran pilihan. Putar otomatis mungkin diblokir: klik **Play** pada kontrol audio, lalu hentikan dan mulai sesi baru jika perlu. Pada setiap kegagalan gunakan **Stop** dan pastikan indikator mikrofon mati. Periksa penggunaan proyek sebelum mengulangi awal dengan hasil permintaan tidak pasti.

## Biaya, privasi, dan batas

OpenAI mencantumkan `gpt-realtime-2.1` masukan/keluaran teks tanpa cache pada **$4/$24 per juta tokens** dan masukan/keluaran audio pada **$32/$64 per juta tokens**, diperiksa 19 Oktober 2026. Biaya bergantung pada tokens yang dipakai, termasuk giliran selanjutnya yang membawa konteks percakapan sebelumnya. Periksa [harga dan batas model](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) serta penggunaan proyek sebelum mencoba; waktu berlalu saja tidak bisa memperkirakan tagihan. [Panduan percakapan Realtime](https://developers.openai.com/api/docs/guides/realtime-conversations) menyatakan sesi maksimal 60 menit.

Audio mikrofon dikirim ke OpenAI selama sesi. Gunakan ucapan yang boleh Anda kirim dan hindari audio pribadi atau pihak lain. Halaman lokal tidak menawarkan masuk akun atau anggaran per pengguna. Layanan bersama memerlukan akses `/session` yang diautentikasi dan dibatasi laju serta pengenal keselamatan dari server; pemeriksaan Host dan Origin lokal tidak cukup. Contoh tidak memanggil alat luar atau menelepon. Tidak ada hasil latensi, keandalan, kualitas suara, penghematan, atau penerapan produksi yang diukur.

## Sumber dan bacaan lanjutan

- [Panduan WebRTC OpenAI](https://developers.openai.com/api/docs/guides/voice-webrtc): pertukaran server Realtime terpadu, audio browser, dan protokol GPT-Live terpisah.
- [Panduan percakapan OpenAI Realtime](https://developers.openai.com/api/docs/guides/realtime-conversations): peristiwa sesi, giliran, status respons, dan batas durasi.
- [OpenAI `gpt-realtime-2.1` halaman model](https://developers.openai.com/api/docs/models/gpt-realtime-2.1): akses model, harga token, dan batas laju. Periksa kembali harga dan akses saat menjalankan contoh.

## Sources

- [Panduan WebRTC OpenAI](https://developers.openai.com/api/docs/guides/voice-webrtc) — Antarmuka Realtime calls terpadu, kunci tepercaya, SDP, audio browser, dan protokol GPT-Live terpisah.
- [Percakapan OpenAI Realtime](https://developers.openai.com/api/docs/guides/realtime-conversations) — Peristiwa sesi, VAD, status respons, dan durasi 60 menit.
- [Model OpenAI gpt-realtime-2.1](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) — Harga token dan akses khusus model.
- [Optimasi biaya suara OpenAI](https://developers.openai.com/api/docs/guides/voice-latency-cost) — Konteks dan penggunaan per respons.
- [Penagihan OpenAI API dan ChatGPT](https://help.openai.com/en/articles/9039756-managing-billing-for-chatgpt-and-the-api-platform) — Penagihan terpisah.