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

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

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

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

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

Kotlin

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

أنيق

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

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

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

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

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

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

إطلاق اللاعب

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

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

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

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

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

Kotlin

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

أنيق

implementation "androidx.media3:media3-session:1.10.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();

تتم مزامنة حالة Player تلقائيًا مع حالة MediaSession في Media3. تعمل هذه الطريقة مع أي عملية تنفيذ 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 callback، يضيف MediaLibrarySession callback طرقًا مثل:

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

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