

# SDK de transmisión de IVS: guía para Android \$1 Transmisión en tiempo real
<a name="broadcast-android"></a>

El SDK de transmisión para transmisión en tiempo real de IVS para Android permite a los participantes enviar y recibir videos en Android.

El paquete de `com.amazonaws.ivs.broadcast` implementa la interfaz descrita en este documento. El SDK admite las siguientes operaciones:
+ Incorporación a un escenario 
+ Publicación de contenido multimedia para otros participantes del escenario
+ Suscripción al contenido multimedia de otros participantes del escenario
+ Administración y monitoreo del video y audio publicados en el escenario
+ Obtención de estadísticas de WebRTC de cada conexión de pares
+ Todas las operaciones del SDK de transmisión para Android de transmisión de baja latencia de IVS

**Última versión del SDK de transmisión para Android:** 1.40.0 ([Notas de la versión](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-android-rt)) 

**Documentación de referencia:** a fin de obtener información sobre los métodos más importantes disponibles en el SDK de transmisión de Android de Amazon IVS, consulte la documentación de referencia en [https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/).

**Código de muestra: **Consulte el repositorio de muestra de Android en 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).

**Requisitos de la plataforma:** Android 9.0\$1

# Introducción al SDK de transmisión para Android de IVS \$1 Transmisión en tiempo real
<a name="broadcast-android-getting-started"></a>

Este documento explica los pasos necesarios para comenzar a utilizar el SDK de transmisión para Android de la transmisión en tiempo real de IVS.

## Instalación de la biblioteca
<a name="broadcast-android-install"></a>

Hay varias formas de agregar la biblioteca de transmisión para Android de Amazon IVS a su entorno de desarrollo de Android: uso directo de Gradle, uso de los catálogos de versiones de Gradle o instalación manual del SDK.

**Uso directo de Gradle**: agregue la biblioteca al archivo `build.gradle` del módulo, como se muestra a continuación (para la versión más reciente del SDK de transmisión de IVS):

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

**Uso de los catálogos de versiones de Gradle**: primero incluya esto en el archivo `build.gradle` del módulo:

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

A continuación, incluya lo siguiente en el archivo `libs.version.toml` (para obtener la versión más reciente del SDK de transmisión de IVS):

```
[versions]
ivs="1.40.0"

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

**Instalación manual del SDK**: descargue la versión más reciente desde esta ubicación:

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

Asegúrese de descargar el archivo `aar` con `-stages` adjunto.

**Permita también el control de SDK a través del altavoz**: independientemente del método de instalación que elija, agregue también el siguiente permiso al manifiesto para permitir que el SDK habilite o deshabilite el altavoz:

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

## Uso del SDK con símbolos de depuración
<a name="broadcast-android-using-debug-symbols-rt"></a>

También publicamos una versión del SDK de transmisión para Android que incluye símbolos de depuración. Puede usar esta versión para mejorar la calidad de los informes de depuración (seguimientos de pila) en Firebase Crashlytics si se produce algún fallo en el SDK de transmisión de IVS, es decir, `libbroadcastcore.so`. Cuando notifica estos bloqueos al equipo del SDK de IVS, los rastreos de pila de mayor calidad facilitan la solución de los problemas.

Para usar esta versión del SDK, coloque lo siguiente en los archivos de compilación de Gradle:

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

Use la línea anterior en lugar de esta:

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

### Carga de símbolos en Firebase Crashlytics
<a name="android-debug-symbols-rt-firebase-crashlytics"></a>

Asegúrese de que los archivos de compilación de Gradle estén configurados para Firebase Crashlytics. Siga las instrucciones de Google aquí:

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

Asegúrese de incluir `com.google.firebase:firebase-crashlytics-ndk` como dependencia.

Al crear la aplicación para su lanzamiento, el complemento de Firebase Crashlytics debería cargar los símbolos automáticamente. Para cargar los símbolos manualmente, ejecute cualquiera de los comandos siguientes:

```
gradle uploadCrashlyticsSymbolFileRelease
```

```
./gradlew uploadCrashlyticsSymbolFileRelease
```

(No pasará nada si los símbolos se cargan dos veces, automática y manualmente).

### Cómo evitar que el archivo .apk de la versión aumente de tamaño
<a name="android-debug-symbols-rt-sizing-apk"></a>

Antes de empaquetar el archivo `.apk` de la versión, el complemento de Gradle para Android intenta eliminar automáticamente la información de depuración de las bibliotecas compartidas (incluida la biblioteca `libbroadcastcore.so` del SDK de transmisión de IVS). Sin embargo, a veces esto no sucede. Como resultado, el archivo `.apk` podría aumentar de tamaño y podría recibir un mensaje de advertencia del complemento de Gradle para Android indicándole que no puede eliminar los símbolos de depuración y que está empaquetando los archivos `.so` tal como están. Si esto sucede, haga lo siguiente:
+ Instale un NDK de Android. Cualquier versión reciente funcionará.
+ Agregue `ndkVersion <your_installed_ndk_version_number>` al archivo `build.gradle` de la aplicación. Haga esto incluso si la propia aplicación no contiene código nativo.

Para obtener más información, consulte este [informe de problemas](https://issuetracker.google.com/issues/353554169).

## Solicitar permisos
<a name="broadcast-android-permissions"></a>

La aplicación debe solicitar permiso para acceder a la cámara y al micrófono del usuario. (Esto no es específico de Amazon IVS; es necesario para cualquier aplicación que necesite acceso a cámaras y micrófonos).

Aquí, verificamos si el usuario ya ha concedido permisos y, de no ser así, preguntamos por ellos:

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

Aquí, obtenemos la respuesta del usuario:

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

# Publicación y suscripción con el SDK de transmisión para Android de IVS \$1 Transmisión en tiempo real
<a name="android-publish-subscribe"></a>

Este documento explica los pasos necesarios para publicar y suscribirse a una fase mediante el SDK de transmisión para Android de la transmisión en tiempo real de IVS.

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

Los siguientes tres conceptos básicos subyacen a la funcionalidad de transmisión en tiempo real: [escenario](#android-publish-subscribe-concepts-stage), [estrategia](#android-publish-subscribe-concepts-strategy) y [renderizador](#android-publish-subscribe-concepts-renderer). El objetivo del diseño es minimizar la cantidad de lógica necesaria por parte del cliente para crear un producto que funcione.

### Etapa
<a name="android-publish-subscribe-concepts-stage"></a>

La clase `Stage` es el principal punto de interacción entre la aplicación host y el SDK. Representa el escenario como tal y se usa para entrar y salir de él. Para crear un escenario e incorporarse a él, es necesaria una cadena de símbolos válida y que no haya vencido del plano de control (representada como `token`). Entrar a un escenario y salir de él es sencillo. 

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

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

stage.leave();
```

