

# IVS Broadcast SDK: Android 가이드 \$1 실시간 스트리밍
<a name="broadcast-android"></a>

IVS 실시간 스트리밍 Android Broadcast SDK를 사용하면 참가자가 Android에서 비디오를 전송하고 수신할 수 있습니다.

`com.amazonaws.ivs.broadcast` 패키지는 본 문서에서 설명하는 인터페이스를 구현합니다. SDK에서는 다음 작업을 지원합니다.
+ 스테이지 참가 
+ 스테이지의 다른 참가자에게 미디어 게시
+ 스테이지에 있는 다른 참가자의 미디어 구독
+ 스테이지에 게시된 비디오 및 오디오 관리 및 모니터링
+ 각 피어 연결에 대한 WebRTC 통계 가져오기
+ IVS 지연 시간이 짧은 스트리밍 Android Broadcast 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 Broadcast 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의 [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 샘플 리포지토리를 참조하세요.

**플랫폼 요구 사항:** Android 9.0 이상

# IVS Android Broadcast SDK 시작하기 \$1 실시간 스트리밍
<a name="broadcast-android-getting-started"></a>

이 문서에서는 IVS Real-Time Streaming Android Broadcast SDK를 시작하는 데 관련된 단계를 안내합니다.

## 라이브러리 설치
<a name="broadcast-android-install"></a>

Amazon IVS Android 브로드캐스트 라이브러리를 Android 개발 환경에 추가하는 방법은 여러 가지입니다(Gradle 직접 사용, Gradle 버전 카탈로그 사용, 또는 수동으로 SDK 설치).

**Gradle 직접 사용**: 다음과 같이 모듈의 `build.gradle` 파일에 라이브러리를 추가합니다(IVS Broadcast 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 Broadcast 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>

디버그 기호가 포함된 Android Broadcast SDK 버전도 게시합니다. IVS Broadcast 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 Broadcast SDK의 `libbroadcastcore.so` 라이브러리 포함)에서 디버그 정보를 자동으로 제거하려고 시도합니다. 그러나 가끔은 이 상황이 발생하지 않습니다. 따라서 `.apk` 파일이 커질 수 있으며, 디버그 기호를 제거할 수 없고 `.so` 파일을 그대로 패키징하고 있다는 Android Gradle 플러그인의 경고 메시지가 표시될 수 있습니다. 이 상황이 발생하면 다음과 같은 작업을 수행합니다.
+ Android NDK를 설치합니다. 최신 버전이 작동합니다.
+ 애플리케이션의 `build.gradle` 파일에 `ndkVersion <your_installed_ndk_version_number>`를 추가합니다. 애플리케이션 자체에 네이티브 코드가 없더라도 이 작업을 수행합니다.

자세한 내용은 이 [문제 보고서](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 Broadcast SDK를 사용한 게시 및 구독 \$1 실시간 스트리밍
<a name="android-publish-subscribe"></a>

이 문서에서는 IVS Real-Time Streaming Android Broadcast SDK를 사용하여 스테이지에 게시하고 구독하는 단계를 안내합니다.

## 개념
<a name="android-publish-subscribe-concepts"></a>

실시간 기능의 3가지 핵심 개념은 [스테이지](#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`가 제공됩니다.

렌더러에서 제공하는 정보가 전략의 반환 값에 영향을 미칠 것으로 예상되지는 않습니다. 예를 들어, `shouldSubscribeToParticipant`의 반환 값은 `onParticipantPublishStateChanged`가 호출될 때 변경되지 않을 것으로 예상됩니다. 호스트 애플리케이션이 특정 참가자를 구독하려는 경우 해당 참가자의 게시 상태와 무관하게 원하는 구독 유형을 반환해야 합니다. SDK는 원하는 전략 상태가 스테이지 상태를 기반을 정확한 시간에 실행되도록 하는 역할을 합니다.

`StageRenderer`는 스테이지 클래스에 연결될 수 있습니다.

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

게시 참가자만 `onParticipantJoined`를 트리거하고, 참가자가 게시를 중단하거나 스테이지 세션에서 나갈 때마다 `onParticipantLeft`가 트리거됩니다.

## 미디어 스트림 게시
<a name="android-publish-subscribe-publish-stream"></a>

내장 마이크 및 카메라와 같은 로컬 디바이스는 `DeviceDiscovery`를 통해 검색됩니다. 다음은 전면 카메라와 기본 마이크를 선택한 다음 SDK에 게시될 `LocalStageStreams`로 반환하는 예제입니다.

```
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를 통해 전송되기 때문에, 구독자에게 반드시 도착한다고 보장할 수 없습니다. 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\$130이어야 합니다. 수신 클라이언트에는 메시지 중복을 제거하는 로직이 있어야 합니다.

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

수신 스트림에서 임베디드 메시지를 읽는 방법은 아래 ‘Get Supplemental Enhancement Information(SEI)’을 참조하세요.

## Supplemental Enhancement Information(SEI) 가져오기
<a name="android-publish-subscribe-sei-attributes"></a>

Supplemental Enhancement Information(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)`를 사용하는 세로 모드 또는 가로 모드 강제 사용을 지원하지 않습니다. 세로 방향에서는 작은 치수가 너비로 사용되고 가로 방향에서는 높이가 너비로 사용됩니다. 즉, `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` 객체를 만들고 네트워크 상태가 개선되면 참가를 시도합니다.

## 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);
```

# IVS Android Broadcast SDK의 알려진 문제 및 해결 방법 \$1 실시간 스트리밍
<a name="broadcast-android-known-issues"></a>

이 문서는 Amazon IVS Real-Time Streaming Android Broadcast 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 칩셋이 장착된 디바이스는 원격 참가자의 비디오를 제대로 렌더링하지 못할 수 있습니다.

  **해결 방법**: 없음
+ 일부 디바이스에서는 디바이스 OS가 SDK를 통해 선택한 것과 다른 마이크를 선택할 수 있습니다. 이는 Amazon IVS Broadcast SDK가 `VOICE_COMMUNICATION` 오디오 경로 정의 방법을 제어할 수 없기 때문이며, 오디오 경로가 디바이스 제조업체마다 다르기 때문입니다.

  **해결 방법**: 없음
+ 일부 Android 비디오 인코더는 비디오 크기를 176x176 미만으로 구성할 수 없습니다. 크기가 작으면 오류가 발생하고 스트리밍되지 않습니다.

  **해결 방법:** 비디오 크기를 176x176 미만으로 구성하지 마세요.

# IVS Android Broadcast SDK의 오류 처리 \$1 실시간 스트리밍
<a name="broadcast-android-error-handling"></a>

이 섹션에서는 오류 조건, IVS Real-Time Streaming Android Broadcast SDK가 애플리케이션에 오류를 보고하는 방법, 이러한 오류가 발생할 경우 애플리케이션이 수행해야 하는 작업에 대한 개요를 다룹니다.

## 치명적인 오류와 치명적이지 않은 오류
<a name="broadcast-android-fatal-vs-nonfatal-errors"></a>

오류 객체에는 `BroadcastException`의 “치명적” 부울 필드가 있습니다.

일반적으로 치명적인 오류는 스테이지 서버 연결과 관련이 있습니다(연결을 설정할 수 없거나 연결이 끊어져 복구할 수 없음). 애플리케이션은 스테이지를 다시 만들고 가능하면 새 토큰을 사용하거나 디바이스 연결이 복구되면 다시 참가해야 합니다.

치명적이지 않은 오류는 일반적으로 게시/구독 상태와 관련이 있으며 게시/구독 작업을 재시도하는 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`에 대한 호출에서 오류 코드 = 1000이고 fatal = true인 Java 예외를 발생시킵니다.

**조치**: 유효한 토큰을 생성한 후 다시 참가해 보세요.

### 만료된 토큰
<a name="broadcast-android-stage-join-errors-expired-token"></a>

스테이지 토큰이 만료된 경우 발생합니다.

SDK는 `stage.join`에 대한 호출에서 오류 코드 = 1001이고 fatal = true인 Java 예외를 발생시킵니다.

**조치**: 새 토큰을 생성한 후 다시 참가해 보세요.

### 유효하지 않거나 취소된 토큰
<a name="broadcast-android-stage-join-errors-invalid-token"></a>

스테이지 토큰의 형식이 잘못되진 않았지만 스테이지 서버에서 거부된 경우 발생합니다. 이는 애플리케이션에서 제공하는 스테이지 렌더러를 통해 비동기적으로 보고됩니다.

SDK는 오류 코드 = 1026이고 fatal = true인 예외로 `onConnectionStateChanged`를 호출합니다.

**조치**: 유효한 토큰을 생성한 후 다시 참가해 보세요.

### 첫 참가 시 네트워크 오류
<a name="broadcast-android-stage-join-errors-network-initial-join"></a>

SDK가 스테이지 서버에 접속하여 연결을 설정할 수 없는 경우 발생합니다. 이는 애플리케이션에서 제공하는 스테이지 렌더러를 통해 비동기적으로 보고됩니다.

SDK는 오류 코드 = 1300이고 fatal = true인 예외로 `onConnectionStateChanged`를 호출합니다.

**조치**: 디바이스 연결이 복구될 때까지 기다린 후 다시 참가해 보세요.

### 이미 참가한 경우 네트워크 오류
<a name="broadcast-android-stage-join-errors-network-already-joined"></a>

디바이스의 네트워크 연결이 끊어지면 SDK와 스테이지 서버 연결이 끊어질 수 있습니다. 이는 애플리케이션에서 제공하는 스테이지 렌더러를 통해 비동기적으로 보고됩니다.

SDK는 오류 코드 = 1300이고 fatal = true인 예외로 `onConnectionStateChanged`를 호출합니다.

**조치**: 디바이스 연결이 복구될 때까지 기다린 후 다시 참가해 보세요.

## 게시/구독 오류
<a name="broadcast-android-publish-subscribe-errors"></a>

### Initial
<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는 관련 오류 코드와 fatal = false로 `onError`를 호출합니다.

**조치**: SDK가 자동으로 재시도하므로 조치가 필요하지 않습니다. 선택적으로 애플리케이션에서 전략을 새로 고쳐 추가 재시도를 강제할 수 있습니다.

### 이미 설정된 후 실패
<a name="broadcast-android-publish-subscribe-errors-established"></a>

게시 또는 구독이 설정된 후 실패할 수 있는데, 이는 대부분 네트워크 오류로 인한 것입니다. “네트워크 오류로 인해 피어 연결이 끊어짐”의 오류 코드는 1400입니다.

이는 애플리케이션에서 제공하는 스테이지 렌더러를 통해 비동기적으로 보고됩니다.

SDK는 게시/구독 작업을 재시도합니다. 재시도 시 게시/구독 상태는 `ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE`입니다. 재시도가 성공하면 상태가 `PUBLISHED`/`SUBSCRIBED`로 변경됩니다.

SDK는 오류 코드 = 1400이고 fatal = false인 `onError`를 호출합니다.

**조치**: SDK가 자동으로 재시도하므로 조치가 필요하지 않습니다. 선택적으로 애플리케이션에서 전략을 새로 고쳐 추가 재시도를 강제할 수 있습니다. 전체 연결이 끊어지는 경우 스테이지에 대한 연결도 실패할 가능성이 높습니다.