Intégrer Cast à votre application Android

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

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

Le framework de l'expéditeur fait référence au binaire de la bibliothèque de classes Cast et aux ressources associées présentes au moment de l'exécution sur l'expéditeur. L'application émettrice ou l'application Cast désigne une application qui s'exécute également sur l'émetteur. L'application Web Receiver désigne l'application HTML qui s'exécute sur l'appareil compatible Cast.

Le framework de l'expéditeur utilise une conception de rappel asynchrone pour informer l'application expéditrice des événements et pour passer d'un état à un autre 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 général typique d'une application Android émettrice :

  • Le framework Cast démarre automatiquement la découverte des appareils MediaRouter en fonction du cycle de vie Activity.
  • Lorsque l'utilisateur clique sur le bouton Cast, le framework affiche 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 Web Receiver sur l'appareil Cast.
  • Le framework appelle 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 l'application émettrice et les applications Web Receiver.
  • Le framework utilise le canal de communication pour charger et contrôler la lecture multimédia sur le Web Receiver.
  • Le framework synchronise l'état de lecture du contenu multimédia entre l'expéditeur et le Web Receiver : lorsque l'utilisateur effectue des actions dans l'UI de l'expéditeur, le framework transmet ces requêtes de contrôle du contenu multimédia au Web Receiver. Lorsque le Web Receiver envoie des mises à jour de l'état du contenu multimédia, le framework met à jour l'état de l'UI de l'expéditeur.
  • Lorsque l'utilisateur clique sur le bouton Cast pour se déconnecter de l'appareil Cast, le framework déconnecte l'application émettrice du Web Receiver.

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 vous oblige à configurer 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, le niveau d'API minimal est 23 et le niveau d'API cible est 34.

<uses-sdk
        android:minSdkVersion="23"
        android:targetSdkVersion="34" />

android:theme

Définissez le thème de votre application en fonction de la version minimale du SDK Android. Par exemple, si vous n'implémentez pas 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 ont une incidence sur le comportement du framework. La plus importante de ces options est l'ID d'application Web Receiver, utilisé pour filtrer les résultats de la découverte et lancer l'application Web Receiver lorsqu'une session Cast est lancée.

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 de l'OptionsProvider implémenté en tant que champ de métadonnées dans le fichier AndroidManifest.xml de l'application expéditrice :

<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 Cast UX

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

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

  • Bouton Caster : Le bouton Caster est visible, que des appareils Cast soient disponibles ou non. Lorsque l'utilisateur clique pour la première fois sur le bouton Cast, une boîte de dialogue Cast s'affiche et liste les appareils détectés. Lorsque l'utilisateur clique sur le bouton Cast alors que l'appareil est connecté, les métadonnées multimédias actuelles s'affichent (par exemple, le titre, le nom du studio d'enregistrement et une miniature) ou l'utilisateur peut se déconnecter de l'appareil Cast. Le "bouton Caster" est parfois appelé "icône Caster".

  • Mini-télécommande : lorsque l'utilisateur diffuse du contenu et quitte la page de contenu actuelle ou la télécommande développée pour accéder à un autre écran de l'application émettrice, la mini-télécommande s'affiche en bas de l'écran pour lui permettre de voir les métadonnées du contenu multimédia en cours de diffusion et de contrôler la lecture.

  • Télécommande développée : lorsque l'utilisateur diffuse du contenu, s'il clique sur la notification multimédia ou la mini-télécommande, la télécommande développée se lance. Elle affiche les métadonnées du contenu multimédia en cours de lecture et fournit plusieurs boutons pour contrôler la lecture.

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

  • Écran de verrouillage : Android uniquement. Lorsque l'utilisateur diffuse du contenu et accède à l'écran de verrouillage (ou que l'appareil atteint le délai d'inactivité), une commande de lecture multimédia s'affiche sur l'écran de verrouillage. Elle indique les métadonnées du contenu multimédia en cours de diffusion et les commandes de lecture.

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

Ajouter un bouton 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 une icône Cast dans leur interface utilisateur pour permettre aux utilisateurs de sélectionner un itinéraire média pour lire du contenu multimédia sur un appareil secondaire tel qu'un appareil Cast.

