在 AAOS 中實作媒體資訊卡

媒體資訊卡是獨立的 ViewGroup,可顯示媒體中繼資料 (例如標題、專輯封面等),並顯示播放控制項,例如「播放」和「暫停」、「略過」,甚至是第三方媒體應用程式提供的自訂動作。媒體資訊卡也可以顯示媒體項目的佇列,例如播放清單。

媒體資訊卡

媒體資訊卡

媒體資訊卡

圖 1. 媒體資訊卡範例實作。

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

顯示媒體資訊的 ViewGroup 會觀察 car-media-common 程式庫 data 模型 (PlaybackViewModel) 的 LiveData 更新,以填入 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

如果用戶端應用程式想要建立媒體資訊卡,且有要在每次 LiveData 更新中處理的其他功能,則應擴充 PlaybackCardController。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,與片段或活動相關聯,應在發生設定變更時 (例如使用者行經隧道時從淺色主題切換為深色主題) 用於重建媒體資訊卡的內容。預設的 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。如果在前者中,容器已包含含有 id queue_listCarUiRecyclerView,且在後者中,佇列沒有標頭,則可以將 queueResourceheaderResource 版面配置資源傳遞為 Resources.ID_NULL

   /**
    * 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 內部類別中使用的 ID 相符。

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 (或子類別) 建立的媒體資訊卡中顯示佇列,您可以在 PlaybackCardController 建構函式中使用 mDataModelmItemsRepository 分別為 PlaybackViewModelMediaItemsRepository 例項建構 PlaybackQueueController

顯示先前播放的 MediaSources 記錄

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

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

PlaybackCardViewModel 提供名為 getHistoryList() 的 LiveData API,用於擷取媒體歷史記錄清單。它會傳回 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 顯示歷史記錄 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 內部類別中使用的 ID 相符。

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