Implémenter une fiche multimédia dans AAOS

Une carte multimédia est un ViewGroup autonome qui affiche des métadonnées multimédias telles que le titre, la pochette de l'album, etc., et des commandes de lecture telles que Lire et Mettre en pause, Passer,et même des actions personnalisées fournies par l'application multimédia tierce. Une carte multimédia peut également afficher une file d'attente d'éléments multimédias, comme une playlist.

Fiche de contenu multimédia

Fiche de contenu multimédia

Fiche de contenu multimédia

Figure 1 : Exemples d'implémentation de fiches multimédias.

Comment les cartes multimédias sont-elles implémentées dans AAOS ?

Les ViewGroups qui affichent des informations multimédias observent les mises à jour LiveData du modèle de données de la bibliothèque car-media-common, PlaybackViewModel, pour remplir le ViewGroup. Chaque mise à jour LiveData correspond à un sous-ensemble d'informations multimédias qui ont changé, comme MediaItemMetadata, PlaybackStateWrapper et MediaSource.

Comme cette approche entraîne la répétition du code (chaque application cliente ajoute des observateurs sur chaque élément LiveData et de nombreuses vues similaires se voient attribuer les données mises à jour), nous avons créé PlaybackCardController.

PlaybackCardController

PlaybackCardController a été ajouté à la bibliothèque car-media-common pour vous aider à créer une fiche média. Il s'agit d'une classe publique construite avec une instance ViewGroup (mView), PlaybackViewModel (mDataModel), PlaybackCardViewModel (mViewModel) et MediaItemsRepository (mItemsRepository).

Dans la fonction setupController, le ViewGroup est analysé pour certaines vues par ID, avec mView.findViewById(R.id.xxx) et attribué à des objets View protégés.

private void getViewsFromWidget() {
        mTitle = mView.findViewById(R.id.title);
        mAlbumCover = mView.findViewById(R.id.album_art);
        mDescription = mView.findViewById(R.id.album_title);
        mLogo = mView.findViewById(R.id.content_format);

        mAppIcon = mView.findViewById(R.id.media_widget_app_icon);
        mAppName = mView.findViewById(R.id.media_widget_app_name);

         // ...
}

Chaque mise à jour LiveData de PlaybackViewModel est observée dans une méthode protégée et effectue des interactions avec les vues pertinentes pour les données reçues. Par exemple, un observateur sur MediaItemMetadata définit le titre sur le TextView mTitle et transmet le MediaItemMetadata.ArtworkRef à l'illustration de l'album ImageBinder mAlbumArtBinder. Si les métadonnées sont nulles, les vues sont masquées. Les sous-classes du contrôleur peuvent remplacer cette logique si nécessaire.

mDataModel.getMetadata().observe(mViewLifecycle, this::updateMetadata);
// ...

/** Update views with {@link MediaItemMetadata} */
protected void updateMetadata(MediaItemMetadata metadata) {
        if (metadata != null) {
            String defaultTitle = mView.getContext().getString(
                    R.string.metadata_default_title);
            updateTextViewAndVisibility(mTitle, metadata.getTitle(),    defaultTitle);
            updateTextViewAndVisibility(mSubtitle, metadata.getSubtitle());
            updateMediaLink(mSubtitleLinker,metadata.getSubtitleLinkMediaId());
            updateTextViewAndVisibility(mDescription, metadata.getDescription());
            updateMediaLink(mDescriptionLinker, metadata.getDescriptionLinkMediaId());
            updateMetadataAlbumCoverArtworkRef(metadata.getArtworkKey());
            updateMetadataLogoWithUri(metadata);
        } else {
            ViewUtils.setVisible(mTitle, false);
            ViewUtils.setVisible(mSubtitle, false);
            ViewUtils.setVisible(mAlbumCover, false);
            ViewUtils.setVisible(mDescription, false);
            ViewUtils.setVisible(mLogo, false);
        }
    }

Étendre le PlaybackCardController

Les applications clientes qui souhaitent créer une carte multimédia doivent étendre PlaybackCardController si elles disposent d'une fonctionnalité supplémentaire qu'elles souhaitent gérer dans chaque mise à jour LiveData. Les clients existants dans AAOS suivent ce modèle. Tout d'abord, une sous-classe PlaybackCardController doit être créée, telle que MediaCardController. Ensuite, MediaCardController doit ajouter une classe Builder interne statique qui étend celle de PlaybackCardController.

public class MediaCardController extends PlaybackCardController {

    // extra fields specific to MediaCardController

    /** Builder for {@link MediaCardController}. Overrides build() method to
     * return NowPlayingController rather than base {@link PlaybackCardController}
     */
    public static class Builder extends PlaybackCardController.Builder {

        @Override
        public MediaCardController build() {
            MediaCardController controller = new MediaCardController(this);
            controller.setupController();
            return controller;
        }
    }

    public MediaCardController(Builder builder) {
        super(builder);
    // any other function calls needed in constructor
    // ...

  }
}

Instanciez PlaybackCardController ou une sous-classe.

La classe Controller doit être instanciée à partir d'un fragment ou d'une activité afin de disposer d'un LifecycleOwner pour les observateurs LiveData.

