سفارشی سازی

در هسته کتابخانه 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 را رهگیری کنند. شما ممکن است بخواهید هدرهای درخواست سفارشی را تزریق کنید، هدرهای پاسخ سرور را بخوانید، 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 رفتار just-in-time را تزریق کنید. قطعه کد زیر نحوه تزریق هدرهای درخواست درست قبل از تعامل با منبع 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 برای انجام تغییرات در لحظه (just-in-time) در 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 به خطاهای بارگذاری را سفارشی کنند. برای مثال، یک برنامه ممکن است بخواهد به جای تلاش مجدد چندین باره، سریعاً با شکست مواجه شود، یا ممکن است بخواهد منطق back-off را که مدت زمان انتظار بازیکن بین هر تلاش مجدد را کنترل می‌کند، سفارشی کند. قطعه کد زیر نحوه پیاده‌سازی منطق back-off سفارشی را نشان می‌دهد:

کاتلین

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 را در حالت ناهمزمان اجرا می‌کند و از رشته‌های اضافی برای زمان‌بندی رمزگشایی و رندر داده‌ها استفاده می‌کند. فعال کردن آن می‌تواند فریم‌های از دست رفته و افت کیفیت صدا را کاهش دهد.

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

سفارشی‌سازی مدیاسورس

مثال‌های بالا، اجزای سفارشی‌شده را برای استفاده در طول پخش تمام اشیاء 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 سفارشی به توسعه‌دهنده برنامه اجازه می‌دهد تا نحوه انتخاب Trackهای نمایش داده شده توسط MediaSource برای مصرف توسط هر یک از Renderer های موجود را تغییر دهد.
  • LoadControl - پیاده‌سازی یک LoadControl سفارشی به توسعه‌دهنده برنامه اجازه می‌دهد تا سیاست بافرینگ پخش‌کننده را تغییر دهد.
  • Extractor - اگر نیاز به پشتیبانی از قالب کانتینری دارید که در حال حاضر توسط کتابخانه پشتیبانی نمی‌شود، پیاده‌سازی یک کلاس Extractor سفارشی را در نظر بگیرید.
  • MediaSource - پیاده‌سازی یک کلاس MediaSource سفارشی ممکن است مناسب باشد اگر می‌خواهید نمونه‌های رسانه‌ای را برای ارائه به رندرکننده‌ها به روشی سفارشی دریافت کنید، یا اگر می‌خواهید رفتار ترکیبی MediaSource سفارشی را پیاده‌سازی کنید.
  • MediaSource.Factory - پیاده‌سازی یک MediaSource.Factory سفارشی به یک برنامه اجازه می‌دهد تا نحوه ایجاد یک MediaSource از یک MediaItem را سفارشی کند.
  • DataSource - بسته بالادستی ExoPlayer از قبل شامل تعدادی پیاده‌سازی DataSource برای موارد استفاده مختلف است. ممکن است بخواهید کلاس DataSource خودتان را برای بارگذاری داده‌ها به روش دیگری، مانند یک پروتکل سفارشی، استفاده از یک پشته HTTP سفارشی یا از یک حافظه پنهان پایدار سفارشی، پیاده‌سازی کنید.

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

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