التخصيص

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

  • MediaSource التي تحدد الوسائط التي سيتم تشغيلها وتحمِّل الوسائط وتتمكن من قراءة الوسائط المحملة. يتم إنشاء مثيل MediaSource من MediaItem بواسطة MediaSource.Factory داخل المشغّل. ويمكن أيضًا إرسالها مباشرةً إلى المشغّل باستخدام واجهة برمجة تطبيقات قائمة التشغيل المستندة إلى مصدر الوسائط.
  • تمثّل هذه السمة MediaSource.Factory التي تحوِّل MediaItem إلى MediaSource. يتم إدخال MediaSource.Factory عند إنشاء المشغّل.
  • Renderer مثيل تعرض مكونات فردية من الوسائط. ويتم إدخالها عند إنشاء المشغّل.
  • TrackSelector الذي يختار المقاطع الصوتية المقدّمة من MediaSource ليشاهدها كل Renderer متاح. يتم إدخال TrackSelector عند إنشاء المشغّل.
  • LoadControl يتحكّم في وقت التخزين المؤقت لمزيد من الوسائط في MediaSource، ومقدار التخزين المؤقت للوسائط. يتم إدخال LoadControl عند إنشاء المشغّل.
  • LivePlaybackSpeedControl الذي يتحكم في سرعة التشغيل أثناء عمليات التشغيل المباشر للسماح للمشغّل بالاقتراب من إزاحة مباشرة تم ضبطها. يتم إدخال LivePlaybackSpeedControl عند إنشاء المشغّل.

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

تخصيص المشغّل

في ما يلي بعض الأمثلة الشائعة لتخصيص المشغل عن طريق إدخال المكونات.

إعداد حِزم الشبكة

لدينا صفحة حول تخصيص حزمة الشبكات التي يستخدمها ExoPlayer.

التخزين المؤقت للبيانات التي تم تحميلها من الشبكة

يمكنك الاطّلاع على الأدلّة المتعلّقة بالتخزين المؤقت المؤقت بشكل فوري وتنزيل الوسائط.

تخصيص تفاعلات الخادم

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

يوضّح المثال التالي كيفية تنفيذ هذه السلوكيات من خلال إدخال DataSource.Factory مخصّص في DefaultMediaSourceFactory:

Kotlin

val dataSourceFactory =
  DataSource.Factory {
    val dataSource = httpDataSourceFactory.createDataSource()
    // Set a custom authentication request header.
    dataSource.setRequestProperty("Header", "Value")
    dataSource
  }
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
    )
    .build()

Java

DataSource.Factory dataSourceFactory =
    () -> {
      HttpDataSource dataSource = httpDataSourceFactory.createDataSource();
      // Set a custom authentication request header.
      dataSource.setRequestProperty("Header", "Value");
      return dataSource;
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory))
        .build();

في مقتطف الرمز أعلاه، يشتمل HttpDataSource الذي تم إدخاله على العنوان "Header: Value" في كل طلب HTTP. يكون هذا السلوك مثبّتًا لكل تفاعل مع مصدر HTTP.

ولتطبيق أسلوب أكثر دقة، يمكنك إدخال سلوك في الوقت المناسب باستخدام ResolvingDataSource. يعرض مقتطف الرمز التالي كيفية إدخال عناوين الطلبات قبل التفاعل مع مصدر HTTP مباشرةً:

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time request headers.
    dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri))
  }

Java

    DataSource.Factory dataSourceFactory =
        new ResolvingDataSource.Factory(
            httpDataSourceFactory,
            // Provide just-in-time request headers.
            dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));

يمكنك أيضًا استخدام ResolvingDataSource لإجراء تعديلات في الوقت المناسب على معرّف الموارد المنتظم (URI)، كما هو موضّح في المقتطف التالي:

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time URI resolution logic.
    dataSpec.withUri(resolveUri(dataSpec.uri))
  }

Java

DataSource.Factory dataSourceFactory =
    new ResolvingDataSource.Factory(
        httpDataSourceFactory,
        // Provide just-in-time URI resolution logic.
        dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));

تخصيص معالجة الأخطاء

يسمح تنفيذ LoadErrorHandlingPolicy مخصّص للتطبيقات بتخصيص طريقة تفاعل ExoPlayer مع أخطاء التحميل. على سبيل المثال، قد يرغب أحد التطبيقات في الفشل بسرعة بدلاً من إعادة المحاولة عدة مرات، أو قد يرغب في تخصيص منطق التراجع الذي يتحكّم في مدة انتظار اللاعب بين كل إعادة محاولة. يوضح المقتطف التالي كيفية تنفيذ منطق تراجع مخصص:

