Sélecteur de sortie

Le sélecteur de sortie est une fonctionnalité du SDK Cast qui permet de transférer facilement des contenus entre la lecture locale et la lecture à distance à partir d'Android 13. L'objectif est d'aider les applications d'envoi à contrôler facilement et rapidement l'emplacement de lecture du contenu. Le commutateur de sortie utilise la bibliothèque MediaRouter pour basculer la lecture du contenu entre le haut-parleur du téléphone, les appareils Bluetooth associés et les appareils Cast à distance. Les cas d'utilisation peuvent être divisés en plusieurs scénarios:

Téléchargez et utilisez l'application exemple CastVideos-android pour savoir comment implémenter le sélecteur de sortie dans votre application.

Le commutateur de sortie doit être activé pour prendre en charge les transferts local-à-distant, distant-à-local et distant-à-distant en suivant les étapes décrites dans ce guide. Aucune étape supplémentaire n'est requise pour prendre en charge le transfert entre les haut-parleurs de l'appareil local et les appareils Bluetooth associés.

UI du sélecteur de sortie

Le sélecteur de sortie affiche les appareils locaux et distants disponibles, ainsi que leur état actuel, y compris si l'appareil est sélectionné, s'il est en cours de connexion et le niveau de volume actuel. Si d'autres appareils sont connectés en plus de l'appareil actuel, cliquer sur "Autre appareil" vous permet de transférer la lecture multimédia vers l'appareil sélectionné.

Problèmes connus

  • Les sessions multimédias créées pour la lecture locale seront ignorées et recréées lorsque vous passerez à la notification du SDK Cast.

Points d'entrée

Notification multimédia

Si une application publie une notification multimédia avec MediaSession pour la lecture locale, un chip de notification s'affiche en haut à droite de la notification multimédia avec le nom de l'appareil (comme le haut-parleur du téléphone) sur lequel le contenu est actuellement lu. Appuyer sur le chip de notification ouvre l'UI du système de la boîte de dialogue de commutation de sortie.

Paramètres de volume

Vous pouvez également déclencher l'UI du système de la boîte de dialogue de commutation de sortie en cliquant sur les boutons de volume physiques de l'appareil, en appuyant sur l'icône des paramètres en bas de l'écran, puis sur le texte "Lire <Nom de l'application> sur <Appareil de diffusion>".

Résumé des étapes

Prérequis

  1. Migrez votre application Android existante vers AndroidX.
  2. Mettez à jour le build.gradle de votre application pour utiliser la version minimale requise du SDK Android Sender pour le sélecteur de sortie:
    dependencies {
      ...
      implementation 'com.google.android.gms:play-services-cast-framework:21.2.0'
      ...
    }
  3. L'application est compatible avec les notifications multimédias.
  4. Appareil équipé d'Android 13

Configurer les notifications multimédias

Pour utiliser le sélecteur de sortie, les applications audio et vidéo doivent créer une notification multimédia pour afficher l'état de la lecture et les commandes de leurs contenus multimédias pour la lecture locale. Pour ce faire, vous devez créer un MediaSession, définir le MediaStyle avec le jeton de MediaSession et définir les commandes multimédias sur la notification.

Si vous n'utilisez pas actuellement de MediaStyle et de MediaSession, l'extrait de code ci-dessous montre comment les configurer. Des guides sont disponibles pour configurer les rappels de session multimédia pour les applications audio et vidéo:

Kotlin
// Create a media session. NotificationCompat.MediaStyle
// PlayerService is your own Service or Activity responsible for media playback.
val mediaSession = MediaSessionCompat(this, "PlayerService")

// Create a MediaStyle object and supply your media session token to it.
val mediaStyle = Notification.MediaStyle().setMediaSession(mediaSession.sessionToken)

// Create a Notification which is styled by your MediaStyle object.
// This connects your media session to the media controls.
// Don't forget to include a small icon.
val notification = Notification.Builder(this@PlayerService, CHANNEL_ID)
    .setStyle(mediaStyle)
    .setSmallIcon(R.drawable.ic_app_logo)
    .build()

// Specify any actions which your users can perform, such as pausing and skipping to the next track.
val pauseAction: Notification.Action = Notification.Action.Builder(
        pauseIcon, "Pause", pauseIntent
    ).build()
