この記事でわかること
✅ Next.js × OpenAI Realtime APIで音声チャットを最短構築
✅ OpenAIのEphemeral Keyを使った最新構成
✅ WebRTCを使ったリアルタイム音声ストリーミングの実装
こんな人にオススメ
- ChatGPTを音声で使えるWebアプリを作りたい
- OpenAI Realtime APIの最新ベストプラクティスを知りたい
- Next.js x OpenAI x WebRTCの実践コードが欲しい
GitHub
環境構築
1 Next.jsプロジェクトを作成します。
npx create-next-app --example with-docker {your project name}
2 GitHub でリポジトリを作成し、ローカルの内容をPushします。(任意)
cd {your project name}
git remote add origin https://github.com/{your git account id}/{your git repository url}
git branch -M main
git push -u origin main
3 Tailwind-CSS をインストールします。(任意)
- Tailwind CSSをインストールする
npm install tailwindcss @tailwindcss/postcss postcss
- PostCSS設定にTailwindを追加する
postcss.config.mjs
export default {
plugins: {
"@tailwindcss/postcss": {},
}
}
- Tailwind CSSをインポートする
globals.css
@import "tailwindcss";
4 .env.local にAPIキー設定
.env.local
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
リソースの配置
/pages/api/openai-realtime/init.ts
OpenAIにエフェメラルキーをリクエストするAPI。
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method Not Allowed' });
}
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
return res.status(500).json({ error: 'API Key not found' });
}
const response = await fetch("https://api.openai.com/v1/realtime/sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4o-realtime-preview-2024-12-17",
voice: "verse",
}),
});
if (!response.ok) {
const errorText = await response.text();
console.error('Failed to create session:', errorText);
return res.status(response.status).json({ error: 'Failed to create session', details: errorText });
}
const data = await response.json();
res.status(200).json({ ephemeralKey: data.client_secret.value });
}
/app/pages/openai-realtime.tsx
音声チャットのUIとWebRTC接続処理。
import { useState, useRef } from 'react';
export default function Chat() {
const [isChatting, setIsChatting] = useState(false);
const peerConnection = useRef<RTCPeerConnection | null>(null);
const dataChannelRef = useRef<RTCDataChannel | null>(null);
const startChat = async () => {
const res = await fetch('/api/openai-realtime/init');
const { ephemeralKey } = await res.json();
const pc = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getTracks().forEach(track => pc.addTrack(track, stream));
const audioEl = document.createElement('audio');
audioEl.autoplay = true;
pc.ontrack = (e) => audioEl.srcObject = e.streams[0];
const dc = pc.createDataChannel('oai-events');
dataChannelRef.current = dc;
dc.onmessage = () => {};
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const sdpResponse = await fetch(`https://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17`, {
method: 'POST',
headers: {
Authorization: `Bearer ${ephemeralKey}`,
'Content-Type': 'application/sdp',
},
body: offer.sdp,
});
const sdpText = await sdpResponse.text();
const answer: RTCSessionDescriptionInit = {
type: 'answer',
sdp: sdpText,
};
await pc.setRemoteDescription(answer);
peerConnection.current = pc;
setIsChatting(true);
};
const stopChat = () => {
peerConnection.current?.close();
dataChannelRef.current?.close();
setIsChatting(false);
};
return (
<div className="p-4">
<h1 className="text-xl font-bold">リアルタイム音声チャット</h1>
<div className="mt-4">
{isChatting ? (
<button onClick={stopChat} className="px-4 py-2 bg-red-500 text-white rounded">
チャット終了
</button>
) : (
<button onClick={startChat} className="px-4 py-2 bg-blue-500 text-white rounded">
チャット開始
</button>
)}
</div>
</div>
);
}
動作確認
開発サーバー起動
npm run dev
ブラウザでアクセス
http://localhost:3000/openai-realtime
完成!! お疲れ様です
✅ Next.jsで超シンプルに音声チャット
✅ OpenAI Realtime APIの正式対応構成
✅ Ephemeral Keyによる安全設計
✅ WebRTCでリアルタイム処理
さらにこんなカスタマイズもオススメ!
- 会話履歴を保存して「チャットログ機能」追加
- スマホ対応デザイン(録音中アイコンとか)
- サーバー側で音声保存してAI音声分析
そのままデプロイしたい方は!
こちらの記事で、Cloud Runを使って秒速デプロイする方法をご紹介しています!
https://qiita.com/dev-cat/items/0de2ca8168684fc21a69