Quick start

This page has not been translated yet.

本チュートリアルでは、録音・録画機能を利用するための基本的な手順を説明します。

クラウドストレージの準備

録音・録画機能を使用するためには Google Cloud Storage、もしくは Amazon S3 のどちらかのクラウドストレージの設定が必要となります。ここではクラウドストレージ別に設定方法を説明します。

Google Cloud Storage を利用する場合

ロールの作成

サービスアカウントに付与する最小権限のロールを作成します。

ロールの管理画面を開きます。

ロールの作成ボタンをクリックします。

gcs-role-1

ロールの名前、説明を設定し、権限を追加ボタンをクリックします。

gcs-role-2

フィルタと書かれた検索欄に storage.objects. と入力し、検索結果から以下の権限を追加します。

  • storage.objects.create
  • storage.objects.delete
  • storage.objects.get
  • storage.objects.list

gcs-role-3

作成ボタンをクリックします。

gcs-role-4

サービスアカウントの作成

サービスアカウントの管理画面を開きます。

サービスアカウントを作成ボタンをクリックします。

gcs-1

サービスアカウント名、ID を入力し、作成して続行ボタンをクリックします。なお、作成されたメールアドレスは後で利用します。

gcs-2

ロールを選択メニューから先ほど作成したロールを選択します。

gcs-sa-role

完了ボタンをクリックし、サービスアカウントを作成します。

gcs-sa-role-2

検索欄にサービスアカウント名を入力し、検索結果のリンクをクリックします。

gcs-3

キーのタブをクリックしてキーの管理画面を開き、鍵を追加メニューを開きます。新しい鍵を作成ボタンをクリックします。

gcs-4

JSON を指定して作成ボタンをクリックします。JSON ファイルがダウンロードされます。

gcs-5

バケットの作成

Google Cloud Storage の管理画面を開きます。

作成ボタンをクリックします。

gcs-6

バケット名、リージョンなどの設定を行い作成ボタンをクリックします。ここで設定したバケット名は後で利用するため、保存してください。

gcs-7

これで操作は完了です。

以下のデータを保存できていることを確認してください。

  • バケット名
  • サービスアカウントの鍵の JSON ファイル

Amazon S3 を利用する場合

コンソールへのサインイン

コンソール にサインインします。アカウントをお持ちでない場合は、同ページで新規に作成してください。

S3バケットの作成

Amazon S3の管理画面 を開きます。

Create bucket をクリックして作成画面を開きます。

s3-1

AWS Region より任意のリージョンを選択し、Bucket name に任意の名前を入力します。

なお、ここで設定したリージョン名(例. ap-northeast-1)、バケット名は後で利用するため、保存してください。

s3-2

最下部の Create bucket をクリックし、S3 バケットを作成します。

s3-3

IAMの設定

IAM の管理画面を開き、メニューよりUsersの管理画面を開きます。

s3-4

Create user ボタンをクリックして作成画面を開きます。

s3-5

User name に任意のユーザー名を入力します。

Next ボタンを押し次の画面を開きます。

s3-6

Permissions options より Attach policies directly を選択します。

s3-7

Permissions policies の右の Create policy リンクを開きます。

s3-8

Select a service の Choose a service より S3を選択します。

s3-9

Access level より Write メニューを開きます。

s3-10

DeleteObject と PutObject にチェックを入れます。

Resources より Specific を選択し、Add ARNs リンクをクリックします。

s3-11

Resource bucket name に先ほど設定したバケット名を入力します。

Resource object name に * を入力します。

Add ARNs ボタンをクリックします。

s3-12

最下部の Next ボタンをクリックします。

s3-13

Policy details の Policy name にポリシー名を入力します。なお、この名前は後で利用するため、保存してください。

s3-14

最下部の Create policy ボタンをクリックします。

s3-15

Permissions policies のタブに戻り、Permissions policies の再読み込みボタンをクリックします。

s3-16

Permissions policies の検索欄に先ほどの Policy name を入力します。 先ほどの Policy name の Policy にチェックを入れ、 最下部の Next ボタンをクリックし次の画面を開きます。

s3-17

最下部の Create user ボタンをクリックし、User を作成します。

s3-18

Users より作成した User をクリックし管理画面を開きます。

