استخدام بطاقة وسائط في نظام التشغيل Android Automotive (AAOS)

بطاقة الوسائط هي مجموعة ViewGroup مستقلة تعرض البيانات الوصفية للوسائط، مثل مثل العنوان وصورة الألبوم والمزيد، وتعرض عناصر التحكم في التشغيل مثل تشغيل وإيقاف مؤقت والتخطي وحتى الإجراءات المخصصة التي يوفرها الطرف الثالث تطبيق موسيقى للحفلات. يمكن لبطاقة الوسائط أيضًا عرض قائمة انتظار من عناصر الوسائط، مثل قائمة التشغيل.

بطاقة الوسائط

بطاقة الوسائط

بطاقة الوسائط

الشكل 1. نموذج عمليات تنفيذ بطاقة الوسائط

كيف يتم تنفيذ بطاقات الوسائط في AAOS؟

تراقب مجموعات ViewGroups التي تعرض معلومات الوسائط تحديثات LiveData من car-media-common المكتبة البيانات النموذج، PlaybackViewModel، لملء ViewGroup يرتبط كل تحديث في LiveData بمجموعة فرعية من معلومات الوسائط التي تم تغييرها، مثل MediaItemMetadata وPlaybackStateWrapper و MediaSource.

وبما أنّ هذا النهج يؤدي إلى تكرار الرموز البرمجية (يضيف كل تطبيق عملاء مراقبين على كل جزء من LiveData ويتم تخصيص البيانات المعدَّلة للعديد من طرق العرض المشابهة)، أنشأنا PlaybackCardController.

PlaybackCardController

تمت إضافة PlaybackCardController إلى مكتبة car-media-common إلى المساعدة في إنشاء بطاقة وسائط. هذه فئة عامة يتم إنشاؤها باستخدام a 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 في أسلوب محمي وينفّذ تفاعلات مع طرق العرض ذات الصلة بالبيانات التي يتم تلقّيها. على سبيل المثال، يضع مراقب على MediaItemMetadata العنوان على "mTitle" TextView وتمرير MediaItemMetadata.ArtworkRef إلى الألبوم الفن ImageBinder mAlbumArtBinder. إذا كانت البيانات الوصفية فارغة، يتم إخفاء "المشاهدات". يمكن للطبقات الفرعية من 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

على تطبيقات العميل التي تريد إنشاء بطاقة وسائط توسيع PlaybackCardController إذا كانت لديهم إمكانات إضافية يريدونها الاسم المعرِّف في كل تحديث من تحديثات LiveData. يتّبع العملاء الحاليون في نظام التشغيل Android Automotive هذا النمط. أولاً، يجب إنشاء فئة فرعية من 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 أو فئة فرعية

يجب إنشاء مثيل لفئة وحدة التحكم من جزء أو نشاط لترتيب تخصيص مالك لدورة الحياة لمراقبي LiveData.

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

الدالة mViewModel هي مثيل عن السمة PlaybackCardViewModel (أو الفئة الفرعية).

نموذج تشغيل CardView لتتمكن من حفظ الحالة

والسمة PlaybackCardViewModel هي نموذج عرض لحفظ البيانات وربطه بجزء النشاط الذي يجب استخدامه لإعادة إنشاء محتويات بطاقة الوسائط إذا حدوث تغيير في التهيئة (مثل التبديل من المظهر الفاتح إلى المظهر الداكن عند يقود المستخدم عبر نفق). يعالج PlaybackCardViewModel التلقائي تخزين نُسخ من MediaModel لتشغيلها، والتي يمكن من خلالها retrievingPlaybackViewModel وMediaItemsRepository. يمكنك استخدام PlaybackCardViewModel لتتبُّع حالة قائمة الانتظار والسجلّ والتجاوز القائمة من خلال الإحضار والتحديدات المقدمة.

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 واجهات برمجة تطبيقات LiveData لرصد ما إذا كان MediaSource يتوافق مع قائمة الانتظار واسترداد قائمة كائنات MediaItemMetadata في قائمة الانتظار. على الرغم من أنّه يمكن استخدام واجهات برمجة التطبيقات هذه مباشرةً لتعبئة RecyclerView عنصر بمعلومات "القائمة الانتظار"، تمت إضافة فئة PlaybackQueueController إلى مكتبة car-media-common لتبسيط هذه العملية. يحدِّد تطبيق العميل تنسيق كل عنصر في CarUiRecyclerView بالإضافة إلى تنسيق العنوان الاختياري. يمكن لتطبيق العميل أيضًا اختيار الحد من عدد العناصر المعروضة في قائمة الانتظار أثناء حالة محرك الأقراص مع قيود مخصصة على تجربة المستخدم (UXR).

يظهر PlaybackQueueController ووظيفتا الإعداد في المثال التالي . يمكن تمرير موارد التنسيق queueResource وheaderResource على النحو Resources.ID_NULL إذا كانت الحاوية تحتوي في الحالة السابقة على CarUiRecyclerView مع id queue_list، وفي الحالة الثانية، قائمة الانتظار لا يحتوي على عنوان.

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

يجب أن يحتوي تنسيق كل عنصر في "القائمة الانتظار" على معرّفات "المشاهدات" التي يريد عرضها والتي تتطابق مع تلك المستخدَمة في الفئة الداخلية 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 التي تم تشغيلها سابقًا

تتعرّف في هذا القسم على كيفية عرض وعرض سجلّ مصادر الوسائط التي تم تشغيلها.

الحصول على قائمة السجلّ باستخدام واجهة برمجة التطبيقات PlaybackCardViewModel API

يوفّر PlaybackCardViewModel واجهة برمجة تطبيقات LiveData API تُسمى getHistoryList() ل retrieving the media history list. تعرض LiveData تحتوي على قائمة MediaSources التي تم تشغيلها من قبل. ويمكن استخدام هذه البيانات لتعبئة عنصر 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 الجديدة للمساعدة في تعبئة بيانات السجلّ إلى 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() {
    }
}

يجب أن يحتوي تخطيط كل عنصر على معرفات طرق العرض التي يريدها تُظهِر توافقًا مع تلك المستخدَمة في الفئة الداخلية للفئة 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.