سفارشی‌سازی

در هسته کتابخانه 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 را رهگیری کنند. ممکن است بخواهید سرایندهای درخواست سفارشی تزریق کنید، سرایندهای پاسخ سرور را بخوانید، URI درخواست‌ها را اصلاح کنید، و غیره. برای مثال، ممکن است برنامه‌تان با تزریق یک توکن به‌عنوان سرایند هنگام درخواست بخش‌های رسانه، اصالت‌سنجی شود.

مثال زیر نشان می‌دهد که چگونه این رفتارها را با تزریق DataSource.Factory سفارشی در DefaultMediaSourceFactory پیاده‌سازی کنید:

کاتلین

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()

جاوا

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 تزریق کنید:

کاتلین

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

جاوا

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

همچنین می‌توانید از ResolvingDataSource برای انجام اصلاحات به‌موقع در URI استفاده کنید، همان‌طور که در قطعه کد زیر نشان داده شده است:

کاتلین

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

جاوا

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

سفارشی‌سازی کردن مدیریت خطا

پیاده‌سازی LoadErrorHandlingPolicy سفارشی به برنامه‌ها اجازه می‌دهد نحوه واکنش ExoPlayer به خطاهای بارگیری را سفارشی‌سازی کنند. برای مثال، ممکن است برنامه‌ای بخواهد به‌جای چندین‌بار امتحان مجدد، به‌سرعت شکست بخورد، یا بخواهد منطق عقب‌گرد را که مدت زمان انتظار بازیکن بین هر امتحان مجدد را کنترل می‌کند سفارشی‌سازی کند. قطعه کد زیر نحوه پیاده‌سازی منطق عقب‌نشینی سفارشی را نشان می‌دهد:

کاتلین

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

جاوا

LoadErrorHandlingPolicy loadErrorHandlingPolicy =
    new DefaultLoadErrorHandlingPolicy() {
      @Override
      public long getRetryDelayMsFor(LoadErrorHandlingPolicy.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 فعال می‌کند.

کاتلین

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

جاوا

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 استفاده کنید.

کاتلین

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

جاوا

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

سپس می‌توان ExtractorsFactory را بااستفاده از DefaultMediaSourceFactory تزریق کرد، همان‌طور که قبلاً برای سفارشی‌سازی پرچم‌های استخراج‌کننده توضیح داده شد.

درحال فعال کردن صف‌بندی میان‌گیر ناهمگام

صف‌بندی میان‌گیر ناهمگام بهبودی در خط لوله پردازش ExoPlayer است که نمونه‌های MediaCodec را در حالت ناهمگام اجرا می‌کند و از رشته‌های اضافی برای زمان‌بندی رمزگشایی و پردازش داده‌ها استفاده می‌کند. فعال کردن آن می‌تواند فریم‌های ازدست‌رفته و خطاهای کمبود داده صوتی را کاهش دهد.

صف‌بندی میان‌گیر ناهمگام به‌طور پیش‌فرض در دستگاه‌های دارای Android نسخه ۱۲ (سطح میانای برنامه‌سازی کاربردی ۳۱) یا بالاتر فعال است و می‌توان آن را به‌طور دستی از Android نسخه ۶.۰ (سطح میانای برنامه‌سازی کاربردی ۲۳) فعال کرد. این ویژگی را برای دستگاه‌های خاصی که در آن‌ها افت فریم یا کمبود داده صوتی مشاهده می‌کنید فعال کنید، به‌ویژه هنگام پخش محتوای محافظت‌شده با DRM یا محتوای با نرخ فریم بالا.

در ساده‌ترین حالت، باید DefaultRenderersFactory را به پخش‌کننده به‌صورت زیر تزریق کنید:

کاتلین

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

جاوا

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 که بیشتر مستعد خطا است ترجیح داده شود، زیرا این تضمین‌های سازگاری را ارائه می‌دهد.

برای مثال، برای افزودن منطق سفارشی هنگام شروع یا توقف بازپخش:

کاتلین

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

جاوا

public static final 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 بدون عملیات است):

کاتلین

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.
}

