Mengimplementasikan kartu media di AAOS

Kartu media adalah ViewGroup mandiri yang menampilkan metadata media seperti judul, gambar album, dan lainnya, serta menampilkan kontrol pemutaran seperti Putar dan Jeda, Lewati, dan bahkan tindakan kustom yang disediakan oleh aplikasi media pihak ketiga. Kartu media juga dapat menampilkan antrean item media, seperti playlist.

Kartu media

Kartu media

Kartu media

Gambar 1. Implementasi contoh Kartu Media.

Bagaimana kartu media diterapkan di AAOS?

ViewGroup yang menampilkan informasi media mengamati update LiveData dari model data library car-media-common, PlaybackViewModel, untuk mengisi ViewGroup. Setiap update LiveData sesuai dengan subkumpulan informasi media yang telah berubah, seperti MediaItemMetadata, PlaybackStateWrapper, dan MediaSource.

Karena pendekatan ini menghasilkan kode berulang (setiap aplikasi klien menambahkan Observer pada setiap bagian LiveData dan banyak View serupa diberi data yang diperbarui), kita membuat PlaybackCardController.

PlaybackCardController

PlaybackCardController telah ditambahkan ke library car-media-common untuk membantu membuat kartu media. Ini adalah class publik yang dibuat dengan instance ViewGroup (mView), PlaybackViewModel (mDataModel), PlaybackCardViewModel (mViewModel), dan MediaItemsRepository (mItemsRepository).

Dalam fungsi setupController, ViewGroup diuraikan untuk tampilan tertentu berdasarkan ID, dengan mView.findViewById(R.id.xxx) dan ditetapkan ke objek View yang dilindungi.

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);

         // ...
}

Setiap update LiveData dari PlaybackViewModel diamati dalam metode yang dilindungi dan melakukan interaksi dengan View yang relevan dengan data yang diterima. Misalnya, observer di MediaItemMetadata menetapkan judul di mTitle TextView dan meneruskan MediaItemMetadata.ArtworkRef ke gambar album ImageBinder mAlbumArtBinder. Jika metadata null, Tampilan akan disembunyikan. Subclass Pengontrol dapat mengganti logika ini jika diperlukan.

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);
        }
    }

Memperluas PlaybackCardController

Aplikasi klien yang ingin membuat kartu media harus memperluas PlaybackCardController jika memiliki kemampuan tambahan yang ingin ditangani di setiap update LiveData. Klien yang ada di AAOS mengikuti pola ini. Pertama, subclass PlaybackCardController harus dibuat, seperti MediaCardController. Selanjutnya, MediaCardController harus menambahkan class Builder dalam statis yang memperluas class 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
    // ...

  }
}

Membuat instance PlaybackCardController atau subclass

Class Pengontrol harus dibuat instance-nya dari Fragment atau Aktivitas agar memiliki LifecycleOwner untuk observer LiveData.

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

mViewModel adalah instance PlaybackCardViewModel (atau subclass).

PlaybackCardViewModel untuk Menyimpan Status

PlaybackCardViewModel adalah ViewModel penyimpanan status yang terikat dengan Fragment atau Aktivitas yang harus digunakan untuk merekonstruksi konten kartu media jika terjadi perubahan konfigurasi (seperti beralih dari tema terang ke gelap saat pengguna mengemudi melalui terowongan). PlaybackCardViewModel default menangani penyimpanan instance MediaModel untuk pemutaran, tempat PlaybackViewModel dan MediaItemsRepository dapat diambil. Gunakan PlaybackCardViewModel untuk melacak status menu antrean, histori, dan tambahan melalui pengambil dan penyetel yang disediakan.

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

Class ini dapat diperpanjang jika status tambahan perlu dilacak.

Menampilkan antrean di kartu media

PlaybackViewModel menyediakan LiveData API untuk mendeteksi apakah MediaSource mendukung antrean dan mengambil daftar objek MediaItemMetadata dalam antrean. Meskipun API ini dapat digunakan secara langsung untuk mengisi objek RecyclerView dengan informasi antrean, class PlaybackQueueController telah ditambahkan ke library car-media-common untuk menyederhanakan proses ini. Tata letak untuk setiap item di CarUiRecyclerView ditetapkan oleh aplikasi klien serta tata letak Header opsional. Aplikasi klien juga dapat memilih untuk membatasi jumlah item yang ditampilkan dalam antrean selama status drive dengan batasan UXR kustom.

Konstruktor dan penyetel PlaybackQueueController ditampilkan dalam contoh berikut. Resource tata letak queueResource dan headerResource dapat diteruskan sebagai Resources.ID_NULL jika, dalam kasus pertama, penampung sudah berisi CarUiRecyclerView dengan id queue_list dan, dalam kasus yang terakhir, antrean tidak memiliki Header.

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

Tata letak untuk setiap item antrean harus berisi ID untuk Tampilan yang ingin ditampilkan yang sesuai dengan yang digunakan di class dalam 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);

            // ...
}

Untuk menampilkan antrean di kartu media yang dibuat dengan PlaybackCardController (atau subclass), PlaybackQueueController dapat dibuat di konstruktor PlaybackCardController menggunakan mDataModel dan mItemsRepository untuk instance PlaybackViewModel dan MediaItemsRepository.

Menampilkan histori MediaSources yang diputar sebelumnya

Di bagian ini, Anda akan mempelajari cara menampilkan dan menampilkan histori sumber media yang diputar sebelumnya.

Mendapatkan daftar histori dengan PlaybackCardViewModel API

PlaybackCardViewModel menyediakan LiveData API yang disebut getHistoryList() untuk mengambil daftar histori media. Metode ini menampilkan LiveData yang berisi daftar MediaSource yang telah diputar sebelumnya. Data ini dapat digunakan untuk mengisi objek CarUiRecyclerView. Serupa dengan PlaybackQueueController, class bernama PlaybackHistoryController telah ditambahkan ke library car-media-common untuk menyederhanakan proses.

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

UI histori platform dengan PlaybackHistoryController

Gunakan PlaybackHistoryController baru untuk membantu mengisi data histori ke CarUiRecyclerView. Konstruktor dan fungsi utama class ini adalah sebagai berikut. Penampung yang diteruskan dari aplikasi klien harus berisi CarUiRecyclerView dengan ID history_list. CarUiRecyclerView menampilkan item daftar dan header opsional. Tata letak untuk item daftar dan header dapat diteruskan dari aplikasi klien. Jika Resources.ID_NULL ditetapkan sebagai headerResource, header tidak akan ditampilkan. Setelah PlaybackCardViewModel diteruskan ke pengontrol, pengontrol akan memantau LiveData<List<MediaSource>> yang diambil dari 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() {
    }
}

Tata letak untuk setiap item harus berisi ID untuk View yang ingin ditampilkan yang sesuai dengan yang digunakan di class dalam 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);
// ...
}

Untuk menampilkan daftar histori dalam kartu media yang dibuat dengan PlaybackCardController (atau subclass), PlaybackHistoryController dapat dibuat dalam konstruktor PlaybackCardController.