Mengimplementasikan kartu media di AAOS

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

Kartu media

Kartu media

Kartu media

Gambar 1. Penerapan contoh Kartu Media.

Bagaimana penerapan kartu media 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 dalam membuat kartu media. Ini adalah class publik yang dibuat dengan ViewGroup (mView), PlaybackViewModel (mDataModel), PlaybackCardViewModel (mViewModel), dan instance MediaItemsRepository (mItemsRepository).

Dalam fungsi setupController, ViewGroup diuraikan untuk tampilan tertentu oleh ID, dengan mView.findViewById(R.id.xxx) dan ditetapkan ke objek Tampilan 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 perlu.

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 lama di AAOS akan 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, yang digunakan untuk 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 dalam CarUiRecyclerView juga ditentukan oleh aplikasi klien sebagai 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 sebelumnya, container 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 masing-masing 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 akan mengambil daftar histori media. Metode ini menampilkan LiveData yang berisi daftar MediaSource yang telah diputar sebelumnya. Data ini dapat digunakan untuk 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;
    }
}

Menampilkan UI histori dengan PlaybackHistoryController

Gunakan PlaybackHistoryController baru untuk membantu mengisi data histori ke CarUiRecyclerView. Konstruktor dan fungsi utama class ini sebagai berikut. Penampung yang diteruskan dari aplikasi klien harus berisi CarUiRecyclerView dengan ID history_list. CarUiRecyclerView menampilkan item daftar dan header opsional. Kedua tata letak untuk item daftar dan {i>header<i} bisa diteruskan dari aplikasi klien. Jika Resources.ID_NULL disetel sebagai headerResource, header tersebut tidak ditampilkan. Setelah PlaybackCardViewModel diteruskan ke pengontrol untuk memantau LiveData<List<MediaSource>> 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 di kartu media yang dibuat dengan PlaybackCardController (atau subclass), PlaybackHistoryController dapat dibuat di konstruktor PlaybackCardController.