notification.addAction(pauseAction)
Java
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    // Create a media session. NotificationCompat.MediaStyle
    // PlayerService is your own Service or Activity responsible for media playback.
    MediaSession mediaSession = new MediaSession(this, "PlayerService");

    // Create a MediaStyle object and supply your media session token to it.
    Notification.MediaStyle mediaStyle = new Notification.MediaStyle().setMediaSession(mediaSession.getSessionToken());

    // Specify any actions which your users can perform, such as pausing and skipping to the next track.
    Notification.Action pauseAction = Notification.Action.Builder(pauseIcon, "Pause", pauseIntent).build();

    // Create a Notification which is styled by your MediaStyle object.
    // This connects your media session to the media controls.
    // Don't forget to include a small icon.
    String CHANNEL_ID = "CHANNEL_ID";
    Notification notification = new Notification.Builder(this, CHANNEL_ID)
        .setStyle(mediaStyle)
        .setSmallIcon(R.drawable.ic_app_logo)
        .addAction(pauseAction)
        .build();
}

De plus, pour renseigner la notification avec les informations de votre contenu multimédia, vous devez ajouter les métadonnées et l'état de lecture de votre contenu multimédia à MediaSession.

Pour ajouter des métadonnées à MediaSession, utilisez setMetaData() et fournissez toutes les constantes MediaMetadata pertinentes pour vos contenus multimédias dans MediaMetadataCompat.Builder().

Kotlin
mediaSession.setMetadata(MediaMetadataCompat.Builder()
    // Title
    .putString(MediaMetadata.METADATA_KEY_TITLE, currentTrack.title)

    // Artist
    // Could also be the channel name or TV series.
    .putString(MediaMetadata.METADATA_KEY_ARTIST, currentTrack.artist)

    // Album art
    // Could also be a screenshot or hero image for video content
    // The URI scheme needs to be "content", "file", or "android.resource".
    .putString(
        MediaMetadata.METADATA_KEY_ALBUM_ART_URI, currentTrack.albumArtUri)
    )

    // Duration
    // If duration isn't set, such as for live broadcasts, then the progress
    // indicator won't be shown on the seekbar.
    .putLong(MediaMetadata.METADATA_KEY_DURATION, currentTrack.duration)

    .build()
)
Java
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    mediaSession.setMetadata(
        new MediaMetadataCompat.Builder()
        // Title
        .putString(MediaMetadata.METADATA_KEY_TITLE, currentTrack.title)

        // Artist
        // Could also be the channel name or TV series.
        .putString(MediaMetadata.METADATA_KEY_ARTIST, currentTrack.artist)

        // Album art
        // Could also be a screenshot or hero image for video content
        // The URI scheme needs to be "content", "file", or "android.resource".
        .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, currentTrack.albumArtUri)

        // Duration
        // If duration isn't set, such as for live broadcasts, then the progress
        // indicator won't be shown on the seekbar.
        .putLong(MediaMetadata.METADATA_KEY_DURATION, currentTrack.duration)

        .build()
    );
}

Pour ajouter l'état de lecture à MediaSession, utilisez setPlaybackState() et fournissez toutes les constantes PlaybackStateCompat pertinentes pour vos contenus multimédias dans PlaybackStateCompat.Builder().

Kotlin
mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setState(
            PlaybackStateCompat.STATE_PLAYING,

            // Playback position
            // Used to update the elapsed time and the progress bar.
            mediaPlayer.currentPosition.toLong(),

            // Playback speed
            // Determines the rate at which the elapsed time changes.
            playbackSpeed
        )

        // isSeekable
        // Adding the SEEK_TO action indicates that seeking is supported
        // and makes the seekbar position marker draggable. If this is not
        // supplied seek will be disabled but progress will still be shown.
        .setActions(PlaybackStateCompat.ACTION_SEEK_TO)
        .build()
)
Java
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    mediaSession.setPlaybackState(
        new PlaybackStateCompat.Builder()
            .setState(
                 PlaybackStateCompat.STATE_PLAYING,

                // Playback position
                // Used to update the elapsed time and the progress bar.
                mediaPlayer.currentPosition.toLong(),

                // Playback speed
                // Determines the rate at which the elapsed time changes.
                playbackSpeed
            )

        // isSeekable
        // Adding the SEEK_TO action indicates that seeking is supported
        // and makes the seekbar position marker draggable. If this is not
        // supplied seek will be disabled but progress will still be shown.
        .setActions(PlaybackStateCompat.ACTION_SEEK_TO)
        .build()
    );
}

Comportement des notifications des applications vidéo

