Aggiungi funzionalità principali al ricevitore Android TV

Questa pagina contiene snippet di codice e descrizioni delle funzionalità disponibili per personalizzare un'app Android TV ricevitore.

Configurazione delle librerie

Per rendere disponibili le API Cast Connect nella tua app Android TV:

Android
  1. Apri il file build.gradle nella directory del modulo dell'applicazione.
  2. Verifica che google() sia incluso nell'elenco repositories.
      repositories {
        google()
      }
  3. A seconda del tipo di dispositivo di destinazione per la tua app, aggiungi le versioni più recenti delle librerie alle dipendenze:
    • Per l'app Android Ricevir:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast-tv:21.0.1'
          implementation 'com.google.android.gms:play-services-cast:21.4.0'
        }
    • Per l'app Android Sender:
        dependencies {
          implementation 'com.google.android.gms:play-services-cast:21.0.1'
          implementation 'com.google.android.gms:play-services-cast-framework:21.4.0'
        }
    Assicurati di aggiornare questo numero di versione ogni volta che i servizi vengono aggiornati.
  4. Salva le modifiche e fai clic su Sync Project with Gradle Files nella barra degli strumenti.
iOS
  1. Assicurati che Podfile abbia come target google-cast-sdk 4.8.0 o versioni successive
  2. Scegli come target iOS 13 o versioni successive. Vedi le note di rilascio per maggiori dettagli.
      platform: ios, '13'
    
      def target_pods
         pod 'google-cast-sdk', '~>4.8.0'
      end
Web
  1. Richiede il browser Chromium M87 o versioni successive.
  2. Aggiungi la libreria API Web Sender al tuo progetto
      <script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>

Requisito AndroidX

Le nuove versioni di Google Play Services richiedono che un'app sia stata aggiornata in modo da utilizzare lo spazio dei nomi androidx. Segui le istruzioni per eseguire la migrazione ad AndroidX.

App Android TV: prerequisiti

Per poter supportare Cast Connect nella tua app Android TV, devi creare e supportare eventi da una sessione multimediale. I dati forniti dalla sessione multimediale forniscono informazioni di base, ad esempio posizione, stato di riproduzione e così via, relative allo stato dei contenuti multimediali. La sessione multimediale viene utilizzata anche dalla raccolta di Cast Connect per segnalare la ricezione di determinati messaggi da un mittente, ad esempio una pausa.

Per ulteriori informazioni sulla sessione multimediale e su come inizializzarla, consulta la guida sull'utilizzo di una sessione multimediale.

Ciclo di vita della sessione multimediale

L'app deve creare una sessione multimediale quando inizia la riproduzione e rilasciarla quando non è più possibile controllarla. Ad esempio, se la tua app è un'app di video, devi rilasciare la sessione quando l'utente esce dall'attività di riproduzione, selezionando "Indietro" per sfogliare altri contenuti o mettendo in background l'app. Se la tua app è un'app di musica, devi rilasciarla quando l'app non riproduce più contenuti multimediali.

Aggiornamento dello stato della sessione

I dati della sessione multimediale devono essere sempre aggiornati con lo stato del player. Ad esempio, quando la riproduzione è in pausa, devi aggiornare lo stato di riproduzione e le azioni supportate. Nelle tabelle che seguono sono elencati gli stati di cui sei responsabile per essere sempre aggiornato.

MediaMetadataCompat

Campo metadati Descrizione
METADATA_KEY_TITLE (obbligatorio) Il titolo dell'elemento multimediale.
METADATA_KEY_DISPLAY_SUBTITLE Il sottotitolo.
METADATA_KEY_DISPLAY_ICON_URI L'URL dell'icona.
METADATA_KEY_DURATION (obbligatorio) Durata dei contenuti multimediali.
METADATA_KEY_MEDIA_URI Content ID.
METADATA_KEY_ARTIST L'artista.
METADATA_KEY_ALBUM L'album.

PlaybackStateCompat

Metodo richiesto Descrizione
setActions() Imposta i comandi multimediali supportati.
setState() Consente di impostare lo stato di riproduzione e la posizione corrente.

MediaSessionCompat

Metodo richiesto Descrizione
setRepeatMode() Imposta la modalità di ripetizione.
setShuffleMode() Imposta la modalità di riproduzione casuale.
setMetadata() Imposta i metadati dei contenuti multimediali.
setPlaybackState() Imposta lo stato di riproduzione.
Kotlin
private fun updateMediaSession() {
    val metadata = MediaMetadataCompat.Builder()
         .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title")
         .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle")
         .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, mMovie.getCardImageUrl())
         .build()

    val playbackState = PlaybackStateCompat.Builder()
         .setState(
             PlaybackStateCompat.STATE_PLAYING,
             player.getPosition(),
             player.getPlaybackSpeed(),
             System.currentTimeMillis()
        )
         .build()

    mediaSession.setMetadata(metadata)
    mediaSession.setPlaybackState(playbackState)
}
Java
private void updateMediaSession() {
  MediaMetadataCompat metadata =
      new MediaMetadataCompat.Builder()
          .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title")
          .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle")
          .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,mMovie.getCardImageUrl())
          .build();

  PlaybackStateCompat playbackState =
      new PlaybackStateCompat.Builder()
          .setState(
               PlaybackStateCompat.STATE_PLAYING,
               player.getPosition(),
               player.getPlaybackSpeed(),
               System.currentTimeMillis())
          .build();

  mediaSession.setMetadata(metadata);
  mediaSession.setPlaybackState(playbackState);
}

Gestione del controllo dei trasporti

La tua app deve implementare il callback di controllo del trasporto della sessione multimediale. La tabella seguente mostra quali azioni di controllo del trasporto devono gestire:

MediaSessionCompat.Callback

