# 録画機能 (セッション単位)

## レガシー録画機能から録画機能(セッション単位)への移行

[レガシー録画機能から新しい録画機能 (セッション単位) への移行](LEGACY_RECORDING_TO_RECORDING.html) を確認してください。

## 概要

Sora では、配信している映像や音声を録画、録音して保存できます。
配信されている映像をできるかぎりそのまま保存するため、CPU リソースを最小限に抑えることができます。
出力される録画ファイルは WebM 形式です。映像のみの録画、音声のみの録音にも対応しています。

録画時には一切トランスコードを行っていません。配信された映像や音声をそのままに記録します。

## 用語

**単一録画ファイル**
: 切断または [StopRecording](API_RECORDING.html#fd0de5) API が実行されるか、録画期限が切れるか、セッションが終了した場合に一つのファイルとして出力される動画ファイル

**分割録画ファイル**
: `split_duration` で指定した時間ごとに区切られ分割されて出力される動画ファイル

## 録画ファイルの出力

録画ファイルの出力には 3 パターンあります。

### 単一録画ファイルのみ出力

- `split_only` が未指定、または `false` に設定されている
- `split_duration` が未指定
- `expire_time` は指定されていてもよい

上記を満たした場合、1 接続で 1 つ録画ファイルが出力されます。

### 分割録画ファイルのみ出力

- `split_only` が `true` に指定されている
- `split_duration` が指定されている
- `expire_time` は指定されていてもよい

上記を満たした場合、分割された録画ファイルのみが出力されます。

### 単一録画ファイルと分割録画ファイルの両方が出力

- `sora.conf` で `recording_dual_output` が `true` に指定されている
- `split_only` が未指定、または `false` に指定されている
- `split_duration` が指定されている
- `expire_time` は指定されていてもよい

上記を満たした場合、 1 接続で 1 つの録画ファイルと分割された録画ファイルの両方が出力されます。

イベントウェブフックも単一録画、分割録画、両方のイベントウェブフックリクエストが送信されます。

## 制限

### コーデック

録画 (録音) は、映像コーデックに VP8 と VP9 と H.264 と AV1 、また音声コーデックに Opus と Lyra を選択した場合のみ利用できます。

> **警告**
>
> H.265 の録画には非対応です。

### 解像度

WebRTC では配信側の CPU リソースが不足した場合や、
回線の品質が悪化した場合に解像度を動的に変更します。
そのため録画したデータの途中で解像度が低くなる可能性があります。

### 音声や映像のクライアント側でのトラック削除

クライアント側でシグナリング接続時に音声や映像を有効にした状態で、
クライアント側で音声トラック、または映像トラックのどちらかを削除した場合でも録画は行われます。
さらに追加して戻した形であれば録画側も戻ります。

ただし、音声と映像両方のトラックを削除した場合は正常に録画が行われません。

### マルチストリーム機能での録画

対応しています。

### サイマルキャスト機能での録画

対応しています。

サイマルキャストを利用している際の録画は **一番優先度が低い** ストリーム、すなわちデフォルトでは最も高い画質の映像を録画します。

詳細はサイマルキャストの [映像の優先度](SIMULCAST.html#36c708) をご確認下さい。

### スポットライト機能での録画

対応しています。

サイマルキャストを利用している際の録画は **一番優先度が低い** ストリーム、すなわちデフォルトでは最も高い画質の映像を録画します。

詳細はサイマルキャストの [映像の優先度](SIMULCAST.html#36c708) をご確認下さい。

### キーフレーム要求間隔

録画機能利用時の Sora からのキーフレーム要求間隔は 20 秒に固定されています。
そのため、分割録画の最小時間は 20 秒 + `split_duration` に指定した秒数となります。

もし録画時のキーフレーム要求間隔を変更したい場合は、サポートまでご連絡ください。

## 無変換録画

WebRTC 経由で流れてきている映像や音声を変換せず、
そのまま録画するファイルの形式に組み立て直してファイルを保存します。
そのため、CPU リソースを最小限に抑えられます。
ブラウザでの録画など、通常の録画は変換が入るため CPU に多くの負荷がかかります。

変換を行わないため、録画を終了した数秒後には録画したファイルを取得できます。

解像度は送られてきた映像の最大値を録画ファイルの解像度として使用します。

## 録画の開始と終了について

Sora の録画機能は **明示的にチャネルの録画を停止するか、
チャネルの録画開始から指定した期限が過ぎるか、
チャネルのセッションが終了するまでは、
そのチャネルでの配信を自動で録画する** といった機能になります。

## 接続単位の録画ブロックについて

認証成功時に `"recording_block": true` を払い出すことでその接続の録画をブロックし、録画ファイルの出力を行わないようにすることができます。

録画ブロックは認証成功時の払い出し時のみ指定ができます。それ以外では指定できません。
そのため、録画ブロックの途中解除はできません。

### シーケンス図

```mermaid
sequenceDiagram
    autonumber
    participant C as クライアント
    participant S as Sora
    participant A as アプリケーションサーバー
    C->>+S: "type": "connect"
    S->>+A: 認証ウェブフック
    A-->>-S: 200 OK<br>"recording_block": true
    S-->>-C: "type": "offer"
    C->>S: "type": "answer"
    note over C,A: WebRTC 確立
    S->>+A: セッションウェブフック: "session.created"
    A-->>-S: 200 OK<br>"recording": true
    note over C,A: 録画開始
    S->>+A: セッションウェブフック<br/>"type": "recording.started"
    A-->>-S: 200 OK
    note over C,A: 録画ブロックしているため "archive.started" が飛ばない<br>さらに録画ファイルは出力されない
```


## sora.conf の設定による録画指定制限

録画ファイルが大きくならないように、sora.conf の指定で録画開始時のオプションを制限することができます。

設定の詳細は以下をご確認ください。

- [recording_max_expire_time](SORA_CONF.html#f3ff8d)
- [recording_max_split_duration](SORA_CONF.html#927da9)
- [recording_expire_time_required](SORA_CONF.html#fcfa2d)
- [recording_dual_output](SORA_CONF.html#4cc9c0)

## 録画関連イベントのウェブフックについて


### session.created ウェブフック

> **注意**
>
> [sora.conf の設定による録画指定制限](RECORDING.html#a56297) を行っていた場合、制限に該当する指定を行っている場合は録画が開始されません。

払い出し時に以下の項目が指定できます。

- recording- boolean
  - そのセッションでの録画を開始するかどうかを指定します
  - オプション
- recording_expire_time- integer
  - そのセッションでの録画期限を指定します
  - デフォルト値は未定義で、期限なしです
  - 指定する場合 0 より大きな値を指定してください
  - 単位は秒です
  - オプション
- recording_split_only- boolean
  - そのセッションでの録画を分割のみにするかを指定します
  - デフォルト値は false で、分割を行いません
  - オプション
- recording_split_duration- integer
  - そのセッションでの録画を分割する場合の分割時間を指定します
  - recording_split_only が true の場合は指定が必須です
  - オプション
- recording_metadata- json value
  - そのセッションでの録画メタデータを指定します
  - オプション

### recording.started セッションウェブフック

> **注釈**
>
> レガシー録画ではイベントウェブフックでしたが、セッションウェブフックに変更されています

録画開始 API が実行されたタイミングで `recording.started` リクエストを送信します。

詳しくは [recording.started](SESSION_WEBHOOK.html#9b5c58) をご確認ください。

### recording.report セッションウェブフック

録画終了 API が実行されたか、
録画の期限が切れたか、
セッションが終了したタイミングで `recording.report` リクエストを送信します。

> **注釈**
>
> セッションが終了したタイミングでの `recording.report` は `session.destroyed` の **後** に送信されます。

詳しくは [recording.report](SESSION_WEBHOOK.html#920a02) をご確認ください。

### archive.started ウェブフック

録画ファイルを保存しはじめたタイミングで `archive.started` リクエストを送信します。

詳しくは [archive.started](EVENT_WEBHOOK.html#462c97) をご確認ください。

### archive.available イベントウェブフック

単一録画ファイルが出力されたタイミングで `archive.available` リクエストを送信します。

詳しくは [archive.available](EVENT_WEBHOOK.html#de9132) をご確認ください。

### split-archive.available イベントウェブフック

録画ファイル分割出力機能を有効にした場合、
分割された録画ファイルが出力されたタイミングで `split-archive.available` リクエストを送信します。

詳しくは [split-archive.available](EVENT_WEBHOOK.html#555071) をご確認ください。

### split-archive.end イベントウェブフック

録画ファイル分割出力機能を有効にした場合、
分割された録画が終了したタイミングで `split-archive.end` リクエストを送信します。

詳しくは [split-archive.end](EVENT_WEBHOOK.html#31be7a) をご確認ください。

### archive.failed イベントウェブフック

録画ファイルの保存に失敗した場合、 `archive.failed` リクエストを送信します。

詳しくは [archive.failed](EVENT_WEBHOOK.html#5006f8) をご確認ください。

## 単一録画ファイル出力のみ

単一録画ファイル出力のみの場合は `sora.conf` の [archive_dir](SORA_CONF.html#ad2156) に指定したディレクトリに `recording_id` 名のディレクトリ以下にファイルが出力されます。

`recording_id` は録画開始 API を実行したときに戻ってくる値で、 Base32 でエンコードされた UUIDv4 となります。

ディレクトリ構造:

```
├── archive
│   ├── 1CS9QJ0XPN4C76HBGBN6MGMK5M
│   │   ├── archive-A4756MXP914ZB265E92JE3ZMWC.json
│   │   ├── archive-A4756MXP914ZB265E92JE3ZMWC.webm
│   │   ├── archive-H2NDA2YCGH7S1E9CVMFMXMA34R.json
│   │   ├── archive-H2NDA2YCGH7S1E9CVMFMXMA34R.webm
│   │   ├── archive-PBVZQQN3JS3MQF8XHVFXDMCEEC.json
│   │   ├── archive-PBVZQQN3JS3MQF8XHVFXDMCEEC.webm
│   │   └── report-1CS9QJ0XPN4C76HBGBN6MGMK5M.json
│   └── CZZ8A8KZB16A1DF5PKERBHGFNR
│       ├── archive-3B7AFF8ZRX6VNEYV40B35Z9S2C.json
│       ├── archive-3B7AFF8ZRX6VNEYV40B35Z9S2C.webm
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR.json
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR.webm
│       └── report-CZZ8A8KZB16A1DF5PKERBHGFNR.json
```

> **注意**
>
> `archive_dir` と `archive_tmp_dir` は違うディレクトリを指定して下さい

### archive-<connection_id>

#### 単一録画ファイル

単一録画ファイルは `<recording_id>/archvie-<connection_id>.webm` に WebM 形式で出力されます。

#### 単一録画メタデータファイル

単一録画メタデータファイルは `<recording_id>/archive-<connection_id>.json` に JSON 形式で出力されます。

メタデータファイルには WebM ファイルがいつ出力され、どんな形式なのか、開始時刻や終了時刻などの情報が含まれています。

- start_timestamp- この録画が開始した時刻を RFC 3339 (UTC) で表しています
- stop_timestamp- この録画が終了した時刻を RFC 3339 (UTC) で表しています
- start_time- この録画が開始された時刻を UNIX 時間で表しています
- stop_time- この録画が終了した時刻を UNIX 時間で表しています

**stats は省略しています**

```javascript
{
  "audio": true,
  "audio_codec_type": "OPUS",
  "channel_id": "sora",
  "session_id": "JA8FB89ZJS1H9CV5GN3NCT5RA0",
  "client_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
  "bundle_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
  "connection_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
  "created_at": 1615524156,
  "file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.webm",
  "filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.webm",
  "metadata_file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.json",
  "metadata_filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.json",
  "recording_id": "WHEJ888HQ55KDCFE3TZ4VPFQHR",
  "size": 0,
  "start_time": 1615524137,
  "start_time_offset": 7,
  "start_timestamp": "2021-03-12T04:42:17.455668Z",
  "stats": {},
  "stop_time": 1615524154,
  "stop_time_offset": 24,
  "stop_timestamp": "2021-03-12T04:42:34.094375Z",
  "label": "WebRTC SFU Sora",
  "node_name": "node1@192.0.2.10",
  "event_metadata": {"spam": "egg"},
  "video": true,
  "video_bit_rate": 1000,
  "video_codec_type": "VP9",
  "video_vp9_params": {
    "profile_id": 0
  },
  "video_height": 480,
  "video_width": 640
}
```

### report-<recording_id>

録画終了時に、それまでにそのセッションで録画したファイル一覧が記載されているレポートファイルが JSON 形式で出力されます。
録画終了は [StopRecording](API_RECORDING.html#fd0de5) API を使用して指定したチャネルに対する録画を停止するか、
録画の期限が切れるか、セッションが終了するかの 3 つのパターンがあります。

このファイルは主にマルチストリームや途中で切れてしまった場合などを考慮しており、
録画ファイルのグルーピングを目的としたファイルです。

ファイルは `<recording_id>/report-<recording_id>.json` に出力されます。

- トップレベルの start_timestamp- [StartRecording](API_RECORDING.html#c5b527) API を受け付た時刻を RFC 3339 (UTC) で表しています
- トップレベルの stop_timestamp- [StopRecording](API_RECORDING.html#fd0de5) API を受け付た時刻か、 [StartRecording](API_RECORDING.html#c5b527) API で設定された期限の時刻を RFC 3339 (UTC) で表しています
- archives 内の start_time_offset- [StartRecording](API_RECORDING.html#c5b527) API を叩いてから何秒経過した後にこの録画が開始したかを表しています
- archives 内の stop_time_offset- [StartRecording](API_RECORDING.html#c5b527) API を叩いてから何秒経過した後にこの録画が終了したかを表しています

```javascript
{
  "archives": [
    {
      "client_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
      "bundle_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
      "connection_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
      "file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.webm",
      "filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.webm",
      "metadata_file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.json",
      "metadata_filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.json",
      "size": 0,
      "start_time_offset": 0,
      "start_timestamp": "2021-03-12T04:42:17.455668Z",
      "stop_time_offset": 17,
      "stop_timestamp": "2021-03-12T04:42:34.094375Z",
      "label": "WebRTC SFU Sora",
      "node_name": "node1@192.0.2.10"
    }
  ],
  "channel_id": "sora",
  "created_at": 1615524137,
  "expire_time": 3600,
  "expired_at": 1615527737,
  "file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/report-WHEJ888HQ55KDCFE3TZ4VPFQHR.json",
  "filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/report-WHEJ888HQ55KDCFE3TZ4VPFQHR.json",
  "metadata": {"spam": "egg"},
  "recording_id": "WHEJ888HQ55KDCFE3TZ4VPFQHR",
  "split_only": false,
  "start_timestamp": "2021-03-12T04:42:17.455668Z",
  "stop_timestamp": "2021-03-12T04:42:34.094375Z"
}
```

## 分割録画ファイル出力のみ

Sora では [StartRecording](API_RECORDING.html#c5b527) API 実行時に `split_only: true` と `split_duration` の 2 つを指定することで、
録画ファイルを指定した間隔で出力する機能を提供しています。

> **重要**
>
> 分割の最小単位はキーフレームから次のキーフレームまでです。例えば `split_duration` を 1 秒に設定した場合は、1 秒経過後に次のキーフレームが来たタイミングで分割出力されます。

録画が完了したファイルは `sora.conf` の [archive_dir](SORA_CONF.html#ad2156) に指定したディレクトリに `recording_id` 名のディレクトリ以下にファイルが出力されます。

`recording_id` は [StartRecording](API_RECORDING.html#c5b527) API を実行したときに戻ってくる値です。

### 録画ファイル分割出力のみを行う

録画開始 API 実行時に `split_only: true` と `split_duration` の 2 つを指定することで、
録画ファイル分割出力 **のみ** を行うことが可能になります。

ディレクトリ構造:

```
├── archive
│   ├── 1CS9QJ0XPN4C76HBGBN6MGMK5M
│   │   ├── split-archive-end-A4756MXP914ZB265E92JE3ZMWC.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0001.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0001.webm
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0002.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0002.webm
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.webm
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.webm
│   │   └── report-1CS9QJ0XPN4C76HBGBN6MGMK5M.json
│   └── CZZ8A8KZB16A1DF5PKERBHGFNR
│       ├── split-archive-end-3B7AFF8ZRX6VNEYV40B35Z9S2C.json
│       ├── split-archive-end-DGSN3TC0E91RSCZT5KVPRWCDHR.json
│       ├── split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_0001.json
│       ├── split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_0001.webm
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0001.json
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0001.webm
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0002.json
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0002.webm
│       └── report-CZZ8A8KZB16A1DF5PKERBHGFNR.json
```

### split-archive-<connection_id>_<split_index>

#### 分割録画ファイル

分割録画ファイルは `<recording_id>/split-archvie-<connection_id>_<split_index>.webm` に WebM 形式で出力されます。

#### 分割録画メタデータファイル

分割録画メタデータファイルは `<recording_id>/split-archive-<connection_id>_<split_index>.json` に JSON 形式で出力されます。

- split_index- ファイル名につくインデックスです
  - 0001 から始まり 9999 の後は 10000 となります

**stats は省略しています**

```javascript
{
  "audio": true,
  "audio_codec_type": "OPUS",
  "recording_id": "CZZ8A8KZB16A1DF5PKERBHGFNR",
  "file_path": "/path/to/sora/archive/CZZ8A8KZB16A1DF5PKERBHGFNR/split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_001.webm",
  "filename": "CZZ8A8KZB16A1DF5PKERBHGFNR/split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_001.webm",
  "metadata_file_path": "/path/to/sora/split-archive/CZZ8A8KZB16A1DF5PKERBHGFNR/archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_001.json",
  "metadata_filename": "CZZ8A8KZB16A1DF5PKERBHGFNR/split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_001.json",
  "channel_id": "sora",
  "session_id": "JA8FB89ZJS1H9CV5GN3NCT5RA0",
  "client_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "bundle_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "connection_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "split_index": "0001",
  "created_at": 1604656364,
  "size": 823263,
  "start_time": 1604656354,
  "start_time_offset": 4,
  "start_timestamp": "2020-11-06T09:52:34.696758Z",
  "stats": {},
  "stop_time": 1604656364,
  "stop_time_offset": 14,
  "stop_timestamp": "2020-11-06T09:52:44.493179Z",
  "label": "WebRTC SFU Sora",
  "node_name": "node1@192.0.2.10",
  "event_metadata": {"spam": "egg"},
  "video": true,
  "video_bit_rate": 1000,
  "video_codec_type": "VP9",
  "video_vp9_params": {
    "profile_id": 0
  },
  "video_height": 480,
  "video_width": 640
}
```

### split-archive-end-<connection_id>

#### 分割録画終了メタデータファイル

分割録画終了メタデータファイルは `<recording_id>/split-archive-end-<connection_id>.json` に JSON 形式で出力されます。

**stats は省略しています**

```javascript
{
  "audio": true,
  "audio_codec_type": "OPUS",
  "split_last_index": "0042",
  "recording_id": "CZZ8A8KZB16A1DF5PKERBHGFNR",
  "file_path": "/path/to/sora/archive/CZZ8A8KZB16A1DF5PKERBHGFNR/split-archive-end-3B7AFF8ZRX6VNEYV40B35Z9S2C.json",
  "filename": "CZZ8A8KZB16A1DF5PKERBHGFNR/split-archive-end-3B7AFF8ZRX6VNEYV40B35Z9S2C.json",
  "channel_id": "sora",
  "session_id": "JA8FB89ZJS1H9CV5GN3NCT5RA0",
  "client_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "bundle_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "connection_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "start_time": 1604656354,
  "start_time_offset": 4,
  "start_timestamp": "2020-11-06T09:52:34.696758Z",
  "stats": {},
  "stop_time": 1604656364,
  "stop_time_offset": 14,
  "stop_timestamp": "2020-11-06T09:52:44.493179Z",
  "label": "WebRTC SFU Sora",
  "node_name": "node1@192.0.2.10",
  "event_metadata": {"spam": "egg"},
  "video": true,
  "video_bit_rate": 1000,
  "video_codec_type": "VP9",
  "video_vp9_params": {
    "profile_id": 0
  },
  "video_height": 480,
  "video_width": 640
}
```

#### 録画ファイル分割出力終了時

接続単位での録画が終了したタイミングでイベントウェブフックリクエスト `split-archive.end` がリクエスト送信されます。

詳細は [split-archive.end](EVENT_WEBHOOK.html#31be7a) をご確認ください。

### report-<recording_id>

録画終了時に、それまでにそのチャネルで録画したファイル一覧が記載されているレポートファイルが JSON 形式で出力されます。
録画終了は [StopRecording](API_RECORDING.html#fd0de5) API を使用して指定したチャネルに対する録画を停止するか、
録画の期限が切れるか、セッションが終了するかの 3 つのパターンがあります。

このファイルは主にマルチストリームや途中で切れてしまった場合などを考慮しており、
録画ファイルのグルーピングを目的としたファイルです。

ファイルは `<recording_id>/report-<recording_id>.json` に出力されます。

分割録画ファイル出力のみの場合は archives には `connection_id` や `client_id` といった接続情報と、
分割録画ファイルの最後のインデックス番号のみが含まれます。

- トップレベルの start_timestamp- [StartRecording](API_RECORDING.html#c5b527) API を受け付た時刻を RFC 3339 (UTC) で表しています
- トップレベルの stop_timestamp- [StopRecording](API_RECORDING.html#fd0de5) API を受け付た時刻か、 [StartRecording](API_RECORDING.html#c5b527) API で設定された期限の時刻を RFC 3339 (UTC) で表しています
- archives 内の start_time_offset- [StartRecording](API_RECORDING.html#c5b527) API を叩いてから何秒経過した後にこの録画が開始したかを表しています
- archives 内の stop_time_offset- [StartRecording](API_RECORDING.html#c5b527) API を叩いてから何秒経過した後にこの録画が終了したかを表しています

```javascript
{
  "archives": [
    {
      "client_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
      "bundle_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
      "connection_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
      "split_last_index": "0042"
      "start_time_offset": 4,
      "start_timestamp": "2020-11-06T09:52:34.696758Z",
      "stop_time_offset": 14,
      "stop_timestamp": "2020-11-06T09:52:44.493179Z",
      "label": "WebRTC SFU Sora",
      "node_name": "node1@192.0.2.10"
    }
  ],
  "channel_id": "sora",
  "created_at": 1615524137,
  "expire_time": 3600,
  "expired_at": 1615527737,
  "file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/report-GK2R6PSDYX68VDQPRX4FVVFN8W.json",
  "filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/report-GK2R6PSDYX68VDQPRX4FVVFN8W.json",
  "recording_id": "WHEJ888HQ55KDCFE3TZ4VPFQHR",
  "split_duration": 3600,
  "split_only": false,
  "metadata": {"spam": "egg"},
  "start_timestamp": "2020-11-06T09:52:34.696758Z",
  "stop_timestamp": "2020-11-06T09:52:44.493179Z"
}
```

## 単一録画ファイルと分割録画ファイル

[StartRecording](API_RECORDING.html#c5b527) API 実行時に `split_only` を有効にしない限り、
単一ファイルと分割ファイルの二つが出力されます。
単一録画ファイルと分割録画ファイルのいいところ取りですが、その分ストレージの容量も 2 倍消費します。

出力されるファイルは単一と分割のファイルが混ざった形式になります。

ディレクトリ構造:

```
├── archive
│   ├── 1CS9QJ0XPN4C76HBGBN6MGMK5M
│   │   ├── split-archive-end-A4756MXP914ZB265E92JE3ZMWC.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0001.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0001.webm
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0002.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0002.webm
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.webm
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.webm
│   │   ├── archive-A4756MXP914ZB265E92JE3ZMWC.json
│   │   ├── archive-A4756MXP914ZB265E92JE3ZMWC.webm
│   │   └── report-1CS9QJ0XPN4C76HBGBN6MGMK5M.json
│   └── CZZ8A8KZB16A1DF5PKERBHGFNR
│       ├── split-archive-end-3B7AFF8ZRX6VNEYV40B35Z9S2C.json
│       ├── split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_0001.json
│       ├── split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_0001.webm
│       ├── split-archive-end-DGSN3TC0E91RSCZT5KVPRWCDHR.json
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0001.json
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0001.webm
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0002.json
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0002.webm
│       ├── archive-3B7AFF8ZRX6VNEYV40B35Z9S2C.json
│       ├── archive-3B7AFF8ZRX6VNEYV40B35Z9S2C.webm
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR.json
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR.webm
│       └── report-CZZ8A8KZB16A1DF5PKERBHGFNR.json
```

### report-<recording_id>

出力される report ファイルは単一と分割が混ざった形式になります。

- トップレベルの start_timestamp- [StartRecording](API_RECORDING.html#c5b527) API を受け付た時刻を RFC 3339 (UTC) で表しています
- トップレベルの stop_timestamp- [StopRecording](API_RECORDING.html#fd0de5) API を受け付た時刻か、 [StartRecording](API_RECORDING.html#c5b527) API で設定された期限の時刻を RFC 3339 (UTC) で表しています
- archives 内の start_time_offset- [StartRecording](API_RECORDING.html#c5b527) API を叩いてから何秒経過した後にこの録画が開始したかを表しています
- archives 内の stop_time_offset- [StartRecording](API_RECORDING.html#c5b527) API を叩いてから何秒経過した後にこの録画が終了したかを表しています

```javascript
{
  "archives": [
    {
      "client_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
      "bundle_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
      "connection_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",

      "file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.webm",
      "filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.webm",
      "metadata_file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.json",
      "metadata_filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.json",
      "size": 0,
      "start_time_offset": 0,
      "start_timestamp": "2021-03-12T04:42:17.455668Z",
      "stop_time_offset": 17,
      "stop_timestamp": "2021-03-12T04:42:34.094375Z",

      "split_last_index": "0042",
      "label": "WebRTC SFU Sora",
      "node_name": "node1@192.0.2.10"
    }
  ],
  "channel_id": "sora",
  "created_at": 1615524137,
  "expire_time": 3600,
  "expired_at": 1615527737,
  "file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/report-WHEJ888HQ55KDCFE3TZ4VPFQHR.json",
  "filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/report-WHEJ888HQ55KDCFE3TZ4VPFQHR.json",
  "recording_id": "WHEJ888HQ55KDCFE3TZ4VPFQHR",
  "label": "WebRTC SFU Sora",
  "node_name": "node1@192.0.2.10",
  "split_duration": 3600,
  "split_only": false,
  "metadata": {"spam": "egg"},
  "start_timestamp": "2021-03-12T04:42:17.455668Z",
  "stop_timestamp": "2021-03-12T04:42:34.094375Z"
}
```

## 録画ファイル出力失敗時の録画一時ファイル

何らかの理由で録画ファイル出力が失敗した場合、 `archive_tmp_dir` で指定したディレクトリに録画一時ファイルが削除されずに残ります。そのため、定期的な削除が必要です。

この録画一時ファイルは WebM 形式のためそのまま再生できます。

## シグナリング通知

シグナリング通知で録画開始と終了を通知できます。

詳細は [録画のシグナリング通知](SIGNALING_NOTIFY.html#7ce275) をご確認ください。


## Lyra コーデックの録画ファイル

### Lyra の WebM 構造

Sora はコーデックの変換を行わないので、
音声コーデックに [Lyra](https://github.com/google/lyra) が指定されている場合には、
録画ファイルには Lyra でエンコードされた音声データが格納されます。

WebM は標準では Lyra に対応していないため、 Sora では独自に次のような `TrackEntry` を Lyra 用に定義しています。

**Lyra 用の TrackEntry**

* - 項目名
  - 値
* - TrackNumber
  - 2
* - TrackUID
  - 2
* - FlagLacing
  - 0
* - Language
  - "und"
* - FlagDefault
  - 0
* - CodecID
  - "A_LYRA"
* - SeekPreRoll
  - 80000000
* - TrackType
  - 2
* - Audio
  - ``Channels`` と ``SamplingFrequency`` を含んだ ``Audio`` 要素
* - CodecPrivate
  - Lyra のエンコード情報を含んだバイナリ文字列。詳細は後述。

Lyra 用の `CodecPrivate` バイナリの中身は次の通りです。

**Lyra 用の CodecPrivate**

* - 項目名
  - バイト長
  - 値
* - マジックシグネチャ
  - 8
  - "LyraHead"
* - CodecPrivate 形式のバージョン
  - 1
  - 1
* - チャネル数
  - 1
  - 整数値
* - DTX
  - 1
  - 0=無効、1=有効
* - サンプルレート
  - 4
  - リトルエンディアンの整数値
* - ビットレート
  - 4
  - リトルエンディアンの整数値
* - Lyra のエンコードフォーマットバージョン
  - 可変長（バイナリ文字列の終端まで）
  - 文字列

なお `TrackEntry` およびその中の各項目の詳細については [WebM の仕様](https://www.matroska.org/technical/elements.html) を参照してください。

### Lyra の WebM 再生方法

通常の動画プレイヤーでは Lyra でエンコードされた音声を含んだ録画ファイルを再生することはできません。
ただし以下のように FFmpeg と Lyra のコマンドラインデコーダを組み合わせることで WebM から音声だけを取り出してデコードおよび再生が行えます。

```console
# 1) FFmpeg を使って WebM ファイルから Lyra でエンコードされた音声データだけを取り出す
#
# [NOTE]
# 以下の FFmpeg コマンドは WebM 内の音声データを未加工のまま取り出して単純に連結し、
# タイムスタンプやエンコードパラメーターの情報は出力ファイルには含まれていないことには注意。
#
# 例えば、DTX が有効になっているとWebM 内の音声データのタイムスタンプは飛び飛びになるが、
# 出力時にはその情報は欠落して連結される。
$ ffmpeg -y -i archive.webm -vn -acodec copy -f u8 archive.lyra

# 2) google/lyra リポジトリで提供されているコマンドラインデコーダを使って Lyra 音声ファイルをデコードする
# コマンドを実行すると ${入力ファイル名}_decoded.wav という名前の WAV ファイルが作成される
#
# [NOTE]
# - decoder_main コマンドのビルド方法は後述
# - --bitrate オプションの値はエンコード設定に合わせて指定する必要がある
$ decoder_main --encoded_path archive.lyra --output_dir . --bitrate 6000

# 3) 適当な動画プレイヤーで WAV ファイルを再生する
$ ffplay archive_decoded.wav
```

`decoder_main` コマンドのビルド方法は次の通りです。

```console
# 1) リポジトリをダウンロード
$ git clone https://github.com/google/lyra.git
$ cd lyra

# 2) 今回は Android 関連のビルドは不要なので、その設定をコメントアウトする
# (Android Studio が既に手元でセットアップ済みの場合には、この手順はスキップ可能）
$ vim WORKSPACE
  # load("//:android_configure.bzl", "android_configure")
  # android_configure(name = "local_config_android")

  # load("@local_config_android//:android_configure.bzl", "android_workspace")
  # android_workspace()

# 3) ビルド
$ bazel build -c opt lyra/cli_example:decoder_main

# 4) ビルドに成功したら以下のパスにデコードコマンドが配置される
$ ls bazel-bin/lyra/cli_example/decoder_main
```

## 録画ファイル合成ツール

マルチストリームを録画した場合はそれぞれの接続に対して録画ファイルが出力されます。このそれぞれ分かれた録画ファイルを合成して一つにするツールをオープンソースとして公開しています。

詳細は [WebRTC 録画合成ツール Hisui](HISUI.html) をご確認ください。

## 録画ファイルアップロードツール

録画ファイルを S3 または S3 互換オブジェクトストレージにアップロードするツールをオープンソースとして公開しています。

詳細は [Sora Archive Uploader](SORA_ARCHIVE_UPLOADER.html) をご確認ください

## 試してみる

Sora では録画機能を試すための開発ツールを提供しています。 [開発ツール](DEVTOOLS.html) を参照の上、開発ツールを有効にしてください。

ここでは Sora が立っているサーバーは example.com としています。

### 録画開始

録画を開始するにはセッションウェブフックで `"recording": true` を払い出すか録画開始 [StartRecording](API_RECORDING.html#c5b527) API を叩く必要があります。

ここでは API を叩いて録画を開始してみましょう。

録画を開始するにはセッションが存在する必要があるため、API を叩く前にクライアントの接続を行い、セッションを作成してください。

httpie:

```
$ http POST example.com:3000/ \
    x-sora-target:Sora_20231220.StartRecording \
    channel_id=sora -vvv
```

curl:

```
$ curl -X POST example.com:3000/ \
    -H "x-sora-target: Sora_20231220.StartRecording" \
    -d '{"channel_id": "sora"}' -vvv
```

その後 `https://example.com/sendonly.html` を開き、 connect ボタンを押して配信を開始します。

切断またはチャネルの録画終了、もしくはチャネルの録画期限が来たタイミングでクライアント単位の録画は終了します。

セッションが終了したタイミングで録画は終了します。

### 録画終了

録画を終了するには全ての参加者が離脱し、セッションが終了するか、 API を叩く必要があります。

ここでは API を叩いて録画を終了してみましょう。

httpie:

```
$ http POST example.com:3000/ \
    x-sora-target:Sora_20231220.StopRecording \
    channel_id=sora -vvv
```

curl:

```
$ curl -X POST example.com:3000/ \
    -H "x-sora-target: Sora_20231220.StopRecording" \
    -d '{"channel_id": "sora"}' -vvv
```

その後 archive/ ディレクトリに webm 形式のファイルが出力されます。
Chrome または Firefox にドラッグアンドドロップして、動作を確認してください。

## シーケンス図

### 録画 API

```mermaid
sequenceDiagram
    autonumber
    participant C as クライアント
    participant S as Sora
    participant A as アプリケーションサーバー
    note over C,A: 認証成功
    S->>+A: セッションウェブフック: "session.created"
    A-->>-S: 200 OK
    S->>+C: "type": "offer"
    C->>-S: "type": "answer"
    note over C,A: WebRTC 確立
    S->>+A: イベントウェブフック: "connection.created"
    A-->>-S: 200 OK
    A->>+S: HTTP API StartRecording
    S-->>-A: 200 OK
    note over C,A: 録画開始
    S->>+A: セッションウェブフック<br/>"type": "recording.started"
    A-->>-S: 200 OK
    S->>+A: イベントウェブフック<br/>"type": "archive.started"
    A-->>-S: 200 OK
    A->>+S: HTTP API StopRecording
    S-->>-A: 200 OK
    note over C,A: 録画終了
    S->>+A: イベントウェブフック<br/>"type": "archive.available"
    A-->>-S: 200 OK
    S->>+A: セッションウェブフック<br/>"type": "recording.report"
    A-->>-S: 200 OK
```

### セッションウェブフック `"recording": true` で録画開始

```mermaid
sequenceDiagram
    autonumber
    participant C as クライアント
    participant S as Sora
    participant A as アプリケーションサーバー
    note over C,A: 認証成功
    S->>+A: セッションウェブフック: "session.created"
    A-->>-S: 200 OK<br>"recording": true
    note over C,A: 録画開始
    S->>+A: セッションウェブフック<br/>"type": "recording.started"
    A-->>-S: 200 OK
    S->>+C: "type": "offer"
    C->>-S: "type": "answer"
    note over C,A: WebRTC 確立
    S->>+A: イベントウェブフック: "connection.created"
    A-->>-S: 200 OK
    S->>+A: イベントウェブフック<br/>"type": "archive.started"
    A-->>-S: 200 OK
```

### 録画期限切れ

```mermaid
sequenceDiagram
    autonumber
    participant C as クライアント
    participant S as Sora
    participant A as アプリケーションサーバー
    note over C,A: 認証成功
    S->>+A: セッションウェブフック: "session.created"
    A-->>-S: 200 OK<br>"recording": true,<br>"recoding_expire_time": 3600
    note over C,A: 録画開始
    S->>+A: セッションウェブフック<br/>"type": "recording.started"
    A-->>-S: 200 OK
    S->>+C: "type": "offer"
    C->>-S: "type": "answer"
    note over C,A: WebRTC 確立
    S->>+A: イベントウェブフック: "connection.created"
    A-->>-S: 200 OK
    S->>+A: イベントウェブフック<br/>"type": "archive.started"
    A-->>-S: 200 OK
    note over C,A: 期限切れにより録画終了
    S->>+A: イベントウェブフック<br/>"type": "archive.available"
    A-->>-S: 200 OK
    S->>+A: セッションウェブフック<br/>"type": "recording.report"
    A-->>-S: 200 OK
```

### クライアント切断

```mermaid
sequenceDiagram
    autonumber
    participant C as クライアント
    participant S as Sora
    participant A as アプリケーションサーバー
    note over C,A: 認証成功
    S->>+A: セッションウェブフック: "session.created"
    A-->>-S: 200 OK<br>"recording": true
    note over C,A: 録画開始
    S->>+A: セッションウェブフック<br/>"type": "recording.started"
    A-->>-S: 200 OK
    S->>+C: "type": "offer"
    C->>-S: "type": "answer"
    note over C,A: WebRTC 確立
    S->>+A: イベントウェブフック: "connection.created"
    A-->>-S: 200 OK
    S->>+A: イベントウェブフック<br/>"type": "archive.started"
    A-->>-S: 200 OK
    C->>S: "type": "disconnect"
    S->>+A: イベントウェブフック<br/>"type" :"connection.destroyed"
    A-->-S: 200 OK
    S->>C: WebSocekt Close
    note over C,A: クライアント切断に伴いクライアントの録画終了
    S->>+A: イベントウェブフック<br/>"type": "archive.available"
    A-->>-S: 200 OK
```

### セッション破棄

```mermaid
sequenceDiagram
    autonumber
    participant C as クライアント
    participant S as Sora
    participant A as アプリケーションサーバー
    note over C,A: 認証成功
    note over C,A: セッション生成
    S->>+A: セッションウェブフック: "session.created"
    A-->>-S: 200 OK<br>"recording": true
    note over C,A: 録画開始
    S->>+A: セッションウェブフック<br/>"type": "recording.started"
    A-->>-S: 200 OK
    S->>+C: "type": "offer"
    C->>-S: "type": "answer"
    note over C,A: WebRTC 確立
    S->>+A: イベントウェブフック: "connection.created"
    A-->>-S: 200 OK
    S->>+A: イベントウェブフック<br/>"type": "archive.started"
    A-->>-S: 200 OK
    C->>S: "type": "disconnect"
    S->>+A: イベントウェブフック<br/>"type" :"connection.destroyed"
    A-->-S: 200 OK
    S->>C: WebSocekt Close
    note over C,A: クライアント切断に伴いクライアントの録画終了
    S->>+A: イベントウェブフック<br/>"type": "archive.available"
    A-->>-S: 200 OK
    note over C,A: セッション破棄
    S->>+A: セッションウェブフック: "session.destroyed"
    A-->>-S: 200 OK
    note over C,A: セッション破棄に伴いセッションの録画終了
    S->>+A: セッションウェブフック: "recording.report"
    A-->>-S: 200 OK
```












