DIE WELT BLEIBT NICHT STEHEN.RSS
BIG CHANGE.

Markdown-Ausgabe

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

# Eine lokale Sprachunterhaltung im Browser mit OpenAI Realtime erstellen

> Erstellen Sie eine lokale Browser-Mikrofonunterhaltung mit OpenAI Realtime über WebRTC. Vollständiger Server- und Seitencode hält den API-Schlüssel auf dem Server und zeigt Wiedergabeprüfung, Mikrofonstopp und Fehlerbehandlung.

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.

OpenAIs Realtime API kann Mikrofon-Audio aus dem Browser über WebRTC senden und eine gesprochene Antwort abspielen. Diese Anleitung erstellt eine lokale Unterhaltung mit `gpt-realtime-2.1`. Der API-Schlüssel bleibt auf einem Node-Server auf Ihrem Computer. Sie brauchen ein OpenAI-API-Projekt mit Modellzugriff und Abrechnung; ein ChatGPT-Abonnement bezahlt keine API-Nutzung. BIG CHANGE prüfte aktuelle Dokumentation und Beispielcode, öffnete aber keine kostenpflichtige Sitzung und hörte keine Antwort.

## Die große Veränderung

- **Was sich geändert hat:** OpenAI dokumentiert einen Browser-WebRTC-Weg für Realtime-Unterhaltungen. Ein lokaler Server behält den Projektschlüssel und tauscht das Verbindungsangebot des Browsers gegen eine Antwort.
- **Warum das wichtig ist:** Entwickler können in einer lokalen Seite den vollständigen Weg vom Mikrofon zur gesprochenen Antwort prüfen, bevor sie ein größeres Sprachprodukt entwickeln. Der Projektinhaber kontrolliert den Schlüssel und bezahlt die API-Nutzung.
- **Worauf zu achten ist:** Ein funktionierender lokaler Austausch belegt keine Produktionsreife. Eine geteilte Version braucht Authentifizierung und Ausgabenkontrollen; eine abgebrochene oder abgelaufene Anfrage kann OpenAI bereits erreicht haben.

## Vor dem Start

