Medienkarte in AAOS implementieren

Eine Mediakarte ist eine eigenständige ViewGroup, die unter anderem Medienmetadaten wie den Titel und das Albumcover anzeigt. Außerdem werden Wiedergabesteuerungen wie Wiedergabe, Pause und Überspringen sowie benutzerdefinierte Aktionen angezeigt, die von der Drittanbieter-Media-App bereitgestellt werden. Eine Mediakarte kann auch eine Warteschlange mit Medienelementen wie einer Playlist enthalten.

Medienkarte

Medienkarte

Medienkarte

Abbildung 1. Beispielimplementierungen für Media Cards

Wie werden Mediacards in AAOS implementiert?

Für ViewGroups, die Medieninformationen enthalten, werden LiveData-Aktualisierungen aus dem PlaybackViewModel-Modell (car-media-common-Bibliothek mit Daten) verwendet, um die ViewGroup zu füllen. Jedes LiveData-Update entspricht einer Teilmenge der Medieninformationen, die sich geändert haben, z. B. MediaItemMetadata, PlaybackStateWrapper und MediaSource.

Da dieser Ansatz zu wiederholtem Code führt (jede Client-App fügt Beobachter zu jedem LiveData-Element hinzu und vielen ähnlichen Ansichten werden die aktualisierten Daten zugewiesen), haben wir die PlaybackCardController erstellt.

PlaybackCardController

Die PlaybackCardController wurde der car-media-common-Mediathek hinzugefügt, um beim Erstellen einer Medienkarte zu helfen. Dies ist eine öffentliche Klasse, die mit einer ViewGroup (mView), PlaybackViewModel (mDataModel), PlaybackCardViewModel (mViewModel) und einer MediaItemsRepository-Instanz (mItemsRepository) erstellt wird.

In der Funktion setupController wird die ViewGroup anhand der ID mit mView.findViewById(R.id.xxx) nach bestimmten Ansichten geparst und geschützten View-Objekten zugewiesen.

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

         // ...
}

Jede LiveData-Aktualisierung von der PlaybackViewModel wird in einer geschützten Methode beobachtet und es werden Interaktionen mit den Ansichten ausgeführt, die für die empfangenen Daten relevant sind. Ein Beobachter auf MediaItemMetadata legt beispielsweise den Titel auf dem mTitle TextView fest und übergibt den MediaItemMetadata.ArtworkRef an das Albumcover ImageBinder mAlbumArtBinder. Wenn die Metadaten null sind, werden die Ansichten ausgeblendet. Unterklassen des Controllers können diese Logik bei Bedarf überschreiben.

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

PlaybackCardController erweitern

Client-Apps, die eine Medienkarte erstellen möchten, sollten PlaybackCardController erweitern, wenn sie zusätzliche Funktionen haben, die bei jeder LiveData-Aktualisierung verarbeitet werden sollen. Bestehende Kunden in AAOS folgen diesem Muster. Zuerst muss eine PlaybackCardController-Unterklasse erstellt werden, z. B. die MediaCardController. Als Nächstes sollte die MediaCardController eine statische innere Builder-Klasse hinzufügen, die die der PlaybackCardController erweitert.

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
    // ...

  }
}

PlaybackCardController oder eine Unterklasse instanziieren

Die Controller-Klasse sollte über ein Fragment oder eine Aktivität instanziiert werden, um einen LifecycleOwner für die LiveData-Beobachter zu haben.

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

mViewModel ist eine Instanz der PlaybackCardViewModel (oder einer abgeleiteten Klasse).

PlaybackCardViewModel zum Speichern des Status

PlaybackCardViewModel ist ein ViewModel zum Speichern des Status, das mit einem Fragment oder einer Aktivität verknüpft ist. Es sollte verwendet werden, um den Inhalt der Medienkarte bei einer Konfigurationsänderung wiederherzustellen, z. B. beim Wechsel vom hellen zum dunklen Design, wenn ein Nutzer durch einen Tunnel fährt. Der Standard-PlaybackCardViewModel kümmert sich um das Speichern von Instanzen der MediaModels zur Wiedergabe, aus denen die PlaybackViewModel und MediaItemsRepository abgerufen werden können. Mit PlaybackCardViewModel können Sie den Status der Warteschlange, des Verlaufs und des Überlaufmenüs über die bereitgestellten Getter und Setter verfolgen.

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

