# 実装ガイド

## 概要

Sora Flutter SDK を利用して Sora に接続する方法について説明します。

## 前提

- Flutter がセットアップ済みであること> Flutter のセットアップ方法については [Flutter ドキュメント](https://docs.flutter.dev/) をご確認ください。
- 利用する Sora 環境があり、接続できる状態であること> Sora については [Sora ドキュメント](https://sora-doc.shiguredo.jp/) をご確認ください。
- 接続先の Sora シグナリング URL が分かっていること
- 接続先のチャネル ID が決まっていること


## 1. パッケージを追加する

`sora_sdk` は `pub.dev` から追加して利用できます。

```bash
flutter pub add sora_sdk
```

または `pubspec.yaml` に直接追加します。

```yaml
dependencies:
  sora_sdk: ^2026.1.0
```

追加後に依存関係を取得してください。

```bash
flutter pub get
```

## 2. SDK を import する

接続処理を書く Dart ファイルの先頭に、次の import を追加します。
最初は `lib/main.dart` や、接続処理を実装するファイルに追加してください。

```dart
import 'package:sora_sdk/sora_sdk.dart';
```

## 3. 接続設定を作成する

`role` には接続時の送受信方式を指定します。

```dart
final config = SoraConnectionConfig(
  signalingUrls: <String>[
    'wss://sora.example.com/signaling',
  ],
  channelId: 'example-channel',
  role: SoraRole.sendrecv,
  video: true,
  audio: true,
);
```

`signalingUrls` は `List<String>` で複数指定できます。
`Sora Flutter SDK` は先頭から順番に `WebSocket` 接続を試みます。
接続を試みた URL が不正な場合は、その時点で接続処理を中止し、残りの URL は試しません。
不正な URL の例は次のとおりです。

- `not-a-url` ：URL の形式になっていない
- `https://sora.example.com/signaling` ：`ws` / `wss` 以外で始まる
- `wss:///signaling` ：接続先のホスト名がない

URL の検証に成功し、候補 URL ごとの `WebSocket` 接続に失敗した場合、またはタイムアウトした場合は、次の URL を試します。

## 4. SoraConnection を生成する

```dart
final conn = await Sora.createConnection(config);
```

`Sora.createConnection()` の時点で、プラットフォーム側のクライアント初期化と WebRTC クライアントの生成が行われます。

## 5. 接続状態を確認する

接続状態の変化やリモートトラックの追加を確認したい場合は、接続前に `conn.events` でイベントを購読します。

イベント購読の例です。下記以外にも購読可能なイベントがあります。
イベントの一覧や発火タイミングの詳細は [イベント](event.html) をご確認ください。

```dart
final subscription = conn.events.listen((event) {
  switch (event) {
    case SoraConnectionStateChangedEvent(:final state):
      // 接続状態の変化を確認する
      print('connection state: $state');
      if (state case SoraDisconnectedState(:final closeInfo)) {
        if (closeInfo != null) {
          // 切断時に close code / reason を取得できた場合
          print('close: ${closeInfo.code} ${closeInfo.reason}');
        }
      }
    case SoraConnectionErrorEvent(:final code, :final message):
      // 接続エラーは別イベントで通知される
      print('connection error: ${code ?? 'unknown'} ${message ?? ''}');
    case SoraTrackEvent(:final track):
      if (track.kind == 'video') {
        // 受信した映像トラックの追加を確認する
        print('remote video added: texture=${track.textureId}');
      } else {
        // 受信した音声トラックの追加を確認する
        print('remote audio added: ${track.trackId}');
      }
    case SoraRemoveTrackEvent(:final track):
      // 受信していたトラックの削除を確認する
      print('remote track removed: ${track.trackId}');
    default:
      break;
  }
});
```

`StreamSubscription` を使う場合はファイル先頭に `import 'dart:async';` が必要です。

## 6. Sora に接続する

`connect()` に `MediaStream` を渡すかどうかは、送信設定で決まります。

- `role` が `recvonly` の場合は `MediaStream` を渡さない
- `role` が `sendonly` / `sendrecv` の場合- `audio` と `video` が両方 `false` の場合は `MediaStream` を渡さない
  - 上記以外（音声または映像を送信する）の場合は `MediaStream` を渡す

```dart
final stream = await MediaDevices.getUserMedia(
  const GetUserMediaOptions(audio: true, video: true),
);
final localTextureId = await stream.getVideoTracks().first.textureId;
await conn.connect(stream);
```

接続が完了すると、 `conn.events` から `SoraConnectionStateChangedEvent` として `SoraConnectedState` が通知されます。

## 7. Flutter で映像をレンダリングする

ローカル映像は `LocalVideoTrack.textureId` 、リモート映像は `conn.events` の `SoraTrackEvent` (`kind == 'video'`) で受け取る `textureId` を、それぞれ `Texture` ウィジェットに渡します。

映像レンダリング詳細については [映像を描画する](video_rendering.html) をご確認ください。

```dart
class VideoView extends StatelessWidget {
  const VideoView({super.key, required this.textureId});

  final int textureId;

  @override
  Widget build(BuildContext context) {
    return Texture(textureId: textureId);
  }
}
```

複数のリモート映像が来る可能性があるため、リスト管理にしておくと扱いやすくなります。

## 8. Sora 接続の切断とリソース破棄

Sora から切断するには `disconnect()` を呼びます。
接続を再利用する場合は、切断後も同じインスタンスを使って再度 `connect()` を呼ぶことができます。
完全に使い終わったら、 `disconnect()` の後に `dispose()` を呼んでリソースを破棄してください。

```dart
await conn.disconnect();
await conn.dispose();
```

購読している `StreamSubscription` も破棄する必要があります。
`getUserMedia()` で作成した `LocalMediaStream` や track の解放方法は [メディアデバイス](media_device.html) をご確認ください。

```dart
for (final subscription in subscriptions) {
  await subscription.cancel();
}
```

## 最小サンプル

Sora に `recvonly` で接続し、映像を受信したときに `textureId` をログ出力する最小サンプルです。

```dart
import 'dart:async';

import 'package:sora_sdk/sora_sdk.dart';

Future<void> startSora() async {
  final conn = await Sora.createConnection(
    SoraConnectionConfig(
      signalingUrls: <String>[
        'wss://sora.example.com/signaling',
      ],
      channelId: 'example-channel',
      role: SoraRole.recvonly,
    ),
  );

  final subscriptions = <StreamSubscription<dynamic>>[
    conn.events.listen((event) {
      switch (event) {
        case SoraConnectionStateChangedEvent(:final state):
          print('state=$state');
          if (state case SoraDisconnectedState(:final closeInfo)) {
            if (closeInfo != null) {
              print('close=${closeInfo.code} ${closeInfo.reason}');
            }
          }
        case SoraConnectionErrorEvent(:final code, :final message):
          print('error=${code ?? 'unknown'} ${message ?? ''}');
        case SoraTrackEvent(:final track):
          if (track.kind == 'video') {
            print('remote texture=${track.textureId}');
          }
        default:
          break;
      }
    }),
  ];

  try {
    await conn.connect();
  } catch (_) {
    for (final subscription in subscriptions) {
      await subscription.cancel();
    }
    await conn.dispose();
    rethrow;
  }

  // 必要に応じて後で切断する
  await conn.disconnect();
  for (final subscription in subscriptions) {
    await subscription.cancel();
  }
  await conn.dispose();
}
```

## 関連ページ

- 接続オプション: [シグナリング設定](signaling.html)
- イベント: [イベント](event.html)
- 映像描画 (`Texture`): [映像を描画する](video_rendering.html)
- OS 別の権限や注意点: [プラットフォーム](overview.html#プラットフォーム) と以下の OS 向けドキュメント- [Android での利用方法](android.html)
  - [iOS / iPadOS での利用方法](ios_ipados.html)
  - [macOS での利用方法](macos.html)
- よくある質問: [FAQ](faq.html)
- カメラ / マイク: [メディアデバイス](media_device.html)
- 音声: [音声の送受信](audio.html)
- DataChannel の利用: [DataChannel 経由のシグナリング](datachannel_signaling.html) と [リアルタイムメッセージング機能](messaging.html)
- RPC 機能: [RPC 機能](rpc.html)
