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

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

メディアカード

メディアカード

メディアカード

図 1. メディアカードの実装例。

AAOS でメディアカードはどのように実装されていますか?

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

このアプローチではコードが重複するため(各クライアント アプリが各 LiveData にオブザーバーを追加し、多くの類似ビューに更新されたデータが割り当てられます)、PlaybackCardController が作成されました。

PlaybackCardController

メディアカードの作成を支援するために、PlaybackCardControllercar-media-common ライブラリに追加されました。これは、ViewGroup(mView)、PlaybackViewModel(mDataModel)、PlaybackCardViewModel(mViewModel)、MediaItemsRepository インスタンス(mItemsRepository)で構成される公開クラスです。

setupController 関数では、ViewGroup が ID で特定のビューを解析し、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);

         // ...
}

PlaybackViewModel からの各 LiveData 更新は、保護されたメソッドで監視され、受信したデータに関連するビューとのインタラクションを実行します。たとえば、MediaItemMetadata のオブザーバーは mTitle TextView にタイトルを設定し、MediaItemMetadata.ArtworkRef をアルバムアート ImageBinder mAlbumArtBinder に渡します。メタデータが null の場合、ビューは非表示になります。必要に応じて、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 の既存のクライアントは、このパターンに従います。まず、MediaCardController などの PlaybackCardController サブクラスを作成する必要があります。次に、MediaCardControllerPlaybackCardController の 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 は、MediaSource がキューをサポートしているかどうかを検出し、キュー内の MediaItemMetadata オブジェクトのリストを取得するための LiveData API を提供します。これらの API を直接使用してキュー情報で RecyclerView オブジェクトを入力することもできますが、このプロセスを効率化するために、car-media-common ライブラリに PlaybackQueueController クラスが追加されています。CarUiRecyclerView の各アイテムのレイアウトは、オプションのヘッダー レイアウトと同様にクライアント アプリによって指定されます。クライアント アプリは、カスタム UXR 制限を使用して、ドライブ中のキューに表示されるアイテムの数を制限することもできます。

PlaybackQueueController のコンストラクタとセッターは次のサンプルに示されています。queueResource レイアウト リソースと headerResource レイアウト リソースは、前者の場合はコンテナに id queue_list を含む CarUiRecyclerView がすでに含まれている場合、後者の場合はキューにヘッダーがない場合に、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 内部クラスで使用されているビューに対応する、表示するビューの 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(またはサブクラス)で作成されたメディアカードにキューを表示するには、PlaybackViewModel インスタンスと MediaItemsRepository インスタンスにそれぞれ mDataModelmItemsRepository を使用して、PlaybackCardController コンストラクタで PlaybackQueueController を作成します。

以前に再生された MediaSource の履歴を表示する

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

PlaybackCardViewModel API を使用して履歴リストを取得する

PlaybackCardViewModel は、メディア履歴リストを取得する getHistoryList() という LiveData API を提供します。以前に再生された MediaSource のリストを含む LiveData を返します。このデータは、CarUiRecyclerView オブジェクトに入力するために使用できます。PlaybackQueueController と同様に、プロセスを効率化するために、car-media-common ライブラリに PlaybackHistoryController というクラスが追加されました。

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_listCarUiRecyclerView が含まれている必要があります。CarUiRecyclerView には、リストアイテムとオプションのヘッダーが表示されます。リストアイテムとヘッダーの両方のレイアウトをクライアント アプリから渡すことができます。headerResource として Resources.ID_NULL が設定されている場合、ヘッダーは表示されません。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 内部クラスで使用されているビューの 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 のコンストラクタで作成します。