Implementare una scheda multimediale in AAOS

Una scheda multimediale è un ViewGroup autonomo che mostra i metadati multimediali, ad esempio il titolo, la copertina dell'album e altro ancora, e i controlli di riproduzione, come Riproduci e Pausa,Salta e persino azioni personalizzate fornite dall'app multimediale di terze parti. Una scheda multimediale può anche mostrare una coda di elementi multimediali, ad esempio una playlist.

Scheda Contenuti multimediali

Scheda Contenuti multimediali

Scheda Contenuti multimediali

Figura 1. Implementazioni di esempio della scheda multimediale.

Come vengono implementate le schede multimediali in AAOS?

I ViewGroups che mostrano le informazioni sui contenuti multimediali osservano gli aggiornamenti di LiveData dal modello di dati della libreria car-media-common, PlaybackViewModel, per popolare il ViewGroup. Ogni aggiornamento di LiveData corrisponde a un sottoinsieme di informazioni sui contenuti multimediali che sono cambiate, ad esempio MediaItemMetadata, PlaybackStateWrapper e MediaSource.

Poiché questo approccio porta a codice ripetuto (ogni app client aggiunge osservatori a ogni elemento LiveData e a molte View simili vengono assegnati i dati aggiornati), abbiamo creato PlaybackCardController.

PlaybackCardController

L'PlaybackCardController è stato aggiunto alla raccolta car-media-common per aiutarti a creare una scheda multimediale. Si tratta di una classe pubblica creata con un ViewGroup (mView), un PlaybackViewModel (mDataModel), un PlaybackCardViewModel (mViewModel) e un'istanza MediaItemsRepository (mItemsRepository).

Nella funzione setupController, il ViewGroup viene analizzato per determinate visualizzazioni in base all'ID, con mView.findViewById(R.id.xxx) e assegnato a oggetti View protetti.

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

         // ...
}

Ogni aggiornamento di LiveData da PlaybackViewModel viene osservato in un metodo protetto ed esegue interazioni con le View pertinenti ai dati ricevuti. Ad esempio, un osservatore su MediaItemMetadata imposta il titolo su mTitle TextView e passa MediaItemMetadata.ArtworkRef alla copertina dell'album ImageBinder mAlbumArtBinder. Se i metadati sono nulli, le visualizzazioni sono nascoste. Le sottoclassi del controller possono ignorare questa logica, se necessario.

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

Estendi PlaybackCardController

Le app client che vogliono creare una scheda multimediale devono estendere PlaybackCardController se hanno funzionalità aggiuntive che vogliono gestire in ogni aggiornamento di LiveData. I client esistenti in AAOS seguono questo pattern. Innanzitutto, deve essere creata una sottoclasse PlaybackCardController, ad esempio MediaCardController. Successivamente, MediaCardController deve aggiungere una classe Builder interna statica che estende quella di 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
    // ...

  }
}

Crea un'istanza di PlaybackCardController o di una sottoclasse

La classe Controller deve essere istanziata da un frammento o da un'attività per avere un LifecycleOwner per gli osservatori LiveData.

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

mViewModel è un'istanza di PlaybackCardViewModel (o sottoclasse).

PlaybackCardViewModel per salvare lo stato

PlaybackCardViewModel è un ViewModel che salva lo stato associato a un fragment o a un'attività che deve essere utilizzato per ricostruire i contenuti della scheda multimediale se si verifica una modifica della configurazione (ad esempio il passaggio dal tema chiaro a quello scuro quando un utente guida in una galleria). Il PlaybackCardViewModel predefinito gestisce l'archiviazione delle istanze di MediaModel per la riproduzione, da cui è possibile recuperare PlaybackViewModel e MediaItemsRepository. Utilizza PlaybackCardViewModel per monitorare lo stato della coda, della cronologia e del menu overflow tramite i metodi getter e setter forniti.

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

Questa classe può essere estesa se è necessario monitorare altri stati.

Mostrare una coda in una scheda multimediale

PlaybackViewModel fornisce API LiveData per rilevare se MediaSource supporta una coda e per recuperare l'elenco di oggetti MediaItemMetadata nella coda. Sebbene queste API possano essere utilizzate direttamente per compilare un oggetto RecyclerView con le informazioni sulla coda, è stata aggiunta una classe PlaybackQueueController alla libreria car-media-common per semplificare questo processo. Il layout di ogni elemento in CarUiRecyclerView viene specificato dall'app client, così come un layout di intestazione facoltativo. L'app client può anche scegliere di limitare il numero di elementi mostrati nella coda durante la guida con limitazioni UXR personalizzate.

Il costruttore PlaybackQueueController e i setter sono mostrati nel seguente esempio. Le risorse di layout queueResource e headerResource possono essere trasmesse come Resources.ID_NULL se, nel primo caso, il contenitore contiene già un CarUiRecyclerView con id queue_list e, nel secondo caso, la coda non ha un'intestazione.

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

Il layout di ogni elemento della coda deve contenere gli ID delle visualizzazioni che vuole mostrare e che corrispondono a quelli utilizzati nella classe 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);

            // ...
}

Per mostrare una coda in una scheda multimediale creata con PlaybackCardController (o una sottoclasse), PlaybackQueueController può essere costruito nel costruttore PlaybackCardController utilizzando mDataModel e mItemsRepository per le istanze PlaybackViewModel e MediaItemsRepository, rispettivamente.

Mostra la cronologia delle MediaSource riprodotte in precedenza

In questa sezione scoprirai come mostrare e visualizzare la cronologia delle origini multimediali riprodotte in precedenza.

Ottenere l'elenco della cronologia con l'API PlaybackCardViewModel

PlaybackCardViewModel fornisce un'API LiveData chiamata getHistoryList() per recuperare l'elenco della cronologia dei contenuti multimediali. Restituisce un LiveData contenente un elenco di MediaSource riprodotti in precedenza. Questi dati possono essere utilizzati per compilare un oggetto CarUiRecyclerView. Simile a PlaybackQueueController, una classe denominata PlaybackHistoryController è stata aggiunta alla libreria car-media-common per semplificare la procedura.

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

UI della cronologia di Surface con PlaybackHistoryController

Utilizza il nuovo PlaybackHistoryController per compilare i dati della cronologia in un CarUiRecyclerView. I costruttori e le funzioni principali di questa classe sono i seguenti. Il contenitore passato dall'app client deve contenere un CarUiRecyclerView con l'ID history_list. CarUiRecyclerView mostra gli elementi dell'elenco e un'intestazione facoltativa. Entrambi i layout per la voce di elenco e l'intestazione possono essere passati dall'app client. Se Resources.ID_NULL è impostato come headerResource, l'intestazione non viene mostrata. Dopo che il PlaybackCardViewModel viene passato al controller, questo monitora il LiveData<List<MediaSource>> recuperato da 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() {
    }
}

Il layout di ogni elemento deve contenere gli ID delle visualizzazioni che vuole mostrare che corrispondono a quelli utilizzati nella classe 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);
// ...
}

Per mostrare un elenco della cronologia in una scheda multimediale creata con PlaybackCardController (o una sottoclasse), PlaybackHistoryController può essere costruito nel costruttore di PlaybackCardController.