Les applications vidéo ou audio qui n'acceptent pas la lecture locale en arrière-plan doivent avoir un comportement spécifique pour les notifications multimédias afin d'éviter les problèmes d'envoi de commandes multimédias lorsque la lecture n'est pas prise en charge:

  • Publiez la notification multimédia lorsque vous lisez des contenus multimédias en local et que l'application est exécutée au premier plan.
  • Mettez en pause la lecture locale et ignorez la notification lorsque l'application est en arrière-plan.
  • Lorsque l'application revient au premier plan, la lecture locale doit reprendre et la notification doit être publiée à nouveau.

Activer le sélecteur de sortie dans AndroidManifest.xml

Pour activer le sélecteur de sortie, vous devez ajouter MediaTransferReceiver au AndroidManifest.xml de l'application. Si ce n'est pas le cas, la fonctionnalité ne sera pas activée et l'indicateur de fonctionnalité à distance vers local sera également incorrect.

<application>
    ...
    <receiver
         android:name="androidx.mediarouter.media.MediaTransferReceiver"
         android:exported="true">
    </receiver>
    ...
</application>

MediaTransferReceiver est un broadcast receiver qui permet le transfert de contenus multimédias entre les appareils avec l'UI du système. Pour en savoir plus, consultez la documentation de référence sur MediaTransferReceiver.

Local-to-remote

Lorsque l'utilisateur passe de la lecture locale à la lecture à distance, le SDK Cast démarre automatiquement la session Cast. Toutefois, les applications doivent gérer le passage de la lecture locale à la lecture à distance, par exemple en arrêtant la lecture locale et en chargeant le contenu multimédia sur l'appareil Cast. Les applications doivent écouter le SessionManagerListener de diffusion à l'aide des rappels onSessionStarted() et onSessionEnded(), et gérer l'action lorsqu'elles reçoivent les rappels SessionManager de diffusion. Les applications doivent s'assurer que ces rappels sont toujours actifs lorsque la boîte de dialogue du sélecteur de sortie est ouverte et que l'application n'est pas au premier plan.

Mise à jour de SessionManagerListener pour le casting en arrière-plan

L'ancienne expérience de diffusion est déjà compatible avec la diffusion locale vers distante lorsque l'application est au premier plan. Une expérience Cast typique commence lorsque les utilisateurs cliquent sur l'icône Cast dans l'application et sélectionnent un appareil pour diffuser des contenus multimédias. Dans ce cas, l'application doit s'enregistrer auprès de SessionManagerListener, dans onCreate() ou onStart(), et désenregistrer l'écouteur dans onStop() ou onDestroy() de l'activité de l'application.

Avec la nouvelle expérience de diffusion à l'aide du sélecteur de sortie, les applications peuvent commencer à diffuser du contenu en arrière-plan. Cela est particulièrement utile pour les applications audio qui publient des notifications lorsque la lecture se fait en arrière-plan. Les applications peuvent enregistrer les écouteurs SessionManager dans le onCreate() du service et se désenregistrer dans le onDestroy() du service. Les applications doivent toujours recevoir les rappels locaux-à-distants (tels que onSessionStarted) lorsqu'elles sont en arrière-plan.

Si l'application utilise MediaBrowserService, il est recommandé d'y enregistrer SessionManagerListener.

Kotlin
class MyService : Service() {
    private var castContext: CastContext? = null
    protected fun onCreate() {
        castContext = CastContext.getSharedInstance(this)
        castContext
            .getSessionManager()
            .addSessionManagerListener(sessionManagerListener, CastSession::class.java)
    }

    protected fun onDestroy() {
        if (castContext != null) {
            castContext
                .getSessionManager()
                .removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
        }
    }
}
Java
public class MyService extends Service {
  private CastContext castContext;

  @Override
  protected void onCreate() {
     castContext = CastContext.getSharedInstance(this);
     castContext
        .getSessionManager()
        .addSessionManagerListener(sessionManagerListener, CastSession.class);
  }

  @Override
  protected void onDestroy() {
    if (castContext != null) {
       castContext
          .getSessionManager()
          .removeSessionManagerListener(sessionManagerListener, CastSession.class);
    }
  }
}

Avec cette mise à jour, la diffusion locale vers distante fonctionne de la même manière que la diffusion classique lorsque l'application est en arrière-plan, et aucune tâche supplémentaire n'est requise pour passer des appareils Bluetooth aux appareils Cast.

À distance vers local

