Core Compose

media3-ui-compose लाइब्रेरी, Jetpack Compose में मीडिया यूज़र इंटरफ़ेस बनाने के लिए बुनियादी कॉम्पोनेंट उपलब्ध कराती है. इसे उन डेवलपर के लिए बनाया गया है जिन्हें media3-ui-compose-material3 लाइब्रेरी के मुकाबले, ज़्यादा बदलाव करने की ज़रूरत होती है. इस पेज पर, कस्टम मीडिया प्लेयर यूज़र इंटरफ़ेस (यूआई) बनाने के लिए, मुख्य कॉम्पोनेंट और स्टेट होल्डर इस्तेमाल करने का तरीका बताया गया है.

Material3 और कस्टम कंपोज़ कॉम्पोनेंट को एक साथ इस्तेमाल करना

media3-ui-compose-material3 लाइब्रेरी को इस तरह डिज़ाइन किया गया है कि इसे आसानी से इस्तेमाल किया जा सके. ज़्यादातर यूज़र इंटरफ़ेस (यूआई) के लिए, पहले से बने कॉम्पोनेंट का इस्तेमाल किया जा सकता है. हालांकि, अगर आपको ज़्यादा कंट्रोल की ज़रूरत है, तो किसी एक कॉम्पोनेंट को कस्टम तरीके से लागू करने के लिए बदला जा सकता है. ऐसे में, 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 को रैप करता है.

यूज़र इंटरफ़ेस (यूआई) स्टेट होल्डर

अगर कोई भी स्केफ़ोल्डिंग कॉम्पोनेंट आपकी ज़रूरतों के मुताबिक नहीं है, तो सीधे तौर पर स्टेट ऑब्जेक्ट का भी इस्तेमाल किया जा सकता है. आम तौर पर, रीयूज़ेबल फ़ंक्शन के बीच अपने यूज़र इंटरफ़ेस (यूआई) को बनाए रखने के लिए, remember तरीकों का इस्तेमाल करने का सुझाव दिया जाता है.

यूज़र इंटरफ़ेस (यूआई) स्टेट होल्डर और कंपोज़ेबल की सुविधा का इस्तेमाल कैसे किया जा सकता है, इस बारे में बेहतर तरीके से जानने के लिए, Compose में स्टेट को मैनेज करने के तरीके के बारे में पढ़ें.

बटन के स्टेट होल्डर

यूज़र इंटरफ़ेस (यूआई) की कुछ स्थितियों के लिए, लाइब्रेरी यह मानती है कि इनका इस्तेमाल बटन जैसे कंपोज़ेबल से किया जाएगा.

राज्य remember*State टाइप
PlayPauseButtonState rememberPlayPauseButtonState 2-टॉगल
PreviousButtonState rememberPreviousButtonState कॉन्स्टेंट
NextButtonState rememberNextButtonState कॉन्स्टेंट
RepeatButtonState rememberRepeatButtonState 3-टॉगल
ShuffleButtonState rememberShuffleButtonState 2-टॉगल
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),
  )
}

विज़ुअल आउटपुट स्टेट होल्डर

PresentationState में यह जानकारी होती है कि PlayerSurface में वीडियो आउटपुट कब दिखाया जा सकता है या इसे प्लेसहोल्डर यूज़र इंटरफ़ेस (यूआई) एलिमेंट से कवर किया जाना चाहिए. 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 का इस्तेमाल करके, Surface को चुने गए आसपेक्ट रेशियो के हिसाब से स्केल कर सकते हैं. ज़्यादा टाइप के लिए, ContentScale के दस्तावेज़ देखें. साथ ही, presentationState.coverSurface का इस्तेमाल करके यह पता लगा सकते हैं कि Surface दिखाने का सही समय कब नहीं है. इस मामले में, अपारदर्शी शटर को सतह के ऊपर रखा जा सकता है. जब सतह तैयार हो जाएगी, तब यह शटर गायब हो जाएगा. ContentFrame की मदद से, शटर को ट्रेलिंग लैम्डा के तौर पर पसंद के मुताबिक बनाया जा सकता है. हालांकि, डिफ़ॉल्ट रूप से यह काला @Composable Box होता है और पैरंट कंटेनर के साइज़ में दिखता है.