Kotlin

val loadErrorHandlingPolicy: LoadErrorHandlingPolicy =
  object : DefaultLoadErrorHandlingPolicy() {
    override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorInfo): Long {
      // Implement custom back-off logic here.
      return 0
    }
  }
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
    )
    .build()

Java

LoadErrorHandlingPolicy loadErrorHandlingPolicy =
    new DefaultLoadErrorHandlingPolicy() {
      @Override
      public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) {
        // Implement custom back-off logic here.
        return 0;
      }
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context)
                .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy))
        .build();

تحتوي الوسيطة LoadErrorInfo على مزيد من المعلومات حول تعذُّر التحميل لتخصيص المنطق استنادًا إلى نوع الخطأ أو الطلب الذي تعذّر تنفيذه.

تخصيص علامات أداة استخراج البيانات

يمكن استخدام علامات أداة الاستخراج لتخصيص كيفية استخراج التنسيقات الفردية من الوسائط المتتالية. ويمكن ضبطها على "DefaultExtractorsFactory" التي يتم توفيرها لـ "DefaultMediaSourceFactory". يمرر المثال التالي علامة تتيح البحث المستند إلى الفهرس لملفات بث MP3.

Kotlin

val extractorsFactory =
  DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING)
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory))
    .build()

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING);

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory))
        .build();

تفعيل وضع التقديم/الترجيع باستمرار لمعدل نقل البيانات

في مجموعات البث بتنسيق MP3 وADTS وAMR، يمكنك تفعيل وضع التقديم/الترجيع بشكل تقريبي باستخدام افتراض معدّل نقل بيانات ثابت مع علامات FLAG_ENABLE_CONSTANT_BITRATE_SEEKING. يمكن إعداد هذه العلامات لأدوات استخراج البيانات الفردية باستخدام طرق DefaultExtractorsFactory.setXyzExtractorFlags الفردية كما هو موضّح أعلاه. لتفعيل البحث عن معدل نقل البيانات المستمر في جميع أدوات استخراج البيانات المتوافقة معه، استخدِم DefaultExtractorsFactory.setConstantBitrateSeekingEnabled.

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);

ويمكن بعد ذلك إدخال ExtractorsFactory من خلال DefaultMediaSourceFactory على النحو الموضَّح لتخصيص علامات أداة الاستخراج أعلاه.

تفعيل قائمة انتظار التخزين المؤقت غير المتزامنة

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

يتم تفعيل "قائمة المحتوى التالي في التخزين المؤقت غير المتزامن" تلقائيًا على الأجهزة التي تعمل بالإصدار 12 من نظام التشغيل Android (المستوى 31 من واجهة برمجة التطبيقات) والإصدارات الأحدث، ويمكن تفعيلها يدويًا بدءًا من الإصدار Android 6.0 (المستوى 23 من واجهة برمجة التطبيقات). جرِّب تفعيل هذه الميزة لأجهزة معينة لاحظت أن إطاراتها مفقودة أو انخفضت عمليات تشغيل الصوت، خاصةً عند تشغيل محتوى محمي بموجب إدارة الحقوق الرقمية أو محتوى عالي السرعة.

في أبسط الحالات، ستحتاج إلى إدخال DefaultRenderersFactory في المشغّل على النحو التالي:

Kotlin

val renderersFactory = 
  DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing()
val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()

Java

DefaultRenderersFactory renderersFactory =
    new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing();
ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();

إذا كنت تنشئ مثيلاً لعارضي العروض مباشرةً، مرِّر AsynchronousMediaCodecAdapter.Factory إلى الدالتين الإنشائية MediaCodecVideoRenderer وMediaCodecAudioRenderer.

اعتراض طلبات البيانات باستخدام ForwardingPlayer

يمكنك تخصيص بعض سلوك المثيل Player من خلال إحاطته بفئة فرعية من ForwardingPlayer وتجاوز الطرق لتنفيذ أي مما يلي:

  • يمكنك الوصول إلى المَعلمات قبل تمريرها إلى المفوَّض Player.
  • عليك الوصول إلى القيمة المعروضة من المستخدم المفوَّض Player قبل عرضها.
  • أعِد تنفيذ الطريقة بالكامل.

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

  • إذا أردت إلغاء كل عملية من عمليات "التشغيل"، يجب إلغاء كلاً من ForwardingPlayer.play وForwardingPlayer.setPlayWhenReady، لأنّ المتصل سيتوقع أن يكون سلوك هاتين الطريقتَين متطابقتَين عند playWhenReady = true.
  • إذا كنت تريد تغيير زيادة التقديم/الترجيع، يجب إلغاء كلاً من ForwardingPlayer.seekForward لتقديم طلب بالزيادة المخصّصة وForwardingPlayer.getSeekForwardIncrement حتى يتم إبلاغ المتصل بالزيادة المخصّصة الصحيحة.
  • إذا أردت التحكّم في ما يتم الإعلان عنه من خلال مثيل Player.Commands للمشغّل، عليك إلغاء كل من Player.getAvailableCommands() وPlayer.isCommandAvailable()، والاستماع أيضًا إلى رد الاتصال في Player.Listener.onAvailableCommandsChanged() ليتم إعلامك بالتغييرات القادمة من المشغّل الأساسي.

