কোর কম্পোজ

media3-ui-compose লাইব্রেরি Jetpack Compose-এ মিডিয়া UI তৈরি করার জন্য প্রয়োজনীয় প্রাথমিক কম্পোনেন্ট প্রদান করে। এটি সেইসব ডেভেলপারদের জন্য ডিজাইন করা হয়েছে যাদের media3-ui-compose-material3 লাইব্রেরির অফার করা কাস্টমাইজেশনের চেয়ে বেশি কাস্টমাইজেশন প্রয়োজন। কাস্টম মিডিয়া প্লেয়ার UI তৈরি করতে কীভাবে মূল কম্পোনেন্ট ও স্টেট হোল্ডার ব্যবহার করতে হয় তা এই পৃষ্ঠায় ব্যাখ্যা করা হয়েছে।

Material3 এবং কাস্টম Compose কম্পোনেন্ট মিক্স করা

media3-ui-compose-material3 লাইব্রেরি এমনভাবে ডিজাইন করা হয়েছে যাতে এটি ফ্লেক্সিবেল হয়। আপনি আপনার বেশিরভাগ UI-এর জন্য আগে থেকে তৈরি কম্পোনেন্ট ব্যবহার করতে পারেন, তবে আরও কন্ট্রোল প্রয়োজন হলে কাস্টম ইমপ্লিমেন্টেশনের জন্য একটি কম্পোনেন্ট সোয়্যাপ করতে পারেন। এই সময় media3-ui-compose লাইব্রেরি কাজে লাগে।

যেমন, ধরুন আপনি Material3 লাইব্রেরি থেকে স্ট্যান্ডার্ড PreviousButton এবং NextButton ব্যবহার করতে চান, কিন্তু আপনার সম্পূর্ণ কাস্টম PlayPauseButton প্রয়োজন। আপনি কোর media3-ui-compose লাইব্রেরি থেকে PlayPauseButton ব্যবহার করে এটি করতে পারেন এবং আগে থেকে তৈরি কম্পোনেন্টের পাশে এটি রাখতে পারেন।

Row {
  // Use prebuilt component from the Media3 UI Compose Material3 library
  PreviousButton(player)
  // Use the scaffold component from Media3 UI Compose library
  PlayPauseButton(player) {
    // `this` is PlayPauseButtonState
    FilledTonalButton(
      onClick = {
        Log.d("PlayPauseButton", "Clicking on play-pause button")
        this.onClick()
      },
      enabled = this.isEnabled,
    ) {
      Icon(
        imageVector = if (showPlay) Icons.Default.PlayArrow else Icons.Default.Pause,
        contentDescription = if (showPlay) "Play" else "Pause",
      )
    }
  }
  // Use prebuilt component from the Media3 UI Compose Material3 library
  NextButton(player)
}

উপলভ্য কম্পোনেন্ট

media3-ui-compose লাইব্রেরি সাধারণ প্লেয়ার কন্ট্রোলের জন্য আগে থেকে তৈরি কম্পোজেবলের সেট প্রদান করে। আপনার অ্যাপে সরাসরি ব্যবহার করতে পারবেন এমন কিছু কম্পোনেন্ট এখানে দেওয়া হল:

কম্পোনেন্ট বর্ণনা
PlayPauseButton একটি বোতামের জন্য স্টেট কন্টেনার যা প্লে এবং পজের মধ্যে টগল করে।
SeekBackButton একটি বোতামের জন্য স্টেট কন্টেনার যা নির্দিষ্ট ইনক্রিমেন্ট অনুযায়ী রিওয়াইন্ড করে।
SeekForwardButton একটি বোতামের জন্য স্টেট কন্টেনার যা নির্দিষ্ট ইনক্রিমেন্ট অনুযায়ী এগিয়ে যায়।
NextButton একটি বোতামের জন্য স্টেট কন্টেনার যা পরবর্তী মিডিয়া আইটেম খোঁজে।
PreviousButton আগের মিডিয়া আইটেমে যাওয়ার বোতামের জন্য একটি স্টেট কন্টেনার।
RepeatButton একটি বোতামের জন্য স্টেট কন্টেনার যা রিপিট মোডের মাধ্যমে সাইকেল করে।
ShuffleButton শাফেল মোড টগল করে এমন বোতামের জন্য একটি স্টেট কন্টেনার।
MuteButton প্লেয়ার মিউট ও আনমিউট করার বোতামের জন্য একটি স্টেট কন্টেনার।
TimeText কম্পোজ করার উপযুক্ত আইটেমের জন্য একটি স্টেট কন্টেনার যা প্লেয়ারের প্রোগ্রেস দেখায়।
ContentFrame মিডিয়া কন্টেন্ট দেখানোর জন্য একটি সারফেস যা অ্যাস্পেক্ট রেশিও ম্যানেজমেন্ট, ছোট বড় করা এবং শাটার হ্যান্ডেল করে
PlayerSurface র সারফেস যা AndroidView-এ SurfaceView এবং TextureView-কে র‍্যাপ করে।