También se puede adjuntar `StageRenderer` en la clase `Stage`:

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

### Strategy (Estrategia)
<a name="android-publish-subscribe-concepts-strategy"></a>

La interfaz `Stage.Strategy` proporciona una forma para que la aplicación host comunique el estado deseado del escenario al SDK. Es necesario implementar las siguientes tres funciones: `shouldSubscribeToParticipant`, `shouldPublishFromParticipant` y `stageStreamsToPublishForParticipant`. Todas se analizan a continuación.

#### Suscripción a participantes
<a name="android-publish-subscribe-concepts-strategy-participants"></a>

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

Cuando un participante remoto se incorpora al escenario, el SDK consulta la aplicación host sobre el estado de suscripción deseado de ese participante. Las opciones son `NONE`, `AUDIO_ONLY` y `AUDIO_VIDEO`. Al devolver un valor para esta función, la aplicación host no tiene que preocuparse por el estado de la publicación, el estado actual de la suscripción ni el estado de la conexión del escenario. Si se devuelve `AUDIO_VIDEO`, el SDK espera a que el participante remoto publique antes de suscribirse y actualiza la aplicación host a través del renderizador durante todo el proceso.

Este es un ejemplo de implementación:

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

Esta es la implementación completa de esta función para una aplicación host que siempre quiere que todos los participantes se vean entre sí; por ejemplo, una aplicación de videochat.

También son posibles implementaciones más avanzadas. Utilice la propiedad `userInfo` en `ParticipantInfo` para suscribirse de forma selectiva a los participantes en función de los atributos proporcionados por el servidor:

```
@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;
	}
}
```

Esto se puede utilizar para crear un escenario en el que los moderadores puedan monitorear a todos los invitados sin que ellos mismos los vean ni los escuchen. La aplicación host podría utilizar una lógica empresarial adicional para permitir que los moderados se vean entre sí, pero permanezcan invisibles para los invitados.

#### Configuración de la suscripción a participantes
<a name="android-publish-subscribe-concepts-strategy-participants-config"></a>

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

