🈳
Sora JavaScript SDKをNext.jsで使う
こちらのSora SFUのSDKをNext.jsで使ってみる。
成果物
サンプルコードはこちら。
Next.jsのプロジェクトの作成
まずNext.jsのプロジェクトを作成する。最初の設定はデフォの通り。
$ yarn create next-app
yarn create v1.22.21
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
[4/4] 🔨 Building fresh packages...
success Installed "create-next-app@14.0.4" with binaries:
- create-next-app
✔ What is your project named? … sora-sdk-nextjs
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
sora-sdk-jsの導入
公式のチュートリアルはviteを使ったサンプルなのでこれを参考にNext.jsに組み込んでいく。
$ yarn add sora-js-sdk
検証用にSora Labを使う
Sora Labo は、時雨堂が開発する WebRTC SFU Sora を無料で検証できるサービス。シグナリングサーバを無料で検証用に使える。GitHubアカウントなどでサインアップする。
以下の情報をSora Labから持ってくる。
- シグナリングURL
- チャネルID
- シークレットキー
.env.local の作成
環境変数ファイルを追加。
$ touch .env.local
中身はこれ。Sora Labのページから持ってきた値を入れていく。
NEXT_PUBLIC_SORA_SIGNALING_URL={Sora LabのシグナリングURL}
NEXT_PUBLIC_SORA_CHANNEL_ID={Sora LabのチャネルID}
NEXT_PUBLIC_ACCESS_TOKEN={Sora Labのシークレットキー}
接続処理
WebRTCの接続のための重要なコードはこのあたり。
const soraConnection = Sora.connection(signalingUrl, debug);
const connection = soraConnection.sendrecv(channelId, metadata, options);
connection.on("track", (event) => {
const stream = event.streams[0];
// streamをvideoタグのsrcObjectにセットする
});
await connection.connect(stream);
Reactのコード
上記コードをNext.jsに組み込むためにsrc/app/page.tsx
のコードを書き換えた。yarn dev
で起動してブラウザを2つ開いて相互に接続できたので成功!
"use client";
import { useCallback, useEffect, useState } from "react";
import Sora from "sora-js-sdk";
import type { ConnectionPublisher } from "sora-js-sdk";
export default function Home() {
const [remoteStreams, setRemoteStreams] = useState<MediaStream[]>([]);
const [sendrecv, setSendrecv] = useState<ConnectionPublisher | null>(null);
const handleConnect = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: true,
});
const debug = true;
const signalingUrl = process.env.NEXT_PUBLIC_SORA_SIGNALING_URL as string;
const channelId = process.env.NEXT_PUBLIC_SORA_CHANNEL_ID as string;
const metadata = {
access_token: process.env.NEXT_PUBLIC_ACCESS_TOKEN,
};
const options = {
multistream: true,
};
const soraConnection = Sora.connection(signalingUrl, debug);
const connection = soraConnection.sendrecv(channelId, metadata, options);
connection.on("track", (event) => {
const stream = event.streams[0];
setRemoteStreams((prevStreams) => [...prevStreams, stream]);
});
connection.on("removetrack", (event) => {
const target = event.target as MediaStream;
setRemoteStreams((prevStreams) =>
prevStreams.filter((stream) => stream.id !== target.id)
);
});
await connection.connect(stream);
setSendrecv(connection);
} catch (error) {
console.error("Failed to connect:", error);
}
};
const handleDisconnect = useCallback(async () => {
if (sendrecv) {
await sendrecv.disconnect();
setRemoteStreams([]);
}
}, [sendrecv]);
useEffect(() => {
return () => {
handleDisconnect();
};
}, [handleDisconnect]);
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<button
id="connectButton"
onClick={handleConnect}
disabled={sendrecv !== null}
>
Connect
</button>
<button
id="disconnectButton"
onClick={handleDisconnect}
disabled={sendrecv === null}
>
Disconnect
</button>
<div id="remoteVideos">
{remoteStreams.map((stream) => (
<video
key={stream.id}
id={`remoteVideo-${stream.id}`}
autoPlay
playsInline
ref={(video) => video && (video.srcObject = stream)}
/>
))}
</div>
</main>
);
}
Discussion