إنشاء تطبيق مشغّل وسائط أساسي باستخدام Media3 ExoPlayer

تحدّد Jetpack Media3 واجهة Player توضّح الوظائف الأساسية لتشغيل ملفات الفيديو والصوت. ‫ExoPlayer هو التنفيذ التلقائي لهذه الواجهة في Media3. ننصحك باستخدام ExoPlayer، لأنّه يوفّر مجموعة شاملة من الميزات التي تغطي معظم حالات استخدام التشغيل، ويمكن تخصيصه للتعامل مع أي حالات استخدام إضافية قد تحتاج إليها. يساعد ExoPlayer أيضًا في تجنُّب تجزئة الأجهزة وأنظمة التشغيل، ما يضمن عمل الرمز البرمجي بشكل متسق في جميع أنحاء نظام Android الأساسي. يتضمّن ExoPlayer ما يلي:

توضّح لك هذه الصفحة بعض الخطوات الأساسية لإنشاء تطبيق تشغيل، ويمكنك الانتقال إلى أدلتنا الكاملة حول Media3 ExoPlayer لمعرفة المزيد من التفاصيل.

خطوات البدء:

للبدء، أضِف تبعية إلى وحدات ExoPlayer وUI وCommon في Jetpack Media3:

implementation "androidx.media3:media3-exoplayer:1.8.0"
implementation "androidx.media3:media3-ui:1.8.0"
implementation "androidx.media3:media3-common:1.8.0"

بناءً على حالة الاستخدام، قد تحتاج أيضًا إلى وحدات إضافية من Media3، مثل exoplayer-dash لتشغيل المحتوى بتنسيق DASH.

تأكَّد من استبدال 1.8.0 بالإصدار المفضّل لديك من المكتبة. يمكنك الرجوع إلى ملاحظات الإصدار للاطّلاع على أحدث إصدار.

إنشاء مشغّل وسائط

باستخدام Media3، يمكنك إما استخدام التنفيذ المضمَّن للواجهة Player ، ExoPlayer، أو يمكنك إنشاء تنفيذ مخصّص خاص بك.

إنشاء ExoPlayer

أبسط طريقة لإنشاء مثيل ExoPlayer هي كما يلي:

Kotlin

val player = ExoPlayer.Builder(context).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

يمكنك إنشاء مشغّل الوسائط في طريقة دورة الحياة onCreate() الخاصة بـ Activity أو Fragment أو Service حيث يتم استخدامه.

يتضمّن Builder مجموعة من خيارات التخصيص التي قد تهمّك، مثل:

توفّر Media3 PlayerView أحد عناصر واجهة المستخدم التي يمكنك تضمينها في ملف التنسيق الخاص بتطبيقك. يحتوي هذا المكوّن على PlayerControlView لعناصر التحكّم في التشغيل، وSubtitleView لعرض الترجمة والشرح، وSurface لعرض الفيديو.

جارٍ تجهيز المشغّل

إضافة ملفات وسائط إلى قائمة تشغيل لتشغيلها باستخدام طُرق مثل setMediaItem() وaddMediaItem() بعد ذلك، اتّصِل بالدالة prepare() لبدء تحميل الوسائط والحصول على الموارد اللازمة.

يجب عدم تنفيذ هذه الخطوات قبل أن يكون التطبيق في المقدّمة. إذا كان مشغّل الفيديو في حالة Activity أو Fragment، يعني ذلك إعداد المشغّل في طريقة دورة الحياة onStart() على المستوى 24 من واجهة برمجة التطبيقات والإصدارات الأحدث أو طريقة دورة الحياة onResume() على المستوى 23 من واجهة برمجة التطبيقات والإصدارات الأقدم. بالنسبة إلى لاعب في Service، يمكنك تحضيره في onCreate(). راجِع Exoplayer codelab للحصول على مثال حول كيفية تنفيذ طرق مراحل النشاط.

التحكّم في المشغّل

بعد إعداد المشغّل، يمكنك التحكّم في التشغيل من خلال استدعاء طرق في المشغّل، مثل:

سيتم تعديل مكوّنات واجهة المستخدم، مثل PlayerView أو PlayerControlView، وفقًا لذلك عند ربطها بمشغّل.

إزالة المشغّل

قد يتطلّب التشغيل موارد محدودة، مثل برامج فك ترميز الفيديو، لذا من المهم استدعاء release() في المشغّل لتحرير الموارد عندما لا يعود المشغّل مطلوبًا.

إذا كان المشغّل في حالة Activity أو Fragment، عليك إيقاف تشغيله في طريقة دورة الحياة onStop() على المستوى 24 من واجهة برمجة التطبيقات والإصدارات الأحدث أو في طريقة onPause() على المستوى 23 من واجهة برمجة التطبيقات والإصدارات الأقدم. بالنسبة إلى اللاعب الذي يقع في Service، يمكنك إصداره في onDestroy(). راجِع Exoplayer codelab للحصول على مثال حول كيفية تنفيذ طرق مراحل النشاط.

