Android 向けガイド

概要

Android アプリで sora_sdk を利用する方法について説明します。

権限

Android で音声や映像を配信する場合は Android 側のカメラとマイクの権限が必要になります。 CAMERARECORD_AUDIO はランタイム権限で、マニフェストへの宣言だけでは付与されません。SDK は権限リクエストを行わないため、アプリ側で AndroidManifest.xml の宣言とランタイム権限のリクエストを実装してください。

AndroidManifest.xml の宣言

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

ランタイム権限のリクエスト

Dart 側の実装例として permission_handler パッケージを使うと、次のように権限を確保できます。

import 'package:permission_handler/permission_handler.dart';

// 必要な権限のみユーザーにリクエストする
Future<bool> ensureMediaPermissions({
  required bool needsCamera,
  required bool needsMicrophone,
}) async {
  // 配信内容に応じて要求する権限を組み立てる
  final permissions = <Permission>[
    if (needsCamera) Permission.camera,
    if (needsMicrophone) Permission.microphone,
  ];
  // 要求する権限がなければそのまま許可扱い
  if (permissions.isEmpty) {
    return true;
  }
  // ダイアログを表示し、すべての権限が許可されたかを返す
  final statuses = await permissions.request();
  return statuses.values.every((status) => status.isGranted);
}

接続前に次のように呼び出します。

// カメラとマイクの権限をリクエストする
final granted = await ensureMediaPermissions(
  needsCamera: true,
  needsMicrophone: true,
);
// いずれかの権限が拒否された場合は接続せずに終了する
if (!granted) {
  return;
}
// 権限が揃ってからメディアを取得する
final stream = await MediaDevices.getUserMedia(
  const GetUserMediaOptions(audio: true, video: true),
);
await client.connect(stream);

MethodChannel とネイティブ実装を組み合わせて権限を扱う場合は、 sora_sdk/example/android/app/src/main/kotlin/com/example/devtools/MainActivity.ktsora_sdk/devtools/lib/main.dart_ensureMediaPermissions を参考にしてください。

映像レンダリング

Android でも映像レンダリングは Flutter の Texture を使います。取得元はローカル映像とリモート映像の 2 種類があり、それぞれ別の経路で textureId を受け取ります。

ローカル映像のレンダリング

ローカル映像は、 MediaDevices.getUserMedia() で取得した LocalVideoTrack から textureId を取り出して Texture に渡すことでレンダリングできます。接続前のカメラプレビューもこの方法で行えます。

// カメラ・マイクから LocalMediaStream を取得する
final stream = await MediaDevices.getUserMedia(
  const GetUserMediaOptions(audio: true, video: true),
);

// 映像トラックから textureId を取得する
final videoTrack = stream.getVideoTracks().first;
final localTextureId = await videoTrack.textureId;
// 取得した textureId を Flutter の Texture に渡してレンダリングする
Texture(textureId: localTextureId)

接続は任意のタイミングで行います。

// Sora に接続して配信を開始する
await client.connect(stream);

リモート映像のレンダリング

接続相手から受信した映像は client.eventsSoraTrackEvent で通知されます。 track.kind'video' のとき、 track.textureIdTexture に渡してレンダリングします。

// 接続相手の映像トラック追加イベントを受け取る
client.events.listen((event) {
  // 映像トラックのときだけ textureId を保持する
  if (event case SoraTrackEvent(:final track) when track.kind == 'video') {
    setState(() {
      remoteTextureId = track.textureId;
    });
  }
});
// 受け取った textureId を Flutter の Texture に渡してレンダリングする
Texture(textureId: remoteTextureId!)

リリースビルドの注意

Android では、リリースビルド時に縮小や難読化の影響でネイティブ初期化に必要なクラスが削除されると動作できません。

この SDK では利用アプリ向けの配布設定が入っていますが、ビルド周りを独自に調整する場合は注意してください。

ビルド時の注意

  • 動作確認の開始時は flutter build apk --debugflutter run を使うと切り分けしやすくなります
  • リリースビルドでは縮小や難読化の影響を受けるため、 release でも必ず起動確認してください
  • R8 や ProGuard の設定を独自に上書きする場合は、SDK が前提にしている keep 設定を壊さないように注意してください
  • JNI 初期化に必要なクラスが削除または難読化されると、起動直後にクラッシュする可能性があります
  • NDK や Gradle 周りを大きく変更する場合は、ネイティブライブラリの読み込み条件が変わらないか確認してください

実装上のポイント

  • WebRTC のコア処理は Dart と dart:ffi で動作します
  • プラットフォーム側は主にカメラキャプチャと映像レンダリングを担当します
  • 受信系でも送信系でも映像レンダリングの基本は Texture ベースです

補足