سفارشی‌سازی

در هسته کتابخانه ExoPlayer رابط Player قرار دارد. Player عملکرد سنتی پخش‌کننده رسانه سطح بالا را آشکار می‌کند، مانند توانایی بافر کردن رسانه، پخش، توقف موقت، و جستجو. پیاده‌سازی پیش‌فرض ExoPlayer به‌گونه‌ای طراحی شده است که فرضیات کمی درباره (و در نتیجه محدودیت‌های کمی برای) نوع رسانه درحال پخش، نحوه و مکان ذخیره‌سازی آن، و نحوه پردازش آن داشته باشد. به‌جای اینکه بارگذاری و پرداز رسانه مستقیماً پیاده‌سازی شود، ExoPlayer پیاده‌سازی‌ها این کار را به عناصری که هنگام ایجاد پخش‌کننده یا هنگام انتقال منابع رسانه‌ای جدید به پخش‌کننده تزریق می‌شوند واگذار می‌کنند. اجزای مشترک در همه پیاده‌سازی‌های ExoPlayer عبارت‌اند از:

  • ‫MediaSource نمونه که رسانه را برای پخش تعریف می‌کنند، رسانه را بار می‌کنند، و رسانه‌های بارشده را می‌توان از آن‌ها خواند. نمونه MediaSource از MediaItem توسط MediaSource.Factory در داخل پخش‌کننده ایجاد می‌شود. همچنین می‌توانند بااستفاده از API فهرست پخش مبتنی بر منبع رسانه مستقیماً به پخش‌کننده منتقل شوند.
  • ‫MediaSource.Factory نمونه‌ای که MediaItem را به MediaSource تبدیل می‌کند. وقتی پخش‌کننده ایجاد می‌شود، MediaSource.Factory تزریق می‌شود.
  • ‫Renderer نمونه که عناصر رسانه را به‌صورت جداگانه ارائه می‌کنند. این‌ها هنگام ایجاد پخش‌کننده تزریق می‌شوند.
  • TrackSelector که قطعات ارائه‌شده توسط MediaSource را برای مصرف توسط هر Renderer موجود انتخاب می‌کند. وقتی پخش‌کننده ایجاد می‌شود، TrackSelector تزریق می‌شود.
  • LoadControl که کنترل می‌کند MediaSource چه زمانی رسانه بیشتری را بافر کند و چه مقدار رسانه بافر شود. وقتی پخش‌کننده ایجاد می‌شود، LoadControl تزریق می‌شود.
  • LivePlaybackSpeedControl که سرعت بازپخش را درحین بازپخش‌های زنده کنترل می‌کند تا به پخش‌کننده اجازه دهد به یک انحراف زنده پیکربندی‌شده نزدیک بماند. وقتی پخش‌کننده ایجاد می‌شود، LivePlaybackSpeedControl تزریق می‌شود.

مفهوم تزریق مؤلفه‌هایی که بخش‌هایی از عملکرد پخش‌کننده را پیاده‌سازی می‌کنند در سراسر کتابخانه وجود دارد. پیاده‌سازی‌های پیش‌فرض برخی‌از عناصر کار را به عناصر تزریق‌شده دیگر واگذار می‌کنند. این کار به بسیاری از زیرمجموعه‌ها امکان می‌دهد به‌صورت جداگانه با پیاده‌سازی‌هایی که به‌روش سفارشی پیکربندی شده‌اند جایگزین شوند.

سفارشی‌سازی پخش‌کننده

برخی‌از نمونه‌های رایج سفارشی‌سازی پخش‌کننده با تزریق عناصر در زیر توضیح داده شده است.

درحال پیکربندی پشته شبکه

صفحه‌ای درباره سفارشی‌سازی پشته شبکه مورد استفاده ExoPlayer داریم.

داده‌های بارشده از شبکه در حافظه نهان ذخیره می‌شود

راهنماهای مربوط به ذخیره‌سازی موقت در لحظه و بارگیری رسانه را ببینید.

درحال سفارشی‌سازی تعاملات سرور

برخی‌از برنامه‌ها ممکن است بخواهند درخواست‌ها و پاسخ‌های HTTP را رهگیری کنند. ممکن است بخواهید سرایندهای درخواست سفارشی تزریق کنید، سرایندهای پاسخ سرور را بخوانید، شناسه منابع یکنواخت درخواست‌ها را اصلاح کنید، و غیره. برای مثال، برنامه شما ممکن است با تزریق یک کد به‌عنوان سرایند هنگام درخواست بخش‌های رسانه، اصالت خود را تأیید کند.

مثال زیر نشان می‌دهد که چگونه این رفتارها را با تزریق 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 برای انجام اصلاحات لحظه‌ای نشانی وب استفاده کنید، همان‌طور که در گزیده زیر نشان داده شده است:

کاتلین

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 12 (میانای برنامه کاربردی سطح ۳۱) و بالاتر فعال است و از Android 6.0 (میانای برنامه کاربردی سطح ۲۳) به‌صورت دستی قابل فعال‌سازی است. این ویژگی را برای دستگاه‌های خاصی که در آن‌ها افت فریم یا کمبود صدا مشاهده می‌کنید فعال کنید، به‌ویژه هنگام پخش محتوای محافظت‌شده با 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 – پیاده‌سازی 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 منتقل کند. ارسال پیام برای تحویل در رشته بازپخش تضمین می‌کند که پیام‌ها به‌ترتیب با هر عملیات دیگری که روی پخش‌کننده انجام می‌شود اجرا شوند.