إدارة التشغيل باستخدام جلسة وسائط

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

لاستخدام جلسات الوسائط، أضِف تبعية إلى وحدة Media3 Session:

implementation "androidx.media3:media3-session:1.8.0"

إنشاء جلسة وسائط

يمكنك إنشاء MediaSession بعد تهيئة مشغّل على النحو التالي:

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

تتم تلقائيًا مزامنة حالة Player مع حالة MediaSession في Media3. تعمل هذه الطريقة مع أي عملية تنفيذ Player، بما في ذلك ExoPlayer أو CastPlayer أو عملية تنفيذ مخصّصة.

منح إذن التحكّم لبرامج أخرى

يمكن لتطبيقات العميل تنفيذ وحدة تحكّم بالوسائط للتحكّم في تشغيل جلسة الوسائط. لتلقّي هذه الطلبات، اضبط عنصر callback عند إنشاء MediaSession.

عندما يكون جهاز التحكّم على وشك الاتصال بجلسة الوسائط، يتم استدعاء الطريقة onConnect(). يمكنك استخدام ControllerInfo المتاح لتحديد ما إذا كنت تريد قبول الطلب أو رفضه. يمكنك الاطّلاع على مثال على ذلك في تطبيق العرض التوضيحي لجلسة Media3.

بعد ربط وحدة التحكّم، يمكنها إرسال أوامر التشغيل إلى الجلسة. بعد ذلك، تفوّض الجلسة هذه الأوامر إلى اللاعب. تتعامل الجلسة تلقائيًا مع أوامر التشغيل وقوائم التشغيل المحدّدة في واجهة Player.

تتيح لك طرق معالجة البيانات الأخرى التعامل مع طلبات، مثل أوامر التشغيل المخصّصة وتعديل قائمة التشغيل. تتضمّن عمليات معاودة الاتصال هذه كائن ControllerInfo بشكل مشابه، ما يتيح لك تحديد عناصر التحكّم في الوصول لكل طلب على حدة.

تشغيل الوسائط في الخلفية

لمواصلة تشغيل الوسائط عندما لا يكون تطبيقك في المقدّمة، مثلاً لتشغيل الموسيقى أو الكتب الصوتية أو برامج البودكاست حتى عندما لا يكون التطبيق مفتوحًا، يجب تضمين Player وMediaSession في خدمة تعمل في المقدّمة. توفّر Media3 الواجهة MediaSessionService لهذا الغرض.

تنفيذ MediaSessionService

أنشئ فئة توسّع MediaSessionService وأنشئ مثيلاً من MediaSession في طريقة دورة الحياة onCreate().

Kotlin

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    // Create your Player and MediaSession in the onCreate lifecycle event
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    // Remember to release the player and media session in onDestroy
    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }
}

Java

public class PlaybackService extends MediaSessionService {
    private MediaSession mediaSession = null;

    @Override
    public void onCreate() {
        super.onCreate();
        ExoPlayer player = new ExoPlayer.Builder(this).build();
        mediaSession = new MediaSession.Builder(this, player).build();
    }

    @Override
    public void onDestroy() {
        mediaSession.getPlayer().release();
        mediaSession.release();
        mediaSession = null;
        super.onDestroy();
    }
}

في ملف البيان، أضِف فئة Service مع فلتر أهداف MediaSessionService واطلب الإذن FOREGROUND_SERVICE لتشغيل خدمة تعمل في المقدّمة:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

أخيرًا، في الفئة التي أنشأتها، عليك إلغاء طريقة onGetSession() للتحكّم في وصول العميل إلى جلسة الوسائط. أرجِع MediaSession لقبول طلب الربط، أو أرجِع null لرفض الطلب.

Kotlin

// This example always accepts the connection request
override fun onGetSession(
    controllerInfo: MediaSession.ControllerInfo
): MediaSession? = mediaSession

Java

@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
  // This example always accepts the connection request
  return mediaSession;
}

الاتصال بواجهة المستخدم

بعد أن أصبحت جلسة الوسائط في Service منفصلة عن Activity أو Fragment حيث تظهر واجهة المستخدم الخاصة بالمشغّل، يمكنك استخدام MediaController لربطها معًا. في طريقة onStart() الخاصة بالفئة Activity أو Fragment مع واجهة المستخدم، أنشئ SessionToken لـ MediaSession، ثم استخدِم SessionToken لإنشاء MediaController. يتم إنشاء MediaController بشكل غير متزامن.

Kotlin