Azioni Descrizione
onPlay() Ripristina
onPause() Metti in pausa
onSeekTo() Vai a una posizione
onStop() Interrompi il contenuto multimediale corrente
Kotlin
class MyMediaSessionCallback : MediaSessionCompat.Callback() {
  override fun onPause() {
    // Pause the player and update the play state.
    ...
  }

  override fun onPlay() {
    // Resume the player and update the play state.
    ...
  }

  override fun onSeekTo (long pos) {
    // Seek and update the play state.
    ...
  }
  ...
}

mediaSession.setCallback( MyMediaSessionCallback() );
Java
public MyMediaSessionCallback extends MediaSessionCompat.Callback {
  public void onPause() {
    // Pause the player and update the play state.
    ...
  }

  public void onPlay() {
    // Resume the player and update the play state.
    ...
  }

  public void onSeekTo (long pos) {
    // Seek and update the play state.
    ...
  }
  ...
}

mediaSession.setCallback(new MyMediaSessionCallback());

Configurare il supporto della trasmissione

Quando un'applicazione del mittente invia una richiesta di avvio, viene creato un intent con uno spazio dei nomi dell'applicazione. L'applicazione è responsabile della sua gestione e della creazione di un'istanza dell'oggetto CastReceiverContext quando viene avviata l'app TV. L'oggetto CastReceiverContext è necessario per interagire con la trasmissione mentre l'app TV è in esecuzione. Questo oggetto consente all'applicazione TV di accettare messaggi multimediali trasmessi da qualsiasi mittente connesso.

Configurazione di Android TV

Aggiungere un filtro per intent di lancio

Aggiungi un nuovo filtro per intent all'attività che vuoi gestire l'intent di avvio dall'app del mittente:

<activity android:name="com.example.activity">
  <intent-filter>
      <action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
      <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>

Specifica il fornitore delle opzioni del destinatario

Devi implementare un ReceiverOptionsProvider per fornire CastReceiverOptions:

Kotlin
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
          .setStatusText("My App")
          .build()
    }
}
Java
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
  @Override
  public CastReceiverOptions getOptions(Context context) {
    return new CastReceiverOptions.Builder(context)
        .setStatusText("My App")
        .build();
  }
}

Quindi specifica il fornitore di opzioni nel tuo AndroidManifest:

 <meta-data
    android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
    android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />

ReceiverOptionsProvider viene utilizzato per fornire CastReceiverOptions quando CastReceiverContext viene inizializzato.

Contesto del ricevitore di trasmissione

Inizializza il CastReceiverContext quando viene creata l'app:

Kotlin
override fun onCreate() {
  CastReceiverContext.initInstance(this)

  ...
}
Java
@Override
public void onCreate() {
  CastReceiverContext.initInstance(this);

  ...
}

Avvia CastReceiverContext quando la tua app passa in primo piano:

Kotlin
CastReceiverContext.getInstance().start()
Java
CastReceiverContext.getInstance().start();

Chiama stop() sul CastReceiverContext dopo che l'app è passata in background per le app video o le app che non supportano la riproduzione in background:

Kotlin
// Player has stopped.
CastReceiverContext.getInstance().stop()
Java
// Player has stopped.
CastReceiverContext.getInstance().stop();

Inoltre, se la tua app supporta la riproduzione in background, chiama stop() sul CastReceiverContext quando la riproduzione viene interrotta in background.

Ti consigliamo vivamente di utilizzare LifecycleExampler disponibile nella raccolta androidx.lifecycle per gestire le chiamate CastReceiverContext.start() e CastReceiverContext.stop(), soprattutto se la tua app nativa ha più attività. In questo modo si evitano le condizioni di gara quando chiami start() e stop() da attività diverse.

Kotlin
// Create a LifecycleObserver class.
class MyLifecycleObserver : DefaultLifecycleObserver {
  override fun onStart(owner: LifecycleOwner) {
    // App prepares to enter foreground.
    CastReceiverContext.getInstance().start()
  }

  override fun onStop(owner: LifecycleOwner) {
    // App has moved to the background or has terminated.
    CastReceiverContext.getInstance().stop()
  }
}

// Add the observer when your application is being created.
class MyApplication : Application() {
  fun onCreate() {
    super.onCreate()

    // Initialize CastReceiverContext.
    CastReceiverContext.initInstance(this /* android.content.Context */)

    // Register LifecycleObserver
    ProcessLifecycleOwner.get().lifecycle.addObserver(
        MyLifecycleObserver())
  }
}
Java
// Create a LifecycleObserver class.
public class MyLifecycleObserver implements DefaultLifecycleObserver {
  @Override
  public void onStart(LifecycleOwner owner) {
    // App prepares to enter foreground.
    CastReceiverContext.getInstance().start();
  }

  @Override
  public void onStop(LifecycleOwner owner) {
    // App has moved to the background or has terminated.
    CastReceiverContext.getInstance().stop();
  }
}

// Add the observer when your application is being created.
public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();

    // Initialize CastReceiverContext.
    CastReceiverContext.initInstance(this /* android.content.Context */);

    // Register LifecycleObserver
    ProcessLifecycleOwner.get().getLifecycle().addObserver(
        new MyLifecycleObserver());
  }
}
// In AndroidManifest.xml set MyApplication as the application class
<application
    ...
    android:name=".MyApplication">

Connessione MediaSession a MediaManager

Quando crei un elemento MediaSession, devi anche fornire l'attuale token MediaSession a CastReceiverContext affinché sappia dove inviare i comandi e recuperare lo stato di riproduzione dei contenuti multimediali:

Kotlin
val mediaManager: MediaManager = receiverContext.getMediaManager()
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
Java
MediaManager mediaManager = receiverContext.getMediaManager();
mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());

