কাস্টমাইজেশন

ExoPlayer লাইব্রেরির মূল বিষয় হল Player ইন্টারফেস। Player মিডিয়া প্লেয়ারের ঐতিহ্যবাহী হাই-লেভেল ফাংশনালিটি প্রকাশ করে, যেমন মিডিয়া বাফার করা, চালানো, পজ করা ও খোঁজার ক্ষমতা। ডিফল্ট প্রয়োগ ExoPlayer হল যে ধরনের মিডিয়া চালানো হচ্ছে, সেটি কীভাবে ও কোথায় স্টোর করা আছে এবং কীভাবে সেটি রেন্ডার করা হচ্ছে সেই সম্পর্কে কিছু অনুমান করার (এবং সেই জন্য কিছু বিধিনিষেধ আরোপ করার) জন্য ডিজাইন করা হয়েছে। সরাসরি মিডিয়া লোড ও রেন্ডার করার পরিবর্তে, ExoPlayer প্লেয়ার তৈরি হলে বা প্লেয়ারে নতুন মিডিয়া সোর্স পাস করা হলে, ইনজেক্ট করা কম্পোনেন্টকে এই কাজগুলি করার দায়িত্ব দেওয়া হয়। সব ExoPlayer প্রয়োগের ক্ষেত্রে সাধারণ কম্পোনেন্ট হল:

  • MediaSource ইনস্ট্যান্স যা চালানো হবে এমন মিডিয়াকে সংজ্ঞায়িত করে, মিডিয়া লোড করে এবং যেখান থেকে লোড করা মিডিয়া পড়া যায়। প্লেয়ারের মধ্যে MediaSource.Factory-এর মাধ্যমে MediaItem থেকে একটি MediaSource ইনস্ট্যান্স তৈরি করা হয়। এছাড়াও, এগুলি মিডিয়া সোর্স ভিত্তিক প্লেলিস্ট API ব্যবহার করে সরাসরি প্লেয়ারে পাস করা যেতে পারে।
  • MediaSource.Factory ইনস্ট্যান্স যা MediaItem-কে MediaSource-এ কনভার্ট করে। প্লেয়ার তৈরি করার সময় MediaSource.Factory ইনজেক্ট করা হয়।
  • Renderer ইনস্ট্যান্স যা মিডিয়ার আলাদা আলাদা কম্পোনেন্ট রেন্ডার করে। প্লেয়ার তৈরি করার সময় এগুলি ইনজেক্ট করা হয়।
  • TrackSelector যা MediaSource-এর দেওয়া ট্র্যাক বেছে নেয় যাতে উপলভ্য প্রতিটি Renderer ব্যবহার করতে পারে। প্লেয়ার তৈরি হলে একটি TrackSelector ইনজেক্ট করা হয় ।
  • LoadControl যা নিয়ন্ত্রণ করে কখন MediaSource আরও মিডিয়া বাফার করবে এবং কতটা মিডিয়া বাফার করা হবে। প্লেয়ার তৈরি করার সময় একটি LoadControl ইনজেক্ট করা হয়।
  • LivePlaybackSpeedControl যা লাইভ প্লেব্যাকের সময় প্লেব্যাক স্পিড কন্ট্রোল করে যাতে প্লেয়ার কনফিগার করা লাইভ অফসেটের কাছাকাছি থাকতে পারে। প্লেয়ার তৈরি করার সময় LivePlaybackSpeedControl ইনজেক্ট করা হয়।

প্লেয়ার ফাংশনের অংশ প্রয়োগ করে এমন কম্পোনেন্ট ইনজেক্ট করার ধারণাটি লাইব্রেরি জুড়ে উপস্থিত। কিছু কম্পোনেন্টের ডিফল্ট প্রয়োগ আরও ইনজেক্ট করা কম্পোনেন্টকে কাজ ডেলিগেট করে। এর ফলে অনেক সাব-কম্পোনেন্টকে আলাদা আলাদাভাবে এমন ইমপ্লিমেন্টেশনের মাধ্যমে পরিবর্তন করা যায় যা কাস্টম উপায়ে কনফিগার করা হয়।

প্লেয়ার কাস্টমাইজ করা

কম্পোনেন্ট ইনজেক্ট করার মাধ্যমে প্লেয়ার কাস্টমাইজ করার কিছু সাধারণ উদাহরণ নিচে বর্ণিত আছে।

নেটওয়ার্ক স্ট্যাক কনফিগার করা

ExoPlayer-এর ব্যবহার করা নেটওয়ার্ক স্ট্যাক কাস্টমাইজ করা সম্পর্কে আমাদের একটি পৃষ্ঠা আছে।

