Pubblicazione e sottoscrizione con l'SDK di trasmissione IVS su Android | Streaming in tempo reale
Questo documento illustra i passaggi necessari per pubblicare e sottoscrivere una fase utilizzando l'SDK di trasmissione IVS per lo streaming in tempo reale su Android.
Concetti
La funzionalità in tempo reale si basa su tre concetti fondamentali: fase, strategia e renderer. L'obiettivo di progettazione è ridurre al minimo la quantità di logica lato client necessaria per creare un prodotto funzionante.
Stage
La classe Stage
è il principale punto di interazione tra l'applicazione host e l'SDK. Rappresenta lo stage stesso e serve per entrare e uscire dallo stage. La creazione e la partecipazione a uno stage richiedono una stringa di token valida e non scaduta dal piano di controllo (control-plane) (rappresentata come token
). Entrare e uscire da uno stage è semplice.
Stage stage = new Stage(context, token, strategy); try { stage.join(); } catch (BroadcastException exception) { // handle join exception } stage.leave();
La classe Stage
è anche il luogo in cui può essere collegato lo StageRenderer
:
stage.addRenderer(renderer); // multiple renderers can be added
Strategia
L'interfaccia Stage.Strategy
consente all'applicazione host di comunicare lo stato desiderato dello stage all'SDK. È necessario implementare tre funzioni: shouldSubscribeToParticipant
, shouldPublishFromParticipant
e stageStreamsToPublishForParticipant
. Sono tutte analizzate di seguito.
Sottoscrizione ai partecipanti
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
Quando un partecipante remoto partecipa allo stage, l'SDK interroga l'applicazione host sullo stato della sottoscrizione desiderato per quel partecipante. Le opzioni sono NONE
, AUDIO_ONLY
e AUDIO_VIDEO
. Quando si restituisce un valore per questa funzione, l'applicazione host non deve preoccuparsi dello stato di pubblicazione, dello stato della sottoscrizione corrente o dello stato della connessione allo stage. Se viene restituito AUDIO_VIDEO
, l'SDK attende che il partecipante remoto effettui la pubblicazione prima della sottoscrizione e aggiorna l'applicazione host tramite il renderer durante tutto il processo.
Di seguito è riportata un'implementazione di esempio:
@Override Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return Stage.SubscribeType.AUDIO_VIDEO; }
Questa è l'implementazione completa di questa funzione per un'applicazione host che vuole sempre che tutti i partecipanti si vedano tra loro, ad esempio un'applicazione di chat video.
Sono possibili anche implementazioni più avanzate. Utilizza la proprietà userInfo
su ParticipantInfo
per iscriverti selettivamente ai partecipanti in base agli attributi forniti dal server:
@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; } }
Questa può essere usata per creare uno stage in cui i moderatori possano monitorare tutti gli ospiti senza essere visti o ascoltati. L'applicazione host potrebbe utilizzare una logica aziendale aggiuntiva per consentire ai moderati di vedersi, ma rimanendo invisibili agli ospiti.
Configurazione dell'abbonamento ai partecipanti
SubscribeConfiguration subscribeConfigurationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
Se un partecipante moto viene abbonato (consulta la sezione Abbonamento ai partecipanti), l'SDK interroga l'applicazione host su una configurazione di abbonamento personalizzata per tale partecipante. Questa configurazione è facoltativa e consente all'applicazione host di controllare determinati aspetti del comportamento dell'abbonato. Per informazioni sui valori che è possibile configurare, consulta la sezione SubscribeConfiguration
Di seguito è riportata un'implementazione di esempio:
@Override public SubscribeConfiguration subscribeConfigrationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { SubscribeConfiguration config = new SubscribeConfiguration(); config.jitterBuffer.setMinDelay(JitterBufferConfiguration.JitterBufferDelay.MEDIUM()); return config; }
Questa implementazione aggiorna il ritardo minimo del jitter-buffer per tutti i partecipanti abbonati a un valore predefinito di MEDIUM
.
Come per shouldSubscribeToParticipant
, sono possibili anche implementazioni più avanzate. Il valore ParticipantInfo
dato può essere utilizzato per aggiornare selettivamente la configurazione di abbonamento per partecipanti specifici.
Consigliamo di utilizzare i valori predefiniti. Specifica la configurazione personalizzata solo se desideri modificare un comportamento particolare.
Pubblicazione
boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
Una volta connesso allo stage, l'SDK interroga l'applicazione host per vedere se un dato partecipante deve eseguire una pubblicazione. Viene richiamata solo per i partecipanti locali che hanno il permesso di pubblicare in base al token fornito.
Di seguito è riportata un'implementazione di esempio:
@Override boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return true; }
Si tratta di un'applicazione di chat video standard in cui gli utenti vogliono sempre pubblicare. Possono disattivare e riattivare l'audio e il video per essere nascosti o visti/ascoltati immediatamente. Possono anche usare il comando di pubblicazione/annullamento della pubblicazione, ma è molto più lento. È preferibile disattivare/riattivare l'audio nei casi d'uso in cui è consigliabile modificare spesso la visibilità.
Scelta dei flussi da pubblicare
@Override List<LocalStageStream> stageStreamsToPublishForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo); }
Durante la pubblicazione, serve a determinare quali flussi audio e video devono essere pubblicati. Questo argomento verrà trattato dettagliatamente più avanti in Pubblicazione di un flusso multimediale.
Aggiornamento della strategia
La strategia è pensata per essere dinamica: è possibile modificare i valori restituiti da una qualsiasi delle funzioni precedenti in qualsiasi momento. Ad esempio, se l'applicazione host non desidera pubblicare finché l'utente finale non tocca un pulsante, è possibile restituire una variabile da shouldPublishFromParticipant
(del tipo hasUserTappedPublishButton
). Quando quella variabile cambia in base a un'interazione da parte dell'utente finale, chiama stage.refreshStrategy()
per segnalare all'SDK che dovrebbe eseguire una query sulla strategia per i valori più recenti, applicando solo quanto modificato. Se l'SDK rileva che il valore shouldPublishFromParticipant
è cambiato, avvierà il processo di pubblicazione. Se le query dell'SDK e tutte le funzioni restituiscono lo stesso valore di prima, la chiamata refreshStrategy
non apporterà alcuna modifica allo stage.
Se il valore restituito di shouldSubscribeToParticipant
cambia da AUDIO_VIDEO
a AUDIO_ONLY
, il flusso video verrà rimosso per tutti i partecipanti con valori restituiti modificati, se in precedenza esisteva un flusso video.
In genere, lo stage utilizza la strategia per applicare in modo più efficiente la differenza tra le strategie precedenti e quelle attuali, senza che l'applicazione host debba preoccuparsi di tutto lo stato necessario per gestirla correttamente. Per questo motivo, considera la chiamata a stage.refreshStrategy()
come un'operazione a basso costo, perché non viene eseguita a meno che la strategia non cambi.
Renderer
L'interfaccia StageRenderer
comunica lo stato desiderato dello stage all'applicazione host. Gli aggiornamenti all'interfaccia utente dell'applicazione host in genere possono essere alimentati interamente dagli eventi forniti dal renderer. Il renderer fornisce le funzioni seguenti:
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);
Per la maggior parte di questi metodi, vengono forniti Stage
e ParticipantInfo
corrispondenti.
Non è previsto che le informazioni fornite dal renderer influiscano sui valori restituiti della strategia. Ad esempio, non è previsto che il valore restituito di shouldSubscribeToParticipant
cambi quando viene chiamato onParticipantPublishStateChanged
. Se l'applicazione host desidera effettuare la sottoscrizione a un particolare partecipante, deve restituire il tipo di abbonamento desiderato indipendentemente dallo stato di pubblicazione di quel partecipante. L'SDK è responsabile di garantire che lo stato desiderato della strategia venga applicato al momento giusto in base allo stato dello stage.
Lo StageRenderer
può essere collegato alla classe dello stage:
stage.addRenderer(renderer); // multiple renderers can be added
Ricorda che solo i partecipanti alla pubblicazione attivano onParticipantJoined
e ogni volta che un partecipante interrompe la pubblicazione o abbandona la sessione dello stage viene attivato onParticipantLeft
.
Pubblicazione di un flusso multimediale
I dispositivi locali, come microfoni e fotocamere integrati, vengono rilevati tramite DeviceDiscovery
. Ecco un esempio di selezione della fotocamera frontale e del microfono predefinito, che vengono poi restituiti come LocalStageStreams
per la pubblicazione dall'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; }
Visualizzazione e rimozione dei partecipanti
Una volta completata la sottoscrizione, riceverai una serie di oggetti StageStream
tramite la funzione onStreamsAdded
del renderer. Puoi recuperare l'anteprima da un 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);
Puoi recuperare le statistiche del livello audio da un AudioStageStream
:
((AudioStageStream)stream).setStatsCallback((peak, rms) -> { // handle statistics });
Quando un partecipante interrompe la pubblicazione o annulla l'iscrizione, la funzione onStreamsRemoved
viene chiamata con i flussi che sono stati rimossi. Le applicazioni host devono utilizzarlo come segnale per rimuovere il flusso video del partecipante dalla gerarchia delle visualizzazioni.
onStreamsRemoved
viene richiamato per tutti gli scenari in cui un flusso potrebbe essere rimosso, tra cui:
-
Il partecipante remoto interrompe la pubblicazione.
-
Un dispositivo locale annulla l'iscrizione o modifica l'abbonamento da
AUDIO_VIDEO
aAUDIO_ONLY
. -
Il partecipante remoto lascia lo stage.
-
Il partecipante locale lascia lo stage.
Poiché onStreamsRemoved
viene richiamato per tutti gli scenari, non è richiesta alcuna logica aziendale personalizzata per la rimozione dei partecipanti dall'interfaccia utente durante le operazioni di abbandono remote o locali.
Disattivazione e riattivazione dell'audio dei flussi multimediali
Gli oggetti LocalStageStream
hanno una funzione setMuted
che controlla se l'audio del flusso è disattivato. Questa funzione può essere richiamata sul flusso prima o dopo la restituzione dalla funzione della strategia streamsToPublishForParticipant
.
Importante: se una nuova istanza di oggetto LocalStageStream
viene restituita da streamsToPublishForParticipant
dopo una chiamata a refreshStrategy
, lo stato di silenziamento del nuovo oggetto di flusso viene applicato allo stage. Fai attenzione quando crei nuove istanze LocalStageStream
per assicurarti che lo stato di silenziamento previsto venga mantenuto.
Monitoraggio dello stato di silenziamento dei contenuti multimediali dei partecipanti remoti
Quando un partecipante modifica lo stato di silenziamento del proprio flusso video o audio, la funzione onStreamMutedChanged
del renderer viene richiamata con un elenco di flussi che sono stati modificati. Usa il metodo getMuted
su StageStream
per aggiornare l'interfaccia utente di conseguenza.
@Override void onStreamsMutedChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams) { for (StageStream stream : streams) { boolean muted = stream.getMuted(); // handle UI changes } }
Ottenimento delle statistiche WebRTC
Per ottenere le statistiche WebRTC più recenti per un flusso di pubblicazione o un flusso di iscrizione, usa requestRTCStats
su StageStream
. Quando una raccolta è completata, riceverai statistiche tramite il StageStream.Listener
che può essere impostato su 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()); } } }
Ottieni gli attributi dei partecipanti
Se specifichi gli attributi nella richiesta dell'endpoint CreateParticipantToken
, puoi visualizzarli nelle proprietà 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()); } }
Continuazione della sessione in background
Quando l'app entra in background, potresti voler interrompere la pubblicazione o iscriverti solo all'audio degli altri partecipanti remoti. A tale scopo, aggiorna l'implementazione Strategy
per interrompere la pubblicazione e iscriviti a AUDIO_ONLY
(o NONE
, se applicabile).
// 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(); }
Abilitazione/disabilitazione della codifica a livelli con simulcast
Quando si pubblica un flusso multimediale, l'SDK trasmette flussi video di alta e bassa qualità, pertanto i partecipanti remoti possono sottoscrivere il flusso anche se hanno una larghezza di banda limitata per il downlink. La codifica a livelli con simulcast è attiva per impostazione predefinita. Puoi disabilitarla usando la classe StageVideoConfiguration.Simulcast
:
// Disable Simulcast StageVideoConfiguration config = new StageVideoConfiguration(); config.simulcast.setEnabled(false); ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config); // Other Stage implementation code
Limitazioni alla configurazione video
L'SDK non supporta l'uso forzato della modalità verticale o della modalità orizzontale StageVideoConfiguration.setSize(BroadcastConfiguration.Vec2 size)
. Nell'orientamento verticale, la dimensione più piccola viene utilizzata come larghezza; nell'orientamento orizzontale, l'altezza. Ciò significa che le due chiamate seguenti a setSize
avranno lo stesso effetto sulla configurazione video:
StageVideo Configuration config = new StageVideo Configuration(); config.setSize(BroadcastConfiguration.Vec2(720f, 1280f); config.setSize(BroadcastConfiguration.Vec2(1280f, 720f);
Gestione dei problemi di rete
Quando si perde la connessione di rete del dispositivo locale, l'SDK tenta internamente di riconnettersi senza alcuna azione da parte dell'utente. In alcuni casi, l'SDK non funziona ed è necessaria un'azione da parte dell'utente. Esistono due errori principali relativi alla perdita della connessione di rete:
-
Codice di errore 1400, messaggio: "PeerConnection is lost due to unknown network error" (Connessione peer interrotta a causa di un errore di rete sconosciuto)
-
Codice di errore 1300, messaggio: "Retry attempts are exhausted" (Nuovi tentativi esauriti)
Se viene ricevuto il primo errore ma il secondo no, l'SDK è ancora connesso allo stage e tenterà di ristabilire automaticamente le connessioni. Come misura di sicurezza, puoi chiamare refreshStrategy
senza modificare i valori restituiti dal metodo di strategia, per attivare un tentativo di riconnessione manuale.
Se viene ricevuto il secondo errore, i tentativi di riconnessione dell'SDK sono falliti e il dispositivo locale non è più connesso allo stage. In questo caso, prova a rientrare nello stage chiamando join
dopo aver ristabilito la connessione di rete.
In generale, il verificarsi di errori dopo l'accesso corretto a uno stage indica che l'SDK non è riuscito a ristabilire una connessione. Crea un nuovo oggetto Stage
e prova ad accedere quando le condizioni della rete migliorano.
Uso dei microfoni Bluetooth
Per pubblicare utilizzando dispositivi microfonici Bluetooth, è necessario avviare una connessione Bluetooth SCO:
Bluetooth.startBluetoothSco(context); // Now bluetooth microphones can be used … // Must also stop bluetooth SCO Bluetooth.stopBluetoothSco(context);