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

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

  • إمكانية استخدام قوائم التشغيل
  • إمكانية استخدام مجموعة متنوعة من تنسيقات البث التدفقي التقدّمي والتكيّفي formats
  • إمكانية إدراج الإعلانات من جهة العميل ومن جهة الخادم ad insertion
  • إمكانية تشغيل المحتوى المحمي بموجب إدارة الحقوق الرقمية

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

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

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

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

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

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

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

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

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

إيقاف المشغّل

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

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

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

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

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

implementation "androidx.media3:media3-session:1.10.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 أو تنفيذ مخصّص.

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

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