Quando rilasci il tuo MediaSession a causa di una riproduzione inattiva, devi impostare un token null su MediaManager:

Kotlin
myPlayer.stop()
mediaSession.release()
mediaManager.setSessionCompatToken(null)
Java
myPlayer.stop();
mediaSession.release();
mediaManager.setSessionCompatToken(null);

Se la tua app supporta la riproduzione di contenuti multimediali mentre è in background, anziché chiamare CastReceiverContext.stop() quando l'app viene inviata in background, dovresti chiamare l'app solo quando l'app è in background e non riproduce più contenuti multimediali. Ad esempio:

Kotlin
class MyLifecycleObserver : DefaultLifecycleObserver {
  ...
  // App has moved to the background.
  override fun onPause(owner: LifecycleOwner) {
    mIsBackground = true
    myStopCastReceiverContextIfNeeded()
  }
}

// Stop playback on the player.
private fun myStopPlayback() {
  myPlayer.stop()

  myStopCastReceiverContextIfNeeded()
}

// Stop the CastReceiverContext when both the player has
// stopped and the app has moved to the background.
private fun myStopCastReceiverContextIfNeeded() {
  if (mIsBackground && myPlayer.isStopped()) {
    CastReceiverContext.getInstance().stop()
  }
}
Java
public class MyLifecycleObserver implements DefaultLifecycleObserver {
  ...
  // App has moved to the background.
  @Override
  public void onPause(LifecycleOwner owner) {
    mIsBackground = true;

    myStopCastReceiverContextIfNeeded();
  }
}

// Stop playback on the player.
private void myStopPlayback() {
  myPlayer.stop();

  myStopCastReceiverContextIfNeeded();
}

// Stop the CastReceiverContext when both the player has
// stopped and the app has moved to the background.
private void myStopCastReceiverContextIfNeeded() {
  if (mIsBackground && myPlayer.isStopped()) {
    CastReceiverContext.getInstance().stop();
  }
}

Utilizzare Exoplayer con Cast Connect

Se usi Exoplayer, invece di monitorare le modifiche manualmente puoi usare MediaSessionConnector per gestire automaticamente la sessione e tutte le informazioni correlate, incluso lo stato di riproduzione.

MediaSessionConnector.MediaButtonEventHandler può essere utilizzato per gestire gli eventi MediaButton chiamando setMediaButtonEventHandler(MediaButtonEventHandler) che sono altrimenti gestiti da MediaSessionCompat.Callback per impostazione predefinita.

Per integrare MediaSessionConnector nella tua app, aggiungi quanto segue alla classe di attività del player o alla sessione in cui gestisci la sessione multimediale:

Kotlin
class PlayerActivity : Activity() {
  private var mMediaSession: MediaSessionCompat? = null
  private var mMediaSessionConnector: MediaSessionConnector? = null
  private var mMediaManager: MediaManager? = null

  override fun onCreate(savedInstanceState: Bundle?) {
    ...
    mMediaSession = MediaSessionCompat(this, LOG_TAG)
    mMediaSessionConnector = MediaSessionConnector(mMediaSession!!)
    ...
  }

  override fun onStart() {
    ...
    mMediaManager = receiverContext.getMediaManager()
    mMediaManager!!.setSessionCompatToken(currentMediaSession.getSessionToken())
    mMediaSessionConnector!!.setPlayer(mExoPlayer)
    mMediaSessionConnector!!.setMediaMetadataProvider(mMediaMetadataProvider)
    mMediaSession!!.isActive = true
    ...
  }

  override fun onStop() {
    ...
    mMediaSessionConnector!!.setPlayer(null)
    mMediaSession!!.release()
    mMediaManager!!.setSessionCompatToken(null)
    ...
  }
}
Java
public class PlayerActivity extends Activity {
  private MediaSessionCompat mMediaSession;
  private MediaSessionConnector mMediaSessionConnector;
  private MediaManager mMediaManager;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    ...
    mMediaSession = new MediaSessionCompat(this, LOG_TAG);
    mMediaSessionConnector = new MediaSessionConnector(mMediaSession);
    ...
  }

  @Override
  protected void onStart() {
    ...
    mMediaManager = receiverContext.getMediaManager();
    mMediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());

    mMediaSessionConnector.setPlayer(mExoPlayer);
    mMediaSessionConnector.setMediaMetadataProvider(mMediaMetadataProvider);
    mMediaSession.setActive(true);
    ...
  }

  @Override
  protected void onStop() {
    ...
    mMediaSessionConnector.setPlayer(null);
    mMediaSession.release();
    mMediaManager.setSessionCompatToken(null);
    ...
  }
}

Configurazione app mittente

Attiva il supporto di Cast Connect

Dopo aver aggiornato l'app del mittente con il supporto di Cast Connect, puoi dichiararne l'idoneità impostando il flag androidReceiverCompatible su LaunchOptions su true.

Android

Richiede play-services-cast-framework versione 19.0.0 o successiva.

Il flag androidReceiverCompatible è impostato in LaunchOptions (che fa parte di CastOptions):

Kotlin
class CastOptionsProvider : OptionsProvider {
  override fun getCastOptions(context: Context?): CastOptions {
    val launchOptions: LaunchOptions = Builder()
          .setAndroidReceiverCompatible(true)
          .build()
    return CastOptions.Builder()
          .setLaunchOptions(launchOptions)
          ...
          .build()
    }
}
Java
public class CastOptionsProvider implements OptionsProvider {
  @Override
  public CastOptions getCastOptions(Context context) {
    LaunchOptions launchOptions = new LaunchOptions.Builder()
              .setAndroidReceiverCompatible(true)
              .build();
    return new CastOptions.Builder()
        .setLaunchOptions(launchOptions)
        ...
        .build();
  }
}
iOS

