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

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

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

Kotlin

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

Groovy

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

हमारा सुझाव है कि आप अपना ऐप्लिकेशन, Compose के साथ डेवलप करें या व्यू का इस्तेमाल करने से माइग्रेट करें.

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

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

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

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

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

हम कुछ यूज़र इंटरफ़ेस (यूआई) स्टेटस के लिए यह मानते हैं कि उन्हें बटन जैसे Composables का इस्तेमाल करके दिखाया जाएगा.

राज्य 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 के साथ सिंक नहीं हैं.

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

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

कस्टम यूज़र इंटरफ़ेस (यूआई) स्टेटस बनाना

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

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