Player কম্পোজেবল কাস্টমাইজ করুন

media3-ui-compose-material3 লাইব্রেরি ব্যবহার করার সময়, Player কম্পোজেবল সম্পূর্ণ মিডিয়া প্লেব্যাক অভিজ্ঞতা প্রদান করে। আপনি এর কন্টেন্ট স্লটে নিজের কম্পোজেবল প্রদান করে এর লেআউট কাস্টমাইজ করতে পারেন অথবা UI-এর নির্দিষ্ট অংশ কাস্টমাইজ করতে PlayerDefaults অবজেক্ট ব্যবহার করতে পারেন, যেমন TopControls, CenterControls, BottomControls এবং ErrorOverlay।

Player কম্পোজেবল কোথায় স্লট প্লেস করে তা ছবি ১-এ দেখানো হয়েছে। ছবি ২-এ দেখানো হয়েছে কোন ক্রমে এটি লেয়ার আঁকে।

ড্যাশ করা আউটলাইন সহ ভিডিও প্লেয়ার যা এর স্লট চিহ্নিত করে। উপর থেকে
      নিচে: উপরের প্রান্তে topControls, কেন্দ্রের উপরে errorOverlay,
      মাঝখানে backSecondary, back, central,
      forward, এবং forwardSecondary বোতাম সহ centerControls, নিচের দিকে সাবটাইটেল এবং
      নিচের প্রান্তে above, left,
      progressSlider, right, এবং below স্লট সহ bottomControls। ContentFrame সেগুলির পিছনে
      থাকা জায়গা পূরণ করে।
ছবি ১. Player কম্পোজেবলের স্লট। মাঝের ও নিচের কন্ট্রোল থেকে PlayerDefaults.CenterControls এবং PlayerDefaults.BottomControls-এর স্লট দেখা যায়। topControls, above ও below স্লট ডিফল্ট হিসেবে খালি থাকে।
ছয়টি স্ট্যাক করা লেয়ারের এক্সপ্লোডেড ভিউ। নিচ থেকে উপরে: ১,
      PlayerSurface; ২, শাটার; ৩, আর্টওয়ার্ক; ৪, সাবটাইটেল; ৫, errorOverlay; এবং
      ৬, topControls, centerControls ও bottomControls। ১ থেকে ৪ নম্বর লেয়ার নিয়ে ContentFrame
      তৈরি হয় এবং ছটি লেয়ার নিয়ে Player তৈরি হয়।
ছবি ২. Player কম্পোজেবলের লেয়ার, যে ক্রমে সেগুলি আঁকা হয়েছে সেই ক্রমে সংখ্যাযুক্ত।

যেমন, আপনি PlayerDefaults.CenterControls ব্যবহার করে মাঝখানের কন্ট্রোলগুলি অক্ষত রেখে শুধু মাঝখানের প্লে/পজ বোতাম ওভাররাইড করতে পারেন। এছাড়াও, আপনি topControls-এর মতো প্যারামিটারের জন্য সম্পূর্ণ কাস্টম কম্পোজেবল পাস করতে পারেন, এবং bottomControls-এর মতো অন্যান্য প্যারামিটার সম্পূর্ণভাবে বাদ দিতে পারেন যাতে সেগুলি ডিফল্ট হিসেবে থাকে।

@Composable
fun CustomPlayerSlots(player: Player, showControls: Boolean, modifier: Modifier = Modifier) {
  Player(
    player = player,
    modifier = modifier,
    showControls = showControls,
    topControls = {
      // Fully custom top controls
      Text("My custom title")
    },
    centerControls = {
      // Use default CenterControls but override the central button
      PlayerDefaults.CenterControls(
        player = player,
        central = {
          // A custom play/pause button
          Material3PlayPauseButton(player, modifier = Modifier.size(64.dp))
        },
      )
    },
    // bottomControls are left as default
  )
}

UI স্টেট হোল্ডার

স্ক্যাফোল্ডিং কম্পোনেন্টের কোনওটিই আপনার প্রয়োজন পূরণ না করলে, আপনি সরাসরি স্টেট অবজেক্টও ব্যবহার করতে পারেন। রিকম্পোজিশনের মধ্যে আপনার UI-এর লুক বজায় রাখতে, সাধারণত সংশ্লিষ্ট remember পদ্ধতি ব্যবহার করার পরামর্শ দেওয়া হয়।

কম্পোজেবলের তুলনায় UI স্টেট হোল্ডারের ফ্লেক্সিবিলিটি কীভাবে ব্যবহার করবেন তা আরও ভালোভাবে বুঝতে, Compose কীভাবে স্টেট ম্যানেজ করে সেই সম্পর্কে পড়ুন।

