# 録画機能

## 概要

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

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

## 用語

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

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

## 録画ファイルの出力

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

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

- `split_duration` を指定していない

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

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

- `split_duration` が指定されている
- `expire_time` が `0` に指定されている
- `split_only` が `true` に指定されている

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

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

- `split_duration` が指定されている

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

ウェブフックも両方のウェブフックが通知されます。

## 制限

### コーデック

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

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

### 解像度

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

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

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

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

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

対応しています。

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

対応しています。

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

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

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

対応しています。

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

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

## 無変換録画

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

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

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

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

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

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

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

単一録画ファイルが出力されたタイミングで `archive.available` という通知が送られます。

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

### recording.report イベントウェブフック

録画終了 API が実行されたか、
録画の期限が切れたタイミングで `recording.report` という通知が送られます。

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

### 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 は省略しています**

```json
{
  "audio": {
    "codec_type": "OPUS"
  },
  "channel_id": "sora",
  "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": {
    "extract_queue_trimmed": 0,
    "final_extract_limit": 0,
    "min_audio_level": 11,
    "min_audio_level_split": 11,
    "total_audio_discraded": 0,
    "total_audio_empty_payload": 0,
    "total_audio_filling": 1,
    "total_audio_lip_sync_mode_recv_time": 0,
    "total_audio_lip_sync_mode_rtcp_sr": 2,
    "total_audio_sender_report": 3,
    "total_h264_nri_zero_drop": 0,
    "total_pli_trigger": 0,
    "total_stored_audio_packet": 833,
    "total_stored_unknown_packet": 0,
    "total_stored_video_keyframe_head": 1,
    "total_stored_video_packet": 1977,
    "total_video_chunk_postponed_partial": 1,
    "total_video_chunk_postponed_queued": 0,
    "total_video_chunk_postponed_undefined": 0,
    "total_video_force_forward": 0,
    "total_video_frame": 489,
    "total_video_lip_sync_mode_recv_time": 0,
    "total_video_lip_sync_mode_rtcp_sr": 2,
    "total_video_reset_no_video_timeout": 0,
    "total_video_reset_other": 0,
    "total_video_reset_pli": 0,
    "total_video_sender_report": 46
  },
  "stop_time": 1615524154,
  "stop_time_offset": 24,
  "stop_timestamp": "2021-03-12T04:42:34.094375Z",
  "video": {
    "bit_rate": 1000,
    "codec_type": "VP9",
    "height": 480,
    "width": 640
  }
}
```

### report-<recording_id>

