

# IVS 廣播 SDK：Android 指南 \$1 即時串流
<a name="broadcast-android"></a>

IVS 即時串流 Android 廣播 SDK 讓參與者能夠在 Android 上傳送和接收影片。

`com.amazonaws.ivs.broadcast` 套件會執行本文件中所述的介面。SDK 支援下列操作：
+ 加入階段 
+ 將媒體發布給階段中的其他參與者
+ 訂閱階段中其他參與者的媒體
+ 管理和監控發布到階段的影片和音訊
+ 取得每個對等連線的 WebRTC 統計資料
+ IVS 低延遲串流 Android 廣播 SDK 的所有操作

**最新版 Android 廣播 SDK：**1.40.0 ([版本備註](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-android-rt)) 

**參考文件：**如需有關 Amazon IVS Android 廣播 SDK 中最重要方法的資訊，請參閱參考文件，網址為 [https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/)。

**範本程式碼：**請參閱 GitHub 上的 Android 範本儲存庫：[https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples)。

**平台要求：**Android 9.0\$1

# 開始使用 IVS Android 廣播 SDK \$1 即時串流
<a name="broadcast-android-getting-started"></a>

本文件將帶您了解開始使用 IVS 即時串流 Android 廣播 SDK 的相關步驟。

## 安裝程式庫
<a name="broadcast-android-install"></a>

有數種方法可將 Amazon IVS Android 廣播程式庫新增至 Android 開發環境：直接使用 Gradle、使用 Gradle 版本目錄，或手動安裝 SDK。

**直接使用 Gradle**：如下所示，將程式庫新增至模組的 `build.gradle` 檔案 (適用於最新版 IVS 廣播 SDK)：

```
repositories {
    mavenCentral()
}
 
dependencies {
     implementation 'com.amazonaws:ivs-broadcast:1.40.0:stages@aar'
}
```

**使用 Gradle 版本目錄**：首先將此目錄加入模組的 `build.gradle` 檔案：

```
implementation(libs.ivs){
   artifact {
      classifier = "stages"
      type = "aar"
   }
}
```

然後在 `libs.version.toml` 檔案中加入下列項目 (適用於最新版 IVS 廣播 SDK)：

```
[versions]
ivs="1.40.0"

[libraries]
ivs = {module = "com.amazonaws:ivs-broadcast", version.ref = "ivs"}
```

**手動安裝 SDK**：請從此位置下載最新版本：

