یک کارت رسانه ای را در AAOS پیاده سازی کنید

کارت رسانه یک ViewGroup مستقل است که ابرداده‌های رسانه‌ای مانند عنوان، تصویر آلبوم و موارد دیگر را نمایش می‌دهد و کنترل‌های پخش مانند پخش و مکث ، رد کردن و حتی اقدامات سفارشی ارائه شده توسط برنامه رسانه‌ای شخص ثالث را در بر می‌گیرد. یک کارت رسانه همچنین می‌تواند صفی از موارد رسانه‌ای مانند لیست پخش را نشان دهد.

کارت رسانه

کارت رسانه

کارت رسانه

شکل ۱. نمونه‌های پیاده‌سازی کارت رسانه.

کارت‌های رسانه چگونه در AAOS پیاده‌سازی می‌شوند؟

ViewGroupهایی که اطلاعات رسانه را نشان می‌دهند، به‌روزرسانی‌های LiveData را از مدل داده کتابخانه car-media-common ، یعنی PlaybackViewModel ، برای پر کردن ViewGroup مشاهده می‌کنند. هر به‌روزرسانی LiveData مربوط به زیرمجموعه‌ای از اطلاعات رسانه‌ای است که تغییر کرده است، مانند MediaItemMetadata ، PlaybackStateWrapper و MediaSource .

از آنجا که این رویکرد منجر به تکرار کد می‌شود (هر برنامه کلاینت، Observerهایی را روی هر قطعه از LiveData اضافه می‌کند و Viewهای مشابه زیادی به داده‌های به‌روزرسانی‌شده اختصاص داده می‌شوند)، ما PlaybackCardController ایجاد کردیم.

کنترل‌کننده کارت پخش

PlaybackCardController به کتابخانه car-media-common اضافه شده است تا در ایجاد کارت رسانه کمک کند. این یک کلاس عمومی است که با یک ViewGroup ( mView )، PlaybackViewModel ( mDataModel )، PlaybackCardViewModel ( mViewModel ) و نمونه MediaItemsRepository ( mItemsRepository ) ساخته شده است.

در تابع setupController ، ViewGroup برای نماهای خاص بر اساس شناسه، با mView.findViewById(R.id.xxx) تجزیه و به اشیاء نمای محافظت‌شده اختصاص داده می‌شود.

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

         // ...
}

هر به‌روزرسانی LiveData از PlaybackViewModel در یک متد protected مشاهده می‌شود و با Viewهای مرتبط با داده‌های دریافتی تعامل انجام می‌دهد. برای مثال، یک observer در MediaItemMetadata عنوان را در mTitle TextView تنظیم می‌کند و MediaItemMetadata.ArtworkRef را به album art ImageBinder mAlbumArtBinder ارسال می‌کند. اگر metadata تهی باشد، Viewها پنهان می‌شوند. در صورت نیاز، زیرکلاس‌های Controller می‌توانند این منطق را لغو کنند.

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

برنامه‌های کلاینتی که می‌خواهند یک کارت رسانه ایجاد کنند، در صورت داشتن قابلیت‌های اضافی که می‌خواهند در هر به‌روزرسانی LiveData مدیریت کنند، باید PlaybackCardController را توسعه دهند. کلاینت‌های موجود در AAOS از این الگو پیروی می‌کنند. ابتدا، باید یک زیرکلاس PlaybackCardController مانند MediaCardController ایجاد شود. در مرحله بعد، MediaCardController باید یک کلاس سازنده داخلی استاتیک اضافه کند که کلاس 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
    // ...

  }
}

نمونه‌سازی PlaybackCardController یا یک زیرکلاس

کلاس Controller باید از یک Fragment یا Activity نمونه‌سازی شود تا یک LifecycleOwner برای ناظران LiveData داشته باشد.

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

mViewModel نمونه‌ای از PlaybackCardViewModel (یا زیرکلاس آن) است.

PlaybackCardViewModel برای ذخیره وضعیت

PlaybackCardViewModel یک ViewModel ذخیره‌کننده وضعیت است که به یک Fragment یا Activity گره خورده است و باید برای بازسازی محتویات کارت رسانه در صورت تغییر پیکربندی (مانند تغییر از تم روشن به تیره هنگام عبور کاربر از تونل) استفاده شود. PlaybackCardViewModel پیش‌فرض، ذخیره‌سازی نمونه‌های MediaModel را برای پخش مدیریت می‌کند، که از آن می‌توان PlaybackViewModel و MediaItemsRepository را بازیابی کرد. از PlaybackCardViewModel برای ردیابی وضعیت صف، تاریخچه و منوی سرریز از طریق getterها و setterهای ارائه شده استفاده کنید.

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

