Compose पर आधारित यूज़र इंटरफ़ेस (यूआई) का इस्तेमाल शुरू करना

डिपेंडेंसी जोड़ना

Media3 लाइब्रेरी में, Jetpack Compose पर आधारित यूज़र इंटरफ़ेस मॉड्यूल शामिल होता है. इसका इस्तेमाल करने के लिए, यह डिपेंडेंसी जोड़ें:

Kotlin

implementation("androidx.media3:media3-ui-compose:1.8.0")

Groovy

implementation "androidx.media3:media3-ui-compose:1.8.0"

हमारा सुझाव है कि आप अपने ऐप्लिकेशन को Compose-first फ़ैशन में डेवलप करें या Views का इस्तेमाल बंद करें.

पूरी तरह से कंपोज़ किया गया डेमो ऐप्लिकेशन

media3-ui-compose लाइब्रेरी में, बॉक्स से बाहर के कंपोज़ेबल (जैसे कि बटन, इंडिकेटर, इमेज या डायलॉग) शामिल नहीं हैं. हालांकि, आपको पूरी तरह से Compose में लिखा गया डेमो ऐप्लिकेशन मिल सकता है. इसमें इंटरऑपरेबिलिटी के किसी भी समाधान का इस्तेमाल नहीं किया गया है. जैसे, PlayerView को AndroidView में रैप करना. डेमो ऐप्लिकेशन, media3-ui-compose मॉड्यूल से यूज़र इंटरफ़ेस (यूआई) स्टेट होल्डर क्लास का इस्तेमाल करता है. साथ ही, Compose Material3 लाइब्रेरी का इस्तेमाल करता है.

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

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

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

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

राज्य remember*State टाइप
PlayPauseButtonState rememberPlayPauseButtonState 2-टॉगल
PreviousButtonState rememberPreviousButtonState कॉन्स्टेंट
NextButtonState rememberNextButtonState कॉन्स्टेंट
RepeatButtonState rememberRepeatButtonState 3-टॉगल
ShuffleButtonState rememberShuffleButtonState 2-टॉगल
PlaybackSpeedState rememberPlaybackSpeedState मेन्यू या N-टॉगल

PlayPauseButtonState के इस्तेमाल का उदाहरण:

@Composable
fun PlayPauseButton(player: Player, modifier: Modifier = Modifier) {
  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),
    )
  }
}

ध्यान दें कि state में थीम से जुड़ी कोई जानकारी नहीं है. जैसे, चलाने या रोकने के लिए इस्तेमाल किया जाने वाला आइकॉन. इसकी सिर्फ़ यह ज़िम्मेदारी है कि यह Player को यूज़र इंटरफ़ेस (यूआई) की स्थिति में बदल दे.

इसके बाद, अपनी पसंद के लेआउट में बटनों को मिक्स और मैच किया जा सकता है:

Row(
  modifier = modifier.fillMaxWidth(),
  horizontalArrangement = Arrangement.SpaceEvenly,
  verticalAlignment = Alignment.CenterVertically,
) {
  PreviousButton(player)
  PlayPauseButton(player)
  NextButton(player)
}

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

PresentationState में यह जानकारी होती है कि PlayerSurface में वीडियो आउटपुट कब दिखाया जा सकता है या इसे प्लेसहोल्डर यूज़र इंटरफ़ेस (यूआई) एलिमेंट से कवर किया जाना चाहिए.

val presentationState = rememberPresentationState(player)
val scaledModifier = Modifier.resize(ContentScale.Fit, presentationState.videoSizeDp)

Box(modifier) {
  // 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 = player,
    surfaceType = SURFACE_TYPE_SURFACE_VIEW,
    modifier = scaledModifier,
  )

  if (presentationState.coverSurface) {
    // Cover the surface that is being prepared with a shutter
    Box(Modifier.background(Color.Black))
  }

यहां, हम presentationState.videoSizeDp का इस्तेमाल करके, Surface को पसंद के मुताबिक आसपेक्ट रेशियो में बदल सकते हैं. ज़्यादा टाइप के लिए, ContentScale के दस्तावेज़ देखें. साथ ही, presentationState.coverSurface का इस्तेमाल करके यह पता लगा सकते हैं कि Surface दिखाने का सही समय कब नहीं है. इस मामले में, अपारदर्शी शटर को सतह के ऊपर रखा जा सकता है. जब सतह तैयार हो जाएगी, तब यह शटर गायब हो जाएगा.

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

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

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