Diese Klasse kann erweitert werden, wenn zusätzliche Status erfasst werden müssen.

Wiedergabeliste auf einer Medienkarte anzeigen

Die PlaybackViewModel bietet LiveData APIs, um zu erkennen, ob die MediaSource eine Warteschlange unterstützt, und um die Liste der MediaItemMetadata-Objekte in der Warteschlange abzurufen. Diese APIs können zwar direkt verwendet werden, um ein RecyclerView-Objekt mit den Warteschlangeninformationen zu füllen, aber der car-media-common-Bibliothek wurde eine PlaybackQueueController-Klasse hinzugefügt, um diesen Vorgang zu optimieren. Das Layout für jedes Element in der CarUiRecyclerView wird von der Client-App sowie als optionales Header-Layout angegeben. Die Client-App kann auch die Anzahl der Elemente begrenzen, die im Drive-Status in der Warteschlange angezeigt werden, indem benutzerdefinierte UXR-Einschränkungen festgelegt werden.

Der Konstruktor und die Setter von PlaybackQueueController sind im folgenden Beispiel zu sehen. Die Layoutressourcen queueResource und headerResource können als Resources.ID_NULL übergeben werden, wenn der Container im ersten Fall bereits eine CarUiRecyclerView mit id queue_list enthält und die Warteschlange im zweiten Fall keinen Header hat.

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

Das Layout für jedes Warteelement sollte die IDs für die anzuzeigenden Ansichten enthalten, die denen in der inneren QueueViewHolder-Klasse entsprechen.

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

            // ...
}

Wenn du eine Warteschlange in einer Medienkarte anzeigen möchtest, die mit der PlaybackCardController (oder einer Unterklasse) erstellt wurde, kannst du die PlaybackQueueController im Konstruktor der PlaybackCardController mit mDataModel und mItemsRepository für die PlaybackViewModel- und MediaItemsRepository-Instanzen erstellen.

Verlauf der zuvor abgespielten Medienquellen anzeigen

In diesem Abschnitt erfahren Sie, wie Sie den Verlauf der zuvor abgespielten Medienquellen anzeigen und präsentieren.

Wiedergabeliste mit der PlaybackCardViewModel API abrufen

PlaybackCardViewModel bietet eine LiveData API namens getHistoryList(), um die Liste der Medienverläufe abzurufen. Es gibt LiveData zurück, die eine Liste der MediaSources enthält, die zuvor wiedergegeben wurden. Diese Daten können zum Ausfüllen eines CarUiRecyclerView-Objekts verwendet werden. Ähnlich wie bei PlaybackQueueController wurde der car-media-common-Bibliothek die Klasse PlaybackHistoryController hinzugefügt, um den Prozess zu optimieren.

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

Benutzeroberfläche für den Wiedergabeverlauf mit PlaybackHistoryController

Verwenden Sie das neue PlaybackHistoryController, um die Verlaufsdaten in einem CarUiRecyclerView zu füllen. Die Konstruktoren und Hauptfunktionen dieser Klasse sind: Der von der Clientanwendung übergebene Container sollte eine CarUiRecyclerView mit der ID history_list enthalten. Im CarUiRecyclerView werden die Listenelemente und eine optionale Überschrift angezeigt. Sowohl das Layout für das Listenelement als auch das Layout für den Header können von der Client-App übergeben werden. Wenn Resources.ID_NULL als „headerResource“ festgelegt ist, wird der Header nicht angezeigt. Nachdem PlaybackCardViewModel an den Controller übergeben wurde, überwacht er die aus playbackCardViewModel.getHistoryList() abgerufenen LiveData<List<MediaSource>>.

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() {
    }
}

Das Layout für jedes Element sollte die IDs für die anzuzeigenden Ansichten enthalten, die denen in der inneren ViewHolder-Klasse entsprechen.

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

Wenn in einer mit der PlaybackCardController (oder einer Unterklasse) erstellten Medienkarte eine Verlaufsliste angezeigt werden soll, kann die PlaybackHistoryController im Konstruktor der PlaybackCardController erstellt werden.