---
lang: ja
path: user-guide/recording/recording-quickstart
labels: ユーザーガイド/録音・録画/クイックスタート
metaTitle: クイックスタート ｜ 録音・録画 ｜ ユーザーガイド ｜ SkyWay（スカイウェイ）
---

# クイックスタート

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

なお、P2P Room で録画・録画を行う場合については[P2P Room での通話を録音・録画する方法](/ja/docs/user-guide/recording/recording-development-guide/#4)をご覧ください。


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

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

### Google Cloud Storage を利用する場合

#### ロールの作成

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

[ロールの管理画面](https://console.cloud.google.com/iam-admin/roles)を開きます。

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

![ロールの管理画面で「ロールの作成」をクリックする](/media/posts/docs/recording/quick/gcs/gcs-role-1.png)

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

![ロールの作成画面で権限を追加ボタンをクリックする](/media/posts/docs/recording/quick/gcs/gcs-role-2.png)

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

- storage.objects.create
- storage.objects.delete
- storage.objects.get
- storage.objects.list

![権限の追加画面で必要な権限を検索して追加する](/media/posts/docs/recording/quick/gcs/gcs-role-3.png)

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

![ロールの作成画面で作成ボタンをクリックする](/media/posts/docs/recording/quick/gcs/gcs-role-4.png)

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

[サービスアカウントの管理画面](https://console.cloud.google.com/iam-admin/serviceaccounts)を開きます。

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

![サービスアカウントの管理画面で「サービスアカウントを作成」をクリックする](/media/posts/docs/recording/quick/gcs/gcs-1.png)

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

![サービスアカウントの作成画面で、作成して続行ボタンをクリックする](/media/posts/docs/recording/quick/gcs/gcs-2.png)

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

![ロールの選択画面で、作成したロールを選択する](/media/posts/docs/recording/quick/gcs/gcs-sa-role.png)

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

![サービスアカウントの作成画面で、完了ボタンをクリックする](/media/posts/docs/recording/quick/gcs/gcs-sa-role-2.png)

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

![サービスアカウントの一覧画面で作成したサービスアカウントを検索し、作成したサービスアカウントを選択する](/media/posts/docs/recording/quick/gcs/gcs-3.png)

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

![「キー」のタブで「鍵を追加」をクリックし、「新しい鍵を作成」を選択する](/media/posts/docs/recording/quick/gcs/gcs-4.png)

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

![キーのタイプでJSONを選択して、作成ボタンをクリックする](/media/posts/docs/recording/quick/gcs/gcs-5.png)

#### バケットの作成

[Google Cloud Storage の管理画面](https://console.cloud.google.com/storage/browser)を開きます。

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

![Google Cloud Storage の管理画面で、作成ボタンをクリックする](/media/posts/docs/recording/quick/gcs/gcs-6.png)

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

![バケットの作成画面で、作成ボタンをクリックする](/media/posts/docs/recording/quick/gcs/gcs-7.png)

これで操作は完了です。

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

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

### Amazon S3 を利用する場合

#### S3 バケットの作成

[Amazon S3 の管理画面](https://s3.console.aws.amazon.com/s3) を開きます。

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

![Amazon S3 の管理画面で、Create bucket をクリックする](/media/posts/docs/recording/quick/s3/s3-1.png)

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

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

![バケットの作成画面で、リージョンとバケット名を入力する](/media/posts/docs/recording/quick/s3/s3-2.png)

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

![バケットの作成画面で、Create bucket をクリックする](/media/posts/docs/recording/quick/s3/s3-3.png)

#### IAM の設定

[IAM](https://console.aws.amazon.com/iam) の管理画面を開き、メニューより[Users](https://console.aws.amazon.com/iam/home#/users)の管理画面を開きます。

![IAM の管理画面で、Users の管理画面を開く](/media/posts/docs/recording/quick/s3/s3-4.png)

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

![Users の管理画面で、Create user ボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-5.png)

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

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

![User の作成画面で、Next ボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-6.png)

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

![権限の設定画面で、Attach policies directly を選択する](/media/posts/docs/recording/quick/s3/s3-7.png)

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

![権限の設定画面で、Create policy をクリックする](/media/posts/docs/recording/quick/s3/s3-8.png)

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

![サービス選択画面で、S3 を選択する](/media/posts/docs/recording/quick/s3/s3-9.png)

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

![権限設定画面で、Write のメニューを開く](/media/posts/docs/recording/quick/s3/s3-10.png)

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

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

![権限設定画面で、Add ARNs リンクをクリックする](/media/posts/docs/recording/quick/s3/s3-11.png)

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

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

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

![ARN の設定画面で、Add ARNs ボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-12.png)

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

![権限設定画面で、Next ボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-13.png)

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

![Policy details 画面で、Policy name にポリシー名を入力する](/media/posts/docs/recording/quick/s3/s3-14.png)

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

![権限設定画面で、Create policy ボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-15.png)

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

![権限設定画面の Permissions policies のタブで、再読み込みボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-16.png)

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

![権限設定画面の検索欄に、作成したポリシーのポリシー名を入力し、検索結果からポリシーにチェックを入れ、Next ボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-17.png)

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

![Users の作成画面で、Create user ボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-18.png)

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

![Users の管理画面で、作成したユーザーを検索し、検索結果から User をクリックする](/media/posts/docs/recording/quick/s3/s3-19.png)

Security credentials タブを開きます。

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

![ユーザーの詳細画面で、Create access key ボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-20.png)

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

![Access key の作成画面で、Third-party service を選択し、Confirmation にチェックを入れ、Next ボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-21.png)

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

![Access key の作成画面で、Create access key ボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-22.png)

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

![Access key の作成画面で、Download .csv file をクリックする](/media/posts/docs/recording/quick/s3/s3-23.png)

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

![Access key の作成画面で、Done ボタンをクリックする](/media/posts/docs/recording/quick/s3/s3-24.png)

これで操作は完了です。

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

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

### Wasabi を利用する場合

#### バケットの作成

[Wasabi の管理画面](https://console.wasabisys.com/file_manager/) を開きます。

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

![Buckets の画面で、Create Bucket をクリックする](/media/posts/docs/recording/quick/wasabi/wasabi-1.png)

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

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

![バケットの作成画面で、バケット名とリージョン名を入力する](/media/posts/docs/recording/quick/wasabi/wasabi-2.png)

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

![バケットの作成画面で、Create Bucket をクリックする](/media/posts/docs/recording/quick/wasabi/wasabi-3.png)

#### IAM の設定

[Policies の管理画面](https://console.wasabisys.com/policies) を開き、Create Policy ボタンをクリックして作成画面を開きます。

![Policies の管理画面で、Create Policy をクリックする](/media/posts/docs/recording/quick/wasabi/wasabi-4.png)

Policy Name に任意の名前を入力し、Policy Editor に以下の設定値を入力した後、Create Policy ボタンをクリックし、Policy を作成します。
設定値の例の Resource にある skyway-recording-tutorial の部分は、先ほど作成したバケット名で置き換えてください。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:DeleteObject", "s3:PutObject"],
      "Resource": ["arn:aws:s3:::skyway-recording-tutorial/*"],
      "Condition": {}
    }
  ]
}
```

![ポリシーの設定画面で、ポリシー名とポリシーの設定を入力し、Create Policy をクリックする](/media/posts/docs/recording/quick/wasabi/wasabi-5.png)

次に、[Users](https://console.wasabisys.com/users) の管理画面を開き、Create User ボタンをクリックして作成画面を開きます。

![Users の管理画面で、Create User をクリックする](/media/posts/docs/recording/quick/wasabi/wasabi-6.png)

Create a Username に任意のユーザー名を入力し、Type of Access の Programmatic (create API key) を選択したあと、Next をクリックします。

![Users の作成画面で、Next ボタンをクリックする](/media/posts/docs/recording/quick/wasabi/wasabi-7.png)

Group の設定が必要な場合は設定し、Next をクリックします。不要な場合は、何も設定せずに Next をクリックします。

![Users の作成画面で、Next ボタンをクリックする](/media/posts/docs/recording/quick/wasabi/wasabi-8.png)

Attach Policy To User で先ほど作成した Policy を選択します。

![Users の作成画面で、Attach Policy To User で先ほど作成した Policy を選択する](/media/posts/docs/recording/quick/wasabi/wasabi-9.png)

Policies than will be attached: で先ほど作成した Policy が表示されていることを確認し、Next をクリックします。

![Users の作成画面で、Next ボタンをクリックする](/media/posts/docs/recording/quick/wasabi/wasabi-10.png)

内容を確認し、問題なければ Create User をクリックします。

![Users の作成画面で、Create User ボタンをクリックする](/media/posts/docs/recording/quick/wasabi/wasabi-11.png)

User が作成され、 数秒ほど時間が経ってから Access Key が表示されます。Download CSV をクリックし、Access Key と Secret Key が記載された CSV ファイルを保存した後、ポップアップを閉じます。

![Create Access Key の画面で、Download CSV ボタンをクリックする](/media/posts/docs/recording/quick/wasabi/wasabi-12.png)

これで操作は完了です。

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

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

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

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

### 環境構築

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

```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": "^2.0.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_

```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 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 s3Config = {
  service: 'AMAZON_S3',
  bucket: '',
  accessKeyId: '',
  secretAccessKey: '',
  region: '',
};

const wasabiConfig = {
  service: 'WASABI',
  bucket: '',
  accessKeyId: '',
  secretAccessKey: '',
  endpoint: '',
};

const recordingApiBaseUrl = 'https://recording.skyway.ntt.com/v1';
const roomApiUrl = 'https://room.skyway.ntt.com/v1/json-rpc';
```

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

また、利用するクラウドストレージに合わせて gcsConfig、s3Config、wasabiConfig のいずれかに必要な情報を入力してください。
wasabiConfig の endpoint は、 `https://s3.<リージョン名>.wasabisys.com` のように設定してください。
(例: `https://s3.ap-northeast-1.wasabisys.com` )

#### SkyWay Admin Auth Token の作成

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

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

[SkyWay Admin Auth Token](/ja/docs/user-guide/authentication/skyway-admin-auth-token/)

_tutorial/server/main.js_

```js
// Recording API と Room 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](/ja/docs/user-guide/authentication/skyway-auth-token/)

_tutorial/server/main.js_

```js
// クライアント用のトークン
const createSkywayAuthToken = (roomName) => {
  const token = new SkyWayAuthToken({
    jti: uuidV4(),
    iat: nowInSec(),
    exp: nowInSec() + 60 * 60 * 24,
    version: 3,
    scope: {
      appId: appId,
      rooms: [
        {
          name: roomName,
          methods: ["create", "close", "updateMetadata"],
          member: {
            name: "*",
            methods: ["publish", "subscribe", "updateMetadata"],
          },
        },
      ],
    },
  }).encode(secret);
  return token;
};
```

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

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

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

_tutorial/server/main.js_

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

#### Room の入室管理

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

[SkyWay Room API](/ja/docs/user-guide/rtc-api-server/room-api)

_tutorial/server/main.js_

```js
const tokenHashRoomIdMap = {};
const roomNameIdMap = {};
const sha256 = (s) => crypto.createHash('sha256').update(s).digest('hex');

app.post('/rooms/:roomName/join', async (req, res) => {
  const { roomName } = req.params;

  console.log('join', { roomName });

  // 入力のroomNameのRoomを作成する
  const response = await fetch(roomApiUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${createSkyWayAdminAuthToken()}`,
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: uuidV4(),
      method: 'findOrCreateRoom',
      params: {
        name: roomName,
      },
    }),
  });
  const {
    result: {
      room: { id: roomId },
    },
  } = await response.json();
  roomNameIdMap[roomName] = roomId;

  // 入室できるroomNameを制限したトークンを作成する
  const token = createSkywayAuthToken(roomName);
  // 今後の録音・録画の開始、終了操作を認証するためにtokenとroomIdの紐付けを行う
  tokenHashRoomIdMap[sha256(token)] = roomId;

  res.send({ token });
});
```

#### 録音・録画 の開始

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

_tutorial/server/main.js_

```js
const roomNameRecordingMap = {};

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 });

  if (roomNameRecordingMap[roomName]) {
    res.status(200).send({ message: 'already recording' });
    return;
  }

  // roomIdとtokenの紐付けを確認する
  if (roomId !== tokenHashRoomIdMap[sha256(authorization ?? '')]) {
    res.status(403).send({ message: 'Forbidden' });
    return;
  }

  // Recordingを開始する
  const response = await fetch(`${recordingApiBaseUrl}/rooms/${roomId}/sessions`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${createSkyWayAdminAuthToken()}`,
    },
    body: JSON.stringify({
      input: {
        kind: 'SFU',
        publications: [{ id: '*' }], // すべてのPublicationを保存する
      },
      output: gcsConfig, // Amazon S3を使う場合は s3Config を、Wasabiを使う場合は wasabiConfig を指定
    }),
  });

  const { error, id } = await response.json();
  if (error) {
    res.status(500).send({ message: error.message });
    return;
  }
  roomNameRecordingMap[roomName] = id;

  res.status(201).send({ id });
});
```

#### 録音・録画の終了

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

_tutorial/server/main.js_

```js
app.delete('/rooms/:roomName/stop', async (req, res) => {
  const { roomName } = req.params;
  const { authorization } = req.headers;
  const roomId = roomNameIdMap[roomName];
  const sessionId = roomNameRecordingMap[roomName];

  console.log('stop', { roomName, roomId, sessionId });

  // roomIdとtokenの紐付けを確認する
  if (roomId !== tokenHashRoomIdMap[sha256(authorization ?? '')]) {
    res.status(403).send({ message: 'Forbidden' });
    return;
  }

  // 録音・録画を終了する
  const response = await fetch(`${recordingApiBaseUrl}/rooms/${roomId}/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 roomNameRecordingMap[roomId];

  res.status(200).send({ filePaths });
});
```

#### サーバーの起動

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

_tutorial/server/main.js_

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

### クライアントサイド

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

_tutorial/client/index.html_

```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_

```js
import { 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/rooms/${roomName}/join`, {
      method: 'POST',
    });
    const { token } = await response.json();

    const context = await SkyWayContext.Create(token);
    const room = await SkyWayRoom.Find(context, { name: roomName }, { type: '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/rooms/${room.name}/start`, {
        method: 'POST',
        headers: {
          Authorization: context.authTokenString,
        },
      });
    };

    stopRecordingButton.onclick = async () => {
      await fetch(`http://localhost:9090/rooms/${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 ボタンを押すと、サーバー側の標準出力にクラウドストレージにアップロードされたファイルのパスが出力されます。
クラウドストレージのコンソールにアクセスし、ファイルがアップロードされていることを確認してください。

保存されるファイルの詳細な仕様については概要の[録音・録画ファイル](/ja/docs/user-guide/recording/recording-overview/#94)の項目を参照してください。