تخصيص MediaSource

تؤدي الأمثلة الواردة أعلاه إلى إدخال مكونات مخصّصة لاستخدامها أثناء تشغيل جميع عناصر MediaItem التي يتم تمريرها إلى المشغّل. وعندما يكون إجراء التخصيص بالغ الدقة مطلوبًا، من الممكن أيضًا إدخال مكوِّنات مخصّصة في مثيلات MediaSource فردية والتي يمكن تمريرها إلى المشغّل مباشرةً. ويوضّح المثال أدناه كيفية تخصيص ProgressiveMediaSource لاستخدام سمات DataSource.Factory وExtractorsFactory وLoadErrorHandlingPolicy مخصّصة:

Kotlin

val mediaSource =
  ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
    .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
    .createMediaSource(MediaItem.fromUri(streamUri))

Java

ProgressiveMediaSource mediaSource =
    new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
        .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
        .createMediaSource(MediaItem.fromUri(streamUri));

إنشاء مكونات مخصصة

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

  • Renderer – يمكنك استخدام Renderer مخصّص لمعالجة نوع وسائط غير متوافق مع عمليات التنفيذ التلقائية التي توفّرها المكتبة.
  • TrackSelector: يتيح تنفيذ TrackSelector مخصّص لمطوّر التطبيقات تغيير طريقة اختيار المقاطع الصوتية التي تم الكشف عنها من خلال MediaSource ليتم استخدامها في كل من Renderer المتاحة.
  • LoadControl – يتيح تنفيذ LoadControl مخصّص لمطوّر التطبيق تغيير سياسة التخزين المؤقت للمشغّل.
  • Extractor – إذا كنت تريد إتاحة تنسيق حاوية غير متوافق مع المكتبة حاليًا، ننصحك بتنفيذ فئة Extractor مخصّصة.
  • MediaSource – قد يكون من المناسب تنفيذ فئة MediaSource مخصّصة إذا كنت تريد الحصول على عيّنات من الوسائط لإضافتها إلى خلاصة العارضين بطريقة مخصّصة، أو إذا كنت تريد تطبيق سلوك MediaSource مخصّص.
  • MediaSource.Factory – يتيح تنفيذ MediaSource.Factory مخصّص للتطبيق تخصيص طريقة إنشاء MediaSource من MediaItem.
  • DataSource – تحتوي حزمة ExoPlayer على عدد من عمليات تنفيذ DataSource لحالات الاستخدام المختلفة. قد يكون من الأفضل استخدام فئة DataSource لتحميل البيانات بطريقة أخرى، مثلاً عبر بروتوكول مخصّص أو من خلال حزمة HTTP مخصصة أو من ذاكرة تخزين مؤقت دائمة مخصَّصة.

عند إنشاء مكونات مخصصة، ننصحك بما يلي:

  • إذا كان أحد المكوّنات المخصّصة يحتاج إلى إرسال تقارير إلى التطبيق مرة أخرى عن الأحداث، ننصحك بإجراء ذلك باستخدام النموذج نفسه المستخدَم في مكوّنات ExoPlayer الحالية، على سبيل المثال، باستخدام فئات EventDispatcher أو تمرير Handler مع أداة إنشاء للمكوِّن.
  • ننصح بأن تستخدم المكوّنات المخصّصة النموذج نفسه المستخدَم في مكوّنات ExoPlayer الحالية للسماح بإعادة ضبط التطبيق أثناء التشغيل. لإجراء ذلك، يجب أن تنفّذ المكونات المخصّصة PlayerMessage.Target وأن تتلقّى تغييرات الإعدادات في طريقة handleMessage. ينبغي أن يجتاز رمز التطبيق تغييرات الإعدادات من خلال استدعاء طريقة createMessage في ExoPlayer، وإعداد الرسالة، وإرسالها إلى المكوِّن باستخدام PlayerMessage.send. يضمن إرسال الرسائل ليتم تسليمها في سلسلة التشغيل أن يتم تنفيذها بالترتيب مع أي عمليات أخرى يتم تنفيذها على المشغّل.