[https://search.maven.org/artifact/com.amazonaws/ivs-broadcast](https://search.maven.org/artifact/com.amazonaws/ivs-broadcast)

請務必下載附加 `-stages` 的 `aar`。

**也允許 SDK 控制擴音電話**：無論您選擇哪種安裝方法，也請將下列許可新增至資訊清單，以允許 SDK 啟用和停用擴音電話：

```
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
```

## 使用包含偵錯符號的 SDK
<a name="broadcast-android-using-debug-symbols-rt"></a>

我們也發布了包含偵錯符號的廣播 SDK Android 版本。如果在 IVS 廣播 SDK 中遇到當機 (即 `libbroadcastcore.so`)，您可以使用此版本來改善 Firebase Crashlytics 中偵錯報告 (堆疊追蹤) 的品質。向 IVS SDK 團隊報告這些當機案例，可以提升堆疊追蹤的品質，協助更輕鬆地修正問題。

若要使用此版本的 SDK，請將下列項目放入 Gradle 建置檔案：

```
implementation "com.amazonaws:ivs-broadcast:$version:stages-unstripped@aar"
```

使用上一行，而非此行：

```
implementation "com.amazonaws:ivs-broadcast:$version:stages@aar"
```

### 將符號上傳至 Firebase Crashlytics
<a name="android-debug-symbols-rt-firebase-crashlytics"></a>

確保已針對 Firebase Crashlytics 設定 Gradle 建置檔案。遵循此處的 Google 指示：

[https://firebase.google.com/docs/crashlytics/ndk-reports](https://firebase.google.com/docs/crashlytics/ndk-reports)

請務必加入 `com.google.firebase:firebase-crashlytics-ndk` 作為相依性。

建置要發布的應用程式時，Firebase Crashlytics 外掛程式應自動上傳符號。若要手動上傳符號，請執行下列任一動作：

```
gradle uploadCrashlyticsSymbolFileRelease
```

```
./gradlew uploadCrashlyticsSymbolFileRelease
```

(自動和手動上傳符號兩次，也不會造成損害。)

### 防止發布 .apk 變大
<a name="android-debug-symbols-rt-sizing-apk"></a>

在封裝發布 `.apk` 檔案之前，Android Gradle 外掛程式會自動嘗試從共用程式庫 (包括 IVS 廣播 SDK 的 `libbroadcastcore.so` 程式庫) 去除偵錯資訊。不過，有時會沒有這樣做。因此，`.apk` 檔案可能會變大，而且您可能會收到來自 Android Gradle 外掛程式的警告訊息，表示無法去除偵錯符號，並且會依原樣封裝 `.so` 檔案。如果發生這種情況，請執行下列操作：
+ 安裝 Android NDK。任何最新的版本都可以。
+ 將 `ndkVersion <your_installed_ndk_version_number>` 新增至應用程式的 `build.gradle` 檔案。即使應用程式本身不包含原生程式碼，也請執行此操作。

如需詳細資訊，請參閱[問題報告](https://issuetracker.google.com/issues/353554169)。

## 請求權限
<a name="broadcast-android-permissions"></a>

您的應用程式必須請求許可才能存取使用者的攝影機和麥克風。(這不限於 Amazon IVS；任何需要存取攝影機和麥克風的應用程式都必須如此)。

在這裡，我們檢查使用者是否已經授予權限，如果沒有則提出請求：

```
final String[] requiredPermissions =
         { Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO };

for (String permission : requiredPermissions) {
    if (ContextCompat.checkSelfPermission(this, permission) 
                != PackageManager.PERMISSION_GRANTED) {
        // If any permissions are missing we want to just request them all.
        ActivityCompat.requestPermissions(this, requiredPermissions, 0x100);
        break;
    }
}
```

在這裡，我們取得使用者的回應：

```
@Override
public void onRequestPermissionsResult(int requestCode, 
                                      @NonNull String[] permissions,
                                      @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode,
               permissions, grantResults);
    if (requestCode == 0x100) {
        for (int result : grantResults) {
            if (result == PackageManager.PERMISSION_DENIED) {
                return;
            }
        }
        setupBroadcastSession();
    }
}
```

# 使用 IVS Android 廣播 SDK 發布和訂閱 \$1 即時串流
<a name="android-publish-subscribe"></a>

本文件將帶您了解開始使用 IVS 即時串流 Android 廣播 SDK 發布和訂閱階段的相關步驟。

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

以下是三個以即時功能為基礎的核心概念：[階段](#android-publish-subscribe-concepts-stage)、[策略](#android-publish-subscribe-concepts-strategy)和[轉譯器](#android-publish-subscribe-concepts-renderer)。設計目標是盡可能減少打造工作產品所需的用戶端邏輯數量。

### 階段
<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`。以下將討論所有內容。

#### 訂閱參與者
<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`。

轉譯器提供的資訊應該不會對策略的傳回值造成影響。例如，呼叫 `onParticipantPublishStateChanged` 時，`shouldSubscribeToParticipant` 的傳回值應該不會變更。若主持人應用程式想要訂閱特定參與者，則無論該參與者的發布狀態為何，它都應傳回所需的訂閱類型。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>

訂閱完成後，您會透過轉譯器的 `onStreamsAdded` 函數收到 `StageStream` 物件陣列。您可以透過 `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` 後由 `streamsToPublishForParticipant` 傳回新的 `LocalStageStream` 物件執行個體，則新串流物件的靜音狀態會套用至階段。建立新 `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` 方法，將訊息承載嵌入至其影片串流。承載大小必須大於 0KB，小於 1KB。每秒插入的嵌入訊息數量不得超過每秒 10KB。

```
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)
<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();
}
```

## Simulcast 分層編碼
<a name="android-publish-subscribe-layered-encoding-simulcast"></a>

Simulcast 分層編碼是一種 IVS 即時串流功能，可讓發布者傳送多個不同品質的影片層，也可讓訂閱用戶動態或手動設定這些層。[串流最佳化](real-time-streaming-optimization.md)文件中詳細介紹了該功能。

### 設定分層編碼 (發布者)
<a name="android-layered-encoding-simulcast-configure-publisher"></a>

若要以發布者身分啟用 Simulcast 分層編碼，請在執行個體化時將下列組態新增至 `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)小節中的定義，來編碼和傳送一定數量的層。

此外，您也可選擇從 Simulcast 組態內設定個別層：

```
// 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
```

或者，您可以建立自訂層組態，最多三層。如果您提供空陣列或未提供任何值，則會使用上述預設值。透過下列必要屬性設定來描述層：
+ `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>

訂閱用戶無需執行任何操作來啟用分層編碼。如果發布者正在傳送 Simulcast 層，則伺服器預設會在各層之間動態調整，根據訂閱用戶的裝置和網路狀況選擇品質最佳的層。

或者，若要挑選發布者正在傳送的明確層，有幾個選項可供選擇，如下所述。

### 選項 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 或 undefined。在此範例中，`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` 還有可傳達層和 Simulcast 調整變更的事件：
  + `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)` 強制執行縱向模式或橫向模式。在縱向模式中，較小的空間為寬度；在橫向模式中，較小的空間則為高度。這表示以下兩次 `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 在部分情況下會執行失敗，這時就需要使用者採取動作。以下是兩個有關網路連線中斷的主要錯誤：
+ 錯誤代碼 1400，訊息：「PeerConnection 由於未知網路錯誤而中斷」
+ 錯誤代碼 1300，訊息：「已用盡重試嘗試次數」

若收到第一種錯誤，但未收到第二種錯誤，SDK 仍會連線至階段，並嘗試自動重新建立連線。保險起見，您可以在不對策略方法的傳回值進行任何更改的情況下呼叫 `refreshStrategy`，以觸發手動重新連線嘗試。

若收到第二種錯誤，則表示 SDK 的重新連線嘗試失敗，且本機裝置已中斷與階段的連線。在此情況下，請嘗試在重新建立網路連線後，呼叫 `join` 來重新加入階段。

一般而言，若成功加入階段後遇到錯誤，則表示 SDK 並沒有成功重新建立連線。建立新的 `Stage` 物件，並在網路情況改善時嘗試加入。

## 使用藍牙麥克風
<a name="android-publish-subscribe-bluetooth-microphones"></a>

若要使用藍牙麥克風裝置發布，您必須啟動藍牙 SCO 連線：

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

# IVS Android 廣播 SDK 中的已知問題和解決方法 \$1 即時串流
<a name="broadcast-android-known-issues"></a>

本文件列出您在使用 Amazon IVS 即時串流功能 Android 廣播 SDK 時可能遇到的已知問題，並建議潛在的解決方法。
+ Android 裝置進入睡眠模式後被喚醒時，預覽畫面可能會卡住。

  **解決方法：** 建立並使用新的 `Stage`。
+ 當參與者以其他參與者正在使用的權杖加入時，第一個連線會中斷連線，且不會顯示具體的錯誤。

  **解決方法**：無。
+ 發布者處於發布狀態但訂閱者收到的發布狀態為 `inactive` 的情況很少見。

  **解決方法：**嘗試離開工作階段後再重新加入。若問題仍無法解決，請為發布者建立新權杖。
+ 極少數情況下，階段工作階段期間可能會斷斷續續出現音訊失真的問題 (通常是呼叫時間較長時會出現)。

  **解決方法：**音訊失真的參與者可以離開工作階段後再重新加入，或取消發布其音訊後再重新發布，以便修正此問題。
+ 發布至階段時，系統不支援外接麥克風。

  **解決方法：** 發布至階段時，請勿使用透過 USB 連接的外接麥克風。
+ 系統不支援使用 `createSystemCaptureSources` 發布至螢幕共用的階段。

  **解決方法：** 使用自訂影像輸入來源和自訂音訊輸入來源手動管理系統擷取。
+ 從父項中移除 `ImagePreviewView`(例如在父項呼叫 `removeView()`) 時，系統會立即釋出 `ImagePreviewView`。將 `ImagePreviewView` 加至其他父項視圖時，它不會顯示任何畫面。

  **解決方法：** 使用 `getPreview` 要求再次預覽。
+ 使用作業系統為 Android 12 的 Samsung Galaxy S22/\$1 加入階段時，您可能會遭遇 1401 錯誤，且本地裝置可能會無法加入階段，或加入後沒有音訊。

  **解決方法：** 升級至 Android 13 作業系統。
+ 使用作業系統為 Android 13 的 Nokia X20 加入階段時，攝影機可能會無法打開，並出現異常狀況。

  **解決方法**：無。
+ 具有 MediaTek Helio 晶片組的裝置可能會無法正確轉譯遠端參與者的影片。

  **解決方法**：無。
+ 在少數裝置上，裝置作業系統可能會選擇與 SDK 選取的麥克風不同的麥克風。這是因為 Amazon IVS 廣播 SDK 無法控制 `VOICE_COMMUNICATION` 音訊路由的定義方式，因為它會根據不同的裝置製造商而有所不同。

  **解決方法**：無。
+ 某些 Android 影片編碼器無法設定為小於 176x176 的影片大小。設定較小的大小會導致錯誤且無法進行串流。

  **因應措施：**請勿將影片大小設定為小於 176x176。

# IVS Android 廣播 SDK 中的錯誤處理 \$1 即時串流
<a name="broadcast-android-error-handling"></a>

本節概述錯誤情況、IVS 即時串流 Android 廣播 SDK 如何向應用程式報告錯誤，以及應用程式在遇到這些錯誤時應執行的動作。

## 嚴重錯誤與非嚴重錯誤
<a name="broadcast-android-fatal-vs-nonfatal-errors"></a>

錯誤物件的 `BroadcastException` 布林值欄位為「is fatal」。

一般而言，嚴重錯誤與 Stages 伺服器的連線有關 (無法建立連線或失去連線且無法復原)。在使用新的權杖或是裝置連線恢復時，應用程式應重新建立階段並重新加入。

非嚴重錯誤通常與發布/訂閱狀態有關，且是由 SDK 處理重試發布/訂閱的作業。

您可以檢查以下屬性：

```
try {
  stage.join(...)
} catch (e: BroadcastException) {
  If (e.isFatal) { 
    // the error is fatal
```

## 加入錯誤
<a name="broadcast-android-stage-join-errors"></a>

### 權杖格式錯誤
<a name="broadcast-android-stage-join-errors-malformed-token"></a>

當階段權杖格式不正確時，就會發生此錯誤。

SDK 會從 `stage.join` 呼叫擲出一個 Java 例外狀況，包含 error code = 1000 及 fatal = true。

**動作**：建立一個有效權杖，然後重試加入。

### 權杖過期
<a name="broadcast-android-stage-join-errors-expired-token"></a>

當階段權杖過期時，就會發生此錯誤。

SDK 會從 `stage.join` 呼叫擲出一個 Java 例外狀況，包含 error code = 1001 及 fatal = true。

**動作**：建立一個新權杖，然後重試加入。

### 權杖無效或撤銷
<a name="broadcast-android-stage-join-errors-invalid-token"></a>

當階段權杖格式正確但遭 Stages 伺服器拒絕時，就會發生此錯誤。此錯誤是透過應用程式提供的階段轉譯器以非同步方式報告。

SDK 會以例外狀況呼叫 `onConnectionStateChanged`，包含 error code = 1026 及 fatal = true。

**動作**：建立一個有效權杖，然後重試加入。

### 初始加入時出現網路錯誤
<a name="broadcast-android-stage-join-errors-network-initial-join"></a>

當 SDK 無法聯絡 Stages 伺服器以建立連線時，就會發生此錯誤。此錯誤是透過應用程式提供的階段轉譯器以非同步方式報告。

SDK 會以例外狀況呼叫 `onConnectionStateChanged`，包含 error code = 1300 及 fatal = true。

**動作**：等待裝置的連線復原，然後重試加入。

### 已加入時出現網路錯誤
<a name="broadcast-android-stage-join-errors-network-already-joined"></a>

如果裝置的網路連線中斷，SDK 可能會失去與 Stage 伺服器的連線。此錯誤是透過應用程式提供的階段轉譯器以非同步方式報告。

SDK 會以例外狀況呼叫 `onConnectionStateChanged`，包含 error code = 1300 及 fatal = true。

**動作**：等待裝置的連線復原，然後重試加入。

## 發布/訂閱錯誤
<a name="broadcast-android-publish-subscribe-errors"></a>

### 初始
<a name="broadcast-android-publish-subscribe-errors-initial"></a>

錯誤包含以下幾種：
+ MultihostSessionOfferCreationFailPublish (1020)
+ MultihostSessionOfferCreationFailSubscribe (1021)
+ MultihostSessionNoIceCandidates (1022)
+ MultihostSessionStageAtCapacity (1024)
+ SignallingSessionCannotRead (1201)
+ SignallingSessionCannotSend (1202)
+ SignallingSessionBadResponse (1203)

這些錯誤是透過應用程式提供的階段轉譯器以非同步方式報告。

SDK 會重試作業，但次數有限。在重試期間，發布/訂閱狀態為 `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`。如果重試成功，狀態會變更為 `PUBLISHED` / `SUBSCRIBED`。

SDK 呼叫 `onError` 包含相關的錯誤代碼，且 fatal = false。

**動作**：SDK 會自動重試，因此不需執行任何動作。或者，應用程式可以重新整理策略以強制執行更多次重試。

### 建立後失敗
<a name="broadcast-android-publish-subscribe-errors-established"></a>

發布或訂閱可能會在建立後失敗，這很可能是因為網路錯誤所致。「對等連線因網路錯誤而中斷」訊息的錯誤代碼是 1400。

此錯誤是透過應用程式提供的階段轉譯器以非同步方式報告。

SDK 會重試發布/訂閱作業。在重試期間，發布/訂閱狀態為 `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`。如果重試成功，狀態會變更為 `PUBLISHED` / `SUBSCRIBED`。

SDK 呼叫 `onError` 包含 error code = 1400 及 fatal = false。

**動作**：SDK 會自動重試，因此不需執行任何動作。或者，應用程式可以重新整理策略以強制執行更多次重試。在網路完全無法連線的情況下，與 Stages 的連線可能也會失敗。