Le sélecteur de sortie permet de passer de la lecture à distance à la sortie audio du téléphone ou à l'appareil Bluetooth local. Pour ce faire, définissez l'option setRemoteToLocalEnabled sur true sur CastOptions.

Dans le cas où l'appareil d'envoi actuel rejoint une session existante avec plusieurs expéditeurs et que l'application doit vérifier si le contenu multimédia actuel est autorisé à être transféré localement, les applications doivent utiliser le rappel onTransferred de SessionTransferCallback pour vérifier SessionState.

Définir l'indicateur setRemoteToLocalEnabled

CastOptions.Builder fournit un setRemoteToLocalEnabled pour afficher ou masquer le haut-parleur du téléphone et les appareils Bluetooth locaux en tant que cibles de transfert dans la boîte de dialogue du sélecteur de sortie lorsqu'une session Cast est active.

Kotlin
class CastOptionsProvider : OptionsProvider {
    fun getCastOptions(context: Context?): CastOptions {
        ...
        return Builder()
            ...
            .setRemoteToLocalEnabled(true)
            .build()
    }
}
Java
public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context context) {
        ...
        return new CastOptions.Builder()
            ...
            .setRemoteToLocalEnabled(true)
            .build()
  }
}

poursuivre la lecture localement ;

Les applications compatibles avec la conversion à distance vers le local doivent enregistrer SessionTransferCallback pour recevoir une notification lorsque l'événement se produit afin de vérifier si le transfert de contenu multimédia est autorisé et de poursuivre la lecture en local.

CastContext#addSessionTransferCallback(SessionTransferCallback) permet à une application d'enregistrer son SessionTransferCallback et d'écouter les rappels onTransferred et onTransferFailed lorsqu'un expéditeur est transféré vers la lecture locale.

Une fois que l'application a désinscrit son SessionTransferCallback, elle ne reçoit plus de SessionTransferCallback.

SessionTransferCallback est une extension des rappels SessionManagerListener existants et se déclenche après le déclenchement de onSessionEnded. L'ordre des rappels à distance vers le local est le suivant:

  1. onTransferring
  2. onSessionEnding
  3. onSessionEnded
  4. onTransferred

Étant donné que le sélecteur de sortie peut être ouvert par le chip de notification multimédia lorsque l'application est en arrière-plan et en streaming, les applications doivent gérer le transfert vers le local différemment selon qu'elles prennent en charge la lecture en arrière-plan ou non. En cas d'échec du transfert, onTransferFailed se déclenche à tout moment où l'erreur se produit.

Applications compatibles avec la lecture en arrière-plan

Pour les applications compatibles avec la lecture en arrière-plan (généralement les applications audio), il est recommandé d'utiliser un Service (par exemple, MediaBrowserService). Les services doivent écouter le rappel onTransferred et reprendre la lecture localement, que l'application soit au premier plan ou en arrière-plan.

Kotlin
class MyService : Service() {
    private var castContext: CastContext? = null
    private var sessionTransferCallback: SessionTransferCallback? = null
    protected fun onCreate() {
        castContext = CastContext.getSharedInstance(this)
        castContext.getSessionManager()
                   .addSessionManagerListener(sessionManagerListener, CastSession::class.java)
        sessionTransferCallback = MySessionTransferCallback()
        castContext.addSessionTransferCallback(sessionTransferCallback)
    }

    protected fun onDestroy() {
        if (castContext != null) {
            castContext.getSessionManager()
                       .removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
            if (sessionTransferCallback != null) {
                castContext.removeSessionTransferCallback(sessionTransferCallback)
            }
        }
    }

    class MySessionTransferCallback : SessionTransferCallback() {
        fun onTransferring(@SessionTransferCallback.TransferType transferType: Int) {
            // Perform necessary steps prior to onTransferred
        }

        fun onTransferred(@SessionTransferCallback.TransferType transferType: Int,
                          sessionState: SessionState?) {
            if (transferType == SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
                // Remote stream is transferred to the local device.
                // Retrieve information from the SessionState to continue playback on the local player.
            }
        }

        fun onTransferFailed(@SessionTransferCallback.TransferType transferType: Int,
                             @SessionTransferCallback.TransferFailedReason transferFailedReason: Int) {
            // Handle transfer failure.
        }
    }
}
Java
public class MyService extends Service {
    private CastContext castContext;
    private SessionTransferCallback sessionTransferCallback;

