AAOS でメディアカードを実装する

メディアカードは自己完結型の ViewGroup で、タイトル、アルバムアートなどのメディア メタデータを表示し、再生一時停止スキップ、さらにサードパーティ メディアアプリが提供するカスタム アクションなどの再生コントロールを表示します。また、メディアカードはプレイリストのようなメディア アイテムのキューも表示できます。

メディアカード

メディアカード

メディアカード

図 1. メディアカードのサンプル実装

AAOS でメディアカードを実装する方法

メディア情報を表示する ViewGroup は、car-media-common ライブラリ データモデルである PlaybackViewModel から LiveData の更新を監視し、ViewGroup にデータを入力します。LiveData の各更新は、MediaItemMetadataPlaybackStateWrapperMediaSource のように変更されたメディア情報のサブセットに対応します。

このアプローチはコードの繰り返しにつながるため(各クライアント アプリは LiveData の各ピースに Observers を追加し、多くの類似した View が更新されたデータに割り当てられます)、PlaybackCardController を作成しました。

PlaybackCardController

メディアカードを作成するのに役立つ PlaybackCardControllercar-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 に渡します。メタデータが null の場合、View は非表示になります。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 に静的な内部 Builder クラスを追加します。これにより、PlaybackCardController の静的な内部 Builder クラスが拡張されます。

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 またはサブクラスをインスタンス化する

LiveData のオブザーバーの LifecycleOwner を含めるために、Controller クラスは Fragment または Activity からインスタンス化する必要があります。

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

mViewModelPlaybackCardViewModel(またはサブクラス)のインスタンスです。

PlaybackCardViewModel で状態を保存する

PlaybackCardViewModel は、Fragment や Activity に関連付けられた状態保存の ViewModel です。設定変更が発生した場合(例: ユーザーがトンネルを運転しているときにライトモードからダークモードに切り替わる)に、メディアカードのコンテンツを再構築する際に使用してください。デフォルトの PlaybackCardViewModel は再生用の MediaModel のインスタントの保存を処理し、これにより PlaybackViewModelMediaItemsRepository が取得されます。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 は LiveData API を提供して、MediaSource がキューをサポートするかどうかを検出し、キューの MediaItemMetadata オブジェクトのリストを取得します。これらの API は RecyclerView オブジェクトにキューの情報を入力するために直接使用できますが、このプロセスを合理化するために PlaybackQueueController クラスが car-media-common ライブラリに追加されています。CarUiRecyclerView の各アイテムのレイアウトは、クライアント アプリおよび任意の Header レイアウトによって指定されます。クライアント アプリはカスタムの UXR 制限を使用して、運転状態の際にキューに表示されるアイテムの数を制限することもできます。

以下に、PlaybackQueueController コンストラクタとセッターの例を示します。queueResourceheaderResource のレイアウト リソースは、前者ではコンテナにすでに id queue_list を使用した CarUiRecyclerView が含まれている場合、後者ではキューに Header は含まれていない場合に、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;
    }

各キューアイテムのレイアウトには、QueueViewHolder 内部クラスで使用されているものに対応する、表示したい View の 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(またはサブクラス)で作成したメディアカードにキューを表示するには、PlaybackViewModelMediaItemsRepository のインスタンスに mDataModelmItemsRepository を使用した PlaybackCardController コンストラクタで PlaybackQueueController を構築します。

以前再生した MediaSource の履歴を表示する

このセクションでは、以前に再生したメディアソースの履歴を表示する方法について説明します。

PlaybackCardViewModel API の履歴リストを取得する

PlaybackCardViewModel は、getHistoryList() と呼ばれる LiveData API を提供してメディア履歴のリストを取得します。これは、以前に再生された MediaSource のリストを含む 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 を使用して履歴 UI を表示する

新しい PlaybackHistoryController を使用して、過去のデータを CarUiRecyclerView に入力できます。このクラスのコンストラクタと主な関数は以下のとおりです。クライアント アプリから渡されるコンテナには、ID history_list を使用した CarUiRecyclerView を含む必要があります。CarUiRecyclerView には、リストアイテムとオプションのヘッダーが表示されます。リストアイテムとヘッダーの両方のレイアウトをクライアント アプリから渡すことができます。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() {
    }
}

各アイテムのレイアウトには、ViewHolder 内部クラスで使用されているものに対応する、表示したい View の 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(またはサブクラス)で作成したメディアカードに履歴リストを表示するには、PlaybackHistoryControllerPlaybackCardController のコンストラクタで構築します。