mMediaCardController = (MediaCardController) new MediaCardController.Builder()
                    .setModels(mViewModel.getPlaybackViewModel(),
                            mViewModel,
                            mViewModel.getMediaItemsRepository())
                    .setViewGroup((ViewGroup) view)
                    .build();

mViewModel est une instance de PlaybackCardViewModel (ou d'une sous-classe).

PlaybackCardViewModel pour enregistrer l'état

PlaybackCardViewModel est un ViewModel de sauvegarde d'état lié à un fragment ou à une activité. Il doit être utilisé pour reconstruire le contenu de la carte multimédia en cas de changement de configuration (par exemple, le passage du thème clair au thème sombre lorsqu'un utilisateur traverse un tunnel). L'PlaybackCardViewModel par défaut gère le stockage des instances de MediaModel pour la lecture, à partir desquelles PlaybackViewModel et MediaItemsRepository peuvent être récupérés. Utilisez PlaybackCardViewModel pour suivre l'état de la file d'attente, de l'historique et du menu de dépassement de capacité à l'aide des getters et setters fournis.

public class PlaybackCardViewModel extends AndroidViewModel {

    private MediaModels mModels;
    private boolean mNeedsInitialization = true;
    private boolean mQueueVisible = false;
    private boolean mHistoryVisible = false;
    private boolean mOverflowExpanded = false;

    public PlaybackCardViewModel(@NonNull Application application) {
        super(application);
    }

    /** Initialize the PlaybackCardViewModel */
    public void init(MediaModels models) {
        mModels = models;
        mNeedsInitialization = false;
    }

    /**
     * Returns whether the ViewModel needs to be initialized. The ViewModel may
     * need re-initialization if a config change occurs or if the system kills
     * the Fragment.
     */
    public boolean needsInitialization() {
        return mNeedsInitialization;
    }

    public MediaItemsRepository getMediaItemsRepository() {
        return mModels.getMediaItemsRepository();
    }

    public PlaybackViewModel getPlaybackViewModel() {
        return mModels.getPlaybackViewModel();
    }

    public MediaSourceViewModel getMediaSourceViewModel() {
        return mModels.getMediaSourceViewModel();
    }

    public void setQueueVisible(boolean visible) {
        mQueueVisible = visible;
    }

    public boolean getQueueVisible() {
        return mQueueVisible;
    }

    public void setHistoryVisible(boolean visible) {
        mHistoryVisible = visible;
    }

    public boolean getHistoryVisible() {
        return mHistoryVisible;
    }

    public void setOverflowExpanded(boolean expanded) {
        mOverflowExpanded = expanded;
    }

    public boolean getOverflowExpanded() {
        return mOverflowExpanded;
    }
}

Cette classe peut être étendue si des états supplémentaires doivent être suivis.

Afficher une file d'attente dans une fiche multimédia

PlaybackViewModel fournit des API LiveData pour détecter si MediaSource est compatible avec une file d'attente et pour récupérer la liste des objets MediaItemMetadata dans la file d'attente. Bien que ces API puissent être utilisées directement pour remplir un objet RecyclerView avec les informations de la file d'attente, une classe PlaybackQueueController a été ajoutée à la bibliothèque car-media-common pour simplifier ce processus. La mise en page de chaque élément de CarUiRecyclerView est spécifiée par l'application cliente, ainsi qu'une mise en page d'en-tête facultative. L'application cliente peut également choisir de limiter le nombre d'éléments affichés dans la file d'attente en mode conduite avec des restrictions UXR personnalisées.

Le constructeur et les setters PlaybackQueueController sont présentés dans l'exemple suivant. Les ressources de mise en page queueResource et headerResource peuvent être transmises en tant que Resources.ID_NULL si, dans le premier cas, le conteneur contient déjà un CarUiRecyclerView avec id queue_list et, dans le second cas, la file d'attente ne comporte pas d'en-tête.

   /**
    * Construct a PlaybackQueueController. If clients don't have a separate
    * layout for the queue, where the queue is already inflated within the
    * container, they should pass {@link Resources.ID_NULL} as the LayoutRes
    * resource. If clients don't require a UxrContentLimiter, they should pass
    * null for uxrContentLimiter and the int passed for uxrConfigurationId will
    * be ignored.
    */
    public PlaybackQueueController(
            ViewGroup container,
            @LayoutRes int queueResource,
            @LayoutRes int queueItemResource,
            @LayoutRes int headerResource,
            LifecycleOwner lifecycleOwner,
            PlaybackViewModel playbackViewModel,
            MediaItemsRepository itemsRepository,
            @Nullable LifeCycleObserverUxrContentLimiter uxrContentLimiter,
            int uxrConfigurationId) {
      // ...
    }

    public void setShowTimeForActiveQueueItem(boolean show) {
        mShowTimeForActiveQueueItem = show;
    }

    public void setShowIconForActiveQueueItem(boolean show) {
        mShowIconForActiveQueueItem = show;
    }

    public void setShowThumbnailForQueueItem(boolean show) {
        mShowThumbnailForQueueItem = show;
    }

    public void setShowSubtitleForQueueItem(boolean show) {
        mShowSubtitleForQueueItem = show;
    }

    /** Calls {@link RecyclerView#setVerticalFadingEdgeEnabled(boolean)} */
    public void setVerticalFadingEdgeLengthEnabled(boolean enabled) {
        mQueue.setVerticalFadingEdgeEnabled(enabled);
    }

    public void setCallback(PlaybackQueueCallback callback) {
        mPlaybackQueueCallback = callback;
    }

