イベント

概要

Sora Flutter SDK のイベントは、主に Stream<SoraConnectionEvent>Stream<SoraDebugEvent> で提供されます。

Stream<SoraConnectionEvent> では、接続状態、リモートトラックの追加・削除、シグナリング、DataChannel (通知・メッセージング・RPC 等)、タイムアウトを購読できます。

Stream<SoraDebugEvent> では、構造化されたログとタイムラインを購読できます。

統合イベント stream

Stream<SoraConnectionEvent> は統合イベント stream です。イベント型ごとに switch で分岐して処理します。

conn.events.listen((event) {
  switch (event) {
    case SoraConnectionStateChangedEvent(:final state):
      print('state: $state');
    case SoraConnectionErrorEvent(:final code, :final message):
      print('error: ${code ?? 'unknown'} ${message ?? ''}');
    case SoraTrackEvent(:final track):
      print('track added: ${track.trackId}');
    case SoraRemoveTrackEvent(:final track):
      print('track removed: ${track.trackId}');
    case SoraNotifyEvent(:final message):
      print('notify: $message');
    case SoraPushEvent(:final message):
      print('push: $message');
    case SoraSwitchedEvent(:final message):
      print('switched: $message');
    case SoraSignalingMessageEvent(:final event):
      print('signaling: ${event.transportType} ${event.direction}');
    case SoraDataChannelOpenEvent(:final event):
      print('datachannel open: ${event.label}');
    case SoraDataChannelMessageEvent(:final message):
      print('message: ${message.label}');
    case SoraTimeoutEvent():
      print('timeout');
  }
});

接続状態の変更

SoraConnectionStateChangedEvent は接続状態が変わったときに通知されます。

conn.events.listen((event) {
  if (event case SoraConnectionStateChangedEvent(:final state)) {
    switch (state) {
      case SoraConnectingState():
        print('connecting');
      case SoraConnectedState():
        print('connected');
      case SoraDisconnectedState(:final closeInfo):
        print('disconnected');
        if (closeInfo != null) {
          print('close code: ${closeInfo.code}');
          print('close reason: ${closeInfo.reason}');
        }
    }
  }
});

通知される状態は次の 3 種類です。

  • SoraConnectingState
  • SoraConnectedState
  • SoraDisconnectedState

SoraConnectingState

接続処理の開始後、シグナリング接続の確立に入ったタイミングで通知されます。

conn.events.listen((event) {
  if (event case SoraConnectionStateChangedEvent(state: SoraConnectingState())) {
    print('connecting');
  }
});

SoraConnectedState

Sora との PeerConnection が接続済になったタイミングで通知されます。

conn.events.listen((event) {
  if (event case SoraConnectionStateChangedEvent(state: SoraConnectedState())) {
    print('connected');
  }
});

SoraDisconnectedState

Sora から切断したタイミングで通知されます。

closeInfo を持つ場合は、切断時の close code と reason を参照できます。

conn.events.listen((event) {
  if (event case SoraConnectionStateChangedEvent(
    state: SoraDisconnectedState(:final closeInfo),
  )) {
    print('disconnected');
    if (closeInfo != null) {
      print('close code: ${closeInfo.code}');
      print('close reason: ${closeInfo.reason}');
    }
  }
});

接続エラー

接続エラーは SoraConnectionErrorEvent で通知されます。

conn.events.listen((event) {
  if (event case SoraConnectionErrorEvent(:final code, :final message)) {
    print('connection error: ${code ?? 'unknown'} ${message ?? ''}');
  }
});

code にはエラー種別が、 message にはエラーの詳細が含まれます。 どちらも null になる可能性があるため、利用の際は null チェックを行ってください。

code には、たとえば次のような値が入ります。

  • event_channel_error
  • websocket_error
  • connection_timeout
  • disconnect_timeout
  • signaling_candidate_timeout
  • offer_invalid

track

リモートトラックが追加されたときは SoraTrackEvent が通知されます。

トラックは RemoteMediaStreamTrack として渡され、 kind で音声か映像かを判別できます。

  • kind == 'audio': 音声トラック (textureIdnull)
  • kind == 'video': 映像トラック (textureId は non-null)
conn.events.listen((event) {
  if (event case SoraTrackEvent(:final track)) {
    if (track.kind == 'audio') {
      print('audio track added: ${track.trackId}');
    } else {
      print('video track added: ${track.trackId}');
      print('textureId: ${track.textureId}');
    }
  }
});

RemoteMediaStreamTrack のフィールドは以下の通りです。

  • trackId: トラック ID
  • kind: audio または video
  • connectionId: Sora の connection_id
  • textureId: kind == 'video' の場合のみ値を取得できます

映像トラックの追加イベントでは、 textureId をそのまま Texture に渡せます。

if (event case SoraTrackEvent(:final track) when track.kind == 'video') {
  return Texture(textureId: track.textureId!);
}

removetrack