Installieren Sie Node.js 22.6 oder neuer und verwenden Sie einen aktuellen Browser mit funktionierendem Mikrofon und Lautsprecher. Mikrofonzugriff braucht einen sicheren Kontext; `http://localhost` genügt für dieses lokale Beispiel. Bereiten Sie einen OpenAI-Projektschlüssel mit Zugriff auf `gpt-realtime-2.1` und aktivierter Abrechnung vor. Prüfen Sie zuerst Raten- und Ausgabenlimits. Die Modellseite nennt den Free-Tarif als nicht unterstützt. Halten Sie den Schlüssel über Ihre normale Geheimnisverwaltung oder lokale Shell-Einrichtung in der Serverprozessumgebung. Legen Sie ihn nicht in `index.html`, committen Sie ihn nicht in einer `.env` Datei und kopieren Sie ihn nicht in ein Gesprächsprotokoll. [OpenAIs WebRTC-Anleitung](https://developers.openai.com/api/docs/guides/voice-webrtc) dokumentiert den Austausch über den vertrauenswürdigen Server; [die Modellseite](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) nennt aktuellen Zugriff und Preise.

Dies ist die vereinheitlichte `/v1/realtime/calls` Route der Realtime API. Die WebRTC-Anleitung beschreibt auch GPT-Live über `/v1/live/sessions` mit anderem Sitzungsprotokoll. Verwenden Sie Route und Nutzlast unten zusammen. Das Beispiel ist auf Ihren Computer beschränkt; lokale Host- und Origin-Prüfungen authentifizieren keine Nutzer eines geteilten Dienstes.

1. Erstellen Sie ein leeres Demoverzeichnis und installieren Sie darin Express:

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

Die Installation erstellt `package.json` und fügt die Webserver-Abhängigkeit hinzu. Speichern Sie die folgenden zwei Dateien in diesem Verzeichnis. Der Code verwendet Nodes eingebaute `fetch`, `FormData` und `AbortSignal.timeout`; die geprüfte Einrichtung braucht Node.js 22.6 oder neuer.

2. Speichern Sie Folgendes als `server.mjs`. Der Server empfängt das SDP-Angebot des Browsers (eine Beschreibung der vorgeschlagenen WebRTC-Verbindung) und sendet es mit den Realtime-Sitzungseinstellungen an OpenAI. Der Standardschlüssel wird nur hier gelesen.

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

Der Server bindet an `127.0.0.1:3000`, akzeptiert nur Host und POST-Origin der lokalen Seite und sendet die SDP-Antwort zurück. Die 25-Sekunden-Grenze für die vorgelagerte Anfrage ist eine Beispielentscheidung. Ein Timeout oder Browserabbruch beweist nicht, dass OpenAI die Anfrage nicht annahm. Prüfen Sie die Nutzung vor einem unklaren Neustart.

3. Speichern Sie Folgendes als `index.html`. Die Seite fragt nach **Start voice** die Mikrofonberechtigung ab, sendet das Angebot an den lokalen Server und spielt eingehendes Audio. **Stop** schließt die Verbindung und stoppt Mikrofonspuren auch während des Aufbaus. Der 30-Sekunden-Aufbautimer ist ebenfalls eine Beispielentscheidung, keine API-Garantie.

```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>
```

Der Browser schließt die Sitzung auch bei Peer- oder Ereigniskanalfehler und beim Verlassen der Seite. Die Mikrofonerlaubnis kann erst nach Stopp oder Timeout eintreffen; die verspätete Spur wird gestoppt. Die Statuszeile zeigt `response.done` samt gemeldetem Status. Das Ereignis allein belegt weder hörbares Audio noch den Erfolg einer abgebrochenen oder fehlgeschlagenen Antwort.

4. Starten Sie den Server im Verzeichnis beider Dateien, während `OPENAI_API_KEY` bereits in dessen Prozessumgebung gesetzt ist:

```bash
node server.mjs
```

Öffnen Sie `http://localhost:3000`. Sie sollten **Start voice**, **Stop**, den Status **Idle** und einen Audioplayer sehen. Klicken Sie **Start voice**, erlauben Sie das Mikrofon und stellen Sie eine kurze harmlose Frage. Laut [Realtime-Unterhaltungsanleitung](https://developers.openai.com/api/docs/guides/realtime-conversations) kann die standardmäßige Sprachaktivitätserkennung eine Pause nach dem Sprechen in eine Antwort umsetzen. Prüfen Sie bei Ihrem Lauf: Browser zeigt aktives Mikrofon, Verbindung erreicht `connected`, ein `session.created` Ereignis erscheint, Sprache ist hörbar und `response.done` meldet eine abgeschlossene Antwort. Klicken Sie **Stop** und prüfen Sie, dass die Mikrofonanzeige erlischt. Diese Prüfungen führen Sie durch; BIG CHANGE hat diese kostenpflichtige Browsersitzung nicht ausgeführt.

## Wenn die Prüfung fehlschlägt

Bei verweigerter Mikrofonerlaubnis oder fehlendem Eingabegerät prüfen Sie Browserrechte und Gerät und nutzen die lokale `localhost` URL oder HTTPS. Meldet die Seite einen `/session` Fehler, prüfen Sie den serverseitigen HTTP-Status, Schlüssel, Modellzugriff, Abrechnung, Ratenlimits und Endpunkt. Der Server gibt Schlüssel und OpenAI-Antworttext bewusst nicht aus. 502 kann auch Netzwerkfehler oder die 25-Sekunden-Frist bedeuten und zeigt nicht, ob eine unklare vorgelagerte Anfrage angenommen wurde.

Gelingt die Signalisierung, aber nicht die Verbindung, prüfen Sie Seitenstatus, Netzbeschränkungen und SDP-Antwort. Bei aktiver, stiller Verbindung prüfen Sie Mikrofoneingang und gewähltes Ausgabegerät. Autoplay kann blockiert sein: Klicken Sie **Play** im Audioplayer und starten Sie nötigenfalls nach einem Stopp eine neue Sitzung. Nutzen Sie bei jedem Fehler **Stop** und bestätigen Sie die erloschene Mikrofonanzeige. Prüfen Sie die Nutzung vor wiederholten Starts mit unklarem Anfrageergebnis.

## Kosten, Datenschutz und Grenzen

OpenAI nennt für `gpt-realtime-2.1` nicht zwischengespeicherte Texteingabe/-ausgabe **$4/$24 pro Million Tokens** und Audioeingabe/-ausgabe **$32/$64 pro Million Tokens**, geprüft am 19. Oktober 2026. Abgerechnet werden genutzte Tokens, auch spätere Runden mit vorherigem Gesprächskontext. Prüfen Sie [Modellpreise und Grenzen](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) sowie Ihre Projektnutzung vorab; die Dauer allein erlaubt keine Kostenschätzung. Die [Realtime-Unterhaltungsanleitung](https://developers.openai.com/api/docs/guides/realtime-conversations) nennt maximal 60 Minuten pro Sitzung.

Während einer Sitzung geht Ihr Mikrofon-Audio an OpenAI. Verwenden Sie nur erlaubte Sprache und vermeiden Sie private oder fremde Aufnahmen. Diese lokale Seite bietet keine Anmeldung oder nutzerbezogene Budgetkontrolle. Ein geteilter Dienst braucht authentifizierten, ratenbegrenzten `/session` Zugriff und eine serverseitig erzeugte Sicherheitskennung; lokale Host- und Origin-Prüfungen reichen nicht. Das Beispiel nutzt keine externen Werkzeuge und führt keine Telefonate. Latenz, Zuverlässigkeit, Sprachqualität, Einsparung oder Produktionsbereitstellung wurden nicht gemessen.

## Quellen und weitere Informationen

- [OpenAI WebRTC-Anleitung](https://developers.openai.com/api/docs/guides/voice-webrtc): einheitlicher Realtime-Serveraustausch, Browser-Audio und separates GPT-Live-Protokoll.
- [OpenAI Realtime-Unterhaltungsanleitung](https://developers.openai.com/api/docs/guides/realtime-conversations): Sitzungsereignisse, Rundenverhalten, Antwortstatus und Zeitgrenze.
- [OpenAI `gpt-realtime-2.1` Modellseite](https://developers.openai.com/api/docs/models/gpt-realtime-2.1): Modellzugriff, Tokenpreise und Ratenlimits. Prüfen Sie Preise und Zugriff beim Ausführen erneut.

## Sources

- [OpenAI WebRTC-Anleitung](https://developers.openai.com/api/docs/guides/voice-webrtc) — Realtime unified calls-Schnittstelle, vertrauenswürdiger Schlüssel, SDP, Browser-Audio und separates GPT-Live-Protokoll.
- [OpenAI Realtime-Unterhaltungen](https://developers.openai.com/api/docs/guides/realtime-conversations) — Sitzungsereignisse, VAD, Antwortstatus und 60-Minuten-Dauer.
- [OpenAI gpt-realtime-2.1 Modell](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) — Modellspezifische Tokenpreise und Zugriff.
- [OpenAI Sprachkosten optimieren](https://developers.openai.com/api/docs/guides/voice-latency-cost) — Kontext und Nutzung pro Antwort.
- [OpenAI API- und ChatGPT-Abrechnung](https://help.openai.com/en/articles/9039756-managing-billing-for-chatgpt-and-the-api-platform) — Getrennte Abrechnung.