Implementare una scheda multimediale in AAOS

Una scheda multimediale è un ViewGroup autonomo che mostra i metadati dei contenuti multimediali, come il titolo, la copertina dell'album e altro ancora, e i controlli di riproduzione come Riproduci e Metti in 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 multimediale

Scheda Contenuti multimediali

Scheda Contenuti multimediali

Figura 1. Implementazioni di esempio di schede multimediali.

Come vengono implementate le schede multimediali in AAOS?

I ViewGroup che mostrano le informazioni multimediali osservano gli aggiornamenti di LiveData dal modello di dati della raccolta car-media-common, ovvero PlaybackViewModel, per compilare il ViewGroup. Ogni aggiornamento di LiveData corrisponde a un sottoinsieme di informazioni multimediali che è cambiato, ad esempio MediaItemMetadata, PlaybackStateWrapper e MediaSource.

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

PlaybackCardController

PlaybackCardController è stato aggiunto alla raccolta car-media-common per aiutarti nella creazione di una scheda dei contenuti multimediali. Si tratta di una classe pubblica costruita con un ViewGroup (mView), PlaybackViewModel (mDataModel), PlaybackCardViewModel (mViewModel) e un'istanza MediaItemsRepository (mItemsRepository).

Nella funzione setupController, il ViewGroup viene analizzato per determinate viste dall'ID, con mView.findViewById(R.id.xxx) e assegnato agli 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 viste pertinenti ai dati ricevuti. Ad esempio, un osservatore su MediaItemMetadata imposta il titolo su mTitle TextView e passa il MediaItemMetadata.ArtworkRef alla copertina dell'albumImageBinder mAlbumArtBinder. Se i metadati sono nulli, le viste 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);
        }
    }

Espandere PlaybackCardController

Le app client che vogliono creare una scheda multimediale devono estendere la proprietà PlaybackCardController se hanno funzionalità aggiuntive che vogliono gestire in ogni aggiornamento di LiveData. I client esistenti in AAOS seguono questo schema. Innanzitutto, devi creare 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 creata da un frammento o 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 di salvataggio dello stato collegato a un frammento o un'attività che deve essere utilizzato per ricostruire i contenuti della scheda multimediale in caso di modifica della configurazione (ad esempio un passaggio dal tema chiaro a quello scuro quando un utente attraversa un tunnel). Il PlaybackCardViewModel predefinito gestisce la memorizzazione delle istanze dei 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 getter e i setters 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 questa procedura. Il layout di ogni elemento in CarUiRecyclerView viene specificato dall'app client, nonché un layout dell'intestazione facoltativo. L'app client può anche scegliere di limitare il numero di elementi visualizzati nella coda durante lo stato di guida con limitazioni UXR personalizzate.

Il costruttore e i setter di PlaybackQueueController sono mostrati nell'esempio seguente. Le risorse di layout queueResource e headerResource possono essere passate 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, corrispondenti 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 visualizzare una coda in una scheda multimediale creata con PlaybackCardController (o una sottoclasse), è possibile creare PlaybackQueueController nel costruttore PlaybackCardController utilizzando mDataModel e mItemsRepository rispettivamente per le istanze PlaybackViewModel e MediaItemsRepository.

Mostra la cronologia dei MediaSource riprodotti in precedenza

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

Recupera 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. Come PlaybackQueueController, una classe denominata PlaybackHistoryController è stata aggiunta alla libreria car-media-common per semplificare il processo.

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

Interfaccia utente 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 riportati di seguito. Il contenitore passato dall'app client deve contenere un CarUiRecyclerView con l'ID history_list. L'elemento CarUiRecyclerView mostra le voci dell'elenco e un'intestazione facoltativa. Entrambi i layout per l'elemento dell'elenco e l'intestazione possono essere trasmessi 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, corrispondenti 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.