Si se está subscribiendo a un participante remoto (consulte [Suscripción a participantes](#android-publish-subscribe-concepts-strategy-participants)), el SDK consulta a la aplicación host sobre una configuración de la subscrición personalizada para ese participante. Esta configuración es opcional y permite a la aplicación host controlar determinados aspectos del comportamiento de los suscriptores. Para obtener información sobre lo que se puede configurar, consulte [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration) en la documentación de referencia del SDK.

Este es un ejemplo de implementación:

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

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

    return config;
}
```

Esta implementación actualiza el retraso mínimo del búfer de fluctuación para todos los participantes suscritos al valor preestablecido `MEDIUM`.

Como en el caso de `shouldSubscribeToParticipant`, también son posibles implementaciones más avanzadas. El valor de `ParticipantInfo` proporcionado se puede utilizar para actualizar selectivamente la configuración de suscripción para participantes específicos.

Se recomienda utilizar los comportamientos predeterminados. Especifique la configuración personalizada solo si hay un comportamiento en particular que desee cambiar.

#### Publicación
<a name="android-publish-subscribe-concepts-strategy-publishing"></a>

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

Una vez realizada la conexión al escenario, el SDK consulta la aplicación host para ver si un participante en particular tiene que publicar. Esto solo se invoca en los participantes locales que tienen permiso para publicar en función del token proporcionado.

Este es un ejemplo de implementación:

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

Esto es para una aplicación de videochat estándar en la que los usuarios siempre quieren publicar. Pueden activar y desactivar el sonido y el video para ocultarse o verse y escucharse al instante. (También pueden usar la opción de publicar o anular la publicación, pero esto es mucho más lento. Es preferible silenciar o activar el sonido en casos de uso en los que se quiera cambiar la visibilidad de manera frecuente).

#### Elección de las transmisiones que publicar
<a name="android-publish-subscribe-concepts-strategy-streams"></a>

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

Al publicar, esto se utiliza para determinar qué transmisiones de audio y video se tienen que publicar. Esto se explica en mayor detalle más adelante en [Publicación de una transmisión multimedia](#android-publish-subscribe-publish-stream).

#### Actualización de la estrategia
<a name="android-publish-subscribe-concepts-strategy-updates"></a>

La estrategia pretende ser dinámica: los valores devueltos por cualquiera de las funciones anteriores se pueden cambiar en cualquier momento. Por ejemplo, si la aplicación host no quiere publicar hasta que el usuario final presione un botón, puede devolver una variable de `shouldPublishFromParticipant` (como `hasUserTappedPublishButton`). Cuando esa variable cambie en función de una interacción del usuario final, llame a `stage.refreshStrategy()` para indicar al SDK que debe consultar la estrategia a fin de obtener los valores más recientes y aplicar solo los cambios. Si el SDK observa que el valor `shouldPublishFromParticipant` cambió, iniciará el proceso de publicación. Si las consultas del SDK y todas las funciones devuelven el mismo valor que antes, la llamada a `refreshStrategy` no hará ninguna modificación en el escenario.

Si el valor devuelto de `shouldSubscribeToParticipant` cambia de `AUDIO_VIDEO` a `AUDIO_ONLY`, la transmisión de video se eliminará para todos los participantes con valores devueltos modificados, si ya existía con anterioridad una transmisión de video.

Por lo general, el escenario utiliza la estrategia para aplicar de la manera más eficiente la diferencia entre las estrategias anteriores y actuales, sin que la aplicación host tenga que preocuparse por todo el estado necesario para administrarla correctamente. Por eso, piense en la llamada a `stage.refreshStrategy()` como una operación barata, porque no hace nada a no ser que cambie la estrategia.

### Renderer
<a name="android-publish-subscribe-concepts-renderer"></a>

La interfaz `StageRenderer` comunica el estado del escenario a la aplicación host. Por lo general, los eventos proporcionados por el renderizador permiten el funcionamiento completo de las actualizaciones de la interfaz de usuario de la aplicación host. El renderizador ofrece el siguiente resultado:

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

Para la mayoría de estos métodos, se proporcionan el `Stage` y la `ParticipantInfo` correspondientes.

No se espera que la información que proporciona el renderizador afecte a los valores de retorno de la estrategia. Por ejemplo, no se espera que el valor devuelto de `shouldSubscribeToParticipant` cambie cuando se llama a `onParticipantPublishStateChanged`. Si la aplicación host quiere suscribirse a un participante en particular, debe devolver el tipo de suscripción deseado, independientemente del estado de publicación de ese participante. El SDK es responsable de garantizar que se aplique el estado deseado de la estrategia en el momento correcto según el estado del escenario.

`StageRenderer` se puede adjuntar a la clase del escenario:

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

Tenga en cuenta que solo los participantes que publican desencadenan `onParticipantJoined`. `onParticipantLeft` se desencadena cada vez que un participante deja de publicar o abandona la sesión del escenario.

## Publicación de una transmisión multimedia
<a name="android-publish-subscribe-publish-stream"></a>

Los dispositivos locales, como las cámaras y los micrófonos integrados, se detectan a través de `DeviceDiscovery`. A continuación, se muestra un ejemplo de cómo seleccionar la cámara frontal y el micrófono predeterminados y devolverlos como `LocalStageStreams` para que los publique el 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;
}
```

## Visualización y eliminación de participantes
<a name="android-publish-subscribe-participants"></a>

Cuando se complete la suscripción, recibirá una matriz de objetos `StageStream` a través de la función `onStreamsAdded` del renderizador. Puede obtener la vista previa de una `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);
```

Puede recuperar las estadísticas de audio de una `AudioStageStream`:

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

Cuando un participante deja de publicar o anula su suscripción, se invoca la función `onStreamsRemoved` con las transmisiones que se eliminaron. Las aplicaciones host tienen que usar esto como una señal para eliminar la transmisión de video del participante de la jerarquía de visualización.

`onStreamsRemoved` se invoca para todas las situaciones en las que se puede eliminar una transmisión, como las siguientes: 
+ El participante remoto deja de publicar.
+ Un dispositivo local cancela la suscripción o cambia la suscripción de `AUDIO_VIDEO` a `AUDIO_ONLY`.
+ El participante remoto abandona el escenario.
+ El participante local abandona el escenario.

Ya que `onStreamsRemoved` se invoca en todas las situaciones, no es necesaria una lógica empresarial personalizada para eliminar a los participantes de la interfaz de usuario durante las operaciones de licencia remota o local.

## Activación y desactivación del sonido de las transmisiones multimedia
<a name="android-publish-subscribe-mute-streams"></a>

Los objetos de `LocalStageStream` tienen una función `setMuted` que controla si la transmisión está silenciada. Esta función se puede invocar en la transmisión antes o después de que la devuelva la función de estrategia `streamsToPublishForParticipant`.

**Importante**: Si `streamsToPublishForParticipant` devuelve una nueva instancia de objeto `LocalStageStream` después de una llamada a `refreshStrategy`, el estado de sonido desactivado del nuevo objeto de transmisión se aplicará al escenario. Tenga cuidado al crear nuevas instancias `LocalStageStream` para asegurarse de que se mantenga el estado de sonido desactivado que se espera.

## Monitoreo del estado de sonido desactivado en los contenidos multimedia del participante remoto
<a name="android-publish-subscribe-mute-state"></a>

Cuando un participante cambia el estado de sonido desactivado de su transmisión de video o audio, se invoca la función `onStreamMutedChanged` de renderizado con una lista de las transmisiones que cambiaron. Use el método `getMuted` en `StageStream` para actualizar la interfaz de usuario según corresponda. 

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

## Obtención de estadísticas de WebRTC
<a name="android-publish-subscribe-webrtc-stats"></a>

Para obtener las estadísticas más recientes de WebRTC de una transmisión de publicación o de suscripción, utilice `requestRTCStats` en `StageStream`. Cuando se complete una recopilación, recibirá estadísticas a través de `StageStream.Listener`, que se puede configurar en `StageStream`.

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

## Obtención de los atributos de los participantes
<a name="android-publish-subscribe-participant-attributes"></a>

Si especifica atributos en la solicitud de la operación de `CreateParticipantToken`, podrá ver los atributos en las propiedades de `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());
	}
}
```

## Mensajes incrustados
<a name="android-publish-subscribe-embed-messages"></a>

El método `embedMessage` en ImageDevice permite insertar cargas útiles de metadatos directamente en los fotogramas de video durante la publicación. Esto activa la mensajería sincronizada por fotogramas para aplicaciones en tiempo real. La incrustación de mensajes solo está disponible cuando se utiliza el SDK para la publicación en tiempo real (no para la publicación de baja latencia).

No se garantiza que los mensajes incrustados lleguen a los suscriptores porque se incrustan directamente en los fotogramas de video y se transmiten a través de UDP, lo que no asegura la entrega de paquetes. La pérdida de paquetes durante la transmisión puede provocar la pérdida de mensajes, especialmente en condiciones de red deficientes. Para mitigar esta situación, el método `embedMessage` incluye un parámetro `repeatCount` que duplica el mensaje en varios fotogramas consecutivos, lo que aumenta la fiabilidad de la entrega. Esta capacidad solo está disponible para transmisiones de video.

### Uso de embedMessage
<a name="android-embed-messages-using-embedmessage"></a>

Los clientes de publicación pueden incrustar cargas útiles de mensajes en su transmisión de video mediante el método `embedMessage` de ImageDevice. El tamaño de la carga útil debe ser superior a 0 KB e inferior a 1 KB. El número de mensajes incrustados insertados por segundo no debe superar los 10 KB por segundo. 

```
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}")
}
```

### Cargas útiles de mensaje que se repiten
<a name="android-embed-messages-repeat-payloads"></a>

Utilice `repeatCount` para duplicar el mensaje en varios marcos a fin de mejorar la fiabilidad. Este valor debe estar entre 0 y 30. Los clientes receptores deben tener una lógica para desduplicar el mensaje.

```
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}")
}
```

### Lectura de mensajes incrustados
<a name="android-embed-messages-read-messages"></a>

Consulte la sección “Obtención de información de mejora adicional (SEI)” a continuación para saber cómo leer los mensajes incrustados de las transmisiones entrantes.

## Obtención de información de mejora adicional (SEI)
<a name="android-publish-subscribe-sei-attributes"></a>

La unidad NAL de información de mejora adicional (SEI) se usa para almacenar metadatos alineados con los fotogramas junto con el video. Los clientes suscriptores pueden leer las cargas útiles SEI de un publicador que publica un video H.264 mediante la inspección de la propiedad `embeddedMessages` en los objetos `ImageDeviceFrame` que salen del `ImageDevice` del publicador. Para ello, adquiera el `ImageDevice` de un publicador y, a continuación, observe cada fotograma a través de una devolución de llamada proporcionada a `setOnFrameCallback`, como se muestra en el siguiente ejemplo:

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

## Continuación de la sesión en segundo plano
<a name="android-publish-subscribe-background-session"></a>

Cuando la aplicación pase a segundo plano, es posible que quiera dejar de publicar o suscribirse solo al audio de otros participantes remotos. Para ello, actualice la implementación `Strategy` para detener la publicación y suscríbase a `AUDIO_ONLY` (o a `NONE`, si corresponde).

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

## Codificación por capas con la transmisión simultánea
<a name="android-publish-subscribe-layered-encoding-simulcast"></a>

La codificación por capas con transmisión simultánea es una característica de transmisión en tiempo real de IVS que permite a quienes publican enviar múltiples capas de video con diferentes niveles de calidad, mientras que los suscriptores pueden configurar estas capas de manera dinámica o manual. Esta característica se describe con más detalle en el documento [Optimizaciones de transmisión](real-time-streaming-optimization.md).

### Configuración de la codificación por capas (publicador)
<a name="android-layered-encoding-simulcast-configure-publisher"></a>

Como publicador, para habilitar la codificación por capas con transmisión simultánea, agregue la siguiente configuración a `LocalStageStream` en la instanciación:

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

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

En función de la resolución establecida en la configuración de video, se codificará y enviará una cantidad determinada de capas, tal como se define en la sección [Capas, calidades y velocidades de fotogramas predeterminadas](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) de *Optimizaciones de transmisión*.

Además, si lo desea, puede configurar capas individuales desde la configuración de transmisión simultánea: 

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

De manera opcional, también puede crear sus propias configuraciones de capas personalizadas para hasta tres capas. Si proporciona una matriz vacía o ningún valor, se utilizan los valores predeterminados descritos con anterioridad. Las capas se describen con los siguientes setters de propiedades obligatorios:
+ `setSize: Vec2;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: integer;`

A partir de los ajustes preestablecidos, puede anular las propiedades individuales o crear una configuración completamente nueva:

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

Para conocer los valores máximos, los límites y los errores que se pueden activar al configurar capas individuales, consulta la documentación de referencia del SDK.

### Configuración de la codificación por capas (suscriptor)
<a name="android-layered-encoding-simulcast-configure-subscriber"></a>

Como suscriptor, no es necesario hacer nada para habilitar la codificación por capas. Si un publicador envía capas de transmisión simultánea, el servidor, de forma predeterminada, se adaptará dinámicamente entre las capas para elegir la calidad óptima en función de las condiciones de la red y del dispositivo del suscriptor.

Como alternativa, existen varias opciones para seleccionar las capas explícitas que envía el publicador, tal y como se describe a continuación.

### Opción 1: preferencia de calidad de la capa inicial
<a name="android-layered-encoding-simulcast-layer-quality-preference"></a>

Mediante la estrategia `subscribeConfigurationForParticipant`, es posible elegir qué capa inicial desea recibir como suscriptor:

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

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

    return config;
}
```

De forma predeterminada, a los suscriptores siempre se les envía primero la capa de calidad más baja; poco a poco se incrementa hasta llegar a la capa de calidad más alta. Esto optimiza el consumo de ancho de banda del usuario final y proporciona el mejor tiempo de inicio del video, lo cual reduce las congelaciones iniciales de video para los usuarios en redes más débiles.

Estas opciones se encuentran disponibles para `InitialLayerPreference`:
+ `LOWEST_QUALITY`: el servidor entrega primero la capa de video de menor calidad. Esto optimiza tanto el consumo de ancho de banda como el tiempo de inicio del contenido multimedia. La calidad se define como la combinación de tamaño, velocidad de bits y velocidad de fotogramas del video. Por ejemplo, un video 720p es de menor calidad que un video 1080p.
+ `HIGHEST_QUALITY`: el servidor entrega primero la capa de video de mayor calidad. Esto optimiza la calidad, pero puede aumentar el tiempo de inicio del contenido multimedia. La calidad se define como la combinación de tamaño, velocidad de bits y velocidad de fotogramas del video. Por ejemplo, el video 1080p es de mayor calidad que el video 720p.

**Nota:** Para que se apliquen las preferencias de la capa inicial (la llamada `setInitialLayerPreference`), es necesario volver a suscribirse, ya que estas actualizaciones no se aplican a la suscripción activa.

### Opción 2: capa preferida para la transmisión
<a name="android-layered-encoding-simulcast-preferred-layer"></a>

El método de estrategia `preferredLayerForStream` le permite seleccionar una capa después de que la transmisión haya comenzado. Este método de estrategia recibe la información del participante y de la transmisión, por lo que puede seleccionar una capa para cada participante de forma individual. El SDK llama a este método en respuesta a eventos específicos, como cuando cambian las capas de transmisión o el estado del participante o la aplicación host actualiza la estrategia.

El método de estrategia devuelve un objeto `RemoteStageStream.Layer`, que puede ser uno de los siguientes:
+ Un objeto de capa, como uno devuelto por `RemoteStageStream.getLayers`.
+ nulo, que indica que no se debe seleccionar ninguna capa y que se prefiere la adaptación dinámica.

Por ejemplo, la siguiente estrategia hará que los usuarios seleccionen siempre la capa de video de menor calidad disponible:

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

Para restablecer la selección de capas y volver a la adaptación dinámica, devuelva nulo o sin definir en la estrategia. En este ejemplo, `appState` es una variable de marcador de posición que representa el estado de la aplicación host.

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

### Opción 3: ayudantes de la capa RemoteStageStream
<a name="android-layered-encoding-simulcast-remotestagestream-helpers"></a>

`RemoteStageStream` cuenta con varios ayudantes que se pueden utilizar para tomar decisiones sobre la selección de capas y mostrar las selecciones correspondientes a los usuarios finales:
+ **Eventos de capa**: además de `StageRenderer`, `RemoteStageStream.Listener` cuenta con eventos que comunican los cambios de adaptación de capas y transmisión simultánea:
  + `void onAdaptionChanged(boolean adaption)`
  + `void onLayersChanged(@NonNull List<Layer> layers)`
  + `void onLayerSelected(@Nullable Layer layer, @NonNull LayerSelectedReason reason)`
+ **Métodos de capa**: `RemoteStageStream` tiene varios métodos de ayudante que se pueden utilizar para obtener información sobre la transmisión y las capas que se presentan. Estos métodos están disponibles en la transmisión remota proporcionada en la estrategia `preferredLayerForStream`, así como en las transmisiones remotas expuestas a través de `StageRenderer.onStreamsAdded`.
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`
  + `stream.getLayersWithConstraints`

