Implementa una tarjeta multimedia en AAOS

Una tarjeta multimedia es un ViewGroup independiente que muestra metadatos multimedia, como el título, la portada del álbum y mucho más, y muestra controles de reproducción, como Reproducir, Pausar, Omitir y hasta acciones personalizadas que proporciona la app de música de terceros. Una tarjeta multimedia también puede mostrar una fila de elementos multimedia, como una playlist.

Tarjeta multimedia

Tarjeta multimedia

Tarjeta multimedia

Figura 1: Implementaciones de ejemplo de tarjetas multimedia.

¿Cómo se implementan las tarjetas multimedia en AAOS?

Los objetos ViewGroup que muestran información de contenido multimedia observan actualizaciones de LiveData desde el El modelo de datos de la biblioteca car-media-common, el PlaybackViewModel, para propagar la ViewGroup. Cada actualización de LiveData corresponde a un subconjunto de información multimedia. que cambió, como MediaItemMetadata, PlaybackStateWrapper y MediaSource

Debido a que este enfoque genera código repetido (cada app cliente agrega observadores en cada elemento de LiveData y a muchas vistas similares se les asignan los datos actualizados), creamos PlaybackCardController.

Controlador de la tarjeta de reproducción

Se agregó PlaybackCardController a la biblioteca car-media-common para ayudar a crear una tarjeta multimedia. Esta es una clase pública que se construye con un ViewGroup (mView), PlaybackViewModel (mDataModel), PlaybackCardViewModel (mViewModel) y una instancia de MediaItemsRepository (mItemsRepository).

En la función setupController, el ViewGroup se analiza en función de ciertas vistas. ID, con mView.findViewById(R.id.xxx) y asignado a objetos View protegidos.

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

         // ...
}

Cada actualización de LiveData de PlaybackViewModel se observa en un método protegido y realiza interacciones con las Views relevantes para los datos recibidos. Por ejemplo, un observador en MediaItemMetadata establece el título en TextView mTitle y pasa el MediaItemMetadata.ArtworkRef a la portada del álbum ImageBinder mAlbumArtBinder. Si los metadatos son nulos, las vistas se ocultan. Las subclases del controlador pueden anular esta lógica si es necesario.

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

Cómo extender PlaybackCardController

Las apps cliente que deseen crear una tarjeta multimedia deben extender PlaybackCardController si tienen capacidades adicionales que deseen controlar en cada actualización de LiveData. Los clientes existentes en AAOS siguen este patrón. Primero, se debe crear una subclase PlaybackCardController, como MediaCardController. A continuación, MediaCardController debe agregar una clase interna de constructor estático que extienda la de 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
    // ...

  }
}

Cómo crear una instancia de PlaybackCardController o una subclase

Se debe crear una instancia de la clase Controller desde un fragmento o una actividad en orden. tener un LifecycleOwner para los observadores de LiveData.

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

mViewModel es una instancia de PlaybackCardViewModel (o una subclase).

PlaybackCardViewModel para guardar el estado

PlaybackCardViewModel es un ViewModel que ahorra estado y está vinculado a un fragmento o Actividad que se debe usar para reconstruir el contenido de la tarjeta multimedia si se trata de se produce un cambio de configuración (como un cambio del tema claro a oscuro cuando se usuario conduzca a través de un túnel). El PlaybackCardViewModel predeterminado controla la de instancias de MediaModel para reproducción, desde las cuales Se pueden recuperar PlaybackViewModel y MediaItemsRepository. Usa el PlaybackCardViewModel para realizar un seguimiento del estado de la cola, el historial y el menú ampliado a través de los métodos get y set.

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

Esta clase se puede extender si se debe hacer un seguimiento de estados adicionales.

Cómo mostrar una fila en una tarjeta multimedia

PlaybackViewModel proporciona APIs de LiveData para detectar si la MediaSource admite una cola y que recupere la lista de objetos MediaItemMetadata en el en la fila. Si bien estas APIs se pueden usar directamente para propagar un objeto RecyclerView con la información de la cola, se agregó una clase PlaybackQueueController a la biblioteca car-media-common para optimizar este proceso. La app cliente especifica el diseño de cada elemento en CarUiRecyclerView, así como un diseño de encabezado opcional. La app cliente también puede optar por limitar el número los elementos que se muestran en la cola durante el estado de conducción con restricciones personalizadas de UXR.

El constructor y los set de PlaybackQueueController se muestran en el siguiente ejemplo. Se pueden pasar los recursos de diseño queueResource y headerResource Resources.ID_NULL si, en el primer caso, el contenedor ya contiene un CarUiRecyclerView con id queue_list y, en este último caso, la cola no tiene un encabezado.

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

El diseño de cada elemento de la cola debe contener los IDs de las vistas que le gustaría mostrar que corresponden a los que se usan en la clase interna 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);

            // ...
}

Para mostrar una fila en una tarjeta multimedia creada con PlaybackCardController, sigue estos pasos: (o una subclase), el PlaybackQueueController se puede construir en el Constructor PlaybackCardController con mDataModel y mItemsRepository para las instancias PlaybackViewModel y MediaItemsRepository, respectivamente.

Mostrar historial de MediaSources que se reprodujeron anteriormente

En esta sección, aprenderás a mostrar y mostrar el historial de las fuentes de contenido multimedia que se reprodujeron anteriormente.

Cómo obtener el historial con la API de PlaybackCardViewModel

PlaybackCardViewModel proporciona una API de LiveData llamada getHistoryList() para recuperar la lista de historial de contenido multimedia. Muestra un LiveData que contiene una lista de MediaSources que se reprodujeron anteriormente. Estos datos se pueden usar para propagar un objeto CarUiRecyclerView. De manera similar a PlaybackQueueController, se agregó una clase llamada PlaybackHistoryController a la biblioteca car-media-common para optimizar el proceso.

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

IU del historial de la plataforma con PlaybackHistoryController

Usa el nuevo PlaybackHistoryController para propagar los datos históricos a un CarUiRecyclerView. Los constructores y las funciones principales de esta clase son de la siguiente manera. El contenedor que se pasa desde la app cliente debe contener un CarUiRecyclerView con el ID history_list CarUiRecyclerView muestra los elementos de la lista y un encabezado opcional. Se pueden pasar ambos diseños del elemento de lista y el encabezado desde la app cliente. Si Resources.ID_NULL se establece como headerResource, no se muestra el encabezado. Después del PlaybackCardViewModel se pasa al controlador, que supervisa LiveData<List<MediaSource>> recuperado de 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() {
    }
}

El diseño de cada elemento debe contener los ID de las vistas que desea que corresponden a los que se usan en la clase interna 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);
// ...
}

Para mostrar una lista de historial en una tarjeta multimedia creada con PlaybackCardController (o una subclase), se puede construir PlaybackHistoryController en el constructor de PlaybackCardController.