クイックスタート
本記事ではサンプルコードをもとに、文字起こし機能が付いた会議アプリの実装方法について説明します。
文字起こし結果のアーカイブ機能を利用する場合は、アーカイブ機能を参照してください。
サンプルコードは skyway-stt-client-js のリポジトリに同梱しています。
サンプルコードの完成形はこちらからご確認いただけます。
クイックスタートの環境
以下の環境で実施してください。
- Node: v20 以降
利用方法
ではさっそく動かしてみましょう。
まずはGitHub skyway-stt-client-jsを Git clone します。
examples/server フォルダに移動して、.env.example を .env に rename します。
cd examples/server && mv .env.example .env.env.example 内の APP_ID と SECRET に値を入力します。
APP_ID=ここにアプリケーションIDをペーストしてください
SECRET=ここにシークレットキーをペーストしてください依存パッケージをインストールします。
npm installサーバー側アプリケーションを起動します。
npm run startその後、別ターミナルでクライアント側アプリケーションを起動します。
cd examples/tutorial && npm run devクライアント側アプリケーションを起動すると、以下のような画面が立ち上がります。

任意の room 名と member 名を入力して、Join ボタンを押すと、room が作成されて入室できます。
その後、Mode を選択し Start ボタンを押します。
- transcription モード: 入力音声の言語を自動で判定し、その言語で文字起こしします
- translation モード: 入力音声の言語を日本語または英語で文字起こしを行い、もう一方の言語での翻訳も併記します
タブを2つ開き、同じ room 名を入力して Join ボタンを押せば、両タブで会議に参加し、文字起こし結果の表示を確認できます。
コード解説
全体像
サンプルコードはクライアントとサーバの2つのアプリケーションに分かれています。
それぞれの概要を説明します。
- クライアント
- JavaScript SDK と STT-Client を利用しています。
- Room 作成や文字起こし開始についてバックエンドサーバにリクエストし、STT-Client 経由で文字起こし結果を受け取ります。
- サーバ
- 3つの endpoint を持っています
- POST
/rooms/:roomName/create- リクエストされた roomName の Room を作成し、SkyWayAuthToken を返却する
- POST
/rooms/:roomName/start- リクエストされた roomName の Room について、文字起こしを開始する
- DELETE
/rooms/:roomName/end- リクエストされた roomName の Room について、文字起こしを終了する
- (以降のコード解説は割愛します)
- POST
sttApiBaseUrlには利用開始案内メールに記載してある接続先情報をご利用ください
- 3つの endpoint を持っています
クライアントのコード解説
チャンネル作成
const response = await fetch(
`${SERVER_HOST}/rooms/${roomName}/create`,
{
method: "POST",
},
);
const { token } = await response.json();
const context = await SkyWayContext.Create(token);
const room = await SkyWayRoom.Find(
context,
{
name: roomName,
},
);チャンネルを作成するサーバ API へリクエストします。レスポンスに SkyWayAuthToken が入っているので、これをもとに context を作成して room を find します。なお、文字起こしする音声をpublishするときのtypeは必ず"sfu"を選択する必要があります。
接続とリッスン
const me = await room.join({ name: memberName });
const sttClient = new SkyWaySTTClient(context, me);
sttClient.onSTTResultReceived.add(({ result }) => {
const member = room.members.find((m) => m.id === result.memberId);
const mode = sttMode.value;
const messageElement = createSTTMessage(result, member, mode);
sttResults.appendChild(messageElement);
sttResults.scrollTop = sttResults.scrollHeight;
});SkyWayContext と、join の返却値である Member を引数にとり、new SkyWaySTTClient でインスタンスを生成します。この時、内部的に STT サーバへ接続します。
onSTTResultReceived で、コールバック関数をセットすると、引数として文字起こし結果を得ることができます。サンプルコードではこれを HTML で表示するようにしています。
文字起こし開始
startSttButton.onclick = async () => {
const result = await fetch(
`${SERVER_HOST}/rooms/${roomName}/start`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
sttMode: sttMode.value,
}),
},
);
if (result.status === 200) {
sttStatus.textContent = "ON";
console.log("STT started");
} else {
console.error("Failed to Start STT");
}
};start エンドポイントにたいしてリクエストを行い、文字起こしを開始します。
サーバのコード解説
トークン生成
// クライアント用のトークン
const createSkywayAuthToken = (roomId) => {
const token = new SkyWayAuthToken({
jti: uuidV4(),
iat: nowInSec(),
exp: nowInSec() + 60 * 60 * 24,
version: 3,
scope: {
appId,
rooms: [
{
id: roomId,
methods: ["create", "close", "updateMetadata"],
member: {
id: "*",
// subscribeを許可する
methods: ["publish", "subscribe", "updateMetadata"],
},
// sttをenabledまたは省略(デフォルトでenabled)する
stt: {
enabled: true,
},
// sfuをenabledまたは省略(デフォルトでenabled)する
sfu: {
enabled: true,
},
},
],
},
}).encode(secret);
return token;
};コメントに記載の通り、文字起こし結果を取得するためには以下の設定にする必要があります。
- subscribe method を追加
stt.enabledを true, またはsttを省略sfu.enabledを true, またはsfuを省略
createエンドポイント
app.post("/rooms/:roomName/create", async (req, res) => {
const { roomName } = req.params;
console.log("create", { roomName });
// リクエストされたroomNameのRoom(Channel)を作成する
const response = await fetch(channelApiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${createSkyWayAdminAuthToken()}`,
},
body: JSON.stringify({
jsonrpc: "2.0",
id: uuidV4(),
method: "findOrCreateChannel",
params: {
name: roomName,
},
}),
});
const {
result: {
channel: { id: roomId },
},
} = await response.json();
roomNameIdMap[roomName] = roomId;
// 入室できるroomIdを制限したトークンを作成する
const token = createSkywayAuthToken(roomId);
// 今後の文字起こしの開始、終了操作を認証するためにtokenとroomIdの紐付けを行う
tokenHashRoomIdMap[sha256(token)] = roomId;
res.send({ token });
});- room の作成には
Channel APIを利用します。 - SkyWayAuthToken を作成し、レスポンスとしてクライアントへ返却します。
SkyWay Channel API は将来的に廃止予定であるため、同等の機能を有する SkyWay Room API への移行を推奨しております。 移行方法は『Channel APIからRoom APIへの移行』をご参照ください。
startエンドポイント
app.post("/rooms/:roomName/start", async (req, res) => {
const { roomName } = req.params;
const { authorization } = req.headers;
const roomId = roomNameIdMap[roomName];
console.log("start", { roomName, roomId, body: req.body });
if (roomNameRecordingMap[roomName]) {
res.status(200).send({ message: "STT already started" });
return;
}
// roomIdとtokenの紐付けを確認する
const tokenStr = (authorization ?? "").replace(/^Bearer\s*/, "");
if (roomId !== tokenHashRoomIdMap[sha256(tokenStr)]) {
res.status(403).send({ message: "Forbidden" });
return;
}
// 文字起こしを開始する
const { sttMode } = req.body;
const response = await fetch(`${sttApiBaseUrl}/rooms/${roomId}/sessions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${createSkyWayAdminAuthToken()}`,
},
body: JSON.stringify({
mode: sttMode.toUpperCase(),
}),
});
const json = await response.json();
if (!response.ok) {
console.error(json.error);
res.status(500).send({ message: json.error.message });
return;
}
roomNameRecordingMap[roomName] = json.id;
res.status(200).send({ id: json.id });
});STT サーバの sessions エンドポイントへリクエストを行い、対象 Room の文字起こしを開始します。mode にクライアントで選択された TRANSCRIPTION、TRANSLATION のいずれかを設定します。