Para conocer los detalles, consulte la clase `RemoteStageStream` en la [documentación de referencia del SDK](https://aws.github.io/amazon-ivs-broadcast-docs/latest/android/). En cuanto a la razón `LayerSelected`, si se devuelve `UNAVAILABLE`, esto indica que no se ha podido seleccionar la capa solicitada. En su lugar, se selecciona la mejor opción posible, que suele ser una capa de menor calidad para mantener la estabilidad de la transmisión.

## Limitaciones de la configuración de video
<a name="android-publish-subscribe-video-limits"></a>

El SDK no permite forzar el uso del modo vertical ni del horizontal mediante `StageVideoConfiguration.setSize(BroadcastConfiguration.Vec2 size)`. En la orientación vertical, la dimensión más pequeña se utiliza como el ancho; en la orientación horizontal, como la altura. Esto significa que las dos siguientes llamadas a `setSize` tendrán el mismo efecto en la configuración de video:

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

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

## Gestión de los problemas de red
<a name="android-publish-subscribe-network-issues"></a>

Cuando se pierde la conexión de red del dispositivo local, el SDK se intenta volver a conectar internamente sin que el usuario lleve a cabo ninguna acción. En algunos casos, el SDK no funciona de manera correcta y es necesario que el usuario actúe. Hay dos errores principales relacionados con la pérdida de la conexión de red:
+ Código de error 1400, mensaje: “Se ha perdido la conexión de pares debido a un error de red desconocido”
+ Código de error 1300, mensaje: “Se han agotado los reintentos”

Si se recibe el primer error, pero no el segundo, el SDK sigue conectado al escenario e intentará restablecer sus conexiones automáticamente. Como medida de seguridad, puede hacer una llamada a `refreshStrategy` sin cambiar los valores devueltos por el método de estrategia para iniciar un intento de reconexión manual.

Si se recibe el segundo error, los intentos de reconexión del SDK fallaron y el dispositivo local ya no está conectado al escenario. En este caso, intente reincorporarse al escenario con una llamada a `join` después de que se restablezca la conexión de red.

En general, el que aparezcan errores después de incorporarse a un escenario correctamente indica que el SDK no pudo restablecer la conexión. Cree un nuevo objeto `Stage` e intente incorporarse cuando mejoren las condiciones de la red.

## Uso de micrófonos Bluetooth
<a name="android-publish-subscribe-bluetooth-microphones"></a>

Para publicar con dispositivos de micrófono Bluetooth, debe iniciar una conexión SCO Bluetooth:

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

# Problemas conocidos y soluciones alternativas del SDK de transmisión para Android de IVS \$1 Transmisión en tiempo real
<a name="broadcast-android-known-issues"></a>

Este documento enumera problemas conocidos que puede experimentar al utilizar el SDK de transmisión para Android de la transmisión en tiempo real de Amazon IVS y sugiere posibles soluciones alternativas.
+ Cuando un dispositivo Android se pone en reposo y se activa, es posible que la vista previa esté congelada.

  **Solución alternativa:** cree y utilice un nuevo `Stage`.
+ Cuando un participante se incorpora con un token que utiliza otro participante, la primera conexión se pierde sin que se produzca un error específico.

  **Solución alternativa:** ninguna. 
+ Hay un problema poco frecuente en el que el editor publica, pero el estado de publicación que reciben los suscriptores es `inactive`.

  **Solución alternativa:** pruebe a salir de la sesión y, a continuación, reincorporarse. Si el problema persiste, cree un nuevo token para el publicador.
+ Durante una sesión del escenario, se puede producir un problema intermitente y poco frecuente de distorsión de audio, por lo general en llamadas de mayor duración.

  **Solución alternativa:** el participante con audio distorsionado puede abandonar la sesión y volver a unirse a ella o anular la publicación y volver a publicar su audio para solucionar el problema.
+ No se admiten micrófonos externos cuando se hacen publicaciones en un escenario.

  **Solución alternativa:** no utilice un micrófono externo conectado por USB para publicar en un escenario.
+ No se admite el uso de `createSystemCaptureSources` para publicar en un escenario con pantalla compartida.

  **Solución alternativa:** administre la captura del sistema de forma manual mediante orígenes de entrada de imágenes personalizados y orígenes de entrada de audio personalizados.
+ Cuando se elimina una `ImagePreviewView` de un elemento principal (por ejemplo, se llama a `removeView()` en el elemento principal), `ImagePreviewView` se libera inmediatamente. La `ImagePreviewView` no muestra ningún fotograma cuando se agrega a otra vista principal.

  **Solución alternativa:** solicite otra vista previa mediante `getPreview`.
+ Al incorporarse a un escenario con un Samsung Galaxy S22/\$1 con Android 12, es posible que aparezca un error 1401 y que el dispositivo local no pueda incorporarse al escenario o lo haga, pero no haya audio.

  **Solución alternativa:** actualice a Android 13.
+ Al incorporarse a un escenario con un Nokia X20 en Android 13, es posible que la cámara no se abra y se produzca una excepción.

  **Solución alternativa:** ninguna.
+ Es posible que los dispositivos con el conjunto de chips MediaTek Helio no reproduzcan correctamente el video de los participantes remotos.

  **Solución alternativa:** ninguna.
+ En algunos dispositivos, el sistema operativo del dispositivo puede elegir un micrófono diferente al seleccionado a través del SDK. Esto se debe a que el SDK de transmisión de Amazon IVS no puede controlar la forma en que se define la ruta de audio de `VOICE_COMMUNICATION`, ya que varía según los distintos fabricantes de dispositivos.

  **Solución alternativa:** ninguna.
+ Algunos codificadores de video de Android no se pueden configurar con un tamaño de video inferior a 176 x 176. La configuración de un tamaño más pequeño provoca un error e impide la transmisión.

  **Solución alternativa:** no configure el tamaño del video para que sea inferior a 176 x 176.

# Control de errores en el SDK de transmisión para Android de IVS \$1 Transmisión en tiempo real
<a name="broadcast-android-error-handling"></a>

En esta sección se ofrece información general sobre las condiciones de error, cómo el SDK de transmisión para Android las notifica a la aplicación y qué debe hacer una aplicación cuando se producen esos errores.

## Errores fatales frente a errores no fatales
<a name="broadcast-android-fatal-vs-nonfatal-errors"></a>

El objeto de error tiene un campo booleano “es grave” de `BroadcastException`.

En general, los errores fatales están relacionados con la conexión al servidor de etapas (o bien no se puede establecer una conexión o bien se pierde y no se puede recuperar). La aplicación debe volver a crear el escenario y volver a unirse, posiblemente con un nuevo token o cuando se recupere la conectividad del dispositivo.

Los errores no fatales suelen estar relacionados con el estado de publicación/suscripción y son gestionados por el SDK, que reintenta la operación de publicación/suscripción.

Puede comprobar esta propiedad:

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

## Errores de unión
<a name="broadcast-android-stage-join-errors"></a>

### Token con formato incorrecto
<a name="broadcast-android-stage-join-errors-malformed-token"></a>

Esto ocurre cuando el token de la etapa tiene un formato incorrecto.

El SDK genera una excepción de Java al llamar a `stage.join`, con el código de error =1000 y fatal =true.

**Acción**: cree un token válido e intente unirse de nuevo.

### Token vencido
<a name="broadcast-android-stage-join-errors-expired-token"></a>

Esto ocurre cuando el token de la etapa está caducado.

El SDK genera una excepción de Java al llamar a `stage.join`, con el código de error =1001 y fatal =true.

**Acción**: cree un token nuevo e intente unirse de nuevo.

### Token no válido o revocado
<a name="broadcast-android-stage-join-errors-invalid-token"></a>

Esto ocurre cuando el token de la etapa no tiene un formato incorrecto, sino que el servidor de etapas lo rechaza. Esto se informa de forma asíncrona a través del renderizador de la etapa suministrado por la aplicación.

El SDK llama a `onConnectionStateChanged` con una excepción, con el código de error =1026 y fatal =true.

**Acción**: cree un token válido e intente unirse de nuevo.

### Errores de red durante la unión inicial
<a name="broadcast-android-stage-join-errors-network-initial-join"></a>

Esto ocurre cuando el SDK no puede ponerse en contacto con el servidor de etapas para establecer una conexión. Esto se informa de forma asíncrona a través del renderizador de la etapa suministrado por la aplicación.

El SDK llama a `onConnectionStateChanged` con una excepción, con el código de error =1300 y fatal =true.

**Acción**: espere a que se recupere la conectividad del dispositivo e intente conectarse de nuevo.

### Errores de red cuando ya está conectado
<a name="broadcast-android-stage-join-errors-network-already-joined"></a>

Si se interrumpe la conexión de red del dispositivo, es posible que el SDK pierda la conexión con los servidores de etapas. Esto se informa de forma asíncrona a través del renderizador de la etapa suministrado por la aplicación.

El SDK llama a `onConnectionStateChanged` con una excepción, con el código de error =1300 y fatal =true.

**Acción**: espere a que se recupere la conectividad del dispositivo e intente conectarse de nuevo.

## Errores de publicación/suscripción
<a name="broadcast-android-publish-subscribe-errors"></a>

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

Existen varios errores:
+ MultihostSessionOfferCreationFailPublish (1020)
+ MultihostSessionOfferCreationFailSubscribe (1021)
+ MultihostSessionNoIceCandidates (1022)
+ MultihostSessionStageAtCapacity (1024)
+ SignallingSessionCannotRead (1201)
+ SignallingSessionCannotSend (1202)
+ SignallingSessionBadResponse (1203)

Estos se informan de forma asíncrona a través del renderizador de la etapa suministrado por la aplicación.

El SDK vuelve a intentar la operación un número limitado de veces. Durante los reintentos, el estado de publicación/suscripción es `ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE`. Si el reintento se realiza correctamente, el estado cambia a `PUBLISHED`/`SUBSCRIBED`.

El SDK llama a `onError` con el código de error correspondiente y fatal =false.

**Acción**: no es necesario realizar ninguna acción, ya que el SDK vuelve a intentarlo automáticamente. Si lo desea, la aplicación puede actualizar la estrategia para forzar más reintentos.

### Ya está establecido, luego falla
<a name="broadcast-android-publish-subscribe-errors-established"></a>

Una publicación o una suscripción pueden fallar una vez establecidas, muy probablemente debido a un error de red. El código de error para “Se ha perdido la conexión de pares debido a un error de red” es 1400.

Esto se informa de forma asíncrona a través del renderizador de la etapa suministrado por la aplicación.

El SDK vuelve a intentar la operación de publicación/suscripción. Durante los reintentos, el estado de publicación/suscripción es `ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE`. Si el reintento se realiza correctamente, el estado cambia a `PUBLISHED`/`SUBSCRIBED`.

El SDK llama a `onError` con el código de error =1400 y fatal =false.

**Acción**: no es necesario realizar ninguna acción, ya que el SDK vuelve a intentarlo automáticamente. Si lo desea, la aplicación puede actualizar la estrategia para forzar más reintentos. En caso de pérdida total de conectividad, es probable que la conexión a las etapas también falle.