# リアルタイムメッセージング機能

## 概要

Sora Flutter SDK では DataChannel を利用したリアルタイムメッセージング機能を利用することができます。

同一チャネルに参加しているクライアント同士でリアルタイムなメッセージを送受信することができます。
また、メッセージにはラベルを指定でき、同一チャネル内で複数の種類のメッセージを扱えます。

リアルタイムメッセージング機能の詳細は Sora のドキュメントの [リアルタイムメッセージング機能](https://sora-doc.shiguredo.jp/MESSAGING) をご確認ください。

> **注釈**
>
> リアルタイムメッセージング機能を利用するには、 Sora 側の設定で DataChannel シグナリングを有効にする必要があります。
> 詳細は [DataChannel 経由のシグナリング](datachannel_signaling.html) をご確認ください。

## リアルタイムメッセージングの設定をする

Sora Flutter SDK でリアルタイムメッセージング機能を利用するには、接続時の設定で dataChannels を指定する必要があります。

dataChannels には必須パラメータとして `label` と `direction` を指定します。 `label` はメッセージの経路を識別するための文字列で、 `direction` はその経路の方向を指定します。

この label には必ず `#` を先頭につける必要があります。 `#` をつけることで、Sora のメッセージング用の DataChannel であることを示しています。

`dataChannels` はリストなので、複数のラベルを同時に指定できます。

`label` は送受信時にそのまま使う識別子です。 `sendDataChannelMessage()` の第 1 引数にも同じ文字列を渡します。

また、ここで指定する `dataChannels` はアプリケーション向けメッセージング用です。 `signaling` や `notify` などの Sora / SDK 内部用 DataChannel を追加する必要はありません。

```dart
final config = SoraConnectionConfig(
  signalingUrls: <String>['wss://example.com/signaling'],
  channelId: 'example-channel',
  role: SoraRole.sendrecv,
  dataChannelSignaling: true,
  dataChannels: <Map<String, Object?>>[
    <String, Object?>{
      'label': '#chat',
      'direction': 'sendrecv',
      // max_retransmits と max_packet_life_time を省略すると完全信頼。
      'ordered': true,
    },
    <String, Object?>{
      'label': '#notifications',
      'direction': 'recvonly',
      // 再送時間を 3000 ms に制限した部分信頼。
      'ordered': false,
      'max_packet_life_time': 3000,
    },
    <String, Object?>{
      'label': '#telemetry',
      'direction': 'sendonly',
      // 再送回数を 2 回に制限した部分信頼。
      'max_retransmits': 2,
    },
  ],
);
```

### 順序保証

リアルタイムメッセージは `label` 毎に順序保証をするかどうかを指定できます。順序保証をする場合は `ordered: true` を指定します。 `ordered: false` を指定すると、到着順とは異なる順序で配信されることがあります。

`ordered` は順序だけを設定します。 `ordered: false` でも、 `max_retransmits` と `max_packet_life_time` を省略すれば完全信頼です。

### 信頼性（再送制御）

リアルタイムメッセージの信頼性は、 `max_retransmits` と `max_packet_life_time` で再送の上限を指定します。これらの項目を指定しない場合は、再送制限のない完全信頼のチャネルになります。

`max_retransmits` または `max_packet_life_time` のどちらか一方を指定すると部分信頼のチャネルになります。

- `max_retransmits` は再送回数の上限です。 `0` を指定すると再送しません
- `max_packet_life_time` は送信および再送を試みる時間の上限をミリ秒で指定します

`max_retransmits` と `max_packet_life_time` は同時に指定できません。両方を指定すると `Sora` / `WebRTC` 側で設定エラーになります。

ここでいう完全信頼は、接続中の `DataChannel` が再送制限なく配信を試みることを意味します。接続が切断された後もアプリケーションへ配信されることや、アプリケーション処理が完了することまで保証するものではありません。

`dataChannels` の各要素は `SoraConnectionConfig.dataChannels` から connect メッセージの `data_channels` へ渡されます。SDK は各要素の `ordered` 、 `max_retransmits` 、 `max_packet_life_time` を別の意味へ変換せず、これらの組み合わせも検証しません。キー名、値の型、同時指定の禁止などの制約は `Sora` / `WebRTC` の仕様に従って指定してください。

### 圧縮機能

リアルタイムメッセージは `label` 毎に圧縮するかどうかを指定できます。圧縮する場合は `compress: true` を指定します。

`compress: true` を指定したラベルでは、 SDK が送信時に自動で圧縮し、受信時に自動で展開します。アプリケーション側で個別に圧縮処理を書く必要はありません。

## メッセージを送信する

メッセージは `sendDataChannelMessage()` で `label` を指定して送ることができます。

送信する `label` は、接続時に `dataChannels` へ設定したものと一致させます。

メッセージの実体は `Uint8List` です。文字列や JSON を送りたい場合は、アプリケーション側で UTF-8 エンコードしてから送信します。

以下のコード例では、接続済みの `SoraConnection` を `conn` として保持しているものとします。

```dart
import 'dart:convert';
import 'dart:typed_data';

final data = Uint8List.fromList(
  utf8.encode('{"type":"chat","text":"hello"}'),
);

conn.sendDataChannelMessage('#chat', data);
```

DataChannel が開く前に送信すると取り扱いが分かりにくくなるため、通常は後述の `SoraDataChannelOpenEvent` を確認してから送信するのが安全です。

## メッセージを受信する

メッセージは `SoraDataChannelMessageEvent` イベント通知で受け取ることができます。
`message` には、送信したときの `label` と `data` が入ります。

`label` でメッセージの経路を識別でき、 `data` で送られてきたデータ内容を受け取ることができます。
`data` は `Uint8List` のため、文字列や JSON として扱いたい場合は UTF-8 でデコードします。

> **注釈**
>
> イベントについては [イベント](event.html) をご確認ください。

```dart
import 'dart:convert';

conn.events.listen((event) {
  if (event case SoraDataChannelMessageEvent(:final message)) {
    final text = utf8.decode(message.data);
    print('${message.label}: $text');
  }
});
```

## DataChannel が使えるようになったタイミング

利用可能になったことは `SoraDataChannelOpenEvent` イベント通知で確認できます。

`SoraDataChannelOpenEvent` は、どのラベルの DataChannel が開いたかを確認するためのイベントです。複数の `label` を設定している場合は、ラベルごとに監視すると分かりやすくなります。

> **注釈**
>
> イベントについては [イベント](event.html) をご確認ください。

```dart
conn.events.listen((event) {
  if (event case SoraDataChannelOpenEvent(:final event)) {
    print(event.label);
    print(event.direction);
    print(event.compress);
  }
});
```

## リアルタイムメッセージのみを利用する

音声 / 映像を送受信せず、リアルタイムメッセージ機能のみを利用できます。
ただし、この場合は `Sora` の設定が必要です。

この構成では、通常 `audio: false` 、 `video: false` 、 `dataChannelSignaling: true` に加えて、必要な `dataChannels` を明示します。
`role` は `sendonly` ですが、音声 / 映像を送信しないため、 `MediaDevices.getUserMedia()` は不要です。
`connect()` は引数なしで呼び出します。

```dart
final config = SoraConnectionConfig(
  signalingUrls: <String>['wss://example.com/signaling'],
  channelId: 'example-channel',
  role: SoraRole.sendonly,
  audio: false,
  video: false,
  dataChannelSignaling: true,
  dataChannels: <Map<String, Object?>>[
    <String, Object?>{
      'label': '#chat',
      'direction': 'sendrecv',
      'ordered': true,
      'compress': true,
    },
  ],
);

final conn = await Sora.createConnection(config);

// 音声 / 映像を送信しないため、MediaStream を渡さずに接続する。
await conn.connect();
```

詳細は `Sora` のドキュメントの [DataChannel を利用したメッセージングのみで接続する](https://sora-doc.shiguredo.jp/MESSAGING#3a3616) をご確認ください。