নেটওয়ার্ক থেকে লোড করা ডেটা ক্যাশে করা

সাময়িক অন-দ্য-ফ্লাই ক্যাশিং ও মিডিয়া ডাউনলোড করা সংক্রান্ত নির্দেশিকা দেখুন।

সার্ভারের সাথে ইন্টার‍্যাকশন কাস্টমাইজ করা

কিছু অ্যাপ HTTP অনুরোধ ও উত্তর ইন্টারসেপ্ট করতে চাইতে পারে। আপনি হয়ত কাস্টম অনুরোধ হেডার ইনজেক্ট করতে, সার্ভারের উত্তর হেডার পড়তে, অনুরোধের URI পরিবর্তন করতে ইত্যাদি চান। যেমন, মিডিয়া সেগমেন্টের অনুরোধ করার সময় আপনার অ্যাপ হয়ত হেডার হিসেবে টোকেন ইনজেক্ট করে নিজেকে যাচাই করিয়ে নিতে পারে।

নিম্নলিখিত উদাহরণে দেখানো হয়েছে যে কীভাবে DefaultMediaSourceFactory-এ কাস্টম DataSource.Factory ইনজেক্ট করে এইসব আচরণ প্রয়োগ করা যায়:

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

জাভা

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 প্রতিটি HTTP অনুরোধে হেডার "Header: Value" অন্তর্ভুক্ত করে। HTTP সোর্সের সাথে প্রতিটি ইন্টার‍্যাকশনের জন্য এই আচরণ নির্ধারিত।

আরও গ্র্যানুলার পদ্ধতির জন্য, আপনি ResolvingDataSource ব্যবহার করে জাস্ট-ইন-টাইম আচরণ ইনজেক্ট করতে পারেন। HTTP সোর্সের সাথে ইন্টার‍্যাক্ট করার ঠিক আগে কীভাবে অনুরোধ হেডার ইনজেক্ট করতে হয় তা নিম্নলিখিত কোড স্নিপেট থেকে বোঝা যায়:

Kotlin

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-তে ঠিক সেই সময়েই পরিবর্তন করতে পারেন, যেমনটি নিম্নলিখিত স্নিপেটে দেখানো হয়েছে:

Kotlin

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 লোড সংক্রান্ত সমস্যার ক্ষেত্রে কীভাবে প্রতিক্রিয়া জানাবে তা অ্যাপ কাস্টমাইজ করতে পারে। যেমন, কোনও অ্যাপ অনেকবার আবার চেষ্টা করার পরিবর্তে দ্রুত ব্যর্থ হতে চাইতে পারে অথবা ব্যাক-অফ লজিক কাস্টমাইজ করতে চাইতে পারে যা প্রতিবার আবার চেষ্টা করার মধ্যে প্লেয়ার কতক্ষণ অপেক্ষা করবে তা কন্ট্রোল করে। নিম্নলিখিত স্নিপেট থেকে কীভাবে কাস্টম ব্যাক-অফ লজিক প্রয়োগ করতে হয় তা জানা যায়:

Kotlin

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 আর্গুমেন্টে লোড না হওয়া সংক্রান্ত আরও তথ্য থাকে। এর ফলে, সমস্যার ধরন বা অনুরোধ ব্যর্থ হওয়ার ভিত্তিতে লজিক কাস্টমাইজ করা যায়।

এক্সট্র্যাক্টর ফ্ল্যাগ কাস্টমাইজ করা

প্রোগ্রেসিভ মিডিয়া থেকে কীভাবে আলাদা আলাদা ফর্ম্যাট এক্সট্র্যাক্ট করা হবে তা কাস্টমাইজ করতে এক্সট্র্যাক্টর ফ্ল্যাগ ব্যবহার করা যেতে পারে । DefaultMediaSourceFactory-কে দেওয়া DefaultExtractorsFactory-এ সেগুলি সেট করা যেতে পারে। নিচের উদাহরণে একটি ফ্ল্যাগ পাস করা হয়েছে যা MP3 স্ট্রিমের জন্য ইন্ডেক্স-ভিত্তিক সিকিং চালু করে।

Kotlin

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 ফ্ল্যাগ সহ ধ্রুবক বিটরেট অনুমান ব্যবহার করে আনুমানিক সিকিং চালু করতে পারবেন। উপরে বর্ণিত individual DefaultExtractorsFactory.setXyzExtractorFlags পদ্ধতি ব্যবহার করে আলাদা আলাদা এক্সট্র্যাক্টরের জন্য এইসব ফ্ল্যাগ সেট করা যেতে পারে। যেসব এক্সট্র্যাক্টর এটি কাজ করে সেগুলির জন্য কনস্ট্যান্ট বিটরেট খোঁজা চালু করতে, DefaultExtractorsFactory.setConstantBitrateSeekingEnabled ব্যবহার করুন।

