কোর কম্পোজ

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

ম্যাটেরিয়াল৩ এবং কাস্টম কম্পোজ উপাদানের মিশ্রণ

media3-ui-compose-material3 লাইব্রেরিটি নমনীয়ভাবে ডিজাইন করা হয়েছে। আপনি আপনার বেশিরভাগ UI এর জন্য পূর্বনির্মিত উপাদানগুলি ব্যবহার করতে পারেন, তবে যখন আপনার আরও নিয়ন্ত্রণের প্রয়োজন হয় তখন একটি একক উপাদানকে কাস্টম বাস্তবায়নের জন্য অদলবদল করুন। এই সময় media3-ui-compose লাইব্রেরি কার্যকর হয়।

উদাহরণস্বরূপ, কল্পনা করুন যে আপনি Material3 লাইব্রেরি থেকে স্ট্যান্ডার্ড PreviousButton এবং NextButton ব্যবহার করতে চান, কিন্তু আপনার একটি সম্পূর্ণ কাস্টম PlayPauseButton প্রয়োজন। আপনি core 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 কে মোড়ানো।

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

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

UI স্টেট হোল্ডার বনাম Composables-এর নমনীয়তা কীভাবে ব্যবহার করবেন তা আরও ভালোভাবে বুঝতে, Compose কীভাবে State পরিচালনা করে সে সম্পর্কে পড়ুন।

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

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

রাজ্য মনে রাখবেন*রাষ্ট্র আদর্শ
PlayPauseButtonState rememberPlayPauseButtonState 2-টগল করুন
PreviousButtonState rememberPreviousButtonState ধ্রুবক
NextButtonState rememberNextButtonState ধ্রুবক
RepeatButtonState rememberRepeatButtonState 3-টগল করুন
ShuffleButtonState rememberShuffleButtonState 2-টগল করুন
PlaybackSpeedState rememberPlaybackSpeedState মেনু অথবা এন-টগল

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),
  )
}

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

PresentationState কখন PlayerSurface এর ভিডিও আউটপুট দেখানো যেতে পারে বা কখন প্লেসহোল্ডার UI উপাদান দ্বারা আচ্ছাদিত করা উচিত তার তথ্য ধারণ করে। ContentFrame Composable আকৃতির অনুপাত পরিচালনার সাথে শাটারটি এমন একটি পৃষ্ঠের উপর দেখানোর যত্ন নেয় যা এখনও প্রস্তুত নয়।

@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.videoSizeDp)

  // Always leave PlayerSurface to be part of the Compose tree because it will be initialised 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.videoSizeDp ব্যবহার করে সারফেসকে নির্বাচিত আকৃতির অনুপাতের সাথে স্কেল করতে পারি (আরও ধরণের জন্য ContentScale ডক্স দেখুন) এবং presentationState.coverSurface ব্যবহার করে জানতে পারি কখন সারফেস দেখানোর সময় সঠিক নয়। এই ক্ষেত্রে, আপনি সারফেসের উপরে একটি অস্বচ্ছ শাটার রাখতে পারেন, যা সারফেস প্রস্তুত হয়ে গেলে অদৃশ্য হয়ে যাবে। ContentFrame আপনাকে শাটারটিকে একটি ট্রেলিং ল্যাম্বডা হিসাবে কাস্টমাইজ করতে দেয়, তবে ডিফল্টরূপে এটি একটি কালো @Composable Box হবে যা মূল ধারকের আকার পূরণ করবে।

ফ্লোস কোথায়?

অনেক অ্যান্ড্রয়েড ডেভেলপারই Kotlin Flow অবজেক্ট ব্যবহার করে ক্রমবর্ধমান UI ডেটা সংগ্রহের সাথে পরিচিত। উদাহরণস্বরূপ, আপনি হয়তো 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 এলিমেন্টের মধ্যে many-to-many ম্যাপিং পরিবর্তন হয়। combine ব্যবহার করার ফলে UI সম্ভাব্য অবৈধ অবস্থায় চলে যেতে পারে।

কাস্টম UI অবস্থা তৈরি করুন

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

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

পর্দার আড়ালে কী ঘটে:

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

  var someField by mutableStateOf(someFieldDefault)
    private set

  fun onClick() {
    player.actionA()
  }

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

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