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

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

Медиа-карта

Медиа-карта

Медиа-карта

Рисунок 1. Примеры реализации медиа-карт.

Как реализованы медиа-карты в AAOS?

ViewGroups, которые показывают информацию о медиа, наблюдают за обновлениями LiveData из модели данных библиотеки car-media-common , PlaybackViewModel , чтобы заполнить ViewGroup. Каждое обновление LiveData соответствует подмножеству измененной информации о медиа, например MediaItemMetadata , PlaybackStateWrapper и MediaSource .

Поскольку такой подход приводит к повторению кода (каждое клиентское приложение добавляет Observer для каждого фрагмента LiveData, и многим похожим Views назначаются обновленные данные), мы создали PlaybackCardController .

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 наблюдается в защищенном методе и выполняет взаимодействия с Views, соответствующими полученным данным. Например, наблюдатель MediaItemMetadata устанавливает заголовок в mTitle TextView и передает MediaItemMetadata.ArtworkRef в обложку альбома ImageBinder mAlbumArtBinder . Если метаданные равны null, Views скрыты. Подклассы Controller могут переопределить эту логику, если это необходимо.

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 должен быть создан из Fragment или Activity, чтобы иметь LifecycleOwner для наблюдателей LiveData.

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

mViewModel — это экземпляр PlaybackCardViewModel (или подкласса).

PlaybackCardViewModel для сохранения состояния

PlaybackCardViewModel — это ViewModel с сохранением состояния, привязанный к Fragment или Activity, который следует использовать для реконструкции содержимого карты памяти в случае изменения конфигурации (например, переключения со светлой на темную тему, когда пользователь проезжает через туннель). 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 соответственно.

Показать историю ранее воспроизведенных MediaSources

В этом разделе вы узнаете, как отображать и выводить на экран историю ранее воспроизведенных медиаисточников.

Получить список истории с помощью API PlaybackCardViewModel

PlaybackCardViewModel предоставляет API LiveData, называемый getHistoryList() , для получения списка истории мультимедиа. Он возвращает LiveData, содержащий список MediaSources, которые были воспроизведены ранее. Эти данные можно использовать для заполнения объекта 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 .