Kotlin

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 ইনজেক্ট করতে হবে:

Kotlin

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-এর মাধ্যমে অপারেশন কাস্টমাইজ করা

ForwardingSimpleBasePlayer-এর সাবক্লাসে র‍্যাপ করে আপনি Player-এর কিছু আচরণ কাস্টমাইজ করতে পারেন। এই ক্লাস আপনাকে সরাসরি Player মেথড প্রয়োগ করার পরিবর্তে নির্দিষ্ট 'অপারেশন' ইন্টারসেপ্ট করতে দেয়। এটি নিশ্চিত করে যে play(), pause() এবং setPlayWhenReady(boolean)-এর মতো আইটেম সামঞ্জস্যপূর্ণ আচরণ করবে। এছাড়াও, এটি নিশ্চিত করে যে সমস্ত স্টেট পরিবর্তন সঠিকভাবে রেজিস্টার করা Player.Listener ইনস্ট্যান্সে প্রোপাগেট করা হয়েছে। বেশিরভাগ কাস্টমাইজেশন ব্যবহারের ক্ষেত্রে, ForwardingSimpleBasePlayer-কে বেশি ভুল-প্রবণ ForwardingPlayer-এর চেয়ে বেশি পছন্দ করা উচিত, কারণ এর মধ্যে এই ধারাবাহিকতা সংক্রান্ত গ্যারান্টি থাকে।

যেমন, প্লেব্যাক শুরু বা বন্ধ করা হলে কিছু কাস্টম লজিক যোগ করতে:

Kotlin

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 হল নো-অপ):

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

জাভা

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 ব্যবহার করা যায়:

Kotlin

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 প্রয়োগ করলে কোনও অ্যাপ্লিকেশন MediaItem থেকে MediaSource তৈরি করার পদ্ধতি কাস্টমাইজ করতে পারে।
  • DataSource – ExoPlayer-এর আপস্ট্রিম প্যাকেজে আগে থেকেই বিভিন্ন ব্যবহারের ক্ষেত্রে DataSource প্রয়োগ করার সুবিধা রয়েছে। আপনি হয়ত অন্য কোনও উপায়ে ডেটা লোড করার জন্য নিজের DataSource ক্লাস প্রয়োগ করতে চাইবেন, যেমন কাস্টম HTTP স্ট্যাক ব্যবহার করে কাস্টম প্রোটোকল বা কাস্টম পারসিস্টেন্ট ক্যাশে থেকে।

কাস্টম কম্পোনেন্ট তৈরি করার সময়, আমরা নিম্নলিখিত বিষয়গুলি সাজেস্ট করি:

  • কোনও কাস্টম কম্পোনেন্টকে যদি অ্যাপে ইভেন্ট রিপোর্ট করতে হয়, তাহলে আমরা সাজেস্ট করি যে আপনি যেন তা বর্তমান ExoPlayer কম্পোনেন্টগুলির মতো একই মডেল ব্যবহার করে করেন। যেমন, EventDispatcher ক্লাস ব্যবহার করা অথবা কম্পোনেন্টের কনস্ট্রাক্টরে লিসনারের সাথে Handler পাস করা।
  • আমরা সাজেস্ট করি যে কাস্টম কম্পোনেন্ট যেন আগে থেকে থাকা ExoPlayer কম্পোনেন্টের মতো একই মডেল ব্যবহার করে যাতে প্লেব্যাকের সময় অ্যাপের মাধ্যমে রিকনফিগার করা যায়। এটি করতে, কাস্টম কম্পোনেন্টকে PlayerMessage.Target প্রয়োগ করতে হবে এবং handleMessage পদ্ধতিতে কনফিগারেশন সংক্রান্ত পরিবর্তন পেতে হবে। অ্যাপ্লিকেশন কোডকে ExoPlayer-এর createMessage মেথড কল করে, মেসেজ কনফিগার করে এবং PlayerMessage.send ব্যবহার করে কম্পোনেন্টে পাঠিয়ে কনফিগারেশন পরিবর্তন পাস করতে হবে। প্লেব্যাক থ্রেডে ডেলিভার করার জন্য মেসেজ পাঠানো নিশ্চিত করে যে সেগুলি প্লেয়ারে করা অন্য যেকোনও অপারেশনের সাথে ক্রম অনুসারে এক্সিকিউট করা হয়েছে।