タイムアウト機能

概要

Sora Flutter SDK は WebRTC 接続のライフサイクル各段階でタイムアウト機能を提供します。これにより、無限待機を防ぎ、より堅牢なアプリケーションを構築できます。

タイムアウトを設定するには SoraConnectionConfig の timeoutOptions に SoraTimeoutOptions を指定します。 timeoutOptions は省略できますが、指定する値は SoraTimeoutOptions です。

設定可能なタイムアウト

connectionTimeout - 接続タイムアウト

WebRTC 接続確立までの制限時間です。 WebSocket 接続の開始からピア接続の確立完了までにこの時間を超えた場合は、候補 URL 単位ではなく接続処理全体が中止されます。

デフォルト値: 30 秒

disconnectWaitTimeout - 切断待機タイムアウト

切断処理待機中の制限時間です。 disconnect() メソッド実行時にリソース解放が完了するまでの制限時間で、この時間を超過した場合は強制的にリソースが解放されます。

デフォルト値: 10 秒

signalingCandidateTimeout - シグナリング候補タイムアウト

シグナリング候補 URL ごとの WebSocket 接続確立までの制限時間です。 候補 URL への接続がこの時間内に完了しない場合は、その候補を終了して次の URL を試します。 最後の候補 URL がタイムアウトした場合は、接続処理全体がエラーになります。 シグナリング URL のリダイレクト先への再接続にもこの設定が適用されますが、リダイレクト先がタイムアウトした場合は別の候補 URL を試しません。

複数の候補 URL を指定した場合、候補 URL の切り替えは SDK が自動で行います。すべての候補 URL がタイムアウトした場合だけ、利用者側で接続全体を再試行してください。

デフォルト値: 5 秒

対象外のタイムアウト

次のタイムアウトは SoraTimeoutOptions では変更できません。

  • rpc(): SoraRpcOptions.timeout (ミリ秒) で呼び出しごとに指定します。未指定の場合はタイムアウトしません。詳細は RPC 機能 をご確認ください
  • replaceVideoTrack(): 映像トラックの差し替え処理に 10 秒の固定タイムアウトが適用されます
  • getStats(): 統計情報の取得に 5 秒の固定タイムアウトが適用されます。詳細は 統計情報 をご確認ください

使用方法

デフォルトのタイムアウト値を使用する

このとき SoraTimeoutOptions を設定する必要はありません。

final config = SoraConnectionConfig(
  signalingUrls: ['wss://example.com/signaling'],
  channelId: 'channel-id',
  role: SoraRole.sendrecv,
);

カスタムタイムアウト値を設定する

final config = SoraConnectionConfig(
  signalingUrls: ['wss://example.com/signaling'],
  channelId: 'channel-id',
  role: SoraRole.sendrecv,
  timeoutOptions: SoraTimeoutOptions(
    connectionTimeout: Duration(seconds: 60),
    disconnectWaitTimeout: Duration(seconds: 15),
    signalingCandidateTimeout: Duration(seconds: 10),
  ),
);

エラーハンドリング

接続処理または切断処理を失敗させるタイムアウトが発生した場合は、 connect() / disconnect() が例外を投げたうえで、 conn.events の SoraConnectionErrorEvent でも通知されます。

  • connectionTimeout 超過: connect() が TimeoutException を投げます
  • disconnectWaitTimeout 超過: disconnect() が TimeoutException を投げます
  • 最後の候補 URL またはリダイレクト先での signalingCandidateTimeout 超過: connect() が StateError を投げます

例外と SoraConnectionErrorEvent は同じタイムアウトに対して両方通知されます。接続処理の失敗の分岐は connect() / disconnect() の例外で行い、 SoraConnectionErrorEvent は接続状態の表示やログ出力に利用するのが基本です。

最後以外の候補 URL が signalingCandidateTimeout を超えても、後続の候補 URL への接続に成功した場合は例外を投げず、 SoraConnectionErrorEvent も通知しません。

