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 i inne, oraz elementy sterujące odtwarzaniem, takie jak OdtwórzWstrzymaj, Pomiń, a nawet niestandardowe działania udostępniane przez aplikację multimedialną innej firmy. Karta multimediów może też wyświetlać kolejkę elementów multimedialnych, np. listę odtwarzania.

Karta multimediów

Karta multimediów

Karta multimediów

Rysunek 1. Przykładowe implementacje karty multimediów.

Jak karty multimediów są wdrażane w AAOS?

Grupy widoków, które wyświetlają informacje o mediach, obserwują aktualizacje LiveData z car-media-commonbiblioteki, modelu danych, PlaybackViewModel, aby wypełnić grupę widoków. Każda aktualizacja LiveData odpowiada podzbiorowi informacji o mediach, które uległy zmianie, np. MediaItemMetadata, PlaybackStateWrapperMediaSource.

Ponieważ takie podejście prowadzi do powtarzania kodu (każda aplikacja kliencka dodaje obserwatorów do każdego elementu LiveData, a wiele podobnych widoków otrzymuje zaktualizowane dane), stworzyliśmy PlaybackCardController.

PlaybackCardController

PlaybackCardController został dodany do biblioteki car-media-common, aby ułatwić tworzenie karty multimedialnej. Jest to klasa publiczna, która jest tworzona za pomocą elementów ViewGroup (mView), PlaybackViewModel (mDataModel), PlaybackCardViewModel (mViewModel) i instancji MediaItemsRepository (mItemsRepository).

W funkcji setupController element ViewGroup jest analizowany pod kątem określonych widoków według identyfikatora, z mView.findViewById(R.id.xxx) i przypisywany 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 wykonuje interakcje z widokami powiązanymi z otrzymanymi danymi. Na przykład obserwator w MediaItemMetadata ustawia tytuł na mTitle TextView i przekazuje MediaItemMetadata.ArtworkRef do okładki albumu ImageBinder mAlbumArtBinder. Jeśli metadane mają wartość null, widoki są ukryte. Podklasy klasy Controller mogą w razie potrzeby 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);
        }
    }

Rozszerzanie klasy PlaybackCardController

Aplikacje klienckie, które chcą utworzyć kartę multimediów, powinny rozszerzyć klasę PlaybackCardController, jeśli mają dodatkowe możliwości, które chcą obsługiwać w każdej aktualizacji LiveData. Obecni klienci w AAOS postępują zgodnie z tym wzorcem. Najpierw należy utworzyć PlaybackCardController podklasę, np. MediaCardController. Następnie MediaCardController powinien dodać statyczną 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 PlaybackCardController lub jej podklasy.

Klasę kontrolera należy utworzyć w fragmencie lub działaniu, 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 to instancja klasy PlaybackCardViewModel (lub podklasy).

PlaybackCardViewModel do zapisywania stanu

PlaybackCardViewModel to ViewModel zapisujący stan, powiązany z fragmentem lub aktywnością. Należy go używać do odtwarzania zawartości karty multimedialnej w przypadku zmiany konfiguracji (np. przełączenia z jasnego na ciemny motyw, gdy użytkownik przejeżdża przez tunel). Domyślny PlaybackCardViewModel obsługuje przechowywanie instancji MediaModel do odtwarzania, z których można pobrać PlaybackViewModelMediaItemsRepository. Użyj PlaybackCardViewModel, aby śledzić stan kolejki, historię i menu przepełnienia za pomocą podanych metod pobierających i ustawiających.

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

W razie potrzeby można rozszerzyć tę klasę, aby śledzić dodatkowe stany.

Wyświetlanie kolejki na karcie multimediów

PlaybackViewModel udostępnia interfejsy LiveData API, które umożliwiają wykrywanie, czy MediaSource obsługuje kolejkę, oraz pobieranie listy obiektów MediaItemMetadata w kolejce. Chociaż tych interfejsów API można używać bezpośrednio do wypełniania obiektu RecyclerView informacjami o kolejce, do biblioteki car-media-common dodano klasę PlaybackQueueController, aby usprawnić ten proces. Układ każdego elementu w CarUiRecyclerView jest określany przez aplikację klienta, podobnie jak opcjonalny układ nagłówka. Aplikacja kliencka może też ograniczyć liczbę elementów wyświetlanych w kolejce podczas jazdy, stosując niestandardowe ograniczenia UXR.

Konstruktor i funkcje ustawiające PlaybackQueueController są pokazane w tym przykładzie. Zasoby układu queueResourceheaderResource można przekazywać jako Resources.ID_NULL, jeśli w pierwszym przypadku kontener zawiera już element CarUiRecyclerView z atrybutem id 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 widoków, które mają być wyświetlane, odpowiadające identyfikatorom używanym w klasie wewnętrznej 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ą klasy PlaybackCardController (lub jej podklasy), klasę PlaybackQueueController można utworzyć w konstruktorze PlaybackCardController za pomocą klas mDataModelmItemsRepository w przypadku instancji PlaybackViewModelMediaItemsRepository.

Wyświetlanie historii wcześniej odtwarzanych źródeł multimediów

W tej sekcji dowiesz się, jak 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 umożliwia pobieranie listy historii multimediów. Zwraca LiveData zawierający listę obiektów MediaSource, które były wcześniej odtwarzane. Te dane można wykorzystać do wypełnienia obiektu CarUiRecyclerView. Podobnie jak w przypadku PlaybackQueueController, do biblioteki car-media-common dodano klasę o nazwie PlaybackHistoryController, aby uprościć ten proces.

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 powierzchni z kontrolerem PlaybackHistoryController

Użyj nowego parametru PlaybackHistoryController, aby wypełnić dane historyczne w CarUiRecyclerView. Konstruktory i główne funkcje tej klasy są następujące. Kontener przekazany z aplikacji klienta powinien zawierać element CarUiRecyclerView z identyfikatorem history_list. Element CarUiRecyclerView wyświetla elementy listy i opcjonalny nagłówek. Oba układy elementu listy i nagłówka mogą być przekazywane z aplikacji klienta. Jeśli jako headerResource ustawisz Resources.ID_NULL, nagłówek nie będzie się wyświetlać. Po przekazaniu wartości PlaybackCardViewModel do kontrolera monitoruje on wartość LiveData<List<MediaSource>> pobraną 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 identyfikatorom używanym w klasie wewnętrznej 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 multimediów utworzonej za pomocą klasy PlaybackCardController (lub jej podklasy), obiekt PlaybackHistoryController można utworzyć w konstruktorze klasy PlaybackCardController.