বোতামের স্টেট হোল্ডার

কিছু UI স্টেটের জন্য, লাইব্রেরি অনুমান করে যে সেগুলি সম্ভবত বোতামের মতো কম্পোজেবল দ্বারা ব্যবহৃত হবে।

রাজ্য মনে রাখবেন*স্টেট ধরন
PlayPauseButtonState rememberPlayPauseButtonState ২-টগল
PreviousButtonState rememberPreviousButtonState স্থির
NextButtonState rememberNextButtonState স্থির
RepeatButtonState rememberRepeatButtonState ৩-টগল
ShuffleButtonState rememberShuffleButtonState ২-টগল
PlaybackSpeedState rememberPlaybackSpeedState মেনু বা N-টগল

PlayPauseButtonState-এর ব্যবহারিক উদাহরণ:

val state = rememberPlayPauseButtonState(player)

IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
  Icon(
    imageVector = if (state.showPlay) Icons.Default.PlayArrow else Icons.Default.Pause,
    contentDescription =
      if (state.showPlay) stringResource(R.string.playpause_button_play)
      else stringResource(R.string.playpause_button_pause),
  )
}

ভিজ্যুয়াল আউটপুট স্টেট হোল্ডার

CurrentMediaItemState বর্তমানে চলছে এমন MediaItem সম্পর্কে তথ্য প্রদান করে, যেখানে PlaylistState প্লেয়ারে সেট করা MediaItems সম্পর্কে তথ্য প্রকাশ করে। আপনার কাস্টম UI-তে মেটাডেটা সংক্রান্ত তথ্য দেখানোর জন্য এগুলি উপযোগী।

ErrorState প্লেয়ারের বর্তমান সমস্যা সংক্রান্ত স্ট্যাটাস সম্পর্কে তথ্য প্রদান করে, যা সমস্যা সংক্রান্ত ওভারলে দেখানোর জন্য ব্যবহার করা যেতে পারে।

PresentationState-এ তথ্য থাকে যে কখন PlayerSurface-এ ভিডিও আউটপুট দেখানো যেতে পারে বা প্লেসহোল্ডার UI এলিমেন্ট দিয়ে ঢাকা উচিত। ContentFrame কম্পোজেবল, অ্যাস্পেক্ট রেশিও ম্যানেজ করার সাথে সাথে এখনও রেডি হয়নি এমন সারফেসের উপর শাটার দেখানোর বিষয়টিও ম্যানেজ করে।

@Composable
fun ContentFrame(
  player: Player?,
  modifier: Modifier = Modifier,
  surfaceType: @SurfaceType Int = SURFACE_TYPE_SURFACE_VIEW,
  contentScale: ContentScale = ContentScale.Fit,
  keepContentOnReset: Boolean = false,
  shutter: @Composable () -> Unit = { Box(Modifier.fillMaxSize().background(Color.Black)) },
) {
  val presentationState = rememberPresentationState(player, keepContentOnReset)
  val scaledModifier =
    modifier.resizeWithContentScale(contentScale, presentationState.videoAspectRatio)

  // Always leave PlayerSurface to be part of the Compose tree because it will be initialized in
  // the process. If this composable is guarded by some condition, it might never become visible
  // because the Player won't emit the relevant event, e.g. the first frame being ready.
  PlayerSurface(player, scaledModifier, surfaceType)

  if (presentationState.coverSurface) {
    // Cover the surface that is being prepared with a shutter
    shutter()
  }
}

এখানে, আমরা সারফেসকে বেছে নেওয়া অ্যাস্পেক্ট রেশিওতে স্কেল করতে presentationState.videoAspectRatio (আরও ধরনের জন্য কন্টেন্ট স্কেল দেখুন) এবং সারফেস দেখানোর জন্য কখন সময় সঠিক নয় তা জানতে presentationState.coverSurface ব্যবহার করতে পারি। এই ক্ষেত্রে, আপনি সারফেসের উপরে একটি অস্বচ্ছ শাটার রাখতে পারেন, যা সারফেসটি প্রস্তুত হলে অদৃশ্য হয়ে যাবে। ContentFrame আপনাকে ট্রেলিং ল্যাম্বডা হিসেবে শাটার কাস্টমাইজ করতে দেয়, তবে ডিফল্ট হিসেবে এটি একটি কালো @Composable Box হবে যা পেরেন্ট কন্টেনারের সাইজ পূরণ করবে।

Flows কোথায় আছে?

