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órz i Wstrzymaj, 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.
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 multimediach, obserwują aktualizacje LiveData z modelu danych biblioteki car-media-common, aby wypełnić grupę widoków.PlaybackViewModel Każda aktualizacja LiveData odpowiada podzbiorowi informacji o mediach, które uległy zmianie, np. MediaItemMetadata, PlaybackStateWrapper i MediaSource.
Ponieważ takie podejście prowadzi do powtarzania kodu (każda aplikacja kliencka dodaje obserwatorów do każdego fragmentu 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 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);
}
}
Rozszerzenie 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ć PlaybackCardControllerpodklasę, np. MediaCardController. Następnie klasa MediaCardController powinna dodać statyczną klasę wewnętrzną 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.
Klasa kontrolera powinna być tworzona w ramach fragmentu lub działania, 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 wjeżdża do tunelu). Domyślny PlaybackCardViewModel obsługuje przechowywanie instancji MediaModel do odtwarzania, z których można pobrać PlaybackViewModel i MediaItemsRepository. Użyj
PlaybackCardViewModel, aby śledzić stan kolejki, historię i menu przepełnienia
za pomocą udostępnionych 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;
}
}
Tę klasę można rozszerzyć, jeśli trzeba śledzić dodatkowe stany.
Wyświetlanie kolejki na karcie multimediów
Interfejs PlaybackViewModel udostępnia interfejsy API LiveData do wykrywania, czy MediaSource obsługuje kolejkę, oraz do pobierania 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, a także 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 settery PlaybackQueueController są pokazane w tym przykładzie. Zasoby układu queueResource i headerResource 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 mDataModel i mItemsRepository dla instancji PlaybackViewModel i MediaItemsRepository.
Wyświetlanie historii wcześniej odtwarzanych źródeł multimediów
Z 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 obiekt LiveData zawierający listę obiektów MediaSource, które były wcześniej odtwarzane. Dane te mogą być używane do wypełniania 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 historii odtwarzania
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 wyświetlany. 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), klasę PlaybackHistoryController można utworzyć w konstruktorze klasy PlaybackCardController.