फ़्लो कहाँ हैं?

कई Android डेवलपर, हमेशा बदलते रहने वाले यूज़र इंटरफ़ेस (यूआई) डेटा को इकट्ठा करने के लिए, Kotlin Flow ऑब्जेक्ट का इस्तेमाल करते हैं. उदाहरण के लिए, आपको Player.isPlaying फ़्लो की ज़रूरत हो सकती है, जिसे लाइफ़साइकल के हिसाब से collect किया जा सकता है. या फिर Player.eventsFlow जैसा कुछ, ताकि आपको Flow<Player.Events> मिल सके. इसे अपनी पसंद के मुताबिक filter किया जा सकता है.

हालांकि, Player यूज़र इंटरफ़ेस (यूआई) की स्थिति के लिए फ़्लो का इस्तेमाल करने के कुछ नुकसान हैं. डेटा ट्रांसफ़र एसिंक्रोनस तरीके से होता है. यह एक मुख्य समस्या है. हमारा मकसद, Player.Event और यूज़र इंटरफ़ेस (यूआई) पर इसके इस्तेमाल के बीच कम से कम देरी करना है. साथ ही, ऐसे यूआई एलिमेंट नहीं दिखाने हैं जो Player के साथ सिंक नहीं हैं.

अन्य बातों में ये शामिल हैं:

  • सभी Player.Events वाले फ़्लो में, एक ज़िम्मेदारी के सिद्धांत का पालन नहीं किया जाएगा. हर उपभोक्ता को काम के इवेंट फ़िल्टर करने होंगे.
  • हर Player.Event के लिए फ़्लो बनाने के लिए, आपको उन्हें हर यूज़र इंटरफ़ेस (यूआई) एलिमेंट के लिए combine के साथ जोड़ना होगा. Player.Event और यूज़र इंटरफ़ेस (यूआई) एलिमेंट में बदलाव के बीच, कई-से-कई मैपिंग होती है. combine का इस्तेमाल करने से, यूज़र इंटरफ़ेस (यूआई) ऐसी स्थितियों में पहुंच सकता है जो गैर-कानूनी हो सकती हैं.

पसंद के मुताबिक यूज़र इंटरफ़ेस (यूआई) की स्थितियां बनाना

अगर मौजूदा यूज़र इंटरफ़ेस (यूआई) स्टेट आपकी ज़रूरतों को पूरा नहीं करती हैं, तो आपके पास कस्टम यूज़र इंटरफ़ेस (यूआई) स्टेट जोड़ने का विकल्प होता है. पैटर्न कॉपी करने के लिए, मौजूदा स्थिति के सोर्स कोड को देखें. आम तौर पर, यूज़र इंटरफ़ेस (यूआई) की स्थिति को सेव करने वाली क्लास ये काम करती है:

  1. Player में लेता है.
  2. यह कुकी, कोरूटीन का इस्तेमाल करके Player की सदस्यता लेती है. ज़्यादा जानकारी के लिए, Player.listen देखें.
  3. यह Player.Events के हिसाब से, अपनी इंटरनल स्थिति को अपडेट करता है.
  4. यह कारोबार के लॉजिक से जुड़े ऐसे निर्देश स्वीकार करता है जिन्हें Player अपडेट में बदल दिया जाएगा.
  5. इसे यूज़र इंटरफ़ेस (यूआई) ट्री में कई जगहों पर बनाया जा सकता है. साथ ही, यह हमेशा प्लेयर की स्थिति को एक जैसा बनाए रखेगा.
  6. यह Compose 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 है, जिसकी मदद से को-रूटीन की दुनिया में शामिल हुआ जा सकता है और Player.Events को अनिश्चित काल तक सुना जा सकता है. Media3 में अलग-अलग यूज़र इंटरफ़ेस (यूआई) स्टेट लागू करने से, डेवलपर को Player.Events के बारे में जानने की ज़रूरत नहीं पड़ती.