Publicación y suscripción con el SDK de transmisión para Android de IVS | Transmisión en tiempo real
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
Los siguientes tres conceptos básicos subyacen a la funcionalidad de transmisión en tiempo real: escenario, estrategia y renderizador. El objetivo del diseño es minimizar la cantidad de lógica necesaria por parte del cliente para crear un producto que funcione.
Escenario
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)
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
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
SubscribeConfiguration subscribeConfigurationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
Si se está subscribiendo a un participante remoto (consulte Suscripción a participantes), 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
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
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
@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.
Actualización de la estrategia
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
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);
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
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
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
aAUDIO_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
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 de 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
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
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
Si especifica atributos en la solicitud del punto de conexión de CreateParticipantToken
, puede 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()); } }
Continuación de la sesión en segundo plano
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(); }
Activación o desactivación de la codificación en capas con transmisión simultánea
Al publicar una transmisión multimedia, el SDK transmite transmisión de video de alta y baja calidad, de modo que los participantes remotos puedan suscribirse a transmisión incluso si tienen un ancho de banda de enlace descendente limitado. La codificación en capas con transmisión simultánea está activada de forma predeterminada. Puede desactivarla mediante la clase StageVideoConfiguration.Simulcast
:
// Disable Simulcast StageVideoConfiguration config = new StageVideoConfiguration(); config.simulcast.setEnabled(false); ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config); // Other Stage implementation code
Limitaciones de la configuración de video
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
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
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);