s3-19

Security credentials タブを開きます。

Access keys より Create access key ボタンをクリックし、作成画面を開きます。

s3-20

Use case より Third-party service を選択します。 Confirmation にチェックを入れ、Next ボタンをクリックし次の画面を開きます。

s3-21

Create access key ボタンをクリックし、次の画面を開きます。

s3-22

Download .csv file をクリックし、保存します。csv ファイルから Access key と Secret access key を取得できます。

s3-23

Done ボタンをクリックします。

s3-24

これで操作は完了です。

この時点で以下のデータを保存できていることを確認してください。

  • リージョン名
  • バケット名
  • .csv ファイル
    • ファイル中に以下の情報が含まれているか確認してください。
      • Access key
      • Secret access key

チュートリアルアプリの作成

JavaScript SDK を利用して、録音・録画機能を体験できるシンプルなアプリケーションを作成します。

環境構築

Node.js のバージョン 18 以降をインストールし、任意の作業ディレクトリに tutorial ディレクトリを作成します。tutorial ディレクトリ直下に以下の内容の package.json ファイルを作成します。

{ "name": "tutorial", "version": "1.0.0", "type": "module", "scripts": { "client": "parcel client/index.html", "server": "node server/main.js" }, "browserslist": [ "last 3 chrome versions" ], "dependencies": { "@skyway-sdk/room": "^1.7.0", "cors": "^2.8.5", "express": "^4.18.2", "jsrsasign": "^11.1.0" }, "devDependencies": { "parcel": "^2.11.0" } }

その後、tutorial ディレクトリで npm i を実行してください。

サーバーサイド

録音・録画機能はサーバーサイドから SkyWay Recording API を通じて操作するので、そのためのサーバーアプリケーションを作成する必要があります。

ソースファイルの作成

tutorial/server/main.js ファイルを作成し、以下のコードを記述します。

tutorial/server/main.js

