「自分のアプリに人間らしい声を追加したい」と思ったことはありませんか? テキスト読み上げ(TTS)機能を実装しようとすると、ブラウザのネイティブ音声は機械的すぎる、かといってGoogle TTSやAmazon Pollyは設定が複雑で費用感が掴みにくい、という壁にぶつかりがちです。
私がいくつかのアプリで試した中で、ElevenLabs はその問題を最もシンプルに解決してくれました。APIの設計がきれいで、Antigravity に依頼すれば基本実装まで数時間で仕上がります。ここでは実際に動いたコードをそのままお見せしながら、音声AIアプリ開発の入り口をご案内します。
ElevenLabsとAntigravityの相性が良い理由
ElevenLabsは「人間らしい音声合成」を武器にしたAI音声プラットフォームです。2026年現在、無料プランでも月10,000文字まで使えるため、個人開発の試作フェーズは費用ゼロで進められます。
Antigravityとの相性が良い点は、ElevenLabsの公式PythonおよびTypeScript SDKのドキュメントが整備されていることです。Antigravity に「ElevenLabsのPython SDKでストリーミングTTSを実装して」と依頼すると、SDKの最新バージョンに合ったコードをほぼ一発で生成してくれます。
費用の目安をまとめておきます(2026年5月時点):
- Starter: $5/月、30,000文字/月
- Creator: $22/月、100,000文字/月
- Pro: $99/月、500,000文字/月
アプリのコアに音声機能を組み込む場合、ユーザー1人あたりの文字数を試算しておくと計画が立てやすいです。1回の会話が平均200文字なら、Creatorプランで月500会話まで対応できる計算です。
プロジェクトのセットアップ
まずはAntigravityに次のプロンプトで初期構成を依頼します。
ElevenLabs APIを使ったPython + FastAPIのバックエンドを作成してください。
機能: テキスト→音声変換(TTS)のストリーミングエンドポイント
環境変数: ELEVENLABS_API_KEY
依存関係のrequirements.txtも含めてください
Antigravityが生成した構成をベースに、次のディレクトリ構成で進めます。
voice-app/
├── backend/
│ ├── main.py
│ ├── requirements.txt
│ └── .env
└── frontend/
├── app/
│ └── page.tsx
└── package.json
依存関係のインストールは Antigravity のターミナルで直接実行できます。
# バックエンド
pip install elevenlabs fastapi uvicorn python-dotenv
# フロントエンド(Next.js)
npm installテキスト→音声変換(TTS)の実装
ElevenLabs Python SDK を使ったバックエンドの実装です。ポイントはストリーミング対応にすることで、長いテキストでも最初の音声が即座に再生されます。
# backend/main.py
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from elevenlabs.client import ElevenLabs
from pydantic import BaseModel
import os
from dotenv import load_dotenv
load_dotenv()
app = FastAPI()
# CORS設定(開発環境用)
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_methods=["*"],
allow_headers=["*"],
)
client = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
class TTSRequest(BaseModel):
text: str
voice_id: str = "JBFqnCBsd6RMkjVDRZzb" # "George" - デフォルト音声
@app.post("/api/tts")
async def text_to_speech(request: TTSRequest):
"""
テキストをストリーミング音声に変換するエンドポイント。
フロントエンドはAudio APIでこのストリームを再生する。
"""
def audio_stream():
# generate()はジェネレーターを返す。チャンク単位で音声データが届く
audio_generator = client.generate(
text=request.text,
voice=request.voice_id,
model="eleven_turbo_v2_5", # 低遅延モデル(個人開発に最適)
stream=True,
)
for chunk in audio_generator:
if chunk:
yield chunk
return StreamingResponse(
audio_stream(),
media_type="audio/mpeg",
headers={"Transfer-Encoding": "chunked"},
)実行して動作確認します。
uvicorn main:app --reload
# → http://localhost:8000 で起動
# 動作テスト(curl)
curl -X POST http://localhost:8000/api/tts \
-H "Content-Type: application/json" \
-d '{"text": "こんにちは、Antigravity Labです"}' \
--output test.mp3
# test.mp3 を再生して音声が生成されていれば成功期待する結果は、日本語テキストが自然な英語アクセントで読み上げられたMP3ファイルです。日本語ネイティブ発音が必要な場合は voice_id を日本語音声に変更します。
フロントエンドでの音声再生
Next.js フロントエンドで、バックエンドのストリームをリアルタイム再生する実装です。
// app/page.tsx
"use client";
import { useState } from "react";
export default function VoiceApp() {
const [text, setText] = useState("");
const [isPlaying, setIsPlaying] = useState(false);
const [error, setError] = useState<string | null>(null);
const handlePlay = async () => {
if (!text.trim()) return;
setIsPlaying(true);
setError(null);
try {
const response = await fetch("/api/tts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
});
if (!response.ok) {
throw new Error(`API エラー: ${response.status}`);
}
// ReadableStream → ArrayBuffer → Blob → Audio
const audioData = await response.arrayBuffer();
const audioBlob = new Blob([audioData], { type: "audio/mpeg" });
const audioUrl = URL.createObjectURL(audioBlob);
const audio = new Audio(audioUrl);
audio.play();
audio.onended = () => {
setIsPlaying(false);
URL.revokeObjectURL(audioUrl); // メモリ解放を忘れずに
};
} catch (err) {
setError(err instanceof Error ? err.message : "エラーが発生しました");
setIsPlaying(false);
}
};
return (
<main className="max-w-xl mx-auto p-8">
<h1 className="text-2xl font-bold mb-4">音声AIデモ</h1>
<textarea
className="w-full border rounded p-2 mb-4 h-32"
placeholder="読み上げるテキストを入力..."
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button
onClick={handlePlay}
disabled={isPlaying}
className="bg-blue-600 text-white px-6 py-2 rounded disabled:opacity-50"
>
{isPlaying ? "再生中..." : "▶ 音声再生"}
</button>
{error && <p className="text-red-500 mt-2">{error}</p>}
</main>
);
}この実装では URL.revokeObjectURL() でのメモリ解放が重要です。Antigravityに最初に依頼したコードではこの処理が抜けることがありますので、確認してください。
音声→テキスト変換(STT)の実装
ElevenLabs にはScribe(音声→テキスト)機能もあります。マイクで録音したデータをそのまま送れます。
# backend/main.py に追加
from fastapi import UploadFile, File
import tempfile
import os
@app.post("/api/stt")
async def speech_to_text(audio: UploadFile = File(...)):
"""
アップロードされた音声ファイルをテキストに変換する。
フロントエンドからは MediaRecorder で録音したBlobを送信する。
"""
# 一時ファイルに保存(ElevenLabs SDKはファイルパスを要求するため)
with tempfile.NamedTemporaryFile(
delete=False, suffix=".webm"
) as tmp_file:
content = await audio.read()
tmp_file.write(content)
tmp_path = tmp_file.name
try:
with open(tmp_path, "rb") as f:
result = client.speech_to_text.convert(
file=f,
model_id="scribe_v1",
language_code="ja", # 日本語指定で精度が上がる
)
return {"text": result.text}
finally:
os.unlink(tmp_path) # 一時ファイルを必ず削除フロントエンドでは MediaRecorder APIでマイク入力を録音し、このエンドポイントに送信します。Antigravityに「MediaRecorder でマイク録音して /api/stt に送信するReactコンポーネントを作って」と依頼すると、この部分もすぐに実装してもらえます。
APIキーの管理とコスト可視化
個人開発で一番注意したいのは APIキーの漏洩 と 予期しない課金 です。
まず、.env ファイルに書いたAPIキーを絶対にGitにコミットしないようにします。
# .gitignore に追加(Antigravity が自動で提案してくれる場合も多い)
.env
.env.localElevenLabsのダッシュボードには使用量モニタリング機能があります。月初にAntigravityに「今月の文字数消費を試算して」と依頼してアラート設定をしておくと、予算超過を事前に防げます。
また、開発中は eleven_turbo_v2_5 モデル(低遅延・低コスト)を使い、本番リリース前に eleven_multilingual_v2(最高品質)に切り替える、という段階的なアプローチが費用を抑えるコツです。
音声UIは画面UIと設計原則が根本的に異なるため、実装と並行して読んでおくと方向性が定まります。
全体を振り返って — まず動くものを作ってみる
今日試せる最初のステップは、「自分のアプリの通知文を音声で読み上げる」という最小構成から始めることです。/api/tts エンドポイントを1つ立ち上げて、ユーザーへのメッセージを音声で届けるところまで実装してみてください。
Antigravityを使えば、ここで紹介したコードをベースに「日本語音声に変更して」「ストリーミングではなくファイル保存に変更して」といった追加依頼で機能を拡張できます。音声AIの可能性は、まずシンプルな実装から体感するのが一番の近道だと思います。
音声とAPIの組み合わせ方については、Antigravity の Gemini Live API 音声エージェント実装ガイドや外部APIとのカスタムエージェント連携も参考にしてみてください。