[StopRecording](API.html#fd0de5) API を使用して指定したチャネルに対する録画を停止するか録画の期限が切れた場合、
それまでにそのチャネルで録画したファイル一覧が記載されているレポートファイルが JSON 形式で出力されます。

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

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

- start_time_offset- [StartRecording](API.html#c5b527) API を叩いてから何秒経過した後にこの録画が開始したかを表しています
- stop_time_offset- [StartRecording](API.html#c5b527) API を叩いてから何秒経過した後にこの録画が終了したかを表しています

```json
{
  "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"
    }
  ],
  "channel_id": "sora",
  "created_at": 1615524137,
  "expire_time": 3600,
  "expired_at": 1615527737,
  "metadata_file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/report-WHEJ888HQ55KDCFE3TZ4VPFQHR.json",
  "metadata_filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/report-WHEJ888HQ55KDCFE3TZ4VPFQHR.json",
  "recording_id": "WHEJ888HQ55KDCFE3TZ4VPFQHR",
  "node_name": "node1@192.0.2.10",
  "label": "WebRTC SFU Sora",
  "split_only": false
}
```

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

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

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

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

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

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

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

ディレクトリ構造:

```
├── 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 は省略しています**

```json
{
  "audio": {
    "codec_type": "OPUS"
  },
  "recording_id": "CZZ8A8KZB16A1DF5PKERBHGFNR",
  "file_path": "/path/to/sora/archive/CZZ8A8KZB16A1DF5PKERBHGFNR/archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_001.webm",
  "filename": "CZZ8A8KZB16A1DF5PKERBHGFNR/archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_001.webm",
  "metadata_file_path": "/path/to/sora/archive/CZZ8A8KZB16A1DF5PKERBHGFNR/archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_001.json",
  "metadata_filename": "CZZ8A8KZB16A1DF5PKERBHGFNR/archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_001.json",
  "channel_id": "sora",
  "client_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "bundle_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "connection_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "timestamp": "2020-11-06T09:52:44.498575Z",
  "data": {
    "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",
    "client_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
    "bundle_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
    "connection_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
    "split_index": "0001",
    "created_at": 1604656364,
    "size": 823263,
    "start_time": 1604656354,
    "start_timestamp": "2020-11-06T09:52:34.696758Z",
    "stats": {},
    "stop_time": 1604656364,
    "stop_timestamp": "2020-11-06T09:52:44.493179Z",
    "video": {
      "bit_rate": 1000,
      "codec_type": "VP9",
      "height": 480,
      "width": 640
    }
  },
  "id": "ZAHYYZPDAH2MFCAWRHSVMHPHRG",
  "label": "WebRTC SFU Sora",
  "version": "2022.1"
}
```

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

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

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

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

```json
{
  "audio": {
    "codec_type": "OPUS"
  },
  "split_last_index": "0042",
  "recording_id": "CZZ8A8KZB16A1DF5PKERBHGFNR",
  "metadata_file_path": "/path/to/sora/archive/CZZ8A8KZB16A1DF5PKERBHGFNR/split-archive-end-3B7AFF8ZRX6VNEYV40B35Z9S2C.json",
  "metadata_filename": "CZZ8A8KZB16A1DF5PKERBHGFNR/split-archive-end-3B7AFF8ZRX6VNEYV40B35Z9S2C.json",
  "channel_id": "sora",
  "client_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "bundle_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "connection_id": "3B7AFF8ZRX6VNEYV40B35Z9S2C",
  "timestamp": "2020-11-06T09:52:44.498575Z",
  "data": {
      "channel_id": "<String>",
      "recording_id": "<Base32-UUIDv4>",
      "client_id": "<String or Base32-UUIDv4>",
      "connection_id": "<Base32-UUIDv4>",
      "video": {
          "codec_type": "<String>",
          "bit_rate": "<Integer>",
          "height": "<Integer>",
          "width": "<Integer>"
      },
      "audio": {
          "codec_type": "<String>",
          "bit_rate": "<Integer>"
      },
      "stats": {},
      "start_time": "<Unix Time>",
      "start_timestamp": "<UTC RFC3339 Timestamp>",
      "stop_time": "<Unix Time>",
      "stop_timestamp": "<UTC RFC3339 Timestamp>",
  },
  "id": "ZAHYYZPDAH2MFCAWRHSVMHPHRG",
  "label": "WebRTC SFU Sora",
  "version": "2022.1"
}
```

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

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

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

### report-<recording_id>

[StopRecording](API.html#fd0de5) API を使用して指定したチャネルに対する録画を停止するか録画の期限が切れた場合、
それまでにそのチャネルで録画したファイル一覧が記載されているレポートファイルが JSON 形式で出力されます。

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

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

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

```json
{
  "archives": [
    {
      "client_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
      "bundle_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
      "connection_id": "GK2R6PSDYX68VDQPRX4FVVFN8W",
      "split_last_index": "0042"
    }
  ],
  "channel_id": "sora",
  "created_at": 1615524137,
  "expire_time": 3600,
  "expired_at": 1615527737,
  "metadata_file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/report-GK2R6PSDYX68VDQPRX4FVVFN8W.json",
  "metadata_filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/report-GK2R6PSDYX68VDQPRX4FVVFN8W.json",
  "recording_id": "WHEJ888HQ55KDCFE3TZ4VPFQHR",
  "node_name": "node1@192.0.2.10",
  "label": "WebRTC SFU Sora",
  "split_only": false
}
```

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

[StartRecording](API.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
│       ├── archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_0001.json
│       ├── archive-3B7AFF8ZRX6VNEYV40B35Z9S2C_0001.webm
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0001.json
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0001.webm
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0002.json
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR_0002.webm
│       ├── archive-3B7AFF8ZRX6VNEYV40B35Z9S2C.json
│       ├── archive-3B7AFF8ZRX6VNEYV40B35Z9S2C.webm
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR.json
│       ├── archive-DGSN3TC0E91RSCZT5KVPRWCDHR.webm
│       └── report-CZZ8A8KZB16A1DF5PKERBHGFNR.json
```

### report-<recording_id>

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

```json
{
  "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"
    }
  ],
  "channel_id": "sora",
  "created_at": 1615524137,
  "expire_time": 3600,
  "expired_at": 1615527737,
  "metadata_file_path": "/path/to/sora/archive/WHEJ888HQ55KDCFE3TZ4VPFQHR/report-WHEJ888HQ55KDCFE3TZ4VPFQHR.json",
  "metadata_filename": "WHEJ888HQ55KDCFE3TZ4VPFQHR/report-WHEJ888HQ55KDCFE3TZ4VPFQHR.json",
  "recording_id": "WHEJ888HQ55KDCFE3TZ4VPFQHR",
  "node_name": "node1@192.0.2.10",
  "label": "WebRTC SFU Sora",
  "split_only": false
}
```

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

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

この録画一時ファイルは WebM 形式のためそのまま再生することが可能です。

## シグナリング通知

シグナリング通知で録画開始と終了を通知することが可能です。

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

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

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

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

## 試してみる

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

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

### チャネルの録画開始

API を叩いて録画を開始してください。

httpie:

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

curl:

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

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

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

### チャネルの録画終了

チャネルの録画を終了するには API を叩く必要があります。

httpie:

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

curl:

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

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

## シーケンス図

### StopRecording API

seqdiag {
    default_fontsize = 15;
    edge_length = 250;

    === 認証成功 ===

    クライアント <<- Sora [label = '"type": "offer"'];
    クライアント ->> Sora [label = '"type": "answer"', noactivate];

    === WebRTC 確立 ===

        アプリケーションサーバー ->> Sora [label = 'HTTP API\nStartRecording'];
        アプリケーションサーバー <-- Sora [label = '200 OK'];

    ... 録画中 ...

        アプリケーションサーバー ->> Sora [label = 'HTTP API\nStopRecording'];
        アプリケーションサーバー <-- Sora [label = '200 OK'];

    ... 録画終了 ...

        Sora -> アプリケーションサーバー [label = 'イベントウェブフック\n"type": "archive.available"'];
        Sora <-- アプリケーションサーバー [label = '200 OK'];

        Sora -> アプリケーションサーバー [label = 'イベントウェブフック\n"type": "recording.report"'];
        Sora <-- アプリケーションサーバー [label = '200 OK'];

}

### 録画期限切れ

seqdiag {
    default_fontsize = 15;
    edge_length = 250;

    === 認証成功 ===

    クライアント <<- Sora [label = '"type": "offer"'];
    クライアント ->> Sora [label = '"type": "answer"', noactivate];

    === WebRTC 確立 ===

        アプリケーションサーバー ->> Sora [label = 'HTTP API\nStartRecording'];
        アプリケーションサーバー <-- Sora [label = '200 OK'];

    ... 録画中 ...

    ... 期限切れにより録画終了 ...

        Sora -> アプリケーションサーバー [label = 'イベントウェブフック\n"type": "archive.available"'];
        Sora <-- アプリケーションサーバー [label = '200 OK'];

        Sora -> アプリケーションサーバー [label = 'イベントウェブフック\n"type": "recording.report"'];
        Sora <-- アプリケーションサーバー [label = '200 OK'];

}

### クライアント切断

seqdiag {
    default_fontsize = 15;
    edge_length = 250;

    === 認証成功 ===

    クライアント <<- Sora [label = '"type": "offer"'];
    クライアント ->> Sora [label = '"type": "answer"', noactivate];

    === WebRTC 確立 ===

        アプリケーションサーバー ->> Sora [label = 'HTTP API\nStartRecording'];
        アプリケーションサーバー <-- Sora [label = '200 OK'];

    ... 録画中 ...

    クライアント ->> Sora [label = '"type": "disconnect"', noactivate];

        Sora -> アプリケーションサーバー [label = 'イベントウェブフック\n"type": "connection.destroyed"'];
        Sora <-- アプリケーションサーバー [label = '200 OK'];

    クライアント <<- Sora [label = 'WebSocket Close'];

    ... クライアント切断により録画終了 ...

        Sora -> アプリケーションサーバー [label = 'イベントウェブフック\n"type": "archive.available"'];
        Sora <-- アプリケーションサーバー [label = '200 OK'];

}
