映像を描画する

概要

Sora Flutter SDK での映像レンダリングの方法について説明します。

描画までの流れは次のとおりです。

  1. 映像の textureId を取得する
    • ローカル映像は LocalVideoTrack.textureId から textureId を取得する
    • リモート映像は SoraTrackEvent で受け取った RemoteMediaStreamTrack.textureId を使う
  2. 取得した textureId を Flutter の Texture に渡して描画する

SDK は Texture を直接使う方法に加えて、 SoraLocalVideoWidget / SoraRemoteVideoWidget を提供しています。 これらは textureId が未確定の間のプレースホルダー表示、親領域へのフィット、左右反転をまとめて扱えます。

イベント全体の一覧や SoraConnection.events の使い方は イベント をご確認ください。

TextureId を取得する

ローカル映像

ローカル映像を表示するには、まず送信用の MediaStream を用意します。 MediaStream の作成方法は メディアデバイス をご確認ください。

ローカル映像の textureId は LocalVideoTrack.textureId ( Future<int> ) で取得します。

final stream = await MediaDevices.getUserMedia(
  const GetUserMediaOptions(audio: true, video: true),
);
final videoTrack = stream.getVideoTracks().first;
final textureId = await videoTrack.textureId;

createExternalVideoTrack() で生成した外部映像入力用の LocalVideoTrack で textureId を取得すると StateError が送出されます。詳細は 外部映像入力を利用する をご確認ください。

SoraConnection.localVideo ( Stream<SoraLocalVideoHandle> ) からも textureId を取得できます。 カメラのトラックが接続に設定され、キャプチャが開始されたタイミングで SoraLocalVideoHandle が emit されます。 textureId は常に有効な非負値です。

conn.localVideo.listen((handle) {
  setState(() {
    localTextureId = handle.textureId;
  });
});

リモート映像

受信したリモート映像から textureId を取得する方法を説明します。 リモート映像を受信するまでの基本的な流れは 実装ガイド をご確認ください。また、 conn.events によりイベント購読しているものとします。

リモート映像は conn.events の SoraTrackEvent で受け取ります。トラックは RemoteMediaStreamTrack として通知され、 kind == 'video' の場合のみ textureId が non-null になります。

final remoteTextureIds = <String, int>{};

conn.events.listen((event) {
  switch (event) {
    case SoraTrackEvent(:final track):
      final textureId = track.textureId;
      if (track.kind == 'video' && textureId != null) {
        setState(() {
          remoteTextureIds[track.trackId] = textureId;
        });
      }
    default:
      break;
  }
});

trackId は、複数リモートトラックを識別する際に利用できます。

RemoteMediaStreamTrack の型や SoraTrackEvent の通知タイミングの詳細は イベント を参照してください。

Texture によるレンダリング

textureId を Flutter の Texture ウィジェットに渡してレンダリングします。リモートトラックの textureId は int? なので、 kind == 'video' を確認してから利用します。

Widget buildVideo(int textureId) {
  return Texture(textureId: textureId);
}

final localVideo = buildVideo(textureId);
final remoteVideo = buildVideo(track.textureId!);

複数のリモート映像を並べて表示する場合は、受信したトラックのリストを GridView や Wrap などに流し込みます。

Widget buildRemoteVideos(Map<String, int> remoteTextureIds) {
  return GridView.count(
    crossAxisCount: 2,
    children: remoteTextureIds.values
        .map((textureId) => Texture(textureId: textureId))
        .toList(),
  );
}

削除されたリモートトラックの映像を取り除く

リモート側の切断等により SoraRemoveTrackEvent が通知された映像トラックの textureId は描画に使えなくなるため、対応する Texture をツリーから取り除きます。

// trackId をキーに textureId を管理するマップです
final remoteTextureIds = <String, int>{};

conn.events.listen((event) {
  switch (event) {
    case SoraTrackEvent(:final track):
      final textureId = track.textureId;
      if (track.kind == 'video' && textureId != null) {
        setState(() {
          remoteTextureIds[track.trackId] = textureId;
        });
      }
    // SoraRemoveTrackEvent で該当する trackId をリストから削除します
    case SoraRemoveTrackEvent(:final track):
      if (track.kind == 'video') {
        setState(() {
          // trackId をキーに管理すると、SoraRemoveTrackEvent を受けたタイミングで該当する Texture を自然に表示から外せます
          remoteTextureIds.remove(track.trackId);
        });
      }
    default:
      break;
  }
});

Widget によるレンダリング

SoraLocalVideoWidget と SoraRemoteVideoWidget を使うと、 textureId の管理と Texture の組み立てを Widget に任せられます。

SoraRemoteVideoWidget

SoraTrackEvent で受け取った RemoteMediaStreamTrack をそのまま渡します。 track.textureId が null の間は placeholder が表示されます。

Widget buildRemoteVideo(RemoteMediaStreamTrack track) {
  return SoraRemoteVideoWidget(track: track);
}

フィールド

説明

track

表示するリモート映像トラック ( kind == 'video' )。 kind == 'audio' を渡した場合は debug 時に assert で検出されます

fit

親領域へのフィット方法。デフォルトは BoxFit.contain

mirror

水平反転するかどうか。デフォルトは false

placeholder

track.textureId が null の間に表示する Widget。デフォルトは黒色の塗りつぶし

SoraLocalVideoWidget

textureId を渡します。 null または負値のときは placeholder が表示されます。 0 は有効な値として扱われます。

Widget buildLocalVideo(int? textureId) {
  return SoraLocalVideoWidget(textureId: textureId, mirror: true);
}

fit / mirror / placeholder は SoraRemoteVideoWidget と同じです。

関連ページ