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

## 概要

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

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

## 用語

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

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


## 録画の開始方法

録画の開始方法には色々なパターンがあります。用途によって使い分けてください。

### セッション生成時の録画開始

セッション生成時に録画を開始することができます。その場合は [session.created](SESSION_WEBHOOK.html#1d1984) の払い出しで `"recording": true` を指定してください。

常に録画を有効にしていたい場合、この機能を利用してください。

### 認証成功時の録画開始

認証成功時の払い出しで録画を開始することができます。その場合は `"recording": true` を指定してください。

特定の接続がチャネルに接続した際に録画を開始したい場合、この機能を利用してください。

詳細は [録画機能の払い出し](AUTH_WEBHOOK_RETURN.html#8a1586) をご確認ください。

### API 経由での録画開始

API 経由で録画を開始することができます。その場合は [StartRecording](API_RECORDING.html#c5b527) API を利用してください。

好きなタイミングで録画を開始したい場合、この機能を利用してください。


## 録画の終了方法

録画の終了にも色々なパターンがあります。用途によって使い分けてください。

### セッション破棄での録画停止

録画が開始されているセッションが破棄されたタイミングで録画を停止します。

### 録画期限切れでの録画停止

録画開始時に録画期限(秒)を指定した場合、その期限が切れたタイミングで録画を停止します。

### API 経由での録画停止

API 経由で録画を停止することができます。その場合は [StopRecording](API_RECORDING.html#fd0de5) API を利用してください。

## 録画ファイルの出力

録画ファイルの出力には 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 つの録画ファイルと分割された録画ファイルの両方が出力されます。

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


## 録画ファイルの出力形式

MP4 と WebM 形式の 2 種類があります。

### デフォルト設定

`sora.conf` で [default_recording_format](SORA_CONF.html#f9fa7d) を指定することで、
録画ファイルのデフォルト出力形式を指定できます。

未指定の場合は **MP4 形式** で出力します。

### MP4

**MP4 形式を推奨します**

Sora 2024.2.0 から対応しました。

H.265 の録画は MP4 形式のみ対応しています。

### WebM

Google が開発した動画フォーマットです。

H.265 コーデックには非対応です。

## 制限

### コーデック

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

> **警告**
>
> - H.265 の録画は MP4 形式のみ対応しています
> - B フレームの録画には非対応です
> - VP8 と Opus の組み合わせの MP4 形式は Firefox で再生できません

### 解像度

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

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

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

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

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

対応しています。

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

対応しています。

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

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

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

対応しています。

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

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

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

録画機能利用時の Sora からのキーフレーム要求間隔は以下の通りです。

- WebM 形式の場合 20 秒固定
- MP4 形式の場合 [default_recording_mp4_pli_interval](SORA_CONF.html#874e5b) で指定した秒数 (デフォルトは 20 秒)

録画時のキーフレーム要求間隔を変更したい場合は、MP4 形式の録画を行ってください。

### 分割録画ファイルの録画時間

分割録画ファイルの時間はキーフレームの到着タイミングにより前後するため、
`split_duration` に指定した時間から前後することがあります。

## 無変換録画

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

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

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

## コネクション単位の録画ブロックについて

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

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

コネクションごとの録画ブロックの設定状況は [ListConnections](API_SIGNALING.html#d3da2a) API の `recording_block` または [ListChannelConnections](API_SIGNALING.html#d388f3) の `recording_block` で確認できます。

### シーケンス図

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


## MP4 形式のキーフレーム要求間隔の変更

録画時には一定間隔で Sora からクライアントへキーフレーム要求 (PLI) を送ります。

WebM 形式ではキーフレームの間隔が最大でも 31 秒までという制約がありましたが、 MP4 形式ではこの制約がなくなりました。

そのため、 MP4 形式ではこの間隔を [default_recording_mp4_pli_interval](SORA_CONF.html#874e5b) で変更する事ができます。

デフォルトは `20 s` で、最小が `1 s` で、最大が `240 s` です。

```ini
default_recording_mp4_pli_interval = 100 s
```

## 録画情報の API 経由での取得

録画情報を取得するには [セッション](API_SESSION.html) API を利用します。

### 指定したセッションの録画情報を取得する

[GetSession](API_SESSION.html#427a59) API を利用することで、指定したセッションの録画状態を取得することができます。

録画が開始されているセッションは `recording` 項目が存在します。

```javascript
{
  "label": "WebRTC SFU Sora",
  "node_name": "sora@127.0.0.1",
  "version": "2024.1.0",
  "channel_id": "sora",
  "group_id": "JJJ5BFH7QN6DQBTKSS7JA8ZYQR",
  "session_id": "JJJ5BFH7QN6DQBTKSS7JA8ZYQR",
  "session_metadata":  {"spam": "egg"},
  "created_time": 1638337454,
  "created_timestamp": "2021-12-01T05:44:14.523736Z",
  "spotlight": false,
  "max_connections": 4,
  "total_connections": 4,
  "external_signaling_url": "wss://node-01.example.com/signaling",
  "recording": {
    "recording_id": "WHEJ888HQ55KDCFE3TZ4VPFQHR",
    "recording_metadata": {"spam": "egg"},
    "expire_time": 3600,
    "expired_at": 1615527737,
    "split_duration": 3600,
    "split_only": false,
    "start_timestamp": "2021-03-12T04:42:17.455668Z",
    "format": "mp4"
  }
}
```

### 全てのセッションの録画情報を取得する

[ListSessions](API_SESSION.html#748f19) API を利用することで、全てのセッションの録画状態を取得することができます。

録画が開始されているセッションは `recording` 項目が存在します。

```javascript
[
  {
    "label": "WebRTC SFU Sora",
    "node_name": "sora@127.0.0.1",
    "version": "2024.1.0",
    "channel_id": "sora",
    "group_id": "JJJ5BFH7QN6DQBTKSS7JA8ZYQR",
    "session_id": "JJJ5BFH7QN6DQBTKSS7JA8ZYQR",
    "session_metadata":  {"spam": "egg"},
    "created_time": 1638337454,
    "created_timestamp": "2021-12-01T05:44:14.523736Z",
    "spotlight": false,
    "max_connections": 4,
    "total_connections": 4,
    "connections": [
      {
        "audio": true,
        "audio_codec_type": "OPUS",
        "client_id": "W9QE86Z0BS1QFFPZ2QB5DRZ2HC",
        "bundle_id": "W9QE86Z0BS1QFFPZ2QB5DRZ2HC",
        "connection_id": "W9QE86Z0BS1QFFPZ2QB5DRZ2HC",
        "connection_created_timestamp": "2021-12-01T05:44:23.051704Z",
        "connection_destroyed_timestamp": "2021-12-01T05:44:56.878019Z",
        "role": "sendrecv",
        "simulcast": false,
        "video": true,
        "video_bit_rate": 1000,
        "video_codec_type": "VP9",
        "video_vp9_params": { "profile_id": 0 },
      },
      {
        "audio": true,
        "audio_codec_type": "OPUS",
        "client_id": "JXMYW6GPX54EH0HGA5X4130FBM",
        "bundle_id": "JXMYW6GPX54EH0HGA5X4130FBM",
        "connection_id": "JXMYW6GPX54EH0HGA5X4130FBM",
        "connection_created_timestamp": "2021-12-01T05:44:23.051704Z",
        "connection_destroyed_timestamp": "2021-12-01T05:44:56.878019Z",
        "role": "sendrecv",
        "simulcast": false,
        "video": true,
        "video_bit_rate": 1000,
        "video_codec_type": "VP9",
        "video_vp9_params": { "profile_id": 0 },
      }
    ],
    "recording": {
      "recording_id": "WHEJ888HQ55KDCFE3TZ4VPFQHR",
      "recording_metadata": {"spam": "egg"},
      "expire_time": 3600,
      "expired_at": 1615527737,
      "split_duration": 3600,
      "split_only": false,
      "start_timestamp": "2021-03-12T04:42:17.455668Z",
      "format": "mp4"
    }
  }
]
```

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


### 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- JSONValue
  - そのセッションでの録画メタデータを指定します
  - オプション
- recording_format- string
  - そのセッションでの録画ファイルのフォーマットを指定します
  - `webm` と `mp4` が指定できます
  - オプション

### 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` にて [auth_webhook_recording](SORA_CONF.html#24d27d) が `true` に設定することで、認証成功時の払い出し時に以下の項目が指定できます。

- 既に録画が開始されている場合はこの払い出しは無視されます
- `"recording": false` を指定しても録画は停止せず、無視されます

払い出せる項目は [session.created ウェブフック](RECORDING.html#3cec61) と同じです。

#### 払い出せる項目

- recording
- recording_expire_time
- recording_split_only
- recording_split_duration
- recording_metadata
- recording_format

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

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

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

ディレクトリ構造:

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

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

### archive-<connection_id>

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

一括録画ファイルは `<recording_id>/archive-<connection_id>.{mp4,webm}` に WebM または MP4 形式で出力されます。

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

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

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

- 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",
  "group_id": "JA8FB89ZJS1H9CV5GN3NCT5RA0",
  "session_id": "JA8FB89ZJS1H9CV5GN3NCT5RA0",
  "client_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
  "bundle_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
  "connection_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
  "created_at": 1615524156,
  "file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.mp4",
  "filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.mp4",
  "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,
  "format": "mp4"
}
```

### 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.mp4",
      "filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.mp4",
      "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",
  "recording_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",
  "format": "mp4"
}
```

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

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.mp4
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0002.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0002.mp4
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.mp4
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.mp4
│   │   └── report-1CS9QJ0XPN4C76HBGBN6MGMK5M.json
│   └── CZZ8A8KZB16A1DF5PKERBHGFNR
│       ├── split-archive-end-3B7AFF8ZRX6VNEYV40B35Z9S2C.json
│       ├── split-archive-end-DGSN3TC0E91RSCZT5KVPRWCDHR.json
│       ├── split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_0001.json
│       ├── split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_0001.mp4
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0001.json
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0001.mp4
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0002.json
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0002.mp4
│       └── report-CZZ8A8KZB16A1DF5PKERBHGFNR.json
```

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

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

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

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

分割録画メタデータファイルは `<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.mp4",
  "filename": "CZZ8A8KZB16A1DF5PKERBHGFNR/split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_001.mp4",
  "metadata_file_path": "/path/to/sora/split-archive/CZZ8A8KZB16A1DF5PKERBHGFNR/archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_001.json",
  "metadata_filename": "CZZ8A8KZB16A1DF5PKERBHGFNR/split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_001.json",
  "channel_id": "sora",
  "group_id": "JA8FB89ZJS1H9CV5GN3NCT5RA0",
  "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,
  "format": "mp4"
}
```

### 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",
  "group_id": "JA8FB89ZJS1H9CV5GN3NCT5RA0",
  "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
  },
  "format": "mp4"
}
```

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

コネクション単位での録画が終了したタイミングでイベントウェブフックリクエスト `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,
  "recording_metadata": {"spam": "egg"},
  "start_timestamp": "2020-11-06T09:52:34.696758Z",
  "stop_timestamp": "2020-11-06T09:52:44.493179Z",
  "format": "mp4"
}
```

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

[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.mp4
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0002.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0002.mp4
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0003.mp4
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0004.json
│   │   ├── split-archive-A4756MXP914ZB265E92JE3ZMWC_0004.mp4
│   │   ├── archive-A4756MXP914ZB265E92JE3ZMWC.json
│   │   ├── archive-A4756MXP914ZB265E92JE3ZMWC.mp4
│   │   └── report-1CS9QJ0XPN4C76HBGBN6MGMK5M.json
│   └── CZZ8A8KZB16A1DF5PKERBHGFNR
│       ├── split-archive-end-3B7AFF8ZRX6VNEYV40B35Z9S2C.json
│       ├── split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_0001.json
│       ├── split-archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_0001.mp4
│       ├── split-archive-end-DGSN3TC0E91RSCZT5KVPRWCDHR.json
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0001.json
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0001.mp4
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0002.json
│       ├── split-archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0002.mp4
│       ├── archive-3B7AFF8ZRX6VNEYV40B35Z9S2C.json
│       ├── archive-3B7AFF8ZRX6VNEYV40B35Z9S2C.mp4
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR.json
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR.mp4
│       └── 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.mp4",
      "filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/archive-GK2R6PSDYX68VDQPRX4FVVFN8W.mp4",
      "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,
  "recording_metadata": {"spam": "egg"},
  "start_timestamp": "2021-03-12T04:42:17.455668Z",
  "stop_timestamp": "2021-03-12T04:42:34.094375Z",
  "format": "mp4"
}
```

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

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

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

## シグナリング通知

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

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

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

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

詳細は [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 を叩く前にクライアントの接続を行い、セッションを作成してください。

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

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

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

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

### 録画終了

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

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

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

その後 archive/ ディレクトリに WebM または MP4 形式のファイルが出力されます。
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>"recording_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: WebSocket 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: WebSocket 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
```