Richiede google-cast-sdk versione v4.4.8 o successiva.

Il flag androidReceiverCompatible è impostato in GCKLaunchOptions (che fa parte di GCKCastOptions):

let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions
GCKCastContext.setSharedInstanceWith(options)
Web

Richiede la versione M87 o versioni successive del browser Chromium.

const context = cast.framework.CastContext.getInstance();
const castOptions = new cast.framework.CastOptions();
castOptions.receiverApplicationId = kReceiverAppID;
castOptions.androidReceiverCompatible = true;
context.setOptions(castOptions);

Configurazione della Console per gli sviluppatori di Google Cast

Configurare l'app Android TV

Aggiungi il nome del pacchetto dell'app Android TV nella Console per gli sviluppatori di Google Cast per associarla al tuo ID app Cast.

Registra i dispositivi degli sviluppatori

Registra il numero di serie del dispositivo Android TV che utilizzerai per lo sviluppo nella Console per gli sviluppatori di Cast.

Senza registrazione, Cast Connect funziona solo per le app installate dal Google Play Store per motivi di sicurezza.

Per ulteriori informazioni sulla registrazione di un dispositivo Google Cast o Android TV per lo sviluppo di Google Cast, visita la pagina di registrazione.

Caricamento contenuti multimediali in corso...

Se hai già implementato il supporto dei link diretti nell'app Android TV, dovresti avere una definizione simile configurata nel file manifest di Android TV:

<activity android:name="com.example.activity">
  <intent-filter>
     <action android:name="android.intent.action.VIEW" />
     <category android:name="android.intent.category.DEFAULT" />
     <data android:scheme="https"/>
     <data android:host="www.example.com"/>
     <data android:pathPattern=".*"/>
  </intent-filter>
</activity>

Carica per entità sul mittente

Per i mittenti, puoi passare il link diretto impostando entity nelle informazioni multimediali per la richiesta di caricamento:

Kotlin
val mediaToLoad = MediaInfo.Builder("some-id")
    .setEntity("https://example.com/watch/some-id")
    ...
    .build()
val loadRequest = MediaLoadRequestData.Builder()
    .setMediaInfo(mediaToLoad)
    .setCredentials("user-credentials")
    ...
    .build()
remoteMediaClient.load(loadRequest)
Android
Java
MediaInfo mediaToLoad =
    new MediaInfo.Builder("some-id")
        .setEntity("https://example.com/watch/some-id")
        ...
        .build();
MediaLoadRequestData loadRequest =
    new MediaLoadRequestData.Builder()
        .setMediaInfo(mediaToLoad)
        .setCredentials("user-credentials")
        ...
        .build();
remoteMediaClient.load(loadRequest);
iOS
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id")
...
mediaInformation = mediaInfoBuilder.build()

let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.mediaInformation = mediaInformation
mediaLoadRequestDataBuilder.credentials = "user-credentials"
...
let mediaLoadRequestData = mediaLoadRequestDataBuilder.build()

remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Web

Richiede la versione M87 o versioni successive del browser Chromium.

let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4');
mediaInfo.entity = 'https://example.com/watch/some-id';
...

let request = new chrome.cast.media.LoadRequest(mediaInfo);
request.credentials = 'user-credentials';
...

cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);

Il comando di caricamento viene inviato tramite un intent con il link diretto e il nome del pacchetto che hai definito nella console per gli sviluppatori.

Impostazione delle credenziali ATV sul mittente

È possibile che le app Ricevitore web e Android TV supportino link diretti e credentials diversi (ad esempio se gestisci l'autenticazione in modo diverso sulle due piattaforme). Per risolvere questo problema, puoi fornire entity e credentials alternativi per Android TV:

Android
Kotlin
val mediaToLoad = MediaInfo.Builder("some-id")
        .setEntity("https://example.com/watch/some-id")
        .setAtvEntity("myscheme://example.com/atv/some-id")
        ...
        .build()
val loadRequest = MediaLoadRequestData.Builder()
        .setMediaInfo(mediaToLoad)
        .setCredentials("user-credentials")
        .setAtvCredentials("atv-user-credentials")
        ...
        .build()
remoteMediaClient.load(loadRequest)
Java
MediaInfo mediaToLoad =
    new MediaInfo.Builder("some-id")
        .setEntity("https://example.com/watch/some-id")
        .setAtvEntity("myscheme://example.com/atv/some-id")
        ...
        .build();
MediaLoadRequestData loadRequest =
    new MediaLoadRequestData.Builder()
        .setMediaInfo(mediaToLoad)
        .setCredentials("user-credentials")
        .setAtvCredentials("atv-user-credentials")
        ...
        .build();
remoteMediaClient.load(loadRequest);
iOS
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id")
mediaInfoBuilder.atvEntity = "myscheme://example.com/atv/some-id"
...
mediaInformation = mediaInfoBuilder.build()

let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.mediaInformation = mediaInformation
mediaLoadRequestDataBuilder.credentials = "user-credentials"
mediaLoadRequestDataBuilder.atvCredentials = "atv-user-credentials"
...
let mediaLoadRequestData = mediaLoadRequestDataBuilder.build()

remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Web

Richiede la versione M87 o versioni successive del browser Chromium.

let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4');
mediaInfo.entity = 'https://example.com/watch/some-id';
mediaInfo.atvEntity = 'myscheme://example.com/atv/some-id';
...

let request = new chrome.cast.media.LoadRequest(mediaInfo);
request.credentials = 'user-credentials';
request.atvCredentials = 'atv-user-credentials';
...

cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);

Se l'app ricevitore web viene avviata, utilizza entity e credentials nella richiesta di caricamento. Tuttavia, se viene avviata l'app Android TV, l'SDK sostituisce entity e credentials con atvEntity e atvCredentials (se specificati).

