Wdrażanie karty multimedialnej w systemie AAOS

Karta multimediów to samodzielna grupa ViewGroup, która wyświetla metadane multimediów, takie jak tytuł, okładka albumu itp., oraz elementy sterujące odtwarzaniem, takie jak Odtwórz, Wstrzymaj, Pomiń, a nawet działania niestandardowe udostępniane przez aplikację multimedialną innej firmy. Karta multimediów może też wyświetlać kolejkę elementów multimedialnych, takich jak playlista.

Karta multimediów

Karta multimediów

Karta multimediów

Rysunek 1. Przykładowe implementacje karty multimedialnej

Jak są implementowane karty multimedialne w AAOS?

ViewGroups, które wyświetlają informacje o multimediach, obserwują aktualizacje LiveData z Model danych biblioteki car-media-common, PlaybackViewModel, do wypełniania ViewGroup. Każda aktualizacja LiveData odpowiada podzbiorowi informacji o multimediach, które uległy zmianie, takich jak MediaItemMetadata, PlaybackStateWrapperMediaSource.

To podejście prowadzi do powtarzania kodu (każda aplikacja kliencka dodaje obserwatora każdy element LiveData i wiele podobnych wyświetleń otrzymuje zaktualizowane dane), utworzył(a) PlaybackCardController.

PlaybackCardController

Pozycja PlaybackCardController została dodana do biblioteki car-media-common do pomagają tworzyć karty multimedialne. To publiczna klasa utworzona z ViewGroup (mView), PlaybackViewModel (mDataModel), PlaybackCardViewModel (mViewModel) i instansji MediaItemsRepository (mItemsRepository).

W funkcji setupController grupa ViewGroup jest analizowana pod kątem określonych widoków za pomocą identyfikatora mView.findViewById(R.id.xxx) i przypisywana do chronionych obiektów 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);

         // ...
}

Każda aktualizacja LiveData z PlaybackViewModel jest obserwowana w chronionej metodzie i wykonywana w ramach interakcji z widokami odpowiednimi do otrzymanych danych. Na przykład obserwator z MediaItemMetadata ustawia tytuł na mTitle TextView i przekazuje MediaItemMetadata.ArtworkRef do albumu grafika ImageBinder mAlbumArtBinder. Jeśli metadane mają wartość null, widoki są ukryte. W razie potrzeby podklasy klasy Controller mogą zastąpić tę logikę.

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

Przedłużenie kontrolera karty odtwarzania

Aplikacje klienckie, które chcą tworzyć karty multimedialne, powinny rozszerzać klasę PlaybackCardController, jeśli mają dodatkowe funkcje, które chcą obsługiwać w każdej aktualizacji LiveData. Obecni klienci w AAOS postępują zgodnie z tym wzorcem. Najpierw należy utworzyć podklasę PlaybackCardController, taką jak MediaCardController. Następnie klasa MediaCardController powinna dodać stałą wewnętrzną klasę Builder, która rozszerza klasę 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
    // ...

  }
}

Utwórz instancję klasy OdtwarzanieCardController lub podklasę

Klasa kontrolera powinna być utworzona z fragmentu lub działania w kolejności aby mieć właściciela cyklu życia dla obserwatorów LiveData.

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

mViewModel jest wystąpieniem klasy PlaybackCardViewModel (lub jej podklasy).

PlaybackCardViewModel do zapisywania stanu

PlaybackCardViewModel to ViewModel zapisujący stan powiązany z Fragmentem lub Aktywnością, który powinien służyć do odtwarzania zawartości karty multimedialnej w przypadku zmiany konfiguracji (np. przejścia z motywu jasnego na ciemny, gdy użytkownik jedzie przez tunel). Domyślna wartość PlaybackCardViewModel obsługuje przechowywania wystąpień instancji MediaModel do odtwarzania, z których Można pobrać PlaybackViewModel i MediaItemsRepository. Użyj PlaybackCardViewModel, aby śledzić stan kolejki, historię i przepełnienie za pomocą podanych funkcji pobierania i seterów.

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