Le framework facilite l'ajout d'un MediaRouteButton en tant que Cast button. Vous devez d'abord ajouter un élément de menu ou un MediaRouteButton dans le fichier XML qui définit votre menu, puis utiliser CastButtonFactory pour le connecter 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 du bouton Cast à l'aide d'un thème, consultez Personnaliser le bouton Cast.

Configurer la découverte des appareils

La détection des appareils est entièrement gérée par CastContext. Lors de l'initialisation de CastContext, l'application émettrice spécifie l'ID de l'application Web Receiver et peut éventuellement demander le filtrage de l'espace de noms en définissant supportedNamespaces dans CastOptions. CastContext contient une référence à MediaRouter en interne et lance le processus de découverte dans les conditions suivantes :

  • Basée sur un algorithme conçu pour équilibrer la latence de découverte des appareils et l'utilisation de la batterie, la découverte est parfois lancée automatiquement lorsque l'application émettrice passe au premier plan.
  • La boîte de dialogue Cast est ouverte.
  • Le SDK Cast tente de récupérer une session Cast.

Le processus de découverte s'arrête lorsque la boîte de dialogue Cast est fermée ou que l'application émettrice 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 introduit le concept de session Cast, dont l'établissement combine les étapes de connexion à un appareil, de lancement (ou de reprise) d'une application Web Receiver, 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 Web Receiver, consultez le guide du cycle de vie des applications Web Receiver.

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 une session qui a été interrompue de manière anormale ou abrupte.

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

Pour mieux comprendre les erreurs de démarrage de Cast, les applications peuvent utiliser CastContext#getCastReasonCodeForCastStatusCode(int) pour convertir l'erreur de démarrage de session en CastReasonCodes. Veuillez noter que certaines erreurs de démarrage de session (par exemple, CastReasonCodes#CAST_CANCELLED) sont un comportement normal et ne doivent pas être consignées comme des erreurs.

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 mCastContext: CastContext
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()

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

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

        override fun onSessionStartFailed(session: CastSession?, error: Int) {
            val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error)
            // Handle error
        }

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

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

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

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

        override fun onSessionEnding(session: CastSession?) {}

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

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

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
    }

    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 class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {
            int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error);
            // Handle error
        }
        @Override
        public void onSessionSuspended(CastSession session, int reason) {}
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

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

    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
    }

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

Transfert de diffusion

La conservation de l'état de la session est à la base du transfert de flux, qui permet aux utilisateurs de déplacer des flux audio et vidéo existants d'un appareil à un autre à l'aide de commandes vocales, de l'application Google Home ou d'écrans connectés. Le contenu multimédia cesse d'être lu sur un appareil (la source) et continue sur un autre (la destination). Tout appareil Cast équipé du dernier micrologiciel peut servir de source ou de destination lors d'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 Transfert de flux sur Web Receiver.

Reconnexion automatique

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

  • Récupérer les données en cas de perte temporaire du Wi-Fi
  • Sortir l'appareil du mode veille
  • Récupérer l'application après l'avoir mise en arrière-plan
  • Récupérer les données en cas de plantage de l'application

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

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

Le framework démarre le service lorsqu'une session multimédia est en cours et l'arrête lorsque la session multimédia se termine.

Fonctionnement du contrôle multimédia

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 d'avoir à transmettre un GoogleApiClient.

Lorsque votre application établit une CastSession avec une application Web Receiver compatible avec l'espace de noms mé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 émettent des requêtes vers le Web Receiver renvoient un objet PendingResult qui peut être utilisé pour suivre cette requête.

Il est prévu que l'instance de RemoteMediaClient puisse être partagée par plusieurs parties de votre application, et même par certains composants internes du framework, tels que les mini-lecteurs persistants et le service de notification. À cette fin, cette instance permet l'enregistrement de plusieurs instances de RemoteMediaClient.Listener.

Définir les métadonnées du contenu multimédia

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 de 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))));

Pour en savoir plus sur l'utilisation d'images avec des métadonnées multimédias, consultez Sélection d'images.