Caricamento tramite Content ID o MediaQueueData

Se non usi entity o atvEntity, Content ID o URL dei contenuti nelle informazioni sui contenuti multimediali o i dati più dettagliati della richiesta di caricamento dei contenuti multimediali, devi aggiungere il seguente filtro per intent predefinito nell'app Android TV:

<activity android:name="com.example.activity">
  <intent-filter>
     <action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
     <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>

Sul lato del mittente, come per la funzione Carica per entità, puoi creare una richiesta di caricamento con le informazioni sui tuoi contenuti e chiamare load().

Android
Kotlin
val mediaToLoad = MediaInfo.Builder("some-id").build()
val loadRequest = MediaLoadRequestData.Builder()
    .setMediaInfo(mediaToLoad)
    .setCredentials("user-credentials")
    ...
    .build()
remoteMediaClient.load(loadRequest)
Java
MediaInfo mediaToLoad =
    new MediaInfo.Builder("some-id").build();
MediaLoadRequestData loadRequest =
    new MediaLoadRequestData.Builder()
        .setMediaInfo(mediaToLoad)
        .setCredentials("user-credentials")
        ...
        .build();
remoteMediaClient.load(loadRequest);
iOS
let mediaInfoBuilder = GCKMediaInformationBuilder(contentId: "some-id")
...
mediaInformation = mediaInfoBuilder.build()

let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.mediaInformation = mediaInformation
mediaLoadRequestDataBuilder.credentials = "user-credentials"
...
let mediaLoadRequestData = mediaLoadRequestDataBuilder.build()

remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Web

Richiede la versione M87 o versioni successive del browser Chromium.

let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4');
...

let request = new chrome.cast.media.LoadRequest(mediaInfo);
...

cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);

Gestione delle richieste di carico

Nella tua attività, per gestire queste richieste di carico, devi gestire gli intent nei callback del ciclo di vita delle attività:

Kotlin
class MyActivity : Activity() {
  override fun onStart() {
    super.onStart()
    val mediaManager = CastReceiverContext.getInstance().getMediaManager()
    // Pass the intent to the SDK. You can also do this in onCreate().
    if (mediaManager.onNewIntent(intent)) {
        // If the SDK recognizes the intent, you should early return.
        return
    }
    // If the SDK doesn't recognize the intent, you can handle the intent with
    // your own logic.
    ...
  }

  // For some cases, a new load intent triggers onNewIntent() instead of
  // onStart().
  override fun onNewIntent(intent: Intent) {
    val mediaManager = CastReceiverContext.getInstance().getMediaManager()
    // Pass the intent to the SDK. You can also do this in onCreate().
    if (mediaManager.onNewIntent(intent)) {
        // If the SDK recognizes the intent, you should early return.
        return
    }
    // If the SDK doesn't recognize the intent, you can handle the intent with
    // your own logic.
    ...
  }
}
Java
public class MyActivity extends Activity {
  @Override
  protected void onStart() {
    super.onStart();
    MediaManager mediaManager =
        CastReceiverContext.getInstance().getMediaManager();
    // Pass the intent to the SDK. You can also do this in onCreate().
    if (mediaManager.onNewIntent(getIntent())) {
      // If the SDK recognizes the intent, you should early return.
      return;
    }
    // If the SDK doesn't recognize the intent, you can handle the intent with
    // your own logic.
    ...
  }

  // For some cases, a new load intent triggers onNewIntent() instead of
  // onStart().
  @Override
  protected void onNewIntent(Intent intent) {
    MediaManager mediaManager =
        CastReceiverContext.getInstance().getMediaManager();
    // Pass the intent to the SDK. You can also do this in onCreate().
    if (mediaManager.onNewIntent(intent)) {
      // If the SDK recognizes the intent, you should early return.
      return;
    }
    // If the SDK doesn't recognize the intent, you can handle the intent with
    // your own logic.
    ...
  }
}

Se MediaManager rileva che l'intent è un intent di caricamento, estrae un oggetto MediaLoadRequestData dall'intent e richiama MediaLoadCommandCallback.onLoad(). Devi eseguire l'override di questo metodo per gestire la richiesta di caricamento. Il callback deve essere registrato prima della chiamata MediaManager.onNewIntent() (è consigliabile utilizzare un metodo Attività o Applicazione onCreate()).

Kotlin
class MyActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val mediaManager = CastReceiverContext.getInstance().getMediaManager()
        mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback())
    }
}

class MyMediaLoadCommandCallback : MediaLoadCommandCallback() {
  override fun onLoad(
        senderId: String?,
        loadRequestData: MediaLoadRequestData
  ): Task {
      return Tasks.call {
        // Resolve the entity into your data structure and load media.
        val mediaInfo = loadRequestData.getMediaInfo()
        if (!checkMediaInfoSupported(mediaInfo)) {
            // Throw MediaException to indicate load failure.
            throw MediaException(
                MediaError.Builder()
                    .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED)
                    .setReason(MediaError.ERROR_REASON_INVALID_REQUEST)
                    .build()
            )
        }
        myFillMediaInfo(MediaInfoWriter(mediaInfo))
        myPlayerLoad(mediaInfo.getContentUrl())

        // Update media metadata and state (this clears all previous status
        // overrides).
        castReceiverContext.getMediaManager()
            .setDataFromLoad(loadRequestData)
        ...
        castReceiverContext.getMediaManager().broadcastMediaStatus()

        // Return the resolved MediaLoadRequestData to indicate load success.
        return loadRequestData
     }
  }

  private fun myPlayerLoad(contentURL: String) {
    myPlayer.load(contentURL)

    // Update the MediaSession state.
    val playbackState: PlaybackStateCompat = Builder()
        .setState(
            player.getState(), player.getPosition(), System.currentTimeMillis()
        )
        ...
        .build()
    mediaSession.setPlaybackState(playbackState)
  }
