導入ガイド

概要

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

前提

  • Flutter がセットアップ済みであること

    Flutter のセットアップ方法については Flutter ドキュメント をご確認ください。

  • 利用する Sora 環境があり、接続できる状態であること

    Sora については Sora ドキュメント をご確認ください。

  • 接続先の Sora シグナリング URL が分かっていること
  • 接続先のチャネル ID が決まっていること

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

sora_sdkpub.dev から追加して利用できます。

flutter pub add sora_sdk

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

dependencies:
  sora_sdk: ^2026.0.0

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

flutter pub get

2. SDK を import する

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

import 'package:sora_sdk/sora_sdk.dart';

3. 接続設定を作成する

role には接続時の送受信方式を指定します。 最初は受信だけを行う recvonly か、送受信を行う sendrecv から始めるのがおすすめです。

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

signalingUrlsList<String> で複数指定できます。SDK は先頭から順番に接続を試み、無効な URL は自動でスキップします。

4. SoraConnection を生成する

final conn = await Sora.createConnection(config);

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

5. 接続状態を確認する

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

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

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 に接続する

recvonly の場合は引数なしで接続します。

await conn.connect();

sendonly / sendrecv の場合は、 MediaDevices.getUserMedia()MediaStream を作って connect() に渡します。ローカル映像をレンダリングする場合は、この MediaStream に含まれる LocalVideoTrack から textureId を取り出しておきます。

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.eventsSoraTrackEvent (kind == 'video') で受け取る textureId を、それぞれ Texture ウィジェットに渡します。

映像レンダリング詳細については 映像レンダリングガイド をご確認ください。

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() を呼んでリソースを破棄してください。

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

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

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

最小サンプル

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();
}

次に読むページ