在 AAOS 中實作媒體資訊卡

媒體資訊卡是獨立的 ViewGroup,用於顯示媒體中繼資料,例如 包括曲名、專輯封面等,以及顯示播放控制項,例如 播放暫停略過,甚至是第三項步驟提供的自訂動作 第三方媒體應用程式媒體資訊卡也會顯示媒體項目佇列,例如 播放清單

媒體資訊卡

媒體資訊卡

媒體資訊卡

圖 1. 媒體資訊卡範例導入。

如何在 AAOS 中實作媒體資訊卡?

顯示媒體資訊的 ViewGroup 會觀察 car-media-common 程式庫「資料」模型為 PlaybackViewModel,用於填入 ViewGroup。每項 LiveData 更新都會對應至已變更的媒體資訊子集,例如 MediaItemMetadataPlaybackStateWrapperMediaSource

由於這種做法會導致程式碼重複 (每個用戶端應用程式都會在每個 LiveData 上新增 Observer,許多類似的 View 會指派更新的資料),因此我們建立了 PlaybackCardController

PlaybackCardController

PlaybackCardController 已新增至 car-media-common 程式庫, 協助建立媒體資訊卡這是公開類別,以 ViewGroup (mView)、PlaybackViewModel (mDataModel)、PlaybackCardViewModel (mViewModel) 和 MediaItemsRepository 執行個體 (mItemsRepository)。

setupController 函式中,ViewGroup 會根據 mView.findViewById(R.id.xxx) 為特定檢視畫面解析 ID,並指派給受保護的 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);

         // ...
}

系統會在受保護的方法中觀察 PlaybackViewModel 的每項 LiveData 更新,並與與收到的資料相關的 View 執行互動。舉例來說,MediaItemMetadata 的觀察器會設定 mTitle TextView,並將 MediaItemMetadata.ArtworkRef 傳遞至相簿 藝術品 ImageBinder mAlbumArtBinder。如果中繼資料為空值,則會隱藏檢視畫面。如有需要,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 應新增靜態內部 擴充 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 或子類別例項化

控制器類別應從 Fragment 或 Activity 例項化,才能為 LiveData 觀察器提供 LifecycleOwner。

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

mViewModelPlaybackCardViewModel (或子類別) 的例項。

PlaybackCardViewModel 儲存狀態

PlaybackCardViewModel 是儲存狀態的 ViewModel,並連結至 Fragment 或 可用來重建媒體資訊卡內容的活動, 發生設定變更時 (例如從淺色主題切換至深色主題時 使用者透過通道行駛)。預設的 PlaybackCardViewModel 會處理 儲存用於播放的 MediaModel 例項, 可擷取 PlaybackViewModelMediaItemsRepository。使用 PlaybackCardViewModel 透過提供的 getter 和 setter 追蹤佇列、記錄和溢出選單的狀態。

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 提供 LiveData API,可偵測 MediaSource 是否 支援佇列並擷取 MediaItemMetadata 物件清單 佇列。雖然這些 API 可以直接用來填入 RecyclerView 含有佇列資訊的物件,但自動參照了 PlaybackQueueController 類別 新增至 car-media-common 程式庫,以簡化這項程序。CarUiRecyclerView 中每個項目的版面配置由用戶端應用程式指定,也可指定為選用的標頭版面配置。用戶端應用程式也可以選擇限制 在行駛狀態期間顯示的項目,使用自訂 UXR 限制。

PlaybackQueueController 建構函式和 setter 顯示如下: 樣本。可以傳遞 queueResourceheaderResource 版面配置資源 與 Resources.ID_NULL 一樣,如果前例的案例中,容器已包含 含有 id queue_listCarUiRecyclerView,後者則是佇列 沒有標頭

   /**
    * 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;
    }

每個佇列項目的版面配置應包含其 ID 的 ID 看起來與 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 可在 使用 mDataModelmItemsRepositoryPlaybackCardController 建構函式 PlaybackViewModelMediaItemsRepository 執行個體。

顯示先前播放的 MediaSources 記錄

在本節中,您將瞭解如何顯示及顯示先前播放的媒體來源記錄。

使用 PlaybackCardViewModel API 取得歷史記錄清單

PlaybackCardViewModel 提供名為 getHistoryList() 的 LiveData API,用於擷取媒體歷史記錄清單。它會傳回 LiveData,其中包含先前播放過的 MediaSource 清單。這項資料可用於填入 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 顯示歷史記錄 UI

使用新的 PlaybackHistoryController 填入歷史資料至 CarUiRecyclerView。這個類別的建構函式和主要函式如下所示。從用戶端應用程式傳遞的容器應包含 ID 為 history_listCarUiRecyclerViewCarUiRecyclerView 會顯示清單項目和選用的標頭。清單項目和標頭的版面配置都可以從用戶端應用程式傳遞。如果將 Resources.ID_NULL 設為 headerResource,系統就不會顯示標頭。PlaybackCardViewModel 傳遞至控制器後,會監控從 playbackCardViewModel.getHistoryList() 擷取的 LiveData<List<MediaSource>>

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() {
    }
}

每個項目的版面配置應包含所需檢視畫面的 ID 下方顯示了對應於 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 (或子類別) 建立的媒體資訊卡中顯示歷史記錄清單,可以使用 PlaybackCardController 建構函式建立 PlaybackHistoryController