Une fiche multimédia est un ViewGroup autonome qui affiche les métadonnées multimédias telles que comme le titre, la pochette de l'album, etc., et affiche des commandes de lecture telles que Lire, Mettre en pause,Passer et même des actions personnalisées fournies par le tiers une application multimédia tierce. Une fiche multimédia peut également présenter une file d'attente d'éléments multimédias, par exemple la playlist.
Figure 1 : Exemples d'implémentations de cartes 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 renseigner le ViewGroup. Chaque mise à jour LiveData correspond à un sous-ensemble d'informations multimédias
qui a changé, comme MediaItemMetadata
, PlaybackStateWrapper
et
MediaSource
Étant donné que cette approche entraîne du code répété (chaque application cliente ajoute des observateurs à chaque élément de LiveData et de nombreuses vues similaires sont attribuées aux données mises à jour), nous avons créé PlaybackCardController
.
PlaybackCardController
PlaybackCardController
a été ajouté à la bibliothèque car-media-common
pour faciliter la création d'une fiche multimédia. Il s'agit d'une classe publique construite avec
ViewGroup (mView
), PlaybackViewModel (mDataModel
) et PlaybackCardViewModel
(mViewModel
) et MediaItemsRepository
(mItemsRepository
).
Dans la fonction setupController
, le ViewGroup est analysé pour certaines vues en
ID, avec mView.findViewById(R.id.xxx)
et attribué aux 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 un bucket
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 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 fiche multimédia doivent étendre PlaybackCardController
si elles disposent de fonctionnalités supplémentaires qu'elles souhaitent gérer dans chaque mise à jour de LiveData. Les clients existants dans AAOS suivent ce modèle.
Tout d'abord, une sous-classe PlaybackCardController
doit être créée, comme MediaCardController
. MediaCardController
doit ensuite ajouter une couche interne statique
Classe "Builder" 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
// ...
}
}
Instancier 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 sous-classe).
PlaybackCardViewModel pour enregistrer l'état
PlaybackCardViewModel
est un ViewModel qui enregistre l'état et lié à un fragment ou
Activité à utiliser pour reconstruire le contenu de la fiche multimédia si une
un changement de configuration se produit (par exemple, le passage du thème clair au thème sombre
l'utilisateur passe par un tunnel). Le PlaybackCardViewModel
par défaut gère le stockage d'instances des MediaModel
pour la lecture, à partir desquelles les PlaybackViewModel
et MediaItemsRepository
peuvent être récupérés. Utilisez les
PlaybackCardViewModel
pour suivre l'état de la file d'attente, l'historique et le dépassement de capacité ;
via les getters et les 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 d'autres états doivent être suivis.
Afficher une file d'attente dans une fiche multimédia
PlaybackViewModel
fournit des API LiveData pour détecter si la MediaSource
prend en charge une file d'attente et récupère la liste des objets MediaItemMetadata
dans
file d'attente. Bien que ces API puissent être utilisées directement pour renseigner un objet RecyclerView
avec les informations de 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 la 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
articles affichés dans la file d'attente à l'état du trajet avec des restrictions de l'expérience utilisateur personnalisée.
Le constructeur et les sétteurs 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 file d'attente doit contenir les ID des vues concernées
que vous souhaitez 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 fiche 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 lus
Dans cette section, vous allez apprendre à afficher et afficher l'historique sources multimédias lues.
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 déjà lus. Ces données peuvent
être utilisées pour remplir
un objet CarUiRecyclerView
. Semblable à PlaybackQueueController
, une classe
PlaybackHistoryController
a été ajouté au 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;
}
}
Afficher l'interface utilisateur de l'historique avec PlaybackHistoryController
Utilisez le nouveau PlaybackHistoryController
pour renseigner plus facilement les données de l'historique
à un CarUiRecyclerView
. Les constructeurs et les fonctions principales de cette classe sont les suivants. Le conteneur transmis à partir de l'application cliente doit contenir un CarUiRecyclerView
avec l'ID history_list
. CarUiRecyclerView
affiche les éléments de liste et un en-tête facultatif. Les deux mises en page de l'élément de liste et de l'en-tête peuvent être transmises à partir de l'application cliente. Si Resources.ID_NULL
est défini comme headerResource, l'en-tête n'est pas affiché. Après le
PlaybackCardViewModel
est transmis au contrôleur, celui-ci surveille
LiveData<List<MediaSource>>
récupérée sur
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 souhaitées.
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 la
PlaybackCardController
(ou une sous-classe), PlaybackHistoryController
peut être
construit dans le constructeur de PlaybackCardController
.