組み込み

Next.js に avacast を組み込む手順

サーバーでセッションを作り、ブラウザで SDK に渡すまでを、App Router の構成で順に説明します。


用意するもの

  • API キー: 管理画面の「API キー」で発行します。
  • アバターの ID: GET /v1/avatars で一覧を取れます。
  • SDK: npm install avacast

アバターの一覧

curl https://api.avacast.jp/v1/avatars \
  -H "Authorization: Bearer $AVACAST_API_KEY"

API キーは .env.local に置き、NEXT_PUBLIC_ を付けません。付けるとブラウザに渡ってしまいます。

.env.local

AVACAST_API_KEY=sk_live_...
AVACAST_AVATAR_ID=avt_xxxxxxxx

セッションを作る Route Handler

サーバー側で POST /v1/sessions を呼び、ブラウザに必要な値だけを返します。ブラウザに渡すのは client_token と webrtc で、API キーは返しません。

app/api/avacast/session/route.ts

export async function POST() {
  // 実際には、ログイン中の利用者だけが呼べるように認証を挟む
  const res = await fetch('https://api.avacast.jp/v1/sessions', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.AVACAST_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ avatar_id: process.env.AVACAST_AVATAR_ID }),
  });
  const body = await res.json();
  if (!res.ok) {
    return Response.json({ error: body.error.code }, { status: res.status });
  }
  const { session_id, client_token, webrtc } = body;
  return Response.json({ session_id, client_token, webrtc });
}

課金はセッションを開いていた時間です。使い終わったら閉じられるように、終了用の Route Handler も作っておきます。Next.js 16 では params が Promise なので await します。

app/api/avacast/session/[id]/route.ts

export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  await fetch(`https://api.avacast.jp/v1/sessions/${id}`, {
    method: 'DELETE',
    headers: { Authorization: `Bearer ${process.env.AVACAST_API_KEY}` },
  });
  return new Response(null, { status: 204 });
}

終了を呼ばなかった場合も、プランの最大長か、話さない時間が続いたとき (既定 180 秒) に自動で閉じます。

映像を出すクライアントコンポーネント

SDK はブラウザの WebRTC を使うので、'use client' のコンポーネントの useEffect の中で動かします。attach は start の前でも後でも呼べます。

app/components/avatar.tsx

'use client';

import { AvacastSession } from 'avacast';
import { useEffect, useRef, useState } from 'react';

export function Avatar() {
  const videoRef = useRef<HTMLVideoElement>(null);
  const [session, setSession] = useState<AvacastSession | null>(null);

  useEffect(() => {
    let s: AvacastSession | null = null;
    let sessionId: string | null = null;
    let cancelled = false;

    (async () => {
      const res = await fetch('/api/avacast/session', { method: 'POST' });
      const { session_id, client_token, webrtc } = await res.json();
      if (cancelled) {
        // 作り終える前に画面を離れた (StrictMode の 1 回目もここ)
        void fetch(`/api/avacast/session/${session_id}`, { method: 'DELETE' });
        return;
      }
      sessionId = session_id;
      s = new AvacastSession(client_token, {
        signalingUrl: webrtc.signaling_url,
        iceServers: webrtc.ice_servers,
        eventsUrl: webrtc.events_url,
      });
      if (videoRef.current) s.attach(videoRef.current);
      s.on('session.ready', () => setSession(s));
      await s.start();
    })();

    return () => {
      cancelled = true;
      void s?.stop();
      // 画面を離れたらセッションを閉じる (keepalive でページ遷移中も送る)
      if (sessionId) {
        void fetch(`/api/avacast/session/${sessionId}`, { method: 'DELETE', keepalive: true });
      }
    };
  }, []);

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline />
      <button
        type="button"
        disabled={!session}
        onClick={() => session?.speak('ご相談ありがとうございます。')}
      >
        話させる
      </button>
    </div>
  );
}
  • speak は映像が届いてから (session.ready の後) 呼びます。その前に呼ぶと session_not_connected になります。
  • 開発中は React の StrictMode で useEffect が 2 回走ります。1 回目のセッションは cleanup で閉じるので、残りません。
  • SDK の speak は client_token で認証するので、ブラウザから呼んでも API キーは出ません。

ページに置く

app/page.tsx

import { Avatar } from './components/avatar';

export default function Page() {
  return <Avatar />;
}

ここまでで、ページを開くとアバターの映像が表示され、ボタンで話します。話させる文章をサーバーで作る場合は、ブラウザの speak の代わりにサーバーから POST /v1/sessions/:id/speak を呼びます。詳しくはドキュメントを参照してください。

月 30 分まで無料

カード登録なしで始められます。

API キーを発行

ブログ一覧へ