التخصيص

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

يوضّح المثال التالي كيفية تنفيذ هذه السلوكيات من خلال إدخال 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 من واجهة برمجة التطبيقات) والإصدارات الأحدث، ويمكن تفعيلها يدويًا بدءًا من الإصدار 6.0 من نظام التشغيل Android (المستوى 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();

إذا كنت تنشئ عناصر العرض مباشرةً، مرِّر new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous() إلى الدالتَين الإنشائيتَين MediaCodecVideoRenderer وMediaCodecAudioRenderer.

تخصيص العمليات باستخدام ForwardingSimpleBasePlayer

يمكنك تخصيص بعض سلوك مثيل Player من خلال تضمينه في فئة فرعية من ForwardingSimpleBasePlayer. تتيح لك هذه الفئة اعتراض "عمليات" معيّنة، بدلاً من الاضطرار إلى تنفيذ طرق Player مباشرةً. ويضمن ذلك سلوكًا متسقًا، على سبيل المثال، play() وpause() وsetPlayWhenReady(boolean). ويضمن أيضًا نقل جميع تغييرات الحالة بشكل صحيح إلى مثيلات Player.Listener المسجّلة. بالنسبة إلى معظم حالات استخدام التخصيص، يُفضَّل استخدام ForwardingSimpleBasePlayer بدلاً من ForwardingPlayer الأكثر عرضةً للأخطاء، وذلك بسبب ضمانات الاتساق هذه.

على سبيل المثال، لإضافة بعض المنطق المخصّص عند بدء التشغيل أو إيقافه:

Kotlin

class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> {
    // Add custom logic
    return super.handleSetPlayWhenReady(playWhenReady)
  }
}

Java

class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer {

  public PlayerWithCustomPlay(Player player) {
    super(player);
  }

  @Override
  protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) {
    // Add custom logic
    return super.handleSetPlayWhenReady(playWhenReady);
  }
}

أو لمنع تنفيذ الأمر SEEK_TO_NEXT (والتأكّد من أنّ Player.seekToNext لا يؤدي إلى أي عملية):

Kotlin

class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun getState(): State {
    val state = super.getState()
    return state
      .buildUpon()
      .setAvailableCommands(
        state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()
      )
      .build()
  }

  // We don't need to override handleSeek, because it is guaranteed not to be called for
  // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable.
}

Java

class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer {

  public PlayerWithoutSeekToNext(Player player) {
    super(player);
  }

  @Override
  protected State getState() {
    State state = super.getState();
    return state
        .buildUpon()
        .setAvailableCommands(
            state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build())
        .build();
  }

  // We don't need to override handleSeek, because it is guaranteed not to be called for
  // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable.
}

تخصيص 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. يضمن إرسال الرسائل ليتم تسليمها في سلسلة تشغيل الفيديو تنفيذها بالترتيب مع أي عمليات أخرى يتم إجراؤها على المشغّل.