التخصيص

واجهة 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 لإجراء تعديلات في الوقت المناسب على عنوان URL، كما هو موضّح في المقتطف التالي:

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

تكون ميزة "قائمة انتظار المخزن المؤقت غير المتزامن" مفعّلة تلقائيًا على الأجهزة التي تعمل بنظام التشغيل Android 12 (المستوى 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.

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

يمكنك تخصيص بعض سلوك مثيل Player من خلال تغليفه في فئة فرعية من ForwardingSimpleBasePlayer. تتيح لك هذه الفئة اعتراض "عمليات" معيّنة، بدلاً من تنفيذ طرق Player بشكل مباشر. يضمن ذلك سلوكًا متسقًا، على سبيل المثال، في play() وpause() وsetPlayWhenReady(boolean). ويضمن أيضًا أنّه يتم نشر جميع تغييرات الحالة بشكل صحيح إلى نُسخ Player.Listener المسجّلة. بالنسبة إلى معظم حالات استخدام customisation ، يجب تفضيل 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 التي يتم تمريرها إلى المشغّل. في الحالات التي يلزم فيها تخصيص دقيق، من الممكن أيضًا إدراج مكوّنات مخصّصة في مثيلات individual 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 مخصّصة أو من ذاكرة cache مخصّصة دائمة.

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

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