リモートトラックが削除されたときは SoraRemoveTrackEvent が通知されます。

conn.events.listen((event) {
  if (event case SoraRemoveTrackEvent(:final track)) {
    print('track removed: ${track.trackId}');
  }
});

削除イベントでも追加イベント時と同様に RemoteMediaStreamTrack 型となります。UI 側は通常 trackId で対象を特定して除去します。

接続単位でトラックを扱いたい場合は、 SoraConnection.remoteMediaStreams も利用できます。 connectionId をキーに RemoteMediaStream が入り、 audioTrack / videoTrack にそれぞれのトラックが格納されます。

notify

Sora からシグナリング通知を受け取ったときは SoraNotifyEvent が通知されます。

Sora のシグナリング通知の詳細は https://sora-doc.shiguredo.jp/SIGNALING_NOTIFY をご確認ください。

conn.events.listen((event) {
  if (event case SoraNotifyEvent(:final message)) {
    print('notify: $message');
  }
});

connection.created などの通知をここで受け取ることができます。

push

Sora からのプッシュ通知を受け取ったときは SoraPushEvent が通知されます。

conn.events.listen((event) {
  if (event case SoraPushEvent(:final message)) {
    print('push: $message');
  }
});

switched

シグナリングが WebSocket から DataChannel に切り替わったときは SoraSwitchedEvent が通知されます。

conn.events.listen((event) {
  if (event case SoraSwitchedEvent(:final message)) {
    print('switched: $message');
  }
});

timeout

WebSocket の close reason が TIMEOUT のときは SoraTimeoutEvent が通知されます。

conn.events.listen((event) {
  if (event case SoraTimeoutEvent()) {
    print('timeout');
  }
});

message

DataChannel を利用したリアルタイムメッセージング機能でメッセージを受信したときは SoraDataChannelMessageEvent が通知されます。

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

datachannel

DataChannel が利用可能になったときは SoraDataChannelOpenEvent が通知されます。

conn.events.listen((event) {
  if (event case SoraDataChannelOpenEvent(:final dataChannel)) {
    print('label: ${dataChannel.label}');
    print('direction: ${dataChannel.direction}');
  }
});

メッセージング機能を使う場合は、このイベントを送信可能の目安として扱えます。

signaling

シグナリングメッセージの送受信を確認したいときは SoraSignalingMessageEvent を利用します。

conn.events.listen((event) {
  if (event case SoraSignalingMessageEvent(:final signaling)) {
    print('${signaling.transportType} ${signaling.direction}');
    print(signaling.data);
  }
});

transportTypewebsocket または datachannel です。 directionsent または received です。

data には、シグナリングメッセージの内容がそのまま入ります。 offer / answer / candidate などの内容を確認することができます。

デバッグイベント stream

conn.debugEvents はデバッグイベント専用 stream です。構造化された logtimeline を受け取りたい場合に利用します。

conn.debugEvents.listen((event) {
  switch (event) {
    case SoraLogDebugEvent(:final event):
      print('${event.title}: ${event.message}');
    case SoraTimelineDebugEvent(:final event):
      print('${event.logType.value} ${event.type}');
      print(event.data);
  }
});

log

SDK の構造化ログは SoraLogDebugEvent で受け取ることができます。

conn.debugEvents.listen((event) {
  if (event case SoraLogDebugEvent(:final log)) {
    print('${log.title}: ${log.message}');
  }
});

timeline

タイムラインの構造化ログは SoraTimelineDebugEvent で受け取ることができます。

conn.debugEvents.listen((event) {
  if (event case SoraTimelineDebugEvent(:final timeline)) {
    print('${timeline.logType.value} ${timeline.type}');
    print(timeline.data);
  }
});

SoraTimelineEvent では、主に次の情報を参照します。

  • type: イベント種別
  • logType: websocket / datachannel / peerconnection / sora
  • data: イベントの詳細データ
  • dataChannelId: DataChannel に紐づく場合の ID
  • dataChannelLabel: DataChannel に紐づく場合の label

接続確立後の識別子

offer を受信すると、次の getter がサーバーから割り当てられた値で更新されます。

  • connectionId
  • serverClientId
  • bundleId
  • sessionId

接続前や offer 未到達のときは null です。RPC のパラメータ組み立てなどで利用できます。

イベント購読の終了

conn.eventsconn.debugEvents を購読した場合は、 conn.dispose() の前に購読を解除してください。

late final StreamSubscription<SoraConnectionEvent> eventSubscription;
late final StreamSubscription<SoraDebugEvent> debugEventSubscription;

void setup(SoraConnection conn) {
  eventSubscription = conn.events.listen((event) {
    // イベント処理
  });
  debugEventSubscription = conn.debugEvents.listen((event) {
    // debug event 処理
  });
}

Future<void> dispose(SoraConnection conn) async {
  await eventSubscription.cancel();
  await debugEventSubscription.cancel();
  await conn.dispose();
}