import cors from "cors"; import express from "express"; import jsrsasign from "jsrsasign"; import { SkyWayAuthToken, nowInSec, uuidV4 } from "@skyway-sdk/token"; import crypto from "crypto"; const appId = "ここにアプリケーションIDをペーストしてください"; const secret = "ここにシークレットキーをペーストしてください"; const s3Config = { service: "AMAZON_S3", bucket: "", accessKeyId: "", secretAccessKey: "", region: "", }; const gcsConfig = { service: "GOOGLE_CLOUD_STORAGE", credential: JSON.stringify({ // サービスアカウントの鍵のJSONファイルの内容をコピーペーストする type: "", project_id: "", private_key_id: "", private_key: "", client_email: "", client_id: "", auth_uri: "", token_uri: "", auth_provider_x509_cert_url: "", client_x509_cert_url: "", }), bucket: "", }; const recordingApiBaseUrl = "https://recording.skyway.ntt.com/v1"; const channelApiUrl = "https://channel.skyway.ntt.com/v1/json-rpc";

なお、appId,secret には自身のアプリケーションの値を入力してください。

また、利用するクラウドストレージに合わせて s3Config か gcsConfig に必要な情報を入力してください。

SkyWay Admin Auth Token の作成

SkyWay の Recording API および Channel API を操作するために SkyWay Admin Auth Token を作成する必要があります。

SkyWay Admin Auth Token の詳細な仕様は次の記事を参照してください。

SkyWay Admin Auth Token

tutorial/server/main.js

// Web APIを操作するためのトークン const createSkyWayAdminAuthToken = () => { const token = jsrsasign.KJUR.jws.JWS.sign( "HS256", JSON.stringify({ alg: "HS256", typ: "JWT" }), JSON.stringify({ exp: nowInSec() + 60, iat: nowInSec(), jti: uuidV4(), appId, }), secret ); return token; };

SkyWay Auth Token の作成

SkyWay のクライアントサイド SDK で利用する SkyWay Auth Token を作成する必要があります。

SkyWay Auth Token の詳細な仕様は次の記事を参照してください。

SkyWay Auth Token

tutorial/server/main.js

// クライアント用のトークン const createSkywayAuthToken = (channelName) => { const token = new SkyWayAuthToken({ jti: uuidV4(), iat: nowInSec(), exp: nowInSec() + 60 * 60 * 24, scope: { app: { id: appId, turn: true, actions: ["read"], channels: [ { name: channelName, actions: ["write"], members: [ { id: "*", name: "*", actions: ["write"], publication: { actions: ["write"], }, subscription: { actions: ["write"], }, }, ], sfuBots: [ { actions: ["write"], forwardings: [ { actions: ["write"], }, ], }, ], }, ], }, }, }).encode(secret); return token; };

ここでは決められた channel 名(room 名)の channel(room)にしか入室できないトークンを作成しています。SkyWay Auth Token の認可部分の設定は開発するサービスの要件に合わせて適切な設定を行ってください。

サーバーフレームワークの設定

このチュートリアルアプリでは、サーバーフレームワークとして express を利用します。こちらの設定を行います。

tutorial/server/main.js

const app = express(); app.use(cors()); app.use(express.json());

Channel(Room)の入室管理

クライアントから Channel 名(Room 名)を受け取って、その Channel 名の Channel を SkyWay Channel API で作成しています。SkyWay Channel API の詳細な仕様は次の記事を参照してください。

SkyWay Channel API

tutorial/server/main.js

const tokenHashChannelIdMap = {}; const channelNameIdMap = {}; const sha256 = (s) => crypto.createHash("sha256").update(s).digest("hex"); app.post("/channels/:channelName/join", async (req, res) => { const { channelName } = req.params; console.log("join", { channelName }); // 入力のchannelNameの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: channelName, }, }), }); const { result: { channel: { id: channelId }, }, } = await response.json(); channelNameIdMap[channelName] = channelId; // 入室できるchannelNameを制限したトークンを作成する const token = createSkywayAuthToken(channelName); // 今後の録音・録画の開始、終了操作を認証するためにtokenとchannelIdの紐付けを行う tokenHashChannelIdMap[sha256(token)] = channelId; res.send({ token }); });

録音・録画 の開始

指定した Channel の全 Publication を録音・録画するために CreateRecordingSession API を呼び出します。

tutorial/server/main.js

const channelNameRecordingMap = {}; app.post("/channels/:channelName/start", async (req, res) => { const { channelName } = req.params; const { authorization } = req.headers; const channelId = channelNameIdMap[channelName]; console.log("start", { channelName, channelId }); if (channelNameRecordingMap[channelName]) { res.status(200).send({ message: "already recording" }); return; } // channelIdとtokenの紐付けを確認する if (channelId !== tokenHashChannelIdMap[sha256(authorization ?? "")]) { res.status(403).send({ message: "Forbidden" }); return; } // Recordingを開始する const response = await fetch( `${recordingApiBaseUrl}/channels/${channelId}/sessions`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${createSkyWayAdminAuthToken()}`, }, body: JSON.stringify({ input: { kind: "SFU", publications: [{ id: "*" }], // すべてのPublicationを保存する }, output: gcsConfig, // S3を使う場合はここにs3Configを指定 }), } ); const { error, id } = await response.json(); if (error) { res.status(500).send({ message: error.message }); return; } channelNameRecordingMap[channelName] = id; res.status(201).send({ id }); });

録音・録画の終了

RecordingSession を削除するとすべての録音・録画処理が終了し、録音・録画ファイルが指定したクラウドストレージに保存されます。

tutorial/server/main.js

app.delete("/channels/:channelName/stop", async (req, res) => { const { channelName } = req.params; const { authorization } = req.headers; const channelId = channelNameIdMap[channelName]; const sessionId = channelNameRecordingMap[channelName]; console.log("stop", { channelName, channelId, sessionId }); // channelIdとtokenの紐付けを確認する if (channelId !== tokenHashChannelIdMap[sha256(authorization ?? "")]) { res.status(403).send({ message: "Forbidden" }); return; } // 録音・録画を終了する const response = await fetch( `${recordingApiBaseUrl}/channels/${channelId}/sessions/${sessionId}`, { method: "DELETE", headers: { Authorization: `Bearer ${createSkyWayAdminAuthToken()}`, }, } ); // 録音・録画したファイルの一覧を取得する const { error, files } = await response.json(); if (error) { res.status(500).send({ message: error.message }); return; } const filePaths = files.map((file) => file.path); // Recordingしたファイルのパスを出力する。クラウドストレージのこのパスにアップロードされている console.log("filePaths", filePaths); delete channelNameRecordingMap[channelId]; res.status(200).send({ filePaths }); });

サーバーの起動

ポート 9090 でサーバーを起動します。

tutorial/server/main.js

app.listen(9090); console.log("Server is running on http://localhost:9090");

クライアントサイド

以下を参考に tutorial/client/index.html ファイルを作成してください。

tutorial/client/index.html

<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width" /> <title>SkyWay Tutorial</title> </head> <body> <p>ID: <span id="my-id"></span></p> <div> room name: <input id="room-name" type="text" /> <button id="join">join</button> <button id="startRecording">start recording</button> <button id="stopRecording">stop recording</button> </div> <video id="local-video" width="400px" muted playsinline></video> <div id="button-area"></div> <div id="remote-media-area"></div> <script type="module" src="main.js"></script> </body> </html>

次にスクリプトファイルを用意します。 以下を参照して、tutorial/client/main.js ファイルを作成してください。

tutorial/client/main.js

import { RemoteVideoStream, SkyWayContext, SkyWayRoom, SkyWayStreamFactory, } from "@skyway-sdk/room"; (async () => { const localVideo = document.getElementById("local-video"); const buttonArea = document.getElementById("button-area"); const remoteMediaArea = document.getElementById("remote-media-area"); const roomNameInput = document.getElementById("room-name"); const myId = document.getElementById("my-id"); const joinButton = document.getElementById("join"); const startRecordingButton = document.getElementById("startRecording"); const stopRecordingButton = document.getElementById("stopRecording"); const { audio, video } = await SkyWayStreamFactory.createMicrophoneAudioAndCameraStream(); video.attach(localVideo); await localVideo.play(); joinButton.onclick = async () => { const roomName = roomNameInput.value; if (roomName === "") return; const response = await fetch( `http://localhost:9090/channels/${roomName}/join`, { method: "POST", } ); const { token } = await response.json(); const context = await SkyWayContext.Create(token); const room = await SkyWayRoom.Find(context, { name: roomName }, "sfu"); const me = await room.join(); myId.textContent = me.id; await me.publish(audio); await me.publish(video); const subscribeAndAttach = (publication) => { if (publication.publisher.id === me.id) return; const subscribeButton = document.createElement("button"); subscribeButton.textContent = `${publication.publisher.id}: ${publication.contentType}`; buttonArea.appendChild(subscribeButton); subscribeButton.onclick = async () => { const { stream } = await me.subscribe(publication.id); let newMedia; switch (stream.track.kind) { case "video": newMedia = document.createElement("video"); newMedia.playsInline = true; newMedia.autoplay = true; break; case "audio": newMedia = document.createElement("audio"); newMedia.controls = true; newMedia.autoplay = true; break; default: return; } stream.attach(newMedia); remoteMediaArea.appendChild(newMedia); }; }; room.publications.forEach(subscribeAndAttach); room.onStreamPublished.add((e) => subscribeAndAttach(e.publication)); startRecordingButton.onclick = async () => { await fetch(`http://localhost:9090/channels/${room.name}/start`, { method: "POST", headers: { Authorization: context.authTokenString, }, }); }; stopRecordingButton.onclick = async () => { await fetch(`http://localhost:9090/channels/${room.name}/stop`, { method: "DELETE", headers: { Authorization: context.authTokenString, }, }); }; }; })().catch((e) => console.error("main error", e));

チュートリアルアプリの実行

アプリケーションの起動

ターミナルで npm run server を実行してサーバーを起動します。

別のターミナルで npm run client を実行してクライアントを起動します。なお、この際にターミナルにローカルアドレスが表示されるので、そのアドレスをブラウザで開いてください。

Room に join したあと、startRecording ボタンを押すと録音・録画が始まります。stopRecording ボタンを押すと録音・録画が終了します。

保存されたファイルの確認

stopRecording ボタンを押すと、サーバー側の標準出力にクラウドストレージにアップロードされたファイルのパスが出力されます。 クラウドストレージのコンソールにアクセスし、ファイルがアップロードされていることを確認してください。

保存されるファイルの詳細な仕様については概要の録音・録画ファイルの項目を参照してください。