Core Compose

media3-ui-compose kitaplığı, Jetpack Compose'da medya kullanıcı arayüzü oluşturmak için temel bileşenleri sağlar. media3-ui-compose-material3 kitaplığının sunduğundan daha fazla özelleştirmeye ihtiyaç duyan geliştiriciler için tasarlanmıştır. Bu sayfada, özel bir medya oynatıcı kullanıcı arayüzü oluşturmak için temel bileşenlerin ve durum bilgisi depolayıcıların nasıl kullanılacağı açıklanmaktadır.

Material3 ve özel Compose bileşenlerini karıştırma

media3-ui-compose-material3 kitaplığı esnek olacak şekilde tasarlanmıştır. Kullanıcı arayüzünüzün çoğu için önceden oluşturulmuş bileşenleri kullanabilir, daha fazla kontrole ihtiyaç duyduğunuzda tek bir bileşeni özel bir uygulamayla değiştirebilirsiniz. Bu noktada media3-ui-compose kitaplığı devreye girer.

Örneğin, Material3 kitaplığındaki standart PreviousButton ve NextButton bileşenlerini kullanmak istediğinizi ancak tamamen özel bir PlayPauseButton bileşenine ihtiyacınız olduğunu varsayalım. Bunu, temel media3-ui-compose kitaplığındaki PlayPauseButton bileşenini kullanarak ve önceden oluşturulmuş bileşenlerin yanına yerleştirerek yapabilirsiniz.

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

Kullanılabilir bileşenler

media3-ui-compose kitaplığı, yaygın oynatıcı kontrolleri için önceden oluşturulmuş bir dizi composable sağlar. Doğrudan uygulamanızda kullanabileceğiniz bileşenlerden bazıları şunlardır:

Bileşen Açıklama
PlayPauseButton Oynatma ve duraklatma arasında geçiş yapan bir düğmenin durum kapsayıcısı.
SeekBackButton Tanımlanmış bir artışla geri sarmayı sağlayan bir düğmenin durum kapsayıcısıdır.
SeekForwardButton Belirli bir artışla ileri saran bir düğmenin durum kapsayıcısı.
NextButton Sonraki medya öğesine saran bir düğme için durum kapsayıcısı.
PreviousButton Önceki medya öğesine saran bir düğmenin durum kapsayıcısı.
RepeatButton Tekrar modları arasında geçiş yapan bir düğme için durum kapsayıcısı.
ShuffleButton Karışık modunu açıp kapatan bir düğme için durum kapsayıcısı.
MuteButton Oynatıcının sesini kapatıp açan bir düğme için durum kapsayıcısı.
TimeText Oynatıcının ilerleme durumunu gösteren bir composable için durum kapsayıcısı.
ContentFrame En boy oranı yönetimini, yeniden boyutlandırmayı ve deklanşörü işleyen medya içeriğini görüntülemek için kullanılan bir yüzey
PlayerSurface AndroidView içinde SurfaceView ve TextureView öğelerini sarmalayan ham yüzey.

Player composable'ını özelleştirme

media3-ui-compose-material3 kitaplığını kullanırken Player composable'ı, eksiksiz bir medya oynatma deneyimi sunar. İçerik alanlarına kendi composable'larınızı ekleyerek düzenini özelleştirebilir veya kullanıcı arayüzünün TopControls, CenterControls, BottomControls ve ErrorOverlay gibi belirli bölümlerini özelleştirmek için PlayerDefaults nesnesinden yararlanabilirsiniz.

Şekil 1'de, Player composable'ının yuvalarını nereye yerleştirdiği gösterilmektedir. Şekil 2'de, katmanların çizilme sırası gösterilmektedir.

Reklam alanlarını işaretleyen kesikli ana hatlara sahip bir video oynatıcı. Yukarıdan aşağıya: Üst kenarda topControls, ortanın üzerinde errorOverlay, ortada backSecondary, back, central, forward ve forwardSecondary düğmeleriyle centerControls, alt kısma yakın altyazılar ve alt kenarda above, left, progressSlider, right ve below yuvalarıyla bottomControls. ContentFrame, bu öğelerin
      arkasındaki alanı doldurur.
Şekil 1. Player composable'ının alanları. Ortadaki ve alttaki kontroller, PlayerDefaults.CenterControls ve PlayerDefaults.BottomControls için ayrılan alanları gösterir. topControls, above ve below yuvaları varsayılan olarak boştur.
Üst üste dizilmiş altı katmanın patlatılmış görünümü. Aşağıdan yukarıya: 1, PlayerSurface; 2, shutter; 3, artwork; 4, subtitles; 5, errorOverlay; ve 6, topControls, centerControls ve bottomControls. 1-4. katmanlar ContentFrame'i, altı katmanın tamamı ise Player'ı oluşturur.
Şekil 2. Player composable bileşeninin katmanları, çizildikleri sıraya göre numaralandırılır.

Örneğin, PlayerDefaults.CenterControls kullanarak yalnızca ortadaki oynat/duraklat düğmesini geçersiz kılabilir ve diğer orta kontrolleri olduğu gibi bırakabilirsiniz. topControls gibi bir parametre için tamamen özel bir composable da iletebilir ve bottomControls gibi diğerlerini tamamen atlayarak varsayılan değerlerinde bırakabilirsiniz.

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

Kullanıcı arayüzü durum bilgisi depolayıcıları

İskelet bileşenlerinden hiçbiri ihtiyaçlarınızı karşılamıyorsa durum nesnelerini doğrudan da kullanabilirsiniz. Yeniden oluşturma işlemleri arasında kullanıcı arayüzü görünümünüzü korumak için genellikle ilgili remember yöntemlerini kullanmanız önerilir.

