Медиа-карта — это автономная группа просмотра, которая отображает метаданные мультимедиа, такие как заголовок, обложка альбома и т. д., а также отображает элементы управления воспроизведением, такие как «Воспроизведение », «Пауза» , «Пропустить» и даже пользовательские действия, предоставляемые сторонним мультимедийным приложением. На медиа-карте также может отображаться очередь мультимедийных элементов, например список воспроизведения.
Рисунок 1. Примеры реализации Media Card.
Как мультимедийные карты реализованы в AAOS?
Группы ViewGroups, отображающие мультимедийную информацию, наблюдают за обновлениями LiveData из модели данных car-media-common
, PlaybackViewModel
, для заполнения ViewGroup. Каждое обновление LiveData соответствует подмножеству изменившейся мультимедийной информации, например MediaItemMetadata
, PlaybackStateWrapper
и MediaSource
.
Поскольку этот подход приводит к повторению кода (каждое клиентское приложение добавляет наблюдателей к каждому фрагменту LiveData, а обновленным данным присваиваются многие аналогичные представления), мы создали PlaybackCardController
.
Контроллер карты воспроизведения
PlaybackCardController
был добавлен в car-media-common
чтобы помочь в создании медиа-карты. Это общедоступный класс, созданный с помощью экземпляра ViewGroup ( mView
), PlaybackViewModel ( mDataModel
), PlaybackCardViewModel ( mViewModel
) и экземпляра MediaItemsRepository
( mItemsRepository
).
В функции setupController
ViewGroup анализируется для определенных представлений по идентификатору с помощью mView.findViewById(R.id.xxx)
и назначается защищенным объектам View.
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);
// ...
}
Каждое обновление LiveData из PlaybackViewModel
отслеживается в защищенном методе и выполняет взаимодействие с представлениями, соответствующими полученным данным. Например, наблюдатель в MediaItemMetadata
устанавливает заголовок в mTitle
TextView
и передает MediaItemMetadata.ArtworkRef
в обложку альбома ImageBinder
mAlbumArtBinder
. Если метаданные имеют значение NULL, представления скрыты. Подклассы контроллера могут переопределить эту логику, если это необходимо.
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);
}
}
Расширьте PlaybackCardController
Клиентские приложения, которым необходимо создать медиа-карту, должны расширить PlaybackCardController
если у них есть дополнительные возможности, которые они хотели бы обрабатывать в каждом обновлении LiveData. Существующие клиенты AAOS следуют этой схеме. Сначала следует создать подкласс PlaybackCardController
, например MediaCardController
. Затем MediaCardController
должен добавить статический внутренний класс Builder, который расширяет класс 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
// ...
}
}
Создайте экземпляр PlaybackCardController или его подкласса.
Класс Controller должен быть создан из фрагмента или действия, чтобы иметь LifecycleOwner для наблюдателей LiveData.
mMediaCardController = (MediaCardController) new MediaCardController.Builder()
.setModels(mViewModel.getPlaybackViewModel(),
mViewModel,
mViewModel.getMediaItemsRepository())
.setViewGroup((ViewGroup) view)
.build();
mViewModel
— это экземпляр PlaybackCardViewModel
(или подкласса).
PlaybackCardViewModel для сохранения состояния
PlaybackCardViewModel
— это ViewModel с сохранением состояния, привязанная к фрагменту или действию, которую следует использовать для восстановления содержимого медиа-карты в случае изменения конфигурации (например, переключение со светлой на темную тему, когда пользователь проезжает через туннель). PlaybackCardViewModel
по умолчанию обрабатывает сохранение экземпляров MediaModel
для воспроизведения, из которых можно получить PlaybackViewModel
и MediaItemsRepository
. Используйте PlaybackCardViewModel
для отслеживания состояния очереди, истории и меню переполнения с помощью предоставленных методов получения и установки.
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;
}
}
Этот класс можно расширить, если необходимо отслеживать дополнительные состояния.
Показать очередь на медиа-карте
PlaybackViewModel
предоставляет API LiveData для определения того, поддерживает ли MediaSource очередь, и для получения списка объектов MediaItemMetadata
в очереди. Хотя эти API-интерфейсы можно использовать непосредственно для заполнения объекта RecyclerView
информацией об очереди, в библиотеку car-media-common
был добавлен класс PlaybackQueueController
, чтобы упростить этот процесс. Макет каждого элемента в CarUiRecyclerView
определяется клиентским приложением, а также необязательным макетом заголовка. Клиентское приложение также может ограничить количество элементов, отображаемых в очереди во время движения, с помощью пользовательских ограничений UXR.
Конструктор и установщики PlaybackQueueController
показаны в следующем примере. Ресурсы queueResource
и headerResource
могут быть переданы как Resources.ID_NULL
, если в первом случае контейнер уже содержит CarUiRecyclerView
с id queue_list
, а во втором случае очередь не имеет заголовка.
/**
* 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;
}
Макет каждого элемента очереди должен содержать идентификаторы представлений, которые он хочет показать, которые соответствуют идентификаторам, используемым во внутреннем классе 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);
// ...
}
Чтобы отобразить очередь на медиа-карте, созданной с помощью PlaybackCardController
(или его подкласса), PlaybackQueueController
можно создать в конструкторе PlaybackCardController
с использованием mDataModel
и mItemsRepository
для экземпляров PlaybackViewModel
и MediaItemsRepository
соответственно.
Показать историю ранее воспроизведенных медиаисточников
В этом разделе вы узнаете, как отображать и просматривать историю ранее воспроизведенных медиа-источников.
Получите список истории с помощью API PlaybackCardViewModel.
PlaybackCardViewModel
предоставляет API LiveData под названием getHistoryList()
для получения списка истории мультимедиа. Он возвращает LiveData, содержащий список медиаисточников, которые воспроизводились ранее. Эти данные можно использовать для заполнения объекта CarUiRecyclerView
. Подобно PlaybackQueueController
, класс PlaybackHistoryController
был добавлен в car-media-common
для оптимизации процесса.
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;
}
}
Пользовательский интерфейс истории поверхности с PlaybackHistoryController
Используйте новый PlaybackHistoryController
, чтобы заполнить данные истории в CarUiRecyclerView
. Конструкторы и основные функции этого класса следующие. Контейнер, переданный из клиентского приложения, должен содержать CarUiRecyclerView
с идентификатором history_list
. CarUiRecyclerView
отображает элементы списка и необязательный заголовок. Оба макета для элемента списка и заголовка могут быть переданы из клиентского приложения. Если Resources.ID_NULL
установлен в качестве headerResource, заголовок не отображается. После того как PlaybackCardViewModel
передается в контроллер, он отслеживает LiveData<List<MediaSource>>
полученный из 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() {
}
}
Макет каждого элемента должен содержать идентификаторы представлений, которые он хочет показать, которые соответствуют идентификаторам, используемым во внутреннем классе 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);
// ...
}
Чтобы отобразить список истории на медиа-карте, созданной с помощью PlaybackCardController
(или его подкласса), PlaybackHistoryController
можно создать в конструкторе PlaybackCardController
.