جاوا

public static final 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 سفارشی استفاده کند:

کاتلین

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

جاوا

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

درحال ایجاد مؤلفه‌های سفارشی

این کتابخانه پیاده‌سازی‌های پیش‌فرضی از مؤلفه‌های فهرست‌شده در بالای این صفحه برای موارد استفاده رایج ارائه می‌دهد. ExoPlayer می‌تواند از این مؤلفه‌ها استفاده کند، اما ممکن است برای استفاده از پیاده‌سازی‌های سفارشی نیز ساخته شود، اگر رفتارهای غیراستاندارد موردنیاز باشد. برخی‌از موارد استفاده برای پیاده‌سازی‌های سفارشی عبارت‌اند از:

  • Renderer – ممکن است بخواهید یک Renderer سفارشی را برای مدیریت نوع رسانه‌ای که توسط پیاده‌سازی‌های پیش‌فرض ارائه‌شده توسط کتابخانه پشتیبانی نمی‌شود، پیاده‌سازی کنید.
  • TrackSelector – Implementing a custom TrackSelector allows an app developer to change the way in which tracks exposed by a MediaSource are selected for consumption by each of the available Renderers.
  • LoadControl – پیاده‌سازی LoadControl سفارشی به توسعه‌دهنده برنامه امکان می‌دهد خط‌مشی میان‌گیری پخش‌کننده را تغییر دهد.
  • Extractor – اگر لازم است از قالب محفظه‌ای پشتیبانی کنید که درحال‌حاضر توسط کتابخانه پشتیبانی نمی‌شود، پیاده‌سازی کلاس Extractor سفارشی را درنظر بگیرید.
  • MediaSource – پیاده‌سازی کلاس MediaSource سفارشی ممکن است مناسب باشد اگر بخواهید نمونه‌های رسانه را برای تغذیه پردازنده گرافیکی به روشی سفارشی به‌دست آورید، یا اگر بخواهید رفتار ترکیب‌بندی MediaSource سفارشی را پیاده‌سازی کنید.
  • MediaSource.Factory – Implementing a custom MediaSource.Factory allows an application to customize the way in which a MediaSource is created from a MediaItem.
  • DataSource – بسته بالادستی ExoPlayer درحال‌حاضر شامل تعدادی DataSource پیاده‌سازی برای موارد استفاده مختلف است. ممکن است بخواهید کلاس DataSource خودتان را برای بار کردن داده‌ها به روشی دیگر پیاده‌سازی کنید، مثلاً ازطریق پروتکل سفارشی، بااستفاده از پشته HTTP سفارشی، یا از حافظه نهان پایدار سفارشی.

هنگام ساختن مؤلفه‌های سفارشی، موارد زیر را توصیه می‌کنیم:

  • اگر مؤلفه سفارشی باید رویدادها را به برنامه گزارش دهد، توصیه می‌کنیم این کار را بااستفاده از همان مدل مؤلفه‌های ExoPlayer موجود انجام دهید، برای مثال بااستفاده از کلاس‌های EventDispatcher یا ارسال Handler به‌همراه شنونده به سازنده مؤلفه.
  • توصیه می‌کنیم مؤلفه‌های سفارشی از همان مدل مؤلفه‌های ExoPlayer موجود استفاده کنند تا برنامه بتواند درطول بازپخش پیکربندی را تغییر دهد. برای انجام این کار، مؤلفه‌های سفارشی باید PlayerMessage.Target را پیاده‌سازی کنند و تغییرات پیکربندی را در روش handleMessage دریافت کنند. کد برنامه باید تغییرات پیکربندی را با فراخوانی روش createMessage ExoPlayer، پیکربندی پیام، و ارسال آن به مؤلفه بااستفاده از PlayerMessage.send منتقل کند. ارسال پیام‌ها برای تحویل در رشته بازپخش تضمین می‌کند که آن‌ها به‌ترتیب با هر عملیات دیگری که روی پخش‌کننده انجام می‌شود اجرا می‌شوند.