Java
public class MyActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    MediaManager mediaManager =
        CastReceiverContext.getInstance().getMediaManager();
    mediaManager.setMediaLoadCommandCallback(new MyMediaLoadCommandCallback());
  }
}

public class MyMediaLoadCommandCallback extends MediaLoadCommandCallback {
  @Override
  public Task onLoad(String senderId, MediaLoadRequestData loadRequestData) {
    return Tasks.call(() -> {
        // Resolve the entity into your data structure and load media.
        MediaInfo mediaInfo = loadRequestData.getMediaInfo();
        if (!checkMediaInfoSupported(mediaInfo)) {
          // Throw MediaException to indicate load failure.
          throw new MediaException(
              new MediaError.Builder()
                  .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED)
                  .setReason(MediaError.ERROR_REASON_INVALID_REQUEST)
                  .build());
        }
        myFillMediaInfo(new MediaInfoWriter(mediaInfo));
        myPlayerLoad(mediaInfo.getContentUrl());

        // Update media metadata and state (this clears all previous status
        // overrides).
        castReceiverContext.getMediaManager()
            .setDataFromLoad(loadRequestData);
        ...
        castReceiverContext.getMediaManager().broadcastMediaStatus();

        // Return the resolved MediaLoadRequestData to indicate load success.
        return loadRequestData;
    });
}

private void myPlayerLoad(String contentURL) {
  myPlayer.load(contentURL);

  // Update the MediaSession state.
  PlaybackStateCompat playbackState =
      new PlaybackStateCompat.Builder()
          .setState(
              player.getState(), player.getPosition(), System.currentTimeMillis())
          ...
          .build();
  mediaSession.setPlaybackState(playbackState);
}

Per elaborare l'intent di caricamento, puoi analizzarlo nelle strutture di dati da noi definite (MediaLoadRequestData per le richieste di caricamento).

Supporto dei comandi multimediali

Supporto per il controllo della riproduzione di base

I comandi di integrazione di base includono i comandi compatibili con la sessione multimediale. Questi comandi vengono avvisati tramite callback delle sessioni multimediali. Devi registrare un callback per la sessione multimediale a supporto della funzionalità (è possibile che tu lo stia già facendo).

Kotlin
private class MyMediaSessionCallback : MediaSessionCompat.Callback() {
  override fun onPause() {
    // Pause the player and update the play state.
    myPlayer.pause()
  }

  override fun onPlay() {
    // Resume the player and update the play state.
    myPlayer.play()
  }

  override fun onSeekTo(pos: Long) {
    // Seek and update the play state.
    myPlayer.seekTo(pos)
  }
    ...
 }

mediaSession.setCallback(MyMediaSessionCallback())
Java
private class MyMediaSessionCallback extends MediaSessionCompat.Callback {
  @Override
  public void onPause() {
    // Pause the player and update the play state.
    myPlayer.pause();
  }
  @Override
  public void onPlay() {
    // Resume the player and update the play state.
    myPlayer.play();
  }
  @Override
  public void onSeekTo(long pos) {
    // Seek and update the play state.
    myPlayer.seekTo(pos);
  }

  ...
}

mediaSession.setCallback(new MyMediaSessionCallback());

Supporto dei comandi di controllo della trasmissione

Alcuni comandi di trasmissione non sono disponibili in MediaSession, ad esempio skipAd() o setActiveMediaTracks(). Inoltre, alcuni comandi della coda devono essere implementati qui perché la coda di trasmissione non è completamente compatibile con la coda MediaSession.

Kotlin
class MyMediaCommandCallback : MediaCommandCallback() {
    override fun onSkipAd(requestData: RequestData?): Task {
        // Skip your ad
        ...
        return Tasks.forResult(null)
    }
}

val mediaManager = CastReceiverContext.getInstance().getMediaManager()
mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
Java
public class MyMediaCommandCallback extends MediaCommandCallback {
  @Override
  public Task onSkipAd(RequestData requestData) {
    // Skip your ad
    ...
    return Tasks.forResult(null);
  }
}

MediaManager mediaManager =
    CastReceiverContext.getInstance().getMediaManager();
mediaManager.setMediaCommandCallback(new MyMediaCommandCallback());

Specifica i comandi multimediali supportati

Come per il ricevitore di trasmissione, anche l'app Android TV deve specificare quali comandi sono supportati, in modo che i mittenti possano attivare o disattivare determinati controlli dell'interfaccia utente. Per i comandi che fanno parte di MediaSession, specifica i comandi in PlaybackStateCompat. I comandi aggiuntivi devono essere specificati nel MediaStatusModifier.

Kotlin
// Set media session supported commands
val playbackState: PlaybackStateCompat = PlaybackStateCompat.Builder()
    .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE)
    .setState(PlaybackStateCompat.STATE_PLAYING)
    .build()

mediaSession.setPlaybackState(playbackState)

// Set additional commands in MediaStatusModifier
val mediaManager = CastReceiverContext.getInstance().getMediaManager()
mediaManager.getMediaStatusModifier()
    .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT)
Java
// Set media session supported commands
PlaybackStateCompat playbackState =
    new PlaybackStateCompat.Builder()
        .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE)
        .setState(PlaybackStateCompat.STATE_PLAYING)
        .build();

mediaSession.setPlaybackState(playbackState);

// Set additional commands in MediaStatusModifier
MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager();
mediaManager.getMediaStatusModifier()
            .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT);

Nascondi pulsanti non supportati

