

# IVS Android Broadcast SDK での配信とサブスクライブ \$1 Real-Time Streaming
<a name="android-publish-subscribe"></a>

このドキュメントでは、IVS Real-Time Streaming Android Broadcast SDK を使用してステージに配信とサブスクライブを行うためのステップについて説明します。

## 概念
<a name="android-publish-subscribe-concepts"></a>

リアルタイム機能には、[ステージ](#android-publish-subscribe-concepts-stage)、[ストラテジー](#android-publish-subscribe-concepts-strategy)、[レンダラー](#android-publish-subscribe-concepts-renderer)という 3 つのコアコンセプトがあります。設計目標は、実際に動作する製品を構築するのに必要となるクライアント側ロジックの量を最小限に抑えることです。

### ステージ
<a name="android-publish-subscribe-concepts-stage"></a>

`Stage` クラスは、ホストアプリケーションと SDK 間の主要な相互作用のポイントです。これはステージそのものを表し、ステージへの参加とステージからの退出に使用されます。ステージの作成と参加には、コントロールプレーンからの有効で有効期限内のトークン文字列 (`token` として表示) が必要です。ステージへの参加と退出は簡単です。

```
Stage stage = new Stage(context, token, strategy);

try {
	stage.join();
} catch (BroadcastException exception) {
	// handle join exception
}

stage.leave();
```

また、`Stage` クラスには `StageRenderer` をアタッチすることもできます。

```
stage.addRenderer(renderer); // multiple renderers can be added
```

### 方針
<a name="android-publish-subscribe-concepts-strategy"></a>

`Stage.Strategy` インターフェースは、ホストアプリケーションがステージの望ましい状態を SDK に伝える方法を提供します。`shouldSubscribeToParticipant`、`shouldPublishFromParticipant`、`stageStreamsToPublishForParticipant` の 3 つの関数を実装する必要があります。以下で、すべて説明します。

#### 参加者へのサブスクライブ
<a name="android-publish-subscribe-concepts-strategy-participants"></a>

```
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
```

リモート参加者がステージに参加すると、SDK はその参加者に対して希望するサブスクリプションの状態についてホストアプリケーションに問い合わせます。使用できるオプションは `NONE`、`AUDIO_ONLY`、および `AUDIO_VIDEO` です。この関数の値を返す場合、ホストアプリケーションは配信の状態、現在のサブスクリプションの状態、またはステージ接続の状態を考慮する必要はありません。`AUDIO_VIDEO` が返された場合、SDK はリモート参加者が配信するまで待ってからサブスクライブし、プロセス全体でレンダラーを通じてホストアプリケーションを更新します。

次に示すのは実装の例です。

```
@Override
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return Stage.SubscribeType.AUDIO_VIDEO;
}
```

これは、ビデオチャットアプリケーションなど、すべての参加者が互いに常に可視化されているホストアプリケーション向けのの完全な実装です。

より高度な実装も可能です。`ParticipantInfo` の `userInfo` プロパティを使用して、サーバーが提供する属性に基づいて、参加者に対して選択的にサブスクライブできます。

```
@Override
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	switch(participantInfo.userInfo.get(“role”)) {
		case “moderator”:
			return Stage.SubscribeType.NONE;
		case “guest”:
			return Stage.SubscribeType.AUDIO_VIDEO;
		default:
			return Stage.SubscribeType.NONE;
	}
}
```

これを使用すると、モデレーターは、自身は視聴の対象とならずに、すべてのゲストを監視できるステージを作ることができます。ホストアプリケーションでは、追加のビジネスロジックを使用して、モデレーター同士は見えるようにしつつ、ゲストには見えないようにすることができます。

#### 参加者へのサブスクライブの設定
<a name="android-publish-subscribe-concepts-strategy-participants-config"></a>

```
SubscribeConfiguration subscribeConfigurationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
```

リモート参加者がサブスクライブしている場合 (「[参加者へのサブスクライブ](#android-publish-subscribe-concepts-strategy-participants)」を参照)、SDK はホストアプリケーションにその参加者のカスタムサブスクライブ設定についてクエリします。この設定はオプションであり、ホストアプリケーションがサブスクライバーの動作の特定の側面を制御できるようにします。設定できる内容の詳細については、SDK リファレンスドキュメントの「[SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration)」を参照してください。

次に示すのは実装の例です。

```
@Override
public SubscribeConfiguration subscribeConfigrationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
    SubscribeConfiguration config = new SubscribeConfiguration();

    config.jitterBuffer.setMinDelay(JitterBufferConfiguration.JitterBufferDelay.MEDIUM());

    return config;
}
```

この実装では、サブスクライブしたすべての参加者のジッターバッファ最小遅延を `MEDIUM` のプリセットに更新します。

`shouldSubscribeToParticipant` を使用した、より高度な実装も可能です。指定された `ParticipantInfo` を使用して、特定の参加者のサブスクライブ設定を選択的に更新できます。

デフォルトの動作を使用することをお勧めします。カスタム設定は、特定の動作を変更したい場合にのみ指定します。

#### 配信
<a name="android-publish-subscribe-concepts-strategy-publishing"></a>

```
boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
```

ステージに接続すると、SDK はホストアプリケーションにクエリを実行し、特定の参加者を配信とすべきかどうかを確認します。これは、提供されたトークンに基づいて配信する権限を持つローカル参加者においてのみ呼び出されます。

次に示すのは実装の例です。

```
@Override
boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return true;
}
```

これは、ユーザーは常に配信状態としたい標準的なビデオチャットアプリケーション用です。オーディオとビデオをミュートまたはミュート解除して、すぐに不可視または可視にできます。(配信/配信停止も使用できますが、この方法では大幅に遅くなります。可視性を頻繁に変更したいユースケースには、ミュート/ミュート解除が適しています。)

#### 配信するストリームの選択
<a name="android-publish-subscribe-concepts-strategy-streams"></a>

```
@Override
List<LocalStageStream> stageStreamsToPublishForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
}
```

配信時には、これを使用して配信するオーディオストリームとビデオストリームが決定されます。これについては、後ほど「[メディアストリームの配信](#android-publish-subscribe-publish-stream)」で詳しく説明します。

#### ストラテジーの更新
<a name="android-publish-subscribe-concepts-strategy-updates"></a>

このストラテジーは動的であることを意図しており、上記の関数のいずれかから返される値はいつでも変更できます。たとえば、エンドユーザーがボタンをタップするまでホストアプリケーションが配信したくない場合、`shouldPublishFromParticipant` (`hasUserTappedPublishButton` など) から変数を返すことができます。その変数がエンドユーザーの相互作用に基づいて変更されたら、`stage.refreshStrategy()` を呼び出して、変更されたもののみを適用して、最新の値のストラテジーを照会する必要があることを SDK に通知します。SDK は、`shouldPublishFromParticipant` 値が変更されたことを検出すると、配信プロセスを開始します。SDK クエリとすべての関数が以前と同じ値を返す場合、`refreshStrategy` 呼び出しはステージに変更を加えません。

`shouldSubscribeToParticipant` の戻り値が `AUDIO_VIDEO` から `AUDIO_ONLY` に変更され、以前にビデオストリームが存在していた場合は、戻り値が変更されたすべての参加者のビデオストリームが削除されます。

通常、ホストアプリケーションは、適切に管理するために必要なすべての状態について考慮する必要はありません。ステージは以前のストラテジーと現在のストラテジーの違いを最も効率的に適用するストラテジーを使用します。このため、`stage.refreshStrategy()` の呼び出しはストラテジーが変わらない限り何もしないため、低コストなオペレーションとみなすことができます。

### レンダラー
<a name="android-publish-subscribe-concepts-renderer"></a>

`StageRenderer` インターフェースはステージの状態をホストアプリケーションに伝えます。ホストアプリケーションの UI の更新は、通常、レンダラーが提供するイベントだけで行うことができます。次の関数では、以下のような結果が生成されます。

```
void onParticipantJoined(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);

void onParticipantLeft(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);

void onParticipantPublishStateChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull Stage.PublishState publishState);

void onParticipantSubscribeStateChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull Stage.SubscribeState subscribeState);

void onStreamsAdded(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams);

void onStreamsRemoved(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams);

void onStreamsMutedChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams);

void onError(@NonNull BroadcastException exception);

void onConnectionStateChanged(@NonNull Stage stage, @NonNull Stage.ConnectionState state, @Nullable BroadcastException exception);
                
void onStreamAdaptionChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream, boolean adaption);

void onStreamLayersChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream, @NonNull List<RemoteStageStream.Layer> layers);

void onStreamLayerSelected(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream, @Nullable RemoteStageStream.Layer layer, @NonNull RemoteStageStream.LayerSelectedReason reason);
```

これらのメソッドのほとんどには、対応する `Stage` および `ParticipantInfo` が用意されています。

レンダラーから提供された情報がストラテジーの戻り値に影響することは想定されていません。たとえば、`shouldSubscribeToParticipant` の戻り値は、`onParticipantPublishStateChanged` が呼び出されても変化しない想定です。ホストアプリケーションが特定の参加者をサブスクライブする場合は、その参加者の配信状態に関係なく、目的のサブスクリプションタイプを返す必要があります。SDK は、ステージの状態に基づいて、望ましいストラテジーの状態が適切なタイミングで実行されるようにする役目を担います。

`StageRenderer` は次のステージクラスにアタッチできます。

```
stage.addRenderer(renderer); // multiple renderers can be added
```

配信参加者のみが `onParticipantJoined` をトリガーし、参加者が配信を停止するか、ステージ セッションを終了すると、`onParticipantLeft` がトリガーされることに注意してください。

## メディアストリームを配信する
<a name="android-publish-subscribe-publish-stream"></a>

内蔵マイクやカメラなどのローカルデバイスは、`DeviceDiscovery` を介して検出されます。以下は、前面カメラとデフォルトのマイクを選択し、それらを `LocalStageStreams` として SDK が配信できるように返す例です。

```
DeviceDiscovery deviceDiscovery = new DeviceDiscovery(context);

List<Device> devices = deviceDiscovery.listLocalDevices();
List<LocalStageStream> publishStreams = new ArrayList<LocalStageStream>();

Device frontCamera = null;
Device microphone = null;

// Create streams using the front camera, first microphone
for (Device device : devices) {
	Device.Descriptor descriptor = device.getDescriptor();
	if (!frontCamera && descriptor.type == Device.Descriptor.DeviceType.Camera && descriptor.position = Device.Descriptor.Position.FRONT) {
		front Camera = device;
	}
	if (!microphone && descriptor.type == Device.Descriptor.DeviceType.Microphone) {
		microphone = device;
	}
}

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera);
AudioLocalStageStream microphoneStream = new AudioLocalStageStream(microphoneDevice);

publishStreams.add(cameraStream);
publishStreams.add(microphoneStream);

// Provide the streams in Stage.Strategy
@Override
@NonNull List<LocalStageStream> stageStreamsToPublishForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return publishStreams;
}
```

## 参加者を表示、削除する
<a name="android-publish-subscribe-participants"></a>

サブスクライブが完了すると、レンダラーの `StageStream` 関数を介して `onStreamsAdded` オブジェクトの配列を受け取ります。`ImageStageStream` からのプレビューは次の場所から取得できます。

```
ImagePreviewView preview = ((ImageStageStream)stream).getPreview();

// Add the view to your view hierarchy
LinearLayout previewHolder = findViewById(R.id.previewHolder);
preview.setLayoutParams(new LinearLayout.LayoutParams(
		LinearLayout.LayoutParams.MATCH_PARENT,
		LinearLayout.LayoutParams.MATCH_PARENT));
previewHolder.addView(preview);
```

オーディオレベルの統計情報は、以下の `AudioStageStream` から取得できます。

```
((AudioStageStream)stream).setStatsCallback((peak, rms) -> {
	// handle statistics
});
```

参加者が配信を停止するか、サブスクライブを解除すると、削除されたストリームを使用して `onStreamsRemoved` 関数が呼び出されます。ホストアプリケーションは、これを通知として使用して、参加者のビデオストリームをビュー階層から削除する必要があります。

`onStreamsRemoved` は、以下を含む、ストリームが削除される可能性のあるすべてのシナリオで呼び出されます。
+ リモート参加者は配信を停止します。
+ ローカルデバイスがサブスクリプションを解除するか、サブスクリプションを `AUDIO_VIDEO` から `AUDIO_ONLY` に変更します。
+ リモート参加者がステージを退出します。
+ ローカルの参加者がステージを退出します。

`onStreamsRemoved` はすべてのシナリオで呼び出されるため、リモートまたはローカルの離脱操作中、 UI から参加者を削除するためのカスタムのビジネスロジックは必要ありません。

## メディアストリームをミュート、ミュート解除する
<a name="android-publish-subscribe-mute-streams"></a>

`LocalStageStream` オブジェクトには、ストリームをミュートするかどうかを制御する `setMuted` 関数があります。この関数は、`streamsToPublishForParticipant` ストラテジー関数から返される前または後にストリームで呼び出すことができます。

**重要**: `refreshStrategy` を呼び出した後に新しい `LocalStageStream` オブジェクトインスタンスが `streamsToPublishForParticipant` によって返された場合、新しいストリームオブジェクトのミュート状態がステージに適用されます。新しい `LocalStageStream` インスタンスを作成するときは、想定どおりのミュート状態を維持するように注意してください。

## リモート参加者のメディアミュート状態の監視
<a name="android-publish-subscribe-mute-state"></a>

参加者がビデオまたはオーディオストリームのミュート状態を変更すると、変更されたストリームのリストとともにレンダラー `onStreamMutedChanged` 関数が呼び出されます。`StageStream` に `getMuted` メソッドを使用して、適宜 UI を更新します。

```
@Override
void onStreamsMutedChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams) {
	for (StageStream stream : streams) {
		boolean muted = stream.getMuted();
		// handle UI changes
	}
}
```

## WebRTC 統計を取得する
<a name="android-publish-subscribe-webrtc-stats"></a>

配信ストリームまたはサブスクライブ中のストリームの最新の WebRTC 統計情報を取得するには、`StageStream` に `requestRTCStats` を使用してください。収集が完了すると、`StageStream` に設定できる `StageStream.Listener` から統計を受け取ります。

```
stream.requestRTCStats();

@Override
void onRTCStats(Map<String, Map<String, String>> statsMap) {
	for (Map.Entry<String, Map<String, string>> stat : statsMap.entrySet()) {
		for(Map.Entry<String, String> member : stat.getValue().entrySet()) {
			Log.i(TAG, stat.getKey() + “ has member “ + member.getKey() + “ with value “ + member.getValue());
		}
	}
}
```

## 参加者属性を取得
<a name="android-publish-subscribe-participant-attributes"></a>

`CreateParticipantToken` オペレーションリクエストで属性を指定した場合、`ParticipantInfo` プロパティに属性が表示されます。

```
@Override
void onParticipantJoined(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	for (Map.Entry<String, String> entry : participantInfo.userInfo.entrySet()) {
		Log.i(TAG, “attribute: “ + entry.getKey() + “ = “ + entry.getValue());
	}
}
```

## メッセージを埋め込む
<a name="android-publish-subscribe-embed-messages"></a>

ImageDevice で `embedMessage` メソッドを使用すると、配信中のビデオフレームにメタデータペイロードを直接挿入できます。これは、リアルタイムアプリケーションのフレーム同期型メッセージングを可能にします。メッセージの埋め込みを使用できるのは、リアルタイム配信(低レイテンシー配信ではない) の SDK を使用している場合のみです。

埋め込みメッセージは、ビデオフレーム内に直接埋め込まれ、パケット配信を保証しない UDP 経由で送信されるため、必ずしもサブスクライバーに届くとは限りません。送信中のパケット損失は、特にネットワーク状態が悪い場合において、メッセージの損失につながる可能性があります。この問題を軽減するため、`embedMessage` メソッドには `repeatCount` パラメータが含まれています。このパラメータは、連続する複数のフレーム全体でメッセージを複製することで、配信信頼性を向上させます。この機能を利用できるのはビデオストリームのみです。

### embedMessage の使用
<a name="android-embed-messages-using-embedmessage"></a>

配信元のクライアントは、ImageDevice で `embedMessage` メソッドを使用することでメッセージペイロードをビデオストリームに埋め込むことができます。ペイロードサイズは 0 KB より大きく 1 KB 未満のサイズにする必要があります。挿入される埋め込みメッセージの 1 秒あたりの数が 10 KB/秒を超えないようにする必要があります。

```
val surfaceSource: SurfaceSource = imageStream.device as SurfaceSource
val message = "hello world"
val messageBytes = message.toByteArray(StandardCharsets.UTF_8)

try {
    surfaceSource.embedMessage(messageBytes, 0)
} catch (e: BroadcastException) {
    Log.e("EmbedMessage", "Failed to embed message: ${e.message}")
}
```

### メッセージペイロードの反復
<a name="android-embed-messages-repeat-payloads"></a>

`repeatCount` を使用して複数のフレーム全体でメッセージを複製し、信頼性を向上させます。この値は 0 ～ 30 の範囲の値にする必要があります。受信クライアントには、メッセージを重複除外するロジックが必要です。

```
try {
    surfaceSource.embedMessage(messageBytes, 5)
    // repeatCount: 0-30, receiving clients should handle duplicates
} catch (e: BroadcastException) {
    Log.e("EmbedMessage", "Failed to embed message: ${e.message}")
}
```

### 埋め込みメッセージの読み取り
<a name="android-embed-messages-read-messages"></a>

受信ストリームから埋め込みメッセージを読み取る方法については、以下の「補足拡張情報 (SEI) を取得する」を参照してください。

## 補足拡張情報 (SEI、Supplemental Enhancement Information) を取得する
<a name="android-publish-subscribe-sei-attributes"></a>

補足拡張情報 (SEI) NAL ユニットは、フレーム整列メタデータを動画と一緒に保存するために使用されます。パブリッシャーの `ImageDevice` から送信される `ImageDeviceFrame` オブジェクトの `embeddedMessages` プロパティを調べることにより、サブスクライブしているクライアントは H.264 ビデオを配信しているパブリッシャーから SEI ペイロードを読み取ることができます。これを行うには、次の例で示すように、パブリッシャーの `ImageDevice` を取得し、`setOnFrameCallback` に提供されるコールバックを介して各フレームを確認します。

```
// in a StageRenderer’s onStreamsAdded function, after acquiring the new ImageStream

val imageDevice = imageStream.device as ImageDevice
imageDevice.setOnFrameCallback(object : ImageDevice.FrameCallback {
	override fun onFrame(frame: ImageDeviceFrame) {
    		for (message in frame.embeddedMessages) {
        		if (message is UserDataUnregisteredSeiMessage) {
            		val seiMessageBytes = message.data
            		val seiMessageUUID = message.uuid
           	 
            		// interpret the message's data based on the UUID
        		}
    		}
	}
})
```

## セッションをバックグラウンドで続行
<a name="android-publish-subscribe-background-session"></a>

アプリがバックグラウンドに入ると、配信を停止するか、他のリモート参加者の音声のみをサブスクライブすることができます。そのためには、`Strategy` 実装を更新して配信を停止し、`AUDIO_ONLY` (または `NONE`。該当する場合) をサブスクライブします。

```
// Local variables before going into the background
boolean shouldPublish = true;
Stage.SubscribeType subscribeType = Stage.SubscribeType.AUDIO_VIDEO;

// Stage.Strategy implementation
@Override
boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return shouldPublish;
}

@Override
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return subscribeType;
}

// In our Activity, modify desired publish/subscribe when we go to background, then call refreshStrategy to update the stage
@Override
void onStop() {
	super.onStop();
	shouldPublish = false;
	subscribeTpye = Stage.SubscribeType.AUDIO_ONLY;
	stage.refreshStrategy();
}
```

## サイマルキャストによるレイヤードエンコーディング
<a name="android-publish-subscribe-layered-encoding-simulcast"></a>

サイマルキャストによるレイヤードエンコーディングは、パブリッシャーが複数の異なるビデオの品質レイヤーを送信し、サブスクライバーがそれらのレイヤーを動的または手動で設定できるようにする IVS リアルタイムのストリーミング機能です。この機能は、「[ストリーミング最適化](real-time-streaming-optimization.md)」ドキュメントで詳しく説明されています。

### レイヤードエンコーディングの設定 (パブリッシャー）
<a name="android-layered-encoding-simulcast-configure-publisher"></a>

パブリッシャーとしてサイマルキャストによるレイヤードエンコーディングを有効にするには、インスタンス化時に `LocalStageStream` に次の設定を追加します。

```
// Enable Simulcast
StageVideoConfiguration config = new StageVideoConfiguration();
config.simulcast.setEnabled(true);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

ビデオ設定で設定した解像度に応じて、「*ストリーミングの最適化*」の「[デフォルトレイヤー、品質、フレームレート](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers)」セクションで定義されているように、設定された数のレイヤーがエンコードされて送信されます。

また、必要に応じて、サイマルキャスト設定内から個々のレイヤーを設定できます。

```
// Enable Simulcast
StageVideoConfiguration config = new StageVideoConfiguration();
config.simulcast.setEnabled(true);

List<StageVideoConfiguration.Simulcast.Layer> simulcastLayers = new ArrayList<>();
simulcastLayers.add(StagePresets.SimulcastLocalLayer.DEFAULT_720);
simulcastLayers.add(StagePresets.SimulcastLocalLayer.DEFAULT_180);

config.simulcast.setLayers(simulcastLayers);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

または、最大で 3 つのレイヤー用に独自のカスタムレイヤー設定を作成することもできます。空のアレイを指定するか、値を指定しない場合、上記のデフォルトが使用されます。レイヤーは、次の必須プロパティセッターで説明されています。
+ `setSize: Vec2;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: integer;`

プリセットから、個々のプロパティを上書きするか、まったく新しい設定を作成できます。

```
// Enable Simulcast
StageVideoConfiguration config = new StageVideoConfiguration();
config.simulcast.setEnabled(true);

List<StageVideoConfiguration.Simulcast.Layer> simulcastLayers = new ArrayList<>();

// Configure high quality layer with custom framerate
StageVideoConfiguration.Simulcast.Layer customHiLayer = StagePresets.SimulcastLocalLayer.DEFAULT_720;
customHiLayer.setTargetFramerate(15);

// Add layers to the list
simulcastLayers.add(customHiLayer);
simulcastLayers.add(StagePresets.SimulcastLocalLayer.DEFAULT_180);

config.simulcast.setLayers(simulcastLayers);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

個々のレイヤーを設定するときにトリガーできる最大値、制限、エラーについては、SDK リファレンスドキュメントを参照してください。

### レイヤードエンコーディングの設定 (サブスクライバー）
<a name="android-layered-encoding-simulcast-configure-subscriber"></a>

サブスクライバーとして、レイヤードエンコーディングを有効にするために必要なものはありません。パブリッシャーがサイマルキャストレイヤーを送信している場合、デフォルトでサーバーによってレイヤー間で動的に適応され、サブスクライバーのデバイスおよびネットワークの状態に基づいて最適な品質が選択されます。

あるいは、パブリッシャーが送信している明示的なレイヤーを選択するには、以下に説明するいくつかのオプションがあります。

### オプション 1: 初期レイヤー品質の選択
<a name="android-layered-encoding-simulcast-layer-quality-preference"></a>

`subscribeConfigurationForParticipant` 戦略を使用すると、サブスクライバーとして受信する初期レイヤーを選択できます。

```
@Override
public SubscribeConfiguration subscribeConfigrationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
    SubscribeConfiguration config = new SubscribeConfiguration();

    config.simulcast.setInitialLayerPreference(SubscribeSimulcastConfiguration.InitialLayerPreference.LOWEST_QUALITY);

    return config;
}
```

デフォルトでは、サブスクライバーは常に最初に最低品質のレイヤーが送信されます。これにより、徐々に最高品質のレイヤーにまで拡大します。エンドユーザーの帯域幅の消費量が最適化され、ビデオ再生に最適な時間が実現されるため、より貧弱なネットワーク上のユーザーに対して初期ビデオフリーズが軽減されます。

これらのオプションは `InitialLayerPreference` で利用できます。
+ `LOWEST_QUALITY` — サーバーは、最初に最低品質のビデオレイヤーを配信します。帯域幅の消費とメディアの時間が最適化されます。品質はビデオのサイズ、ビットレート、フレームレートの組み合わせとして定義されます。例えば、720p ビデオは 1080p ビデオよりも品質が低くなります。
+ `HIGHEST_QUALITY` — サーバーは、最初に最高品質のビデオレイヤーを配信します。品質が最適化されますが、メディアの時間が長くなる場合があります。品質はビデオのサイズ、ビットレート、フレームレートの組み合わせとして定義されます。例えば、1080p ビデオは 720p ビデオよりも高品質です。

**注:** 初期レイヤー設定 (`setInitialLayerPreference` の呼び出し) を反映させるには、再サブスクライブが必要です。これらの更新はアクティブなサブスクリプションに適用されないためです。

### オプション 2: ストリームに優先されるレイヤー
<a name="android-layered-encoding-simulcast-preferred-layer"></a>

`preferredLayerForStream` 戦略メソッドを使用すると、ストリームの開始後にレイヤーを選択できます。この戦略メソッドは参加者とストリーム情報を受け取るため、参加者ごとにレイヤーを選択できます。このメソッドは、ストリームのレイヤーが変化したとき、参加者の状態が変化したとき、またはホストアプリケーションが戦略を更新したときなど、特定のイベントに応じて SDK が呼び出します。

この戦略メソッドは `RemoteStageStream.Layer` オブジェクトを返します。これは次のいずれかになります。
+ `RemoteStageStream.getLayers` によって返されるレイヤーオブジェクトなど。
+ レイヤーを選択せず、動的適応が優先されることを示す null。

例えば、次の戦略ではユーザーが常に最低品質のビデオレイヤーを選択するようにします。

```
@Nullable
@Override
public RemoteStageStream.Layer preferredLayerForStream(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream) {
    return stream.getLowestQualityLayer();
}
```

レイヤーの選択をリセットして動的適応に戻るには、戦略で null または未定義を返します。この例では、`appState` はホストアプリケーションの状態を表すプレースホルダー変数です。

```
@Nullable
@Override
public RemoteStageStream.Layer preferredLayerForStream(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream) {
    if (appState.isAutoMode) {
        return null;
    } else {
        return appState.layerChoice;
    }
}
```

### オプション 3: RemoteStageStream レイヤーヘルパー
<a name="android-layered-encoding-simulcast-remotestagestream-helpers"></a>

`RemoteStageStream` には、レイヤーの選択について決定し、対応する選択をエンドユーザーに表示するために使用できるいくつかのヘルパーがあります。
+ **レイヤーイベント** — `StageRenderer` に加え、`RemoteStageStream.Listener` にはレイヤーおよびサイマルキャストの適応変更を伝えるイベントがあります。
  + `void onAdaptionChanged(boolean adaption)`
  + `void onLayersChanged(@NonNull List<Layer> layers)`
  + `void onLayerSelected(@Nullable Layer layer, @NonNull LayerSelectedReason reason)`
+ **レイヤーメソッド** — `RemoteStageStream` には、ストリームおよび提示されるレイヤーに関する情報を取得するために使用できるいくつかのヘルパーメソッドがあります。これらのメソッドは、`preferredLayerForStream` 戦略で提供されるリモートストリームに加え、`StageRenderer.onStreamsAdded` を介して配信されるリモートストリームで利用できます。
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`
  + `stream.getLayersWithConstraints`

詳細については、「[SDK リファレンスドキュメント](https://aws.github.io/amazon-ivs-broadcast-docs/latest/android/)」の「`RemoteStageStream`」クラスを参照してください。`LayerSelected` の理由として `UNAVAILABLE` が返された場合、これはリクエストされたレイヤーが選択できなかったことを示します。代わりにベストエフォートの選択が行われ、ストリームの安定性を維持するために、通常は低品質のレイヤーが選択されます。

## ビデオ設定の制限
<a name="android-publish-subscribe-video-limits"></a>

SDK は、`StageVideoConfiguration.setSize(BroadcastConfiguration.Vec2 size)` を使用したポートレートモードまたはランドスケープモードの強制をサポートしていません。縦の向きでは、小さい方の寸法が幅として使用され、横の向きでは高さとして使用されます。つまり、次の 2 つの `setSize` への呼び出しが動画の設定に同じ効果をもたらすということです。

```
StageVideo Configuration config = new StageVideo Configuration();

config.setSize(BroadcastConfiguration.Vec2(720f, 1280f);
config.setSize(BroadcastConfiguration.Vec2(1280f, 720f);
```

## ネットワーク問題の処理
<a name="android-publish-subscribe-network-issues"></a>

ローカルデバイスのネットワーク接続が失われると、SDK はユーザーアクションなしで内部で再接続を試みます。場合によっては、SDK が正常に動作せず、ユーザーアクションが必要なる可能性があります。ネットワーク接続の切断に関連する主なエラーは 2 つあります。
+ エラーコード 1400、メッセージ:「不明なネットワークエラーにより PeerConnection が失われました」
+ エラーコード 1300、メッセージ:「再試行の試行回数制限に達しました」

最初のエラーを受け取って 2 番目のエラーを受け取らない場合、SDK はまだステージに接続されており、自動的に接続を再確立しようとします。安全対策として、ストラテジーメソッドの戻り値を変更せずに `refreshStrategy` を呼び出すと、手動で再接続を試みることができます。

2 番目のエラーを受け取った場合、SDK の再接続は失敗し、ローカルデバイスはステージに接続されていません。この場合は、ネットワーク接続が再確立された後に `join` を呼び出してステージに再び参加してみてください。

一般に、ステージに正常に参加した後にエラーが発生した場合は、SDK が接続を再確立できなかったことを示します。新しい `Stage` オブジェクトを作成し、ネットワークの状態が向上したら参加してみます。

## Bluetooth マイクの使用
<a name="android-publish-subscribe-bluetooth-microphones"></a>

Bluetooth マイクデバイスを使用して配信するには、Bluetooth SCO 接続を開始する必要があります。

```
Bluetooth.startBluetoothSco(context);
// Now bluetooth microphones can be used
…
// Must also stop bluetooth SCO
Bluetooth.stopBluetoothSco(context);
```