リダイレクト先の signalingCandidateTimeout 超過では、別の候補 URL への切り替えは行われません。 connect() の完了前であれば connect() が StateError を投げ、接続完了後であれば SoraConnectionErrorEvent の通知だけが行われます。

タイムアウトの対象外のエラーもあります。次のエラーは例外と SoraConnectionErrorEvent のどちらか一方でのみ通知されます。

  • rpc() のタイムアウト: TimeoutException が rpc() の呼び出し元へ返るのみです。 SoraConnectionErrorEvent は通知されません
  • 接続確立後に発生するエラー (例: websocket_error / camera_open_error): connect() の例外ではなく SoraConnectionErrorEvent でのみ通知されます

SoraTimeoutEvent は WebSocket の reason が TIMEOUT の場合に通知されます。ただし、切断処理中や PeerConnection の接続確立後は通知されません。タイムアウトの検知は SoraConnectionErrorEvent と connect() / disconnect() の例外で行うのが基本です。

タイムアウトエラーコード

  • connection_timeout - 接続タイムアウト発生
  • disconnect_timeout - 切断タイムアウト発生
  • signaling_candidate_timeout - シグナリング候補タイムアウト発生

サンプルコード

final conn = await Sora.createConnection(config);

conn.events.listen((event) {
  if (event case SoraConnectionErrorEvent(:final code, :final message)) {
    switch (code) {
      case SoraErrorCode.connectionTimeout:
        print('接続タイムアウト: ${message ?? ''}');
      case SoraErrorCode.disconnectTimeout:
        print('切断タイムアウト: ${message ?? ''}');
      case SoraErrorCode.signalingCandidateTimeout:
        print('シグナリング接続タイムアウト: ${message ?? ''}');
    }
  }
});

リトライロジック

タイムアウトが発生した場合のリトライ処理例です。複数の候補 URL は SDK が自動で試行するため、この例は全候補 URL の接続に失敗した後の接続全体の再試行だけを扱います。

conn.events は connect() より前に購読してください。全候補 URL の signalingCandidateTimeout では、 connect() が StateError を投げるため、 SoraConnectionErrorEvent.code を確認して再試行対象を判定します。その他の StateError は再試行しません。

import 'dart:async';

import 'package:sora_sdk/sora_sdk.dart';

Future<SoraConnection> connectWithRetry(
  SoraConnectionConfig config,
  int maxRetries,
) async {
  int retries = 0;
  while (true) {
    final conn = await Sora.createConnection(config);
    var candidateTimeout = false;
    var connected = false;
    final subscription = conn.events.listen((event) {
      if (event is SoraConnectionErrorEvent &&
          event.code == SoraErrorCode.signalingCandidateTimeout) {
        candidateTimeout = true;
      }
    });

    try {
      await conn.connect();
      connected = true;
      return conn;
    } on TimeoutException catch (error, stackTrace) {
      if (retries >= maxRetries) {
        Error.throwWithStackTrace(error, stackTrace);
      }
      retries++;
      print('接続タイムアウト (リトライ $retries/$maxRetries)');
    } on StateError catch (error, stackTrace) {
      // StreamController は非同期でイベントを配信するため、
      // connect() の例外を受け取った後にイベントを処理する時間を確保します。
      await Future<void>.delayed(Duration.zero);
      if (!candidateTimeout) {
        Error.throwWithStackTrace(error, stackTrace);
      }
      if (retries >= maxRetries) {
        Error.throwWithStackTrace(error, stackTrace);
      }
      retries++;
      print('シグナリング候補タイムアウト (リトライ $retries/$maxRetries)');
    } finally {
      await subscription.cancel();
      if (!connected) {
        await conn.dispose();
      }
    }
    await Future<void>.delayed(const Duration(seconds: 2));
  }
}

推奨タイムアウト値

設定項目

推奨値

備考

connectionTimeout

30-60 秒

ネットワーク遅延を考慮

disconnectWaitTimeout

10-15 秒

リソース解放時間を確保

signalingCandidateTimeout

5-10 秒

URL 試行時間を確保

タイムアウト値は環境やネットワーク状況に応じて調整してください。