در صورت نیاز به ردیابی حالت‌های اضافی، می‌توان این کلاس را گسترش داد.

نمایش صف در کارت رسانه

PlaybackViewModel ، رابط‌های برنامه‌نویسی کاربردی (API) LiveData را برای تشخیص اینکه آیا MediaSource از صف پشتیبانی می‌کند یا خیر و بازیابی لیست اشیاء MediaItemMetadata در صف ارائه می‌دهد. اگرچه می‌توان از این APIها مستقیماً برای پر کردن یک شیء RecyclerView با اطلاعات صف استفاده کرد، اما یک کلاس PlaybackQueueController به کتابخانه car-media-common اضافه شده است تا این فرآیند را ساده‌تر کند. طرح‌بندی هر آیتم در CarUiRecyclerView توسط برنامه کلاینت و همچنین یک طرح‌بندی Header اختیاری مشخص می‌شود. برنامه کلاینت همچنین می‌تواند تعداد آیتم‌های نشان داده شده در صف را در حالت رانندگی با محدودیت‌های UXR سفارشی محدود کند.

سازنده و تنظیم‌کننده‌های PlaybackQueueController در نمونه زیر نشان داده شده‌اند. منابع طرح‌بندی queueResource و headerResource را می‌توان به صورت Resources.ID_NULL ارسال کرد اگر در حالت اول، کانتینر از قبل حاوی CarUiRecyclerView با id queue_list باشد و در حالت دوم، صف Header نداشته باشد.

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

طرح‌بندی هر آیتم صف باید شامل شناسه‌های (id) مربوط به Viewهایی باشد که می‌خواهند نمایش دهند و با شناسه‌های استفاده شده در کلاس داخلی 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);

            // ...
}

برای نمایش یک صف در یک کارت رسانه ایجاد شده با PlaybackCardController (یا یک زیرکلاس)، می‌توان PlaybackQueueController در سازنده‌ی PlaybackCardController با استفاده mDataModel و mItemsRepository به ترتیب برای نمونه‌های PlaybackViewModel و MediaItemsRepository ساخت.

نمایش تاریخچه MediaSources های پخش شده قبلی

در این بخش، یاد می‌گیرید که چگونه تاریخچه‌ی منابع رسانه‌ای که قبلاً پخش شده‌اند را نمایش داده و نمایش دهید.

دریافت لیست تاریخچه با API مربوط به PlaybackCardViewModel

PlaybackCardViewModel یک LiveData API به نام getHistoryList() برای بازیابی لیست تاریخچه رسانه ارائه می‌دهد. این API یک LiveData حاوی لیستی از MediaSourceهایی که قبلاً پخش شده‌اند را برمی‌گرداند. این داده‌ها می‌توانند برای پر کردن یک شیء CarUiRecyclerView استفاده شوند. مشابه PlaybackQueueController ، کلاسی به نام PlaybackHistoryController به کتابخانه car-media-common اضافه شده است تا این فرآیند را ساده‌تر کند.

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

رابط کاربری تاریخچه سرفیس با PlaybackHistoryController

از PlaybackHistoryController جدید برای کمک به پر کردن داده‌های تاریخچه در CarUiRecyclerView استفاده کنید. سازنده‌ها و توابع اصلی این کلاس به شرح زیر است. کانتینری که از برنامه کلاینت ارسال می‌شود باید حاوی یک CarUiRecyclerView با شناسه history_list باشد. CarUiRecyclerView موارد لیست و یک سربرگ اختیاری را نمایش می‌دهد. هر دو طرح‌بندی برای مورد لیست و سربرگ را می‌توان از برنامه کلاینت ارسال کرد. اگر Resources.ID_NULL به عنوان headerResource تنظیم شده باشد، سربرگ نمایش داده نمی‌شود. پس از اینکه PlaybackCardViewModel به کنترلر ارسال شد، LiveData<List<MediaSource>> بازیابی شده از 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() {
    }
}

طرح‌بندی هر آیتم باید شامل شناسه‌هایی برای Viewهایی باشد که می‌خواهد نمایش دهد و با شناسه‌های استفاده شده در کلاس داخلی 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);
// ...
}

برای نمایش لیست تاریخچه در یک کارت رسانه ایجاد شده با PlaybackCardController (یا یک زیرکلاس)، می‌توان PlaybackHistoryController در سازنده‌ی PlaybackCardController ایجاد کرد.