Se la tua app Android TV supporta solo il controllo dei contenuti multimediali di base, ma l'app web ricevitore supporta un controllo più avanzato, devi assicurarti che l'app del mittente si comporti correttamente quando trasmetti all'app Android TV. Ad esempio, se l'app Android TV non supporta la modifica della velocità di riproduzione mentre l'app ricevitore web lo supporta, devi impostare correttamente le azioni supportate su ogni piattaforma e assicurarti che l'app del mittente visualizzi correttamente l'interfaccia utente.

Modifica di MediaStatus

Per supportare funzionalità avanzate come tracce, annunci, live streaming e coda, l'app Android TV deve fornire informazioni aggiuntive che non possono essere verificate tramite MediaSession.

Per raggiungere questo obiettivo, ti forniamo la lezione MediaStatusModifier. MediaStatusModifier opererà sempre sul MediaSession che hai impostato in CastReceiverContext.

Per creare e trasmettere MediaStatus:

Kotlin
val mediaManager: MediaManager = castReceiverContext.getMediaManager()
val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier()

statusModifier
    .setLiveSeekableRange(seekableRange)
    .setAdBreakStatus(adBreakStatus)
    .setCustomData(customData)

mediaManager.broadcastMediaStatus()
Java
MediaManager mediaManager = castReceiverContext.getMediaManager();
MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier();

statusModifier
    .setLiveSeekableRange(seekableRange)
    .setAdBreakStatus(adBreakStatus)
    .setCustomData(customData);

mediaManager.broadcastMediaStatus();

La nostra libreria client riceverà MediaStatus di base da MediaSession. La tua app Android TV può specificare uno stato aggiuntivo e sostituire lo stato tramite un modificatore MediaStatus.

Alcuni stati e metadati possono essere impostati sia in MediaSession che in MediaStatusModifier. Ti consigliamo vivamente di impostarli solo in MediaSession. Puoi comunque utilizzare il modificatore per eseguire l'override degli stati in MediaSession. Questa operazione è sconsigliata perché lo stato nel modificatore ha sempre una priorità più alta rispetto ai valori forniti da MediaSession.

Intercettazione di MediaStatus prima dell'invio

Come per l'SDK Web ricevir, se vuoi apportare gli ultimi ritocchi prima di inviare il messaggio, puoi specificare un elemento MediaStatusInterceptor per l'elaborazione di MediaStatus da inviare. Passiamo a un MediaStatusWriter per manipolare il MediaStatus prima che venga inviato.

Kotlin
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor {
    override fun intercept(mediaStatusWriter: MediaStatusWriter) {
      // Perform customization.
        mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}"))
    }
})
Java
mediaManager.setMediaStatusInterceptor(new MediaStatusInterceptor() {
    @Override
    public void intercept(MediaStatusWriter mediaStatusWriter) {
        // Perform customization.
        mediaStatusWriter.setCustomData(new JSONObject("{data: \"my Hello\"}"));
    }
});

Gestione delle credenziali utente

L'app Android TV potrebbe consentire solo a determinati utenti di avviare o partecipare alla sessione dell'app. Ad esempio, puoi consentire a un mittente di avviarsi o partecipare solo se:

  • L'app del mittente ha eseguito l'accesso allo stesso account e allo stesso profilo dell'app ATV.
  • L'app del mittente ha eseguito l'accesso allo stesso account, ma a un profilo diverso dell'app ATV.

Se l'app può gestire più utenti anonimi, puoi consentire a qualsiasi utente aggiuntivo di partecipare alla sessione ATV. Se l'utente fornisce le credenziali, l'app ATV deve gestire le sue credenziali in modo che i suoi progressi e altri dati utente possano essere monitorati correttamente.

Quando l'app del mittente avvia la tua app Android TV o si unisce, l'app del mittente deve fornire le credenziali che rappresentano chi partecipa alla sessione.

Prima che un mittente avvii la tua app Android TV e vi partecipi, puoi specificare un controllo di avvio per verificare se le credenziali del mittente sono consentite. In caso contrario, l'SDK di Cast Connect torna all'avvio del tuo ricevitore web.

Dati delle credenziali per l'avvio dell'app del mittente

Sul lato del mittente, puoi specificare il CredentialsData per rappresentare chi partecipa alla sessione.

credentials è una stringa che può essere definita dall'utente, a condizione che l'app ATV sia in grado di comprenderla. credentialsType definisce la piattaforma da cui CredentialsData proviene o può essere un valore personalizzato. Per impostazione predefinita, è impostata sulla piattaforma da cui viene inviata.

Il CredentialsData viene trasmesso alla tua app Android TV soltanto durante il momento di avvio o di iscrizione. Se lo imposti di nuovo mentre il dispositivo è connesso, non verrà trasmesso all'app Android TV. Se il mittente cambia il profilo mentre è connesso, potresti rimanere nella sessione o chiamare il numero SessionManager.endCurrentCastSession(boolean stopCasting) se ritieni che il nuovo profilo non sia compatibile con la sessione.

Il CredentialsData per ciascun mittente può essere recuperato utilizzando getSenders sul CastReceiverContext per ricevere il SenderInfo, getCastLaunchRequest() per prendere CastLaunchRequest e poi getCredentialsData().

Android

Richiede play-services-cast-framework versione 19.0.0 o successiva.

Kotlin
CastContext.getSharedInstance().setLaunchCredentialsData(
    CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build()
)
Java
CastContext.getSharedInstance().setLaunchCredentialsData(
    new CredentialsData.Builder()
        .setCredentials("{\"userId\": \"abc\"}")
        .build());
iOS

Richiede google-cast-sdk versione v4.8.0 o successiva.

Puoi chiamare in qualsiasi momento dopo aver impostato le opzioni: GCKCastContext.setSharedInstanceWith(options).

