Medienkarte in AAOS implementieren

Eine Medienkarte ist eine in sich geschlossene ViewGroup, in der Medienmetadaten wie Titel, Albumcover und mehr angezeigt werden. Außerdem enthält sie Wiedergabesteuerungen wie Wiedergabe und Pause, Überspringen, und sogar benutzerdefinierte Aktionen, die von der Media-App eines Drittanbieters bereitgestellt werden. Auf einer Medienkarte kann auch eine Warteschlange mit Medienelementen wie einer Playlist angezeigt werden.

Medienkarte

Medienkarte

Medienkarte

Abbildung 1 : Beispielimplementierungen von Medienkarten

Wie werden Medienkarten in AAOS implementiert?

ViewGroups, in denen Medieninformationen angezeigt werden, beobachten LiveData-Aktualisierungen aus dem Datenmodell der car-media-common-Bibliothek, dem PlaybackViewModel, um die ViewGroup zu füllen. Jede LiveData-Aktualisierung entspricht einer Teilmenge der geänderten Medieninformationen, z. B. MediaItemMetadata, PlaybackStateWrapper und MediaSource.

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

PlaybackCardController

Der PlaybackCardController wurde der car-media-common-Bibliothek hinzugefügt, um beim Erstellen einer Medienkarte zu helfen. Dies ist eine öffentliche Klasse, die mit einer ViewGroup (mView), einem PlaybackViewModel (mDataModel), einem 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 durchsucht 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 aus dem PlaybackViewModel wird in einer geschützten Methode beobachtet und führt Interaktionen mit den für die empfangenen Daten relevanten Ansichten aus. Ein Observer für MediaItemMetadata legt beispielsweise den Titel im mTitle TextView fest und übergibt MediaItemMetadata.ArtworkRef an den Album cover 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 den PlaybackCardController erweitern, wenn sie zusätzliche Funktionen haben, die bei jeder LiveData-Aktualisierung verarbeitet werden sollen. Vorhandene Clients in AAOS folgen diesem Muster. Zuerst sollte eine PlaybackCardController-Unterklasse erstellt werden, z. B. MediaCardController. Als Nächstes sollte der MediaCardController eine statische innere Builder-Klasse hinzufügen, die die des 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 aus einem Fragment oder einer Activity instanziiert werden, um einen LifecycleOwner für die LiveData-Observer zu haben.

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

mViewModel ist eine Instanz des PlaybackCardViewModel (oder einer Unterklasse).

PlaybackCardViewModel zum Speichern des Status

Das PlaybackCardViewModel ist ein ViewModel zum Speichern des Status, das mit einem Fragment oder einer Activity verknüpft ist. Es sollte verwendet werden, um die Inhalte der Medienkarte wiederherzustellen, wenn eine Konfigurationsänderung auftritt (z. B. ein Wechsel vom hellen zum dunklen Design, wenn ein Nutzer durch einen Tunnel fährt). Das Standard-PlaybackCardViewModel speichert Instanzen der MediaModels für die Wiedergabe, aus denen das PlaybackViewModel und das MediaItemsRepository abgerufen werden können. Verwenden Sie das PlaybackCardViewModel, um den Status der Warteschlange, des Verlaufs und des Überlaufmenüs über die bereitgestellten Getter und Setter zu 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 verfolgt werden müssen.

Warteschlange auf einer Medienkarte anzeigen

Das 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 Prozess zu optimieren. Das Layout für jedes Element im CarUiRecyclerView wird von der Client-App angegeben, ebenso wie ein optionales Header-Layout. Die Client-App kann auch die Anzahl der Elemente begrenzen, die während des Fahrzustands in der Warteschlange angezeigt werden, indem sie benutzerdefinierte UXR-Einschränkungen verwendet.

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 ein 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 Warteschlangenelement sollte die IDs für die Ansichten enthalten, die angezeigt werden sollen und die mit den IDs in der inneren Klasse QueueViewHolder übereinstimmen.

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 Sie eine Warteschlange auf einer Medienkarte anzeigen möchten, die mit dem PlaybackCardController (oder einer abgeleiteten Klasse) erstellt wurde, kann der PlaybackQueueController im Konstruktor des PlaybackCardController mit mDataModel und mItemsRepository für die Instanzen PlaybackViewModel bzw. MediaItemsRepository erstellt werden.

Verlauf der zuvor wiedergegebenen MediaSources anzeigen

In diesem Abschnitt erfahren Sie, wie Sie den Verlauf der zuvor wiedergegebenen MediaSources anzeigen.

Verlaufsliste mit der PlaybackCardViewModel API abrufen

PlaybackCardViewModel bietet eine LiveData API namens getHistoryList(), um die Liste des Medienverlaufs abzurufen. Sie gibt ein LiveData-Objekt mit einer Liste von MediaSources zurück, die zuvor wiedergegeben wurden. Diese Daten können verwendet werden, um ein CarUiRecyclerView-Objekt zu füllen. Ähnlich wie beim PlaybackQueueController wurde der car-media-common-Bibliothek eine Klasse namens 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;
    }
}

Verlaufsoberfläche mit PlaybackHistoryController anzeigen

Verwenden Sie den neuen PlaybackHistoryController, um die Verlaufsdaten in ein CarUiRecyclerView einzufügen. Die Konstruktoren und Hauptfunktionen dieser Klasse sind wie folgt. Der von der Client-App übergebene Container sollte ein CarUiRecyclerView mit der ID history_list enthalten. Das CarUiRecyclerView zeigt die Listenelemente und einen optionalen Header an. Beide Layouts für das Listenelement und den Header können von der Client-App übergeben werden. Wenn Resources.ID_NULL als headerResource festgelegt ist, wird der Header nicht angezeigt. Nachdem das PlaybackCardViewModel an den Controller übergeben wurde, überwacht es das LiveData<List<MediaSource>>, das von playbackCardViewModel.getHistoryList() abgerufen wurde.

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 Ansichten enthalten, die angezeigt werden sollen und die mit den IDs in der inneren Klasse ViewHolder übereinstimmen.

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 Sie eine Verlaufsliste auf einer Medienkarte anzeigen möchten, die mit dem PlaybackCardController (oder einer abgeleiteten Klasse) erstellt wurde, kann der PlaybackHistoryController im Konstruktor des PlaybackCardController erstellt werden.