অনেক Android ডেভেলপার, ক্রমাগত পরিবর্তনশীল UI ডেটা সংগ্রহ করার জন্য Kotlin Flow অবজেক্ট ব্যবহার করার ব্যাপারে পরিচিত। যেমন, আপনি হয়ত এমন Player.isPlaying ফ্লো খুঁজছেন যা আপনি লাইফসাইকেল-অ্যাওয়ার পদ্ধতিতে collect করতে পারবেন। অথবা Player.eventsFlow-এর মতো কিছু যা আপনাকে Flow<Player.Events> প্রদান করবে যাতে আপনি নিজের মতো করে filter করতে পারেন।

তবে, Player UI স্টেট-এর জন্য ফ্লো ব্যবহার করার কিছু অসুবিধা আছে। ডেটা ট্রান্সফারের অ্যাসিঙ্ক্রোনাস প্রকৃতি হল অন্যতম প্রধান উদ্বেগ। আমরা চাই যে Player.Event এবং UI-তে সেটি দেখানোর মধ্যে যেন যতটা সম্ভব কম লেটেন্সি থাকে এবং Player-এর সাথে সিঙ্ক করা নেই এমন UI এলিমেন্ট দেখানো এড়িয়ে যেতে চাই।

অন্যান্য পয়েন্টের মধ্যে রয়েছে:

  • সব Player.Events সহ একটি ফ্লো একটি একক দায়িত্ব নীতি মেনে চলবে না, প্রতিটি গ্রাহককে প্রাসঙ্গিক ইভেন্টগুলি ফিল্টার করতে হবে।
  • প্রতিটি Player.Event-এর জন্য ফ্লো তৈরি করতে হলে আপনাকে প্রতিটি UI এলিমেন্টের জন্য সেগুলি একত্রিত করতে হবে (combine-এর সাথে)। Player.Event এবং UI এলিমেন্ট পরিবর্তনের মধ্যে অনেকের-সাথে-অনেক ম্যাপিং আছে। combine ব্যবহার করতে হলে UI সম্ভাব্য বেআইনি অবস্থায় চলে যেতে পারে।

কাস্টম UI স্টেট তৈরি করুন

আগে থেকে থাকা UI স্টেট আপনার প্রয়োজন পূরণ না করলে, আপনি কাস্টম UI স্টেট যোগ করতে পারবেন। প্যাটার্ন কপি করতে বর্তমান অবস্থার সোর্স কোড চেক করুন। একটি সাধারণ UI স্টেট হোল্ডার ক্লাস নিম্নলিখিত কাজগুলি করে:

  1. Player অবজেক্ট নেয়।
  2. কোরুটিন ব্যবহার করে Player-এ সাবস্ক্রাইব করে। আরও বিবরণের জন্য Player.listen দেখুন।
  3. ইন্টার্নাল স্ট্যাটাস আপডেট করে নির্দিষ্ট Player.Events-এর উত্তর দেয়।
  4. বিজনেস-লজিক কমান্ড গ্রহণ করে যা উপযুক্ত Player আপডেটে রূপান্তরিত হবে।
  5. UI ট্রি জুড়ে একাধিক জায়গায় তৈরি করা যেতে পারে এবং সবসময় প্লেয়ারের স্ট্যাটাসের একই ভিউ বজায় রাখবে।
  6. Compose State ফিল্ড এক্সপোজ করে যা কম্পোজেবল ব্যবহার করতে পারে পরিবর্তনে ডায়নামিকভাবে প্রতিক্রিয়া জানাতে।
  7. কম্পোজিশনের মধ্যে ইনস্ট্যান্স মনে রাখার জন্য remember*State ফাংশন সহ আসে।

নেপথ্যে কী হয়:

class SomeButtonState(private val player: Player) {
  var isEnabled by mutableStateOf(player.isCommandAvailable(COMMAND_ACTION_A))
    private set

  var someFieldValue by mutableStateOf(someFieldDefault)
    private set

  fun onClick() {
    player.actionA()
  }

  suspend fun observe(): Nothing = player.listen { events ->
    if (events.containsAny(EVENT_B_CHANGED, EVENT_C_CHANGED, EVENT_AVAILABLE_COMMANDS_CHANGED)) {
      someFieldValue = this.someField
      isEnabled = this.isCommandAvailable(COMMAND_ACTION_A)
    }
  }
}

আপনার নিজের Player.Events-এ প্রতিক্রিয়া জানাতে, আপনি Player.listen ব্যবহার করে সেগুলি ক্যাচ করতে পারেন যা হল একটি suspend fun যা আপনাকে কোরুটিন জগতে প্রবেশ করতে এবং অনির্দিষ্টকালের জন্য Player.Events শুনতে দেয়। বিভিন্ন UI স্ট্যাটাসের Media3 ইমপ্লিমেন্টেশন এন্ড ডেভেলপারকে Player.Events সম্পর্কে জানার ব্যাপারে চিন্তা না করতে সাহায্য করে।