GCKCastContext.sharedInstance().setLaunch(
    GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Web

Richiede la versione M87 o versioni successive del browser Chromium.

Puoi chiamare in qualsiasi momento dopo aver impostato le opzioni: cast.framework.CastContext.getInstance().setOptions(options);.

let credentialsData =
    new chrome.cast.CredentialsData("{\"userId\": \"abc\"}");
cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);

Implementazione del controllo della richiesta di lancio dell'ATV

Il CredentialsData viene trasmesso alla tua app Android TV quando un mittente prova ad avviarlo o a partecipare. Puoi implementare un LaunchRequestChecker. per consentire o rifiutare la richiesta.

Se una richiesta viene rifiutata, il ricevitore web viene caricato anziché essere avviato in modo nativo nell'app ATV. Devi rifiutare una richiesta se l'ATV non è in grado di gestire l'utente che richiede di lanciare o partecipare. Alcuni esempi potrebbero essere che un utente diverso ha eseguito l'accesso all'app ATV rispetto a quello richiesto e l'app non è in grado di gestire il cambio di credenziali o che al momento non esiste un utente che ha eseguito l'accesso all'app ATV.

Se la richiesta è consentita, viene avviata l'app ATV. Puoi personalizzare questo comportamento a seconda che la tua app supporti l'invio di richieste di carico quando un utente non ha eseguito l'accesso all'app ATV o se si verifica una mancata corrispondenza dell'utente. Questo comportamento è completamente cusomizzabile in LaunchRequestChecker.

Crea una classe che implementa l'interfaccia CastReceiverOptions.LaunchRequestChecker:

Kotlin
class MyLaunchRequestChecker : LaunchRequestChecker {
  override fun checkLaunchRequestSupported(launchRequest: CastLaunchRequest): Task {
    return Tasks.call {
      myCheckLaunchRequest(
           launchRequest
      )
    }
  }
}

private fun myCheckLaunchRequest(launchRequest: CastLaunchRequest): Boolean {
  val credentialsData = launchRequest.getCredentialsData()
     ?: return false // or true if you allow anonymous users to join.

  // The request comes from a mobile device, e.g. checking user match.
  return if (credentialsData.credentialsType == CredentialsData.CREDENTIALS_TYPE_ANDROID) {
     myCheckMobileCredentialsAllowed(credentialsData.getCredentials())
  } else false // Unrecognized credentials type.
}
Java
public class MyLaunchRequestChecker
    implements CastReceiverOptions.LaunchRequestChecker {
  @Override
  public Task checkLaunchRequestSupported(CastLaunchRequest launchRequest) {
    return Tasks.call(() -> myCheckLaunchRequest(launchRequest));
  }
}

private boolean myCheckLaunchRequest(CastLaunchRequest launchRequest) {
  CredentialsData credentialsData = launchRequest.getCredentialsData();
  if (credentialsData == null) {
    return false;  // or true if you allow anonymous users to join.
  }

  // The request comes from a mobile device, e.g. checking user match.
  if (credentialsData.getCredentialsType().equals(CredentialsData.CREDENTIALS_TYPE_ANDROID)) {
    return myCheckMobileCredentialsAllowed(credentialsData.getCredentials());
  }

  // Unrecognized credentials type.
  return false;
}

Quindi impostala nel tuo ReceiverOptionsProvider:

Kotlin
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
        ...
        .setLaunchRequestChecker(MyLaunchRequestChecker())
        .build()
  }
}
Java
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
  @Override
  public CastReceiverOptions getOptions(Context context) {
    return new CastReceiverOptions.Builder(context)
        ...
        .setLaunchRequestChecker(new MyLaunchRequestChecker())
        .build();
  }
}

La risoluzione di true in LaunchRequestChecker avvia l'app ATV e false avvia l'app Web ricevir.

Invio e ricezione di messaggi personalizzati

Il protocollo Cast consente di inviare messaggi stringa personalizzati tra i mittenti e l'applicazione destinatario. Devi registrare uno spazio dei nomi (canale) per l'invio di messaggi prima di inizializzare CastReceiverContext.

Android TV: specifica lo spazio dei nomi personalizzato

Devi specificare gli spazi dei nomi supportati in CastReceiverOptions durante la configurazione:

Kotlin
class MyReceiverOptionsProvider : ReceiverOptionsProvider {
  override fun getOptions(context: Context?): CastReceiverOptions {
    return CastReceiverOptions.Builder(context)
        .setCustomNamespaces(
            Arrays.asList("urn:x-cast:com.example.cast.mynamespace")
        )
        .build()
  }
}
Java
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider {
  @Override
  public CastReceiverOptions getOptions(Context context) {
    return new CastReceiverOptions.Builder(context)
        .setCustomNamespaces(
              Arrays.asList("urn:x-cast:com.example.cast.mynamespace"))
        .build();
  }
}

Android TV - Invio di messaggi

Kotlin
// If senderId is null, then the message is broadcasted to all senders.
CastReceiverContext.getInstance().sendMessage(
    "urn:x-cast:com.example.cast.mynamespace", senderId, customString)
Java
// If senderId is null, then the message is broadcasted to all senders.
CastReceiverContext.getInstance().sendMessage(
    "urn:x-cast:com.example.cast.mynamespace", senderId, customString);

Android TV: ricezione di messaggi personalizzati per lo spazio dei nomi

Kotlin
class MyCustomMessageListener : MessageReceivedListener {
    override fun onMessageReceived(
        namespace: String, senderId: String?, message: String ) {
        ...
    }
}

CastReceiverContext.getInstance().setMessageReceivedListener(
    "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());
Java
class MyCustomMessageListener implements CastReceiverContext.MessageReceivedListener {
  @Override
  public void onMessageReceived(
      String namespace, String senderId, String message) {
    ...
  }
}

CastReceiverContext.getInstance().setMessageReceivedListener(
    "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());