    @Override
    protected void onCreate() {
        castContext = CastContext.getSharedInstance(this);
        castContext.getSessionManager()
                   .addSessionManagerListener(sessionManagerListener, CastSession.class);
        sessionTransferCallback = new MySessionTransferCallback();
        castContext.addSessionTransferCallback(sessionTransferCallback);
    }

    @Override
    protected void onDestroy() {
        if (castContext != null) {
            castContext.getSessionManager()
                       .removeSessionManagerListener(sessionManagerListener, CastSession.class);
            if (sessionTransferCallback != null) {
                castContext.removeSessionTransferCallback(sessionTransferCallback);
            }
        }
    }

    public static class MySessionTransferCallback extends SessionTransferCallback {
        public MySessionTransferCallback() {}

        @Override
        public void onTransferring(@SessionTransferCallback.TransferType int transferType) {
            // Perform necessary steps prior to onTransferred
        }

        @Override
        public void onTransferred(@SessionTransferCallback.TransferType int transferType,
                                  SessionState sessionState) {
            if (transferType==SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
                // Remote stream is transferred to the local device.
                // Retrieve information from the SessionState to continue playback on the local player.
            }
        }

        @Override
        public void onTransferFailed(@SessionTransferCallback.TransferType int transferType,
                                     @SessionTransferCallback.TransferFailedReason int transferFailedReason) {
            // Handle transfer failure.
        }
    }
}

Applications non compatibles avec la lecture en arrière-plan

Pour les applications qui ne prennent pas en charge la lecture en arrière-plan (généralement les applications vidéo), il est recommandé d'écouter le rappel onTransferred et de reprendre la lecture localement si l'application est au premier plan.

Si l'application est en arrière-plan, elle doit mettre en pause la lecture et stocker les informations nécessaires à partir de SessionState (par exemple, les métadonnées multimédias et la position de lecture). Lorsque l'application passe au premier plan depuis l'arrière-plan, la lecture locale doit se poursuivre avec les informations stockées.

Kotlin
class MyActivity : AppCompatActivity() {
    private var castContext: CastContext? = null
    private var sessionTransferCallback: SessionTransferCallback? = null
    protected fun onCreate() {
        castContext = CastContext.getSharedInstance(this)
        castContext.getSessionManager()
                   .addSessionManagerListener(sessionManagerListener, CastSession::class.java)
        sessionTransferCallback = MySessionTransferCallback()
        castContext.addSessionTransferCallback(sessionTransferCallback)
    }

    protected fun onDestroy() {
        if (castContext != null) {
            castContext.getSessionManager()
                       .removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
            if (sessionTransferCallback != null) {
                castContext.removeSessionTransferCallback(sessionTransferCallback)
            }
        }
    }

    class MySessionTransferCallback : SessionTransferCallback() {
        fun onTransferring(@SessionTransferCallback.TransferType transferType: Int) {
            // Perform necessary steps prior to onTransferred
        }

        fun onTransferred(@SessionTransferCallback.TransferType transferType: Int,
                          sessionState: SessionState?) {
            if (transferType == SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
                // Remote stream is transferred to the local device.

                // Retrieve information from the SessionState to continue playback on the local player.
            }
        }

        fun onTransferFailed(@SessionTransferCallback.TransferType transferType: Int,
                             @SessionTransferCallback.TransferFailedReason transferFailedReason: Int) {
            // Handle transfer failure.
        }
    }
}
Java
public class MyActivity extends AppCompatActivity {
  private CastContext castContext;
  private SessionTransferCallback sessionTransferCallback;

  @Override
  protected void onCreate() {
     castContext = CastContext.getSharedInstance(this);
     castContext
        .getSessionManager()
        .addSessionManagerListener(sessionManagerListener, CastSession.class);
     sessionTransferCallback = new MySessionTransferCallback();
     castContext.addSessionTransferCallback(sessionTransferCallback);
  }

  @Override
  protected void onDestroy() {
    if (castContext != null) {
       castContext
          .getSessionManager()
          .removeSessionManagerListener(sessionManagerListener, CastSession.class);
      if (sessionTransferCallback != null) {
         castContext.removeSessionTransferCallback(sessionTransferCallback);
      }
    }
  }

  public static class MySessionTransferCallback extends SessionTransferCallback {
    public MySessionTransferCallback() {}

    @Override
    public void onTransferring(@SessionTransferCallback.TransferType int transferType) {
        // Perform necessary steps prior to onTransferred
    }

    @Override
    public void onTransferred(@SessionTransferCallback.TransferType int transferType,
                               SessionState sessionState) {
      if (transferType==SessionTransferCallback.TRANSFER_TYPE_FROM_REMOTE_TO_LOCAL) {
        // Remote stream is transferred to the local device.

        // Retrieve information from the SessionState to continue playback on the local player.
      }
    }

    @Override
    public void onTransferFailed(@SessionTransferCallback.TransferType int transferType,
                                 @SessionTransferCallback.TransferFailedReason int transferFailedReason) {
      // Handle transfer failure.
    }
  }
}

Télécommande à télécommande

Le sélecteur de sortie permet d'étendre la diffusion à plusieurs appareils de haut-parleurs compatibles avec Cast pour les applications audio à l'aide de la diffusion étendue.

Les applications audio sont celles qui prennent en charge Google Cast pour l'audio dans les paramètres de l'application réceptrice dans la console développeur du SDK Google Cast.

Diffusion étendue avec des enceintes

Les applications audio qui utilisent le sélecteur de sortie peuvent diffuser l'audio sur plusieurs appareils de haut-parleurs compatibles avec Cast lors d'une session Cast à l'aide de l'extension de flux.

Cette fonctionnalité est compatible avec la plate-forme Cast et ne nécessite aucune autre modification si l'application utilise l'UI par défaut. Si une UI personnalisée est utilisée, l'application doit mettre à jour l'UI pour indiquer qu'elle diffuse du contenu vers un groupe.

Pour obtenir le nouveau nom du groupe développé lors d'une expansion de flux, enregistrez un Cast.Listener à l'aide de CastSession#addCastListener. Appelez ensuite CastSession#getCastDevice() lors du rappel onDeviceNameChanged.

Kotlin
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mCastContext: CastContext
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()
    private val mCastListener = CastListener()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarting(session: CastSession?) {}

        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            addCastListener(session)
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {}

        override fun onSessionSuspended(session: CastSession?, reason Int) {
            removeCastListener()
        }

        override fun onSessionResuming(session: CastSession?, sessionId: String) {}

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            addCastListener(session)
        }

        override fun onSessionResumeFailed(session: CastSession?, error: Int) {}

        override fun onSessionEnding(session: CastSession?) {}

        override fun onSessionEnded(session: CastSession?, error: Int) {
            removeCastListener()
        }
    }

    private inner class CastListener : Cast.Listener() {
        override fun onDeviceNameChanged() {
            mCastSession?.let {
                val castDevice = it.castDevice
                val deviceName = castDevice.friendlyName
                // Update UIs with the new cast device name.
            }
        }
    }

    private fun addCastListener(castSession: CastSession) {
        mCastSession = castSession
        mCastSession?.addCastListener(mCastListener)
    }

    private fun removeCastListener() {
        mCastSession?.removeCastListener(mCastListener)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mCastContext = CastContext.getSharedInstance(this)
        mSessionManager = mCastContext.sessionManager
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onDestroy() {
        super.onDestroy()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }
}
Java
public class MyActivity extends Activity {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();
    private Cast.Listener mCastListener = new CastListener();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            addCastListener(session);
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {}
        @Override
        public void onSessionSuspended(CastSession session, int reason) {
            removeCastListener();
        }
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            addCastListener(session);
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            removeCastListener();
        }
    }

    private class CastListener extends Cast.Listener {
         @Override
         public void onDeviceNameChanged() {
             if (mCastSession == null) {
                 return;
             }
             CastDevice castDevice = mCastSession.getCastDevice();
             String deviceName = castDevice.getFriendlyName();
             // Update UIs with the new cast device name.
         }
    }

    private void addCastListener(CastSession castSession) {
        mCastSession = castSession;
        mCastSession.addCastListener(mCastListener);
    }

    private void removeCastListener() {
        if (mCastSession != null) {
            mCastSession.removeCastListener(mCastListener);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCastContext = CastContext.getSharedInstance(this);
        mSessionManager = mCastContext.getSessionManager();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
}

Tester un transfert à distance

Pour tester la fonctionnalité:

  1. Caster du contenu sur un appareil compatible Cast à l'aide de la fonctionnalité de casting classique ou de la fonctionnalité local-to-remote
  2. Ouvrez le sélecteur de sortie à l'aide de l'un des points d'entrée.
  3. Appuyez sur un autre appareil compatible avec Cast. Les applications audio étendent le contenu sur l'appareil supplémentaire, créant ainsi un groupe dynamique.
  4. Appuyez à nouveau sur l'appareil compatible avec Cast. Il sera supprimé du groupe dynamique.