Charger un 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 contenu multimédia pour créer une instance MediaInfo. Obtenez RemoteMediaClient à partir de CastSession actuel, puis chargez MediaInfo dans ce RemoteMediaClient. Utilisez RemoteMediaClient pour lire, mettre en pause ou contrôler une application de lecteur multimédia exécutée sur le Web Receiver.

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 vérifier 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 de l'écran en pixels. Les variantes du format 4K sont indiquées par des constantes HDR_TYPE_*.

Notifications de télécommande sur plusieurs appareils

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

Ajouter une mini-télécommande

Selon la checklist de conception Cast, une application émettrice doit fournir un contrôle persistant appelé mini-télécommande, qui doit s'afficher lorsque l'utilisateur quitte la page de contenu actuelle pour accéder à une autre partie de l'application émettrice. La mini-télécommande rappelle visiblement à l'utilisateur la session Cast en cours. En appuyant sur la mini-télécommande, l'utilisateur peut revenir à la vue agrandie en plein écran de la télécommande Cast.

Le framework fournit une vue personnalisée, MiniControllerFragment, que vous pouvez ajouter en bas du fichier de mise en page de chaque activité pour laquelle vous souhaitez afficher la mini-télécommande.

<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 votre application émettrice 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 dans la mini-télécommande.

Pour définir l'apparence du texte du titre et du sous-titre de cette vue personnalisée, et pour choisir les boutons, consultez Personnaliser le petit lecteur.

Ajouter une télécommande agrandie

La checklist de conception de Google Cast exige qu'une application émettrice soit dotée d'une télécommande agrandie associée au 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 la télécommande agrandie, qui s'appelle ExpandedControllerActivity. Il s'agit d'une classe abstraite que vous devez sous-classer pour ajouter une icône Cast.

Commencez par créer un fichier de ressources de menu pour la télécommande agrandie afin d'intégrer 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;
    }
}

Dans le manifeste de l'application, déclarez votre nouvelle activité au sein du 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, puis changez NotificationOptions et CastMediaOptions de façon à 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 de LocalPlayerActivity afin d'afficher votre nouvelle activité lorsque le chargement du contenu multimédia à distance est terminé :

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 votre application émettrice lit une vidéo ou un flux 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 étendue.

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

Réglage du volume

Le framework gère automatiquement le volume de l'application émettrice. Il synchronise automatiquement les applications émettrice et Web Receiver afin que l'UI de l'émetteur indique toujours le volume spécifié par Web Receiver.

Contrôle du volume avec les boutons physiques

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

Contrôle du volume avec les boutons physiques avant Jelly Bean

Pour utiliser les touches de volume physiques afin de contrôler le volume de l'appareil Web Receiver 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 à la notification et à l'écran de verrouillage

Sur Android uniquement, la checklist de conception de Google Cast exige qu'une application émettrice implémente des commandes multimédias dans une notification et sur l'écran de verrouillage, lorsque l'émetteur diffuse du contenu, mais que l'application émettrice n'est pas au premier plan. Le framework fournit MediaNotificationService et MediaIntentReceiver pour aider l'application émettrice à créer des commandes multimédias dans une notification et sur l'écran de verrouillage.

MediaNotificationService s'exécute lorsque l'émetteur caste un contenu et affiche une notification qui contient une vignette d'image et des informations sur l'élément en cours de diffusion, ainsi qu'un bouton de lecture/pause et un bouton pour arrêter la lecture.

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

Votre application peut configurer les notifications et les commandes multimédias depuis l'écran de verrouillage via NotificationOptions. Votre application peut configurer les boutons de commande à afficher dans la notification et l'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 seront 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 dans la notification et sur 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é de l'écran de verrouillage est activée tant que les notifications le sont.

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 votre application émettrice 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 dans la commande de notification, mais pas dans la commande de l'écran de verrouillage.

Remarque : Pour afficher les commandes de l'écran de verrouillage sur les appareils antérieurs à Lollipop, RemoteMediaClient demandera automatiquement le focus audio en votre nom.

Gérer les erreurs

Il est très important que les applications émettrices 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 des boîtes de dialogue d'erreur à l'utilisateur ou décider de rompre la connexion au Web Receiver.