override fun onStart() {
  val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
  val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
  controllerFuture.addListener(
    {
        // Call controllerFuture.get() to retrieve the MediaController.
        // MediaController implements the Player interface, so it can be
        // attached to the PlayerView UI component.
        playerView.setPlayer(controllerFuture.get())
      },
    MoreExecutors.directExecutor()
  )
}

Java

@Override
public void onStart() {
  SessionToken sessionToken =
    new SessionToken(this, new ComponentName(this, PlaybackService.class));
  ListenableFuture<MediaController> controllerFuture =
    new MediaController.Builder(this, sessionToken).buildAsync();
  controllerFuture.addListener(() -> {
    // Call controllerFuture.get() to retrieve the MediaController.
    // MediaController implements the Player interface, so it can be
    // attached to the PlayerView UI component.
    playerView.setPlayer(controllerFuture.get());
  }, MoreExecutors.directExecutor())
}

تنفِّذ MediaController واجهة Player، لذا يمكنك استخدام الطرق نفسها، مثل play() وpause()، للتحكّم في التشغيل. كما هو الحال مع المكوّنات الأخرى، تذكَّر إصدار MediaController عندما لا يعود مطلوبًا، مثل طريقة دورة حياة onStop() الخاصة بـ Activity، وذلك من خلال استدعاء MediaController.releaseFuture().

نشر إشعار

يجب أن تنشر الخدمات التي تعمل في المقدّمة إشعارًا أثناء نشاطها. ستنشئ MediaSessionService تلقائيًا MediaStyle إشعارًا لك على شكل MediaNotification. لتوفير إشعار مخصّص، أنشئ MediaNotification.Provider باستخدام DefaultMediaNotificationProvider.Builder أو من خلال إنشاء عملية تنفيذ مخصّصة لواجهة موفّر الإشعارات. أضِف موفّر الخدمة إلى جهاز MediaSession باستخدام setMediaNotificationProvider.

الإعلان عن مكتبة المحتوى

تعتمد MediaLibraryService على MediaSessionService من خلال السماح لتطبيقات العميل بتصفّح محتوى الوسائط الذي يوفّره تطبيقك. وتنفّذ تطبيقات العميل MediaBrowser للتفاعل مع MediaLibraryService.

إنّ تنفيذ MediaLibraryService يشبه تنفيذ MediaSessionService، إلا أنّه في onGetSession()، عليك عرض MediaLibrarySession بدلاً من MediaSession. مقارنةً بـ MediaSession.Callback، تتضمّن MediaLibrarySession.Callback طرقًا إضافية تتيح لعميل المتصفّح التنقّل في المحتوى الذي تقدّمه خدمة المكتبة.

على غرار MediaSessionService، يجب تعريف MediaLibraryService في ملف البيان وطلب إذن FOREGROUND_SERVICE لتشغيل خدمة تعمل في المقدّمة:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

يتضمّن المثال أعلاه فلتر أهداف لكل من MediaLibraryService وMediaBrowserService القديم للتوافق مع الأنظمة القديمة. يتيح فلتر الأهداف الإضافي للتطبيقات العميلة التي تستخدم واجهة برمجة التطبيقات MediaBrowserCompat التعرّف على Service.

تتيح لك MediaLibrarySession عرض مكتبة المحتوى الخاصة بك في بنية شجرية، مع جذر واحد MediaItem. يمكن أن يحتوي كل MediaItem في الشجرة على أي عدد من عقد MediaItem الفرعية. يمكنك عرض جذر مختلف أو شجرة مختلفة استنادًا إلى طلب تطبيق العميل. على سبيل المثال، قد تحتوي الشجرة التي تعرضها على عميل يبحث عن قائمة بعناصر الوسائط المقترَحة على العقدة الجذر MediaItem ومستوى واحد فقط من عقد الأطفال MediaItem، بينما قد تمثّل الشجرة التي تعرضها على تطبيق عميل مختلف مكتبة محتوى أكثر اكتمالاً.

إنشاء MediaLibrarySession

MediaLibrarySession هي واجهة برمجة تطبيقات توسّع نطاق واجهة برمجة التطبيقات MediaSession لإضافة واجهات برمجة تطبيقات لتصفّح المحتوى. مقارنةً بطريقة رد الاتصال MediaSession، تضيف طريقة رد الاتصال MediaLibrarySession طُرقًا مثل:

  • onGetLibraryRoot() عندما يطلب العميل الجذر MediaItem لشجرة محتوى
  • onGetChildren() عندما يطلب عميل عناصر فرعية من MediaItem في شجرة المحتوى
  • onGetSearchResult() عندما يطلب عميل نتائج بحث من شجرة المحتوى لاستعلام معيّن

ستتضمّن طرق معاودة الاتصال ذات الصلة عنصر LibraryParams مع إشارات إضافية حول نوع شجرة المحتوى التي يهتم بها تطبيق العميل.