Kullanıcı arayüzü durum bilgisi depolayıcıların esnekliğini, composable'lara kıyasla nasıl kullanabileceğinizi daha iyi anlamak için Compose'un durumu nasıl yönettiği hakkında bilgi edinin.

Düğme durum bilgisi depolayıcıları

Kitaplık, bazı kullanıcı arayüzü durumlarının büyük olasılıkla düğme benzeri composable'lar tarafından kullanılacağını varsayar.

Eyalet remember*State Tür
PlayPauseButtonState rememberPlayPauseButtonState 2-Toggle
PreviousButtonState rememberPreviousButtonState Sabit
NextButtonState rememberNextButtonState Sabit
RepeatButtonState rememberRepeatButtonState 3-Toggle
ShuffleButtonState rememberShuffleButtonState 2-Toggle
PlaybackSpeedState rememberPlaybackSpeedState Menü veya N-Toggle

PlayPauseButtonState için örnek kullanım:

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

Görsel çıkış durum bilgisi depolayıcıları

CurrentMediaItemState, oynatılmakta olan MediaItem hakkında bilgi verirken PlaylistState, oynatıcıda ayarlanan MediaItems hakkında bilgi verir. Bunlar, özel kullanıcı arayüzünüzde yayınlanan içerik meta verisi bilgilerini göstermek için kullanışlıdır.

ErrorState, oynatıcının mevcut hata durumu hakkında bilgi sağlar. Bu bilgi, hata yer paylaşımı görüntülemek için kullanılabilir.

PresentationState, PlayerSurface içindeki video çıkışının ne zaman gösterilebileceği veya yer tutucu kullanıcı arayüzü öğesiyle ne zaman kapatılması gerektiğiyle ilgili bilgileri içerir. ContentFrame composable, en boy oranı işlemeyi henüz hazır olmayan bir yüzeyde deklanşörün gösterilmesiyle birleştirir.

@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()
  }
}

Burada, Surface'ı seçilen en boy oranına ölçeklendirmek için presentationState.videoAspectRatio (daha fazla tür için İçerik ölçeği bölümüne bakın) ve Surface'ı göstermek için doğru zamanın ne zaman olmadığını öğrenmek için presentationState.coverSurface kullanabiliriz. Bu durumda, yüzeyin üzerine opak bir deklanşör yerleştirebilirsiniz. Yüzey hazır olduğunda bu deklanşör kaybolur. ContentFrame, deklanşörü sondaki lambda olarak özelleştirmenize olanak tanır ancak varsayılan olarak üst kapsayıcının boyutunu dolduran siyah bir @Composable Box olur.

Flows nerede?

Birçok Android geliştirici, sürekli değişen kullanıcı arayüzü verilerini toplamak için Kotlin Flow nesnelerini kullanmaya aşinadır. Örneğin, yaşam döngüsüne duyarlı bir şekilde collect yapabileceğiniz bir Player.isPlaying akışı arıyor olabilirsiniz. Ya da Player.eventsFlow gibi bir şey kullanarak istediğiniz gibi filter yapabileceğiniz bir Flow<Player.Events> oluşturabilirsiniz.

Ancak Player kullanıcı arayüzü durumu için akışları kullanmanın bazı dezavantajları vardır. En önemli endişelerden biri, veri aktarımının eşzamansız olmasıdır. Player.Event ile kullanıcı arayüzü tarafında tüketimi arasında mümkün olduğunca az gecikme olmasını ve Player ile senkronize olmayan kullanıcı arayüzü öğelerinin gösterilmesini önlemek istiyoruz.

Diğer noktalar şunlardır:

  • Tüm Player.Events içeren bir akış, tek sorumluluk ilkesine uymaz. Her tüketicinin ilgili etkinlikleri filtrelemesi gerekir.
  • Her Player.Event için bir akış oluşturmak, her kullanıcı arayüzü öğesi için bunları birleştirmenizi (combine ile) gerektirir. Player.Event ile kullanıcı arayüzü öğesi değişikliği arasında çoktan çoğa eşleme vardır. combine kullanmak zorunda kalmak, kullanıcı arayüzünü yasa dışı olabilecek durumlara yönlendirebilir.

Özel kullanıcı arayüzü durumları oluşturma

Mevcut kullanıcı arayüzü durumları ihtiyaçlarınızı karşılamıyorsa özel kullanıcı arayüzü durumları ekleyebilirsiniz. Mevcut durumun kaynak kodunu inceleyerek kalıbı kopyalayın. Tipik bir kullanıcı arayüzü durum bilgisi depolayıcı sınıfı şunları yapar:

  1. Player nesnesi alır.
  2. Eş yordamları kullanarak Player akışına abone olur. Daha fazla bilgi için Player.listen sayfasına bakın.
  3. Dahili durumunu güncelleyerek belirli Player.Events yanıt verir.
  4. Uygun bir güncellemeye dönüştürülecek iş mantığı komutlarını kabul eder. Player
  5. Kullanıcı arayüzü ağacında birden fazla yerde oluşturulabilir ve her zaman oynatıcının durumunun tutarlı bir görünümünü korur.
  6. Değişikliklere dinamik olarak yanıt vermek için bir composable tarafından kullanılabilecek Compose State alanlarını gösterir.
  7. Oluşturmalar arasında örneği hatırlamak için remember*State işleviyle birlikte gelir.

Arka planda neler olur?

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

Kendi Player.Events akışınıza tepki vermek için Player.listen kullanarak akışı yakalayabilirsiniz. Bu, eş yordam dünyasına girmenize ve Player.Events akışını süresiz olarak dinlemenize olanak tanıyan bir suspend fun işlevidir. Media3'ün çeşitli kullanıcı arayüzü durumlarını uygulaması, son geliştiricinin Player.Events hakkında bilgi edinme konusunda endişelenmemesine yardımcı olur.