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

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

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

الخطوات الأولى

للبدء، أضِف تبعية على وحدات ExoPlayer وواجهة المستخدم والوحدات الشائعة في Jetpack Media3:

Kotlin

implementation("androidx.media3:media3-exoplayer:1.11.1")
implementation("androidx.media3:media3-ui:1.11.1")
implementation("androidx.media3:media3-common:1.11.1")

أنيق

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

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

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

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

باستخدام 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 للاطّلاع على مثال على كيفية تنفيذ طرق مراحل النشاط.

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

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

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

تحرير اللاعب

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

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

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

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

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

Kotlin

implementation("androidx.media3:media3-session:1.11.1")

أنيق

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

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

يمكنك إنشاء 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();

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

منح إذن التحكّم لعملاء آخرين

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

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

بعد الاتصال، يمكن لوحدة التحكّم إرسال أوامر تشغيل إلى الجلسة. بعد ذلك، تفوّض الجلسة هذه الأوامر إلى المشغّل. تتم معالجة أوامر التشغيل وقائمة التشغيل المحدّدة في واجهة 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 باستخدام intent filter 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" />

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

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

إنشاء MediaLibrarySession

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

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

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