Implementare una scheda multimediale in AAOS

Una scheda multimediale è un ViewGroup autonomo che mostra i metadati 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 Contenuti multimediali

Scheda Contenuti multimediali

Scheda Contenuti multimediali

Figura 1. Esempi di implementazioni di schede multimediali.

Come vengono implementate le schede multimediali in AAOS?

I ViewGroup che mostrano informazioni multimediali osservano gli aggiornamenti di LiveData dal modello dati della libreria car-media-common, PlaybackViewModel, per popolare il ViewGroup. Ogni aggiornamento di LiveData corrisponde a un sottoinsieme di informazioni multimediali modificate, come MediaItemMetadata, PlaybackStateWrapper e MediaSource.

Poiché questo approccio comporta la ripetizione del codice (ogni app client aggiunge osservatori a ogni parte di LiveData e a molte visualizzazioni simili vengono assegnati i dati aggiornati), abbiamo creato PlaybackCardController.

PlaybackCardController

La libreria car-media-common è stata aggiunta PlaybackCardController per facilitare la creazione di una scheda multimediale. 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 visualizzazioni per 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 visualizzazioni pertinenti ai dati ricevuti. Ad esempio, un osservatore su MediaItemMetadata imposta il titolo su mTitle TextView e passa MediaItemMetadata.ArtworkRef a art ImageBinder mAlbumArtBinder dell'album. Se i metadati sono null, le visualizzazioni vengono nascoste. Le sottoclassi del controller possono sostituire 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);
        }
    }

Estendere 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, è necessario creare una sottoclasse PlaybackCardController, ad esempio MediaCardController. Poi, MediaCardController deve aggiungere una classe Builder interna statica che estenda 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
    // ...

  }
}

Creare un'istanza di PlaybackCardController o di una sottoclasse

La classe Controller deve essere creata da un Fragment o da un'Activity per avere un LifecycleOwner per gli osservatori di 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 per il salvataggio dello stato collegato a un Fragment o a un'Activity 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). 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 getter e i 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 stati aggiuntivi.

Mostrare una coda in una scheda multimediale

PlaybackViewModel fornisce API LiveData per rilevare se MediaSource supporta una coda e per recuperare l'elenco degli oggetti MediaItemMetadata nella coda. Sebbene queste API possano essere utilizzate direttamente per popolare un oggetto RecyclerView con le informazioni della coda, alla libreria car-media-common è stata aggiunta una classe PlaybackQueueController 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 lo stato di guida con restrizioni 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 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 creato nel costruttore PlaybackCardController utilizzando mDataModel e mItemsRepository rispettivamente per le istanze PlaybackViewModel e MediaItemsRepository.

Mostrare la cronologia di MediaSource riprodotte in precedenza

In questa sezione imparerai a mostrare e visualizzare la cronologia delle origini multimediali riprodotte in precedenza.

Recuperare l'elenco della cronologia con l'API PlaybackCardViewModel

PlaybackCardViewModel fornisce un'API LiveData chiamata getHistoryList() per recuperare l'elenco della cronologia multimediale. Restituisce un LiveData contenente un elenco di MediaSource riprodotte in precedenza. Questi dati possono essere utilizzati per popolare un oggetto CarUiRecyclerView. Analogamente a PlaybackQueueController, alla libreria car-media-common è stata aggiunta una classe denominata PlaybackHistoryController 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;
    }
}

Visualizzare l'UI della cronologia con PlaybackHistoryController

Utilizza il nuovo PlaybackHistoryController per popolare 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 l'elemento dell'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, 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 creato nel costruttore di PlaybackCardController.