Ta klasa może zostać rozszerzona, jeśli trzeba śledzić dodatkowe stany.

Wyświetlanie kolejki na karcie multimedialnej

PlaybackViewModel udostępnia interfejsy API LiveData do wykrywania, czy MediaSource obsługuje kolejkę i pobiera listę obiektów MediaItemMetadata w funkcji kolejkę. Mimo że tych interfejsów API można używać bezpośrednio do wypełniania pola RecyclerView. obiektu z informacjami o kolejce, klasa PlaybackQueueController została dodano do biblioteki car-media-common, aby usprawnić ten proces. Układ każdego elementu w CarUiRecyclerView jest określany przez aplikację klienta, a także przez opcjonalny układ nagłówka. Aplikacja kliencka może również ograniczyć liczbę elementy wyświetlane w kolejce w stanie dysku z niestandardowymi ograniczeniami UXR.

Konstruktor i metody ustawiające PlaybackQueueController są pokazane w tym przykładzie. Zasoby układu queueResourceheaderResource mogą być przekazywane jako Resources.ID_NULL, jeśli w pierwszym przypadku kontener zawiera już CarUiRecyclerViewid queue_list, a w drugim przypadku kolejka nie ma nagłówka.

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

Układ każdego elementu kolejki powinien zawierać identyfikatory wyświetleń, które odpowiadają klasom wewnętrznym 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);

            // ...
}

Aby wyświetlić kolejkę na karcie multimediów utworzonej za pomocą PlaybackCardController (lub podklasy), PlaybackQueueController można utworzyć w funkcji Konstruktor PlaybackCardController używający mDataModel i mItemsRepository odpowiednio dla instancji PlaybackViewModel i MediaItemsRepository.

Pokaż historię wcześniej odtwarzanych treści z MediaSource

Z tej sekcji dowiesz się, jak wyświetlać i wyświetlać historię wcześniej odtwarzanych źródeł multimediów.

Pobieranie listy historii za pomocą interfejsu PlaybackCardViewModel API

PlaybackCardViewModel udostępnia interfejs LiveData API o nazwie getHistoryList(), który służy do pobierania listy historii multimediów. Zwraca on dane LiveData zawierające listę źródeł multimediów, które były odtwarzane wcześniej. Te dane mogą zostać wykorzystane do wypełniania obiekt CarUiRecyclerView. Aby usprawnić proces, do biblioteki car-media-common dodano klasę PlaybackHistoryController, która jest podobna do klasy PlaybackQueueController.

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

Interfejs historii Surface z PlaybackHistoryController

Użyj nowego elementu PlaybackHistoryController, aby wypełnić dane historyczne w elementach CarUiRecyclerView. Konstruktory i główne funkcje tej klasy są następujące: Kontener przekazany przez aplikację klienta powinien zawierać CarUiRecyclerView o identyfikatorze history_list. CarUiRecyclerView wyświetla elementy listy i opcjonalny nagłówek. Oba układy elementu listy a nagłówek może być przekazywany z aplikacji klienckiej. Jeśli zasada Resources.ID_NULL jest ustawiona , nagłówek nie jest wyświetlany. Po Element PlaybackCardViewModel jest przekazywany do kontrolera i monitoruje Pobrano LiveData<List<MediaSource>> z 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() {
    }
}

Układ każdego elementu powinien zawierać identyfikatory widoków, które mają być wyświetlane, odpowiadające tym, które są używane w wewnętrznej klasie 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);
// ...
}

Aby wyświetlić listę historii na karcie multimedialnej utworzonej za pomocą klasy PlaybackCardController (lub jej podklasy), możesz skonstruować obiekt PlaybackHistoryController w konstruktorze klasy PlaybackCardController.