Реализация медиа-карты в AAOS

Медиа-карта — это автономная группа просмотра, которая отображает метаданные мультимедиа, такие как заголовок, обложка альбома и т. д., а также отображает элементы управления воспроизведением, такие как «Воспроизведение », «Пауза» , «Пропустить» и даже пользовательские действия, предоставляемые сторонним мультимедийным приложением. На медиа-карте также может отображаться очередь мультимедийных элементов, например список воспроизведения.

Медиа-карта

Медиа-карта

Медиа-карта

Рисунок 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 .