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

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

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

البدء

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

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

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

احرص على استبدال 1.3.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().

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

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

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

ارفع إصبعك عن المشغّل.

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

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

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

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

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

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