La mise en page de chaque élément de la file d'attente doit contenir les ID des vues qu'il souhaite afficher et qui correspondent à ceux utilisés dans la classe interne QueueViewHolder.

QueueViewHolder(View itemView) {
            super(itemView);
            mView = itemView;
            mThumbnailContainer = itemView.findViewById(R.id.thumbnail_container);
            mThumbnail = itemView.findViewById(R.id.thumbnail);
            mSpacer = itemView.findViewById(R.id.spacer);
            mTitle = itemView.findViewById(R.id.queue_list_item_title);
            mSubtitle = itemView.findViewById(R.id.queue_list_item_subtitle);
            mCurrentTime = itemView.findViewById(R.id.current_time);
            mMaxTime = itemView.findViewById(R.id.max_time);
            mTimeSeparator = itemView.findViewById(R.id.separator);
            mActiveIcon = itemView.findViewById(R.id.now_playing_icon);

            // ...
}

Pour afficher une file d'attente dans une carte multimédia créée avec PlaybackCardController (ou une sous-classe), PlaybackQueueController peut être construit dans le constructeur PlaybackCardController à l'aide de mDataModel et mItemsRepository pour les instances PlaybackViewModel et MediaItemsRepository, respectivement.

Afficher l'historique des MediaSources précédemment lues

Dans cette section, vous allez apprendre à afficher l'historique des sources multimédias lues précédemment.

Obtenir la liste de l'historique avec l'API PlaybackCardViewModel

PlaybackCardViewModel fournit une API LiveData appelée getHistoryList() pour récupérer la liste de l'historique multimédia. Il renvoie un LiveData contenant une liste de MediaSources qui ont déjà été lues. Ces données peuvent être utilisées pour remplir un objet CarUiRecyclerView. Comme pour PlaybackQueueController, une classe nommée PlaybackHistoryController a été ajoutée à la bibliothèque car-media-common pour simplifier le processus.

public class PlaybackCardViewModel extends AndroidViewModel {

    public PlaybackCardViewModel(@NonNull Application application) {
    }

    /** Initialize the PlaybackCardViewModel */
    public void init(MediaModels models) {
    }

    public LiveData<List<MediaSource>> getHistoryList() {
        return mHistoryListData;
    }
}

UI de l'historique Surface avec PlaybackHistoryController

Utilisez le nouveau PlaybackHistoryController pour remplir les données de l'historique dans un CarUiRecyclerView. Les constructeurs et les fonctions principales de cette classe sont les suivants. Le conteneur transmis par l'application cliente doit contenir un CarUiRecyclerView avec l'ID history_list. CarUiRecyclerView affiche les éléments de la liste et un en-tête facultatif. Les deux mises en page pour l'élément de liste et l'en-tête peuvent être transmises depuis l'application cliente. Si Resources.ID_NULL est défini comme headerResource, l'en-tête n'est pas affiché. Une fois que PlaybackCardViewModel est transmis au contrôleur, il surveille LiveData<List<MediaSource>> récupéré à partir de playbackCardViewModel.getHistoryList().

public class PlaybackHistoryController {

    public PlaybackHistoryController(
            LifecycleOwner lifecycleOwner,
            PlaybackCardViewModel playbackCardViewModel,
            ViewGroup container,
            @LayoutRes int itemResource,
            @LayoutRes int headerResource,
            int uxrConfigurationId) {
    }

    /**
     * Renders the view.
     */
    public void setupView() {
    }
}

La mise en page de chaque élément doit contenir les ID des vues qu'il souhaite afficher et qui correspondent à ceux utilisés dans la classe interne ViewHolder.

HistoryItemViewHolder(View itemView) {
            super(itemView);
            mContext = itemView.getContext();
            mActiveView = itemView.findViewById(R.id.history_card_container_active);
            mInactiveView = itemView.findViewById(R.id.history_card_container_inactive);
            mMetadataTitleView = itemView.findViewById(R.id.history_card_title_active);
            mAdditionalInfo = itemView.findViewById(R.id.history_card_subtitle_active);
            mAppIcon = itemView.findViewById(R.id.history_card_app_thumbnail);
            mAlbumArt = itemView.findViewById(R.id.history_card_album_art);
            mAppTitleInactive = itemView.findViewById(R.id.history_card_app_title_inactive);
            mAppIconInactive = itemView.findViewById(R.id.history_item_app_icon_inactive);
// ...
}

Pour afficher une liste d'historique dans une fiche multimédia créée avec PlaybackCardController (ou une sous-classe), PlaybackHistoryController peut être construit dans le constructeur de PlaybackCardController.