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

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

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

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

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

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

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

احرص على استبدال 1.4.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()method lifecycle (طريقة دورة حياة) على المستوى 24 من واجهة برمجة التطبيقات والإصدارات الأحدث أو onResume()method lifecycle (طريقة دورة الحياة) على المستوى 23 من واجهة برمجة التطبيقات والإصدارات الأقدم. بالنسبة إلى اللاعب الذي يلعب في Service، يمكنك إعداده في onCreate().

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

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

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

حرر المشغّل

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

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

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

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

لاستخدام جلسات الوسائط، أضِف عنصرًا تابعًا لمكوّن "جلسة Media3":

implementation "androidx.media3:media3-session:1.4.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 التي تتضمن فلتر أهداف 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 يتضمّن إشارات إضافية حول نوع شجرة المحتوى التي يهتم بها تطبيق العميل.