سفارشی سازی

هسته اصلی کتابخانه 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 تزریق کنید. قطعه کد زیر نحوه تزریق هدر درخواست را درست قبل از تعامل با منبع 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: 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(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 (سطح API 31) و بالاتر فعال است و می‌توان آن را با شروع Android 6.0 (سطح API 23) به صورت دستی فعال کرد. فعال کردن این ویژگی را برای دستگاه‌های خاصی در نظر بگیرید که در آن‌ها فریم‌های افت شده یا کاهش صدا را مشاهده می‌کنید، به‌ویژه هنگام پخش محتوای محافظت‌شده با 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();

اگر مستقیماً رندرکننده‌ها را نمونه‌سازی می‌کنید، یک AsynchronousMediaCodecAdapter.Factory را به سازنده MediaCodecVideoRenderer و MediaCodecAudioRenderer ارسال کنید.

رهگیری تماس‌های متد با ForwardingPlayer

می‌توانید برخی از رفتارهای یک نمونه Player را با قرار دادن آن در یک زیر کلاس از ForwardingPlayer و روش‌های لغو به منظور انجام هر یک از موارد زیر سفارشی کنید:

  • قبل از ارسال آنها به Player نماینده به پارامترها دسترسی داشته باشید.
  • قبل از بازگرداندن به مقدار بازگشتی از Player نماینده دسترسی پیدا کنید.
  • روش را مجدداً به طور کامل اجرا کنید.

هنگام نادیده گرفتن روش‌های ForwardingPlayer ، مهم است که اطمینان حاصل شود که پیاده‌سازی با رابط Player سازگار و منطبق است، به‌ویژه زمانی که با روش‌هایی برخورد می‌کنید که قرار است رفتاری مشابه یا مرتبط داشته باشند. به عنوان مثال:

  • اگر می‌خواهید هر عملیات «play» را لغو کنید، باید هر دو ForwardingPlayer.play و ForwardingPlayer.setPlayWhenReady را لغو کنید، زیرا زمانی که playWhenReady = true تماس‌گیرنده انتظار دارد رفتار این روش‌ها یکسان باشد.
  • اگر می‌خواهید افزایش جستجو را تغییر دهید، باید هم ForwardingPlayer.seekForward را لغو کنید تا جستجو را با افزایش سفارشی‌شده خود انجام دهید و هم ForwardingPlayer.getSeekForwardIncrement به منظور گزارش افزایش سفارشی صحیح به تماس‌گیرنده.
  • اگر می‌خواهید کنترل کنید که چه Player.Commands توسط یک نمونه بازیکن تبلیغ می‌شود، باید هر دو Player.getAvailableCommands() و Player.isCommandAvailable() را لغو کنید و همچنین به فراخوان Player.Listener.onAvailableCommandsChanged() گوش دهید تا از تغییرات مطلع شوید. از بازیکن اصلی می آید.

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