Intégrer Cast dans votre application Android

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Ce guide du développeur explique comment ajouter la compatibilité Google Cast à votre application émettrice Android à l'aide du SDK Android Sender.

L'appareil mobile ou l'ordinateur portable est l'expéditeur qui contrôle la lecture, et l'appareil Google Cast est le destinataire qui affiche le contenu sur le téléviseur.

Le framework d'envoi fait référence au binaire de la bibliothèque de classes Cast et aux ressources associées présentes lors de l'exécution sur l'expéditeur. L'application expéditeur ou l'application Cast fait référence à une application s'exécutant également sur l'expéditeur. L'application de récepteur Web fait référence à l'application HTML exécutée sur l'appareil compatible Cast.

Le framework d'expéditeur utilise une conception de rappel asynchrone pour informer l'application émettrice des événements et pour la transition entre les différents états du cycle de vie de l'application Cast.

Déroulement des opérations de l'application

Les étapes suivantes décrivent le flux d'exécution standard de haut niveau pour une application Android émettrice:

  • Le framework Cast commence automatiquement la détection d'appareils MediaRouter en fonction du cycle de vie Activity.
  • Lorsque l'utilisateur clique sur l'icône Cast, le framework présente la boîte de dialogue Cast avec la liste des appareils Cast détectés.
  • Lorsque l'utilisateur sélectionne un appareil Cast, le framework tente de lancer l'application Récepteur Web sur l'appareil Cast.
  • Le framework invoque des rappels dans l'application émettrice pour confirmer que l'application Web Receiver a été lancée.
  • Le framework crée un canal de communication entre les applications émetteur et récepteur Web.
  • Le framework utilise le canal de communication pour charger et contrôler la lecture des contenus multimédias sur le récepteur Web.
  • Le framework synchronise l'état de lecture du contenu multimédia entre l'expéditeur et le récepteur Web. Lorsque l'utilisateur effectue des actions dans l'interface utilisateur de l'expéditeur, le framework transmet ces requêtes de contrôle multimédia au récepteur Web. Lorsque le récepteur Web envoie des mises à jour de l'état du contenu multimédia, le framework met à jour l'état de l'interface utilisateur de l'expéditeur.
  • Lorsque l'utilisateur clique sur l'icône Cast pour se déconnecter de l'appareil Cast, le framework déconnecte l'application expéditeur du récepteur Web.

Pour obtenir la liste complète des classes, méthodes et événements du SDK Google Cast pour Android, consultez la documentation de référence de l'API Google Cast Sender pour Android. Les sections suivantes vous expliquent comment ajouter Cast à votre application Android.

Configurer le fichier manifeste Android

Le fichier AndroidManifest.xml de votre application nécessite que vous configuriez les éléments suivants pour le SDK Cast:

uses-sdk

Définissez les niveaux d'API Android minimaux et cibles compatibles avec le SDK Cast. Actuellement, la valeur minimale est de niveau 19 et l'objectif est de 28.

<uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="28" />

android:theme.

Définissez le thème de votre application en fonction de la version minimale du SDK Android. Par exemple, si vous ne mettez pas en œuvre votre propre thème, vous devez utiliser une variante de Theme.AppCompat lorsque vous ciblez une version minimale du SDK Android antérieure à Lollipop.

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat" >
       ...
</application>

Initialiser le contexte Cast

Le framework possède un objet singleton global, CastContext, qui coordonne toutes les interactions du framework.

Votre application doit implémenter l'interface OptionsProvider pour fournir les options nécessaires à l'initialisation du singleton CastContext. OptionsProvider fournit une instance de CastOptions qui contient des options qui affectent le comportement du framework. Le plus important est l'ID de l'application du récepteur Web, qui permet de filtrer les résultats de la découverte et de lancer l'application du récepteur Web lors du démarrage d'une session Cast.

Kotlin
class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        return Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
Java
public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context context) {
        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build();
        return castOptions;
    }
    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

Vous devez déclarer le nom complet du fichier OptionsProvider implémenté en tant que champ de métadonnées dans le fichier AndroidManifest.xml de l'application de l'expéditeur:

<application>
    ...
    <meta-data
        android:name=
            "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
        android:value="com.foo.CastOptionsProvider" />
</application>

CastContext est initialisé de manière différée lorsque CastContext.getSharedInstance() est appelé.

Kotlin
class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val castContext = CastContext.getSharedInstance(this)
    }
}
Java
public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        CastContext castContext = CastContext.getSharedInstance(this);
    }
}

Widgets UX Cast

Le framework Cast fournit les widgets conformes à la checklist de conception Cast:

  • Superposition d'introduction : le framework fournit une vue personnalisée, IntroductoryOverlay, qui est présentée à l'utilisateur pour attirer l'attention sur l'icône Cast la première fois qu'un récepteur est disponible. L'application Expéditeur peut personnaliser le texte et la position du texte du titre.

  • Icône Cast : l'icône Cast est visible lorsqu'un destinataire compatible avec votre application est détecté. Lorsque l'utilisateur clique pour la première fois sur l'icône Cast, une boîte de dialogue Cast s'affiche. Elle répertorie les appareils détectés. Lorsque l'utilisateur clique sur l'icône Cast lorsque l'appareil est connecté, les métadonnées multimédias actuelles (telles que le titre, le nom du studio d'enregistrement et une vignette) s'affichent, ou l'utilisateur peut se déconnecter de l'appareil Cast.

  • Mini-télécommande : lorsque l'utilisateur caste du contenu et a quitté la page de contenu actuelle ou la télécommande agrandie vers un autre écran de l'application émettrice, la mini-télécommande s'affiche au bas de l'écran pour permettre à l'utilisateur de voir les métadonnées multimédias en cours de diffusion et de contrôler la lecture.

  • Manette agrandie : lorsque l'utilisateur caste du contenu, s'il clique sur la notification multimédia ou la mini-télécommande, la télécommande agrandie s'ouvre. Elle affiche les métadonnées multimédias en cours de lecture et fournit plusieurs boutons pour contrôler la lecture des contenus multimédias.

  • Notification : Android uniquement. Lorsque l'utilisateur caste du contenu et quitte l'application émettrice, une notification multimédia s'affiche. Elle contient les métadonnées multimédias et les commandes de lecture en cours de diffusion.

  • Verrouillage de l'écran : Android uniquement. Lorsque l'utilisateur caste du contenu et accède à l'écran de verrouillage (ou que l'appareil arrive à expiration), une commande d'écran de verrouillage multimédia s'affiche, indiquant les métadonnées multimédias et les commandes de lecture en cours de diffusion.

Le guide suivant explique comment ajouter ces widgets à votre application.

Ajouter un icône Cast

Les API Android MediaRouter sont conçues pour permettre l'affichage et la lecture de contenus multimédias sur des appareils secondaires. Les applications Android qui utilisent l'API MediaRouter doivent inclure un bouton Cast dans leur interface utilisateur, afin de permettre aux utilisateurs de sélectionner une route multimédia pour lire des contenus multimédias sur un appareil secondaire tel qu'un appareil Cast.

Le framework facilite grandement l'ajout de MediaRouteButton en tant que Cast button. Vous devez d'abord ajouter un élément de menu ou MediaRouteButton dans le fichier XML qui définit votre menu, puis utiliser CastButtonFactory pour le relier au framework.

// To add a Cast button, add the following snippet.
// menu.xml
<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
    app:showAsAction="always" />
Kotlin
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.kt
override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)
    menuInflater.inflate(R.menu.main, menu)
    CastButtonFactory.setUpMediaRouteButton(
        applicationContext,
        menu,
        R.id.media_route_menu_item
    )
    return true
}
Java
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.java
@Override public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.main, menu);
    CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
                                            menu,
                                            R.id.media_route_menu_item);
    return true;
}

Ensuite, si votre Activity hérite de FragmentActivity, vous pouvez ajouter un MediaRouteButton à votre mise en page.

// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:gravity="center_vertical"
   android:orientation="horizontal" >

   <androidx.mediarouter.app.MediaRouteButton
       android:id="@+id/media_route_button"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:mediaRouteTypes="user"
       android:visibility="gone" />

</LinearLayout>
Kotlin
// MyActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_layout)

    mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton
    CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton)

    mCastContext = CastContext.getSharedInstance(this)
}
Java
// MyActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_layout);

   mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
   CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton);

   mCastContext = CastContext.getSharedInstance(this);
}

Pour définir l'apparence de l'icône Cast à l'aide d'un thème, consultez Personnaliser l'icône Cast.

Configurer la détection d'appareils

La détection d'appareils est entièrement gérée par le CastContext. Lors de l'initialisation de CastContext, l'application expéditeur spécifie l'ID d'application du récepteur Web et peut éventuellement demander un filtrage des espaces de noms en définissant supportedNamespaces dans CastOptions. CastContext contient une référence à MediaRouter en interne. Il démarre le processus de découverte lorsque l'application expéditeur passe au premier plan et s'arrête lorsque l'application expéditeur passe en arrière-plan.

Kotlin
class CastOptionsProvider : OptionsProvider {
    companion object {
        const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"
    }

    override fun getCastOptions(appContext: Context): CastOptions {
        val supportedNamespaces: MutableList<String> = ArrayList()
        supportedNamespaces.add(CUSTOM_NAMESPACE)

        return CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
Java
class CastOptionsProvider implements OptionsProvider {
    public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace";

    @Override
    public CastOptions getCastOptions(Context appContext) {
        List<String> supportedNamespaces = new ArrayList<>();
        supportedNamespaces.add(CUSTOM_NAMESPACE);

        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build();
        return castOptions;
    }

    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

Fonctionnement de la gestion des sessions

Le SDK Cast présente le concept de session Cast, dont l'établissement combine les étapes de connexion à un appareil, de lancement (ou de participation) d'une application récepteur Web, de connexion à cette application et d'initialisation d'un canal de commande multimédia. Pour en savoir plus sur les sessions Cast et le cycle de vie du récepteur Web, consultez le Guide de cycle de vie des applications.

Les sessions sont gérées par la classe SessionManager, à laquelle votre application peut accéder via CastContext.getSessionManager(). Les sessions individuelles sont représentées par des sous-classes de la classe Session. Par exemple, CastSession représente les sessions avec des appareils Cast. Votre application peut accéder à la session Cast actuellement active via SessionManager.getCurrentCastSession().

Votre application peut utiliser la classe SessionManagerListener pour surveiller les événements de session, tels que la création, la suspension, la reprise et l'arrêt. Le framework tente automatiquement de reprendre après une interruption anormale ou soudaine lorsqu'une session était active.

Les sessions sont créées et détruites automatiquement en réponse aux gestes de l'utilisateur à partir des boîtes de dialogue MediaRouter.

Si vous devez être informé des changements d'état de la session, vous pouvez implémenter un SessionManagerListener. Cet exemple écoute la disponibilité d'un CastSession dans un Activity.

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

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

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

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

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mSessionManager = CastContext.getSharedInstance(this).sessionManager
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onPause() {
        super.onPause()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
        mCastSession = null
    }
}
Java
public class MyActivity extends Activity {
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSessionManager = CastContext.getSharedInstance(this).getSessionManager();
    }
    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
    @Override
    protected void onPause() {
        super.onPause();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
        mCastSession = null;
    }
}

Transfert de diffusion

La conservation de l'état de la session est la base du transfert de flux, où les utilisateurs peuvent déplacer des flux audio et vidéo existants sur différents appareils à l'aide de commandes vocales, de l'application Google Home ou d'écrans connectés. La lecture du contenu multimédia s'arrête sur un appareil (la source) et sur un autre (la destination). Tout appareil Cast doté du dernier micrologiciel peut servir de sources ou de destinations dans un transfert de flux.

Pour obtenir le nouvel appareil de destination lors d'un transfert ou d'une expansion de flux, enregistrez un Cast.Listener à l'aide de CastSession#addCastListener. Appelez ensuite CastSession#getCastDevice() pendant le rappel onDeviceNameChanged.

Pour en savoir plus, consultez la page Transfert de flux sur le récepteur Web.

Reconnexion automatique

Le framework fournit un ReconnectionService qui peut être activé par l'application émettrice pour gérer la reconnexion dans de nombreux cas subtils, tels que:

  • Récupérer en cas de perte temporaire du Wi-Fi
  • Récupérer de l'appareil en veille
  • Récupérer les données de l'application en arrière-plan
  • Restaurer si l'appli a planté

Ce service est activé par défaut et peut être désactivé dans CastOptions.Builder.

Ce service peut être automatiquement fusionné dans le fichier manifeste de votre application si la fusion automatique est activée dans votre fichier Gradle.

Le framework démarrera le service en cas de session multimédia et l'arrêtera à la fin de cette session.

Fonctionnement des commandes multimédias

Le framework Cast abandonne la classe RemoteMediaPlayer de Cast 2.x au profit d'une nouvelle classe RemoteMediaClient, qui offre les mêmes fonctionnalités dans un ensemble d'API plus pratiques, et évite de transmettre un client GoogleApiClient.

Lorsque votre application établit une CastSession avec une application de récepteur Web compatible avec l'espace de noms multimédia, une instance de RemoteMediaClient est automatiquement créée par le framework. Votre application peut y accéder en appelant la méthode getRemoteMediaClient() sur l'instance CastSession.

Toutes les méthodes de RemoteMediaClient qui envoient des requêtes au récepteur Web renvoient un objet PendingResult qui peut être utilisé pour suivre cette requête.

Il est normal que l'instance de RemoteMediaClient puisse être partagée par plusieurs parties de votre application, et donc par certains composants internes du framework, tels que les mini-contrôleurs persistants et le service de notification. À cette fin, cette instance accepte l'enregistrement de plusieurs instances de RemoteMediaClient.Listener.

Définir les métadonnées multimédias

La classe MediaMetadata représente les informations sur un élément multimédia que vous souhaitez caster. L'exemple suivant crée une instance MediaMetadata d'un film, et définit le titre, le sous-titre et deux images.

Kotlin
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle())
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio())
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0))))
movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
Java
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle());
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio());
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0))));
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));

Consultez la section Sélection d'images sur l'utilisation d'images avec des métadonnées multimédias.

Charger le contenu multimédia

Votre application peut charger un élément multimédia, comme indiqué dans le code suivant. Commencez par utiliser MediaInfo.Builder avec les métadonnées du média pour créer une instance MediaInfo. Obtenez le RemoteMediaClient à partir du CastSession actuel, puis chargez le MediaInfo dans ce RemoteMediaClient. Utilisez RemoteMediaClient pour lire, mettre en pause et contrôler une application de lecture multimédia qui s'exécute sur le récepteur Web.

Kotlin
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl())
    .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
    .setContentType("videos/mp4")
    .setMetadata(movieMetadata)
    .setStreamDuration(mSelectedMedia.getDuration() * 1000)
    .build()
val remoteMediaClient = mCastSession.getRemoteMediaClient()
remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
Java
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl())
        .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
        .setContentType("videos/mp4")
        .setMetadata(movieMetadata)
        .setStreamDuration(mSelectedMedia.getDuration() * 1000)
        .build();
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());

Consultez également la section sur l'utilisation des pistes multimédias.

Format vidéo 4K

Pour connaître le format vidéo de votre contenu multimédia, utilisez getVideoInfo() dans MediaStatus pour obtenir l'instance actuelle de VideoInfo. Cette instance contient le type de format TV HDR, ainsi que la hauteur et la largeur d'affichage en pixels. Les variantes du format 4K sont indiquées par des constantes HDR_TYPE_*.

Notifications via une télécommande pour plusieurs appareils

Lorsqu'un utilisateur caste du contenu, d'autres appareils Android du même réseau reçoivent une notification leur permettant de contrôler la lecture. Toute personne dont l'appareil reçoit ces notifications peut les désactiver pour cet appareil dans l'application Paramètres de Google > Google Cast > Afficher les notifications de la télécommande. (Les notifications incluent un raccourci vers l'application Paramètres.) Pour en savoir plus, consultez Recevoir les notifications relatives à la télécommande Cast.

Ajouter une mini-télécommande

Conformément à la checklist de conception de Cast, une application d'expéditeur doit fournir une commande persistante appelée mini-contrôleur qui doit s'afficher lorsque l'utilisateur quitte la page de contenu actuelle pour accéder à une autre partie de l'application d'expéditeur. La mini-télécommande fournit un rappel visible à l'utilisateur de la session Cast en cours. En appuyant sur la mini-télécommande, l'utilisateur peut revenir à la vue agrandie de la télécommande Cast en plein écran.

Le framework fournit une vue personnalisée, MiniControllerFragment, que vous pouvez ajouter au bas du fichier de mise en page de chaque activité dans laquelle vous souhaitez afficher le mini-contrôleur.

<fragment
    android:id="@+id/castMiniController"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />

Lorsque l'application émetteur lit un flux vidéo ou en direct audio, le SDK affiche automatiquement un bouton de lecture/arrêt à la place du bouton de lecture/pause sur la mini-télécommande.

Pour définir l'apparence du texte du titre et du sous-titre de cette vue personnalisée, et choisir des boutons, consultez Personnaliser la mini-télécommande.

Ajouter une télécommande agrandie

La checklist de conception de Google Cast exige qu'une application émettrice envoie un contrôleur étendu pour le contenu multimédia en cours de diffusion. La télécommande agrandie est une version en plein écran de la mini-télécommande.

Le SDK Cast fournit un widget pour le contrôleur étendu appelé ExpandedControllerActivity. Il s'agit d'une classe abstraite que vous devez sous-classer pour ajouter une icône Cast.

Tout d'abord, créez un fichier de ressources de menu permettant au contrôleur développé de fournir l'icône Cast:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
            android:id="@+id/media_route_menu_item"
            android:title="@string/media_route_menu_title"
            app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
            app:showAsAction="always"/>

</menu>

Créez une classe qui étend ExpandedControllerActivity.

Kotlin
class ExpandedControlsActivity : ExpandedControllerActivity() {
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        menuInflater.inflate(R.menu.expanded_controller, menu)
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
        return true
    }
}
Java
public class ExpandedControlsActivity extends ExpandedControllerActivity {
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.expanded_controller, menu);
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
        return true;
    }
}

Déclarez ensuite votre nouvelle activité dans le fichier manifeste de l'application dans le tag application:

<application>
...
<activity
        android:name=".expandedcontrols.ExpandedControlsActivity"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:theme="@style/Theme.CastVideosDark"
        android:screenOrientation="portrait"
        android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
    </intent-filter>
</activity>
...
</application>

Modifiez CastOptionsProvider ainsi que NotificationOptions et CastMediaOptions pour définir l'activité cible sur votre nouvelle activité:

Kotlin
override fun getCastOptions(context: Context): CastOptions? {
    val notificationOptions = NotificationOptions.Builder()
        .setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()
    val mediaOptions = CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()

    return CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build()
}
Java
public CastOptions getCastOptions(Context context) {
    NotificationOptions notificationOptions = new NotificationOptions.Builder()
            .setTargetActivityClassName(ExpandedControlsActivity.class.getName())
            .build();
    CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
            .setNotificationOptions(notificationOptions)
            .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
            .build();

    return new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setCastMediaOptions(mediaOptions)
            .build();
}

Mettez à jour la méthode loadRemoteMedia LocalPlayerActivity pour afficher votre nouvelle activité lorsque le contenu multimédia distant est chargé:

Kotlin
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
    val remoteMediaClient = mCastSession?.remoteMediaClient ?: return

    remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
        override fun onStatusUpdated() {
            val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java)
            startActivity(intent)
            remoteMediaClient.unregisterCallback(this)
        }
    })

    remoteMediaClient.load(
        MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position.toLong()).build()
    )
}
Java
private void loadRemoteMedia(int position, boolean autoPlay) {
    if (mCastSession == null) {
        return;
    }
    final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
    if (remoteMediaClient == null) {
        return;
    }
    remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() {
        @Override
        public void onStatusUpdated() {
            Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class);
            startActivity(intent);
            remoteMediaClient.unregisterCallback(this);
        }
    });
    remoteMediaClient.load(new MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position).build());
}

Lorsque l'application émettrice diffuse un flux vidéo ou audio en direct, le SDK affiche automatiquement un bouton de lecture/arrêt à la place du bouton de lecture/pause dans la télécommande agrandie.

Pour définir l'apparence à l'aide de thèmes, choisir les boutons à afficher et ajouter des boutons personnalisés, consultez l'article Personnaliser le contrôleur étendu.

Contrôle du volume

Le framework gère automatiquement le volume de l'application émettrice. Il synchronise automatiquement les applications émetteurs et récepteurs Web afin que l'interface utilisateur de l'expéditeur signale toujours le volume spécifié par le récepteur Web.

Contrôle du volume du bouton physique

Sur Android, les boutons physiques de l'appareil expéditeur peuvent être utilisés par défaut pour modifier le volume de la session Cast dans le récepteur Web pour tout appareil utilisant Jelly Bean ou une version ultérieure.

Contrôle du volume du bouton physique avant Jelly Bean

Pour utiliser les touches de volume physiques afin de contrôler le volume de l'appareil du récepteur Web sur les appareils Android antérieurs à Jelly Bean, l'application émettrice doit remplacer dispatchKeyEvent dans ses activités et appeler CastContext.onDispatchVolumeKeyEventBeforeJellyBean():

Kotlin
class MyActivity : FragmentActivity() {
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        return (CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
                || super.dispatchKeyEvent(event))
    }
}
Java
class MyActivity extends FragmentActivity {
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        return CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
            || super.dispatchKeyEvent(event);
    }
}

Ajouter des commandes multimédias à l'écran de notification et de verrouillage

Sur la plate-forme Android uniquement, la checklist de conception de Google Cast exige que l'application émettrice implémente les commandes multimédias dans une notification et sur l'écran de verrouillage, où l'expéditeur diffuse du contenu, mais où l'application émettrice n'est pas sélectionnée. Le framework fournit MediaNotificationService et MediaIntentReceiver pour aider l'application émetteur à créer des commandes multimédias dans une notification et sur l'écran de verrouillage.

MediaNotificationService s'exécute lorsque l'émetteur caste des contenus. Il affiche une notification avec une miniature de l'image ainsi que des informations sur l'élément de diffusion en cours, un bouton de lecture/pause et un bouton d'arrêt.

MediaIntentReceiver est un BroadcastReceiver qui gère les actions de l'utilisateur depuis la notification.

Votre application peut configurer les notifications et le contrôle multimédia depuis l'écran de verrouillage jusqu'à NotificationOptions. Votre application peut configurer les boutons de commande à afficher dans la notification et les Activity à ouvrir lorsque l'utilisateur appuie sur la notification. Si les actions ne sont pas explicitement fournies, les valeurs par défaut, MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK et MediaIntentReceiver.ACTION_STOP_CASTING, sont utilisées.

Kotlin
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
val buttonActions: MutableList<String> = ArrayList()
buttonActions.add(MediaIntentReceiver.ACTION_REWIND)
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK)
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD)
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING)

// Showing "play/pause" and "stop casting" in the compat view of the notification.
val compatButtonActionsIndices = intArrayOf(1, 3)

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
val notificationOptions = NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity::class.java.name)
    .build()
Java
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
List<String> buttonActions = new ArrayList<>();
buttonActions.add(MediaIntentReceiver.ACTION_REWIND);
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK);
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD);
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING);

// Showing "play/pause" and "stop casting" in the compat view of the notification.
int[] compatButtonActionsIndices = new int[]{1, 3};

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
NotificationOptions notificationOptions = new NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity.class.getName())
    .build();

L'affichage des commandes multimédias depuis la notification et l'écran de verrouillage est activé par défaut. Vous pouvez le désactiver en appelant setNotificationOptions avec la valeur null dans CastMediaOptions.Builder. Actuellement, la fonctionnalité d'écran de verrouillage est activée tant que la notification est activée.

Kotlin
// ... continue with the NotificationOptions built above
val mediaOptions = CastMediaOptions.Builder()
    .setNotificationOptions(notificationOptions)
    .build()
val castOptions: CastOptions = Builder()
    .setReceiverApplicationId(context.getString(R.string.app_id))
    .setCastMediaOptions(mediaOptions)
    .build()
Java
// ... continue with the NotificationOptions built above
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .build();
CastOptions castOptions = new CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build();

Lorsque l'application émetteur lit un flux vidéo ou audio en direct, le SDK affiche automatiquement un bouton de lecture/arrêt à la place du bouton de lecture/pause sur la commande de notification, mais pas sur la commande d'écran de verrouillage.

Remarque: Pour afficher les commandes de l'écran de verrouillage sur les appareils antérieurs à Lollipop, RemoteMediaClient demande automatiquement la mise au point audio en votre nom.

Gérer les erreurs

Il est très important que les applications émetteurs gèrent tous les rappels d'erreur et décident de la meilleure réponse pour chaque étape du cycle de vie Cast. L'application peut afficher les boîtes de dialogue d'erreur à